@ -0,0 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
</profile>
|
||||
</component>
|
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/bookstore.iml" filepath="$PROJECT_DIR$/.idea/bookstore.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
@ -0,0 +1,23 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
@ -0,0 +1,17 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset',
|
||||
["@babel/preset-env", {
|
||||
"modules": false
|
||||
}]
|
||||
],
|
||||
plugins: [
|
||||
[
|
||||
"component",
|
||||
{
|
||||
libraryName: "element-ui",
|
||||
styleLibraryName: "theme-chalk"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"module": "esnext",
|
||||
"baseUrl": "./",
|
||||
"moduleResolution": "node",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"src/*"
|
||||
]
|
||||
},
|
||||
"lib": [
|
||||
"esnext",
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"scripthost"
|
||||
]
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
{
|
||||
"name": "bookstore",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"animate.css": "^4.1.1",
|
||||
"chart.js": "^4.4.7",
|
||||
"core-js": "^3.8.3",
|
||||
"dayjs": "^1.11.6",
|
||||
"element-ui": "^2.15.14",
|
||||
"vue": "^2.6.14",
|
||||
"vue-router": "^3.5.2",
|
||||
"vuex": "^3.6.2",
|
||||
"vuex-persist": "^3.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.12.16",
|
||||
"@babel/eslint-parser": "^7.12.16",
|
||||
"@vue/cli-plugin-babel": "~5.0.0",
|
||||
"@vue/cli-plugin-eslint": "~5.0.0",
|
||||
"@vue/cli-service": "~5.0.0",
|
||||
"babel-plugin-component": "^1.1.1",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-plugin-vue": "^8.0.3",
|
||||
"vue-template-compiler": "^2.6.14"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:vue/essential",
|
||||
"eslint:recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"parser": "@babel/eslint-parser"
|
||||
},
|
||||
"rules": {}
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not dead"
|
||||
]
|
||||
}
|
After Width: | Height: | Size: 4.2 KiB |
@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<div class="router">
|
||||
<router-view></router-view>
|
||||
</div>
|
||||
<!-- <Header v-show="!this.$route.meta.show"></Header>
|
||||
<div class="router">
|
||||
<router-view></router-view>
|
||||
</div>
|
||||
<Footer v-show="!this.$route.meta.show"></Footer> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// import Header from "./components/header";
|
||||
// import Footer from "./components/footer";
|
||||
export default {
|
||||
name: "App",
|
||||
// components: {
|
||||
// Header,
|
||||
// Footer,
|
||||
// },
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#app {
|
||||
/* display: flex;
|
||||
flex-direction: column; */
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
min-width: 1366px;
|
||||
}
|
||||
.router {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
After Width: | Height: | Size: 310 KiB |
After Width: | Height: | Size: 194 KiB |
After Width: | Height: | Size: 292 KiB |
After Width: | Height: | Size: 221 KiB |
After Width: | Height: | Size: 334 KiB |
After Width: | Height: | Size: 344 KiB |
After Width: | Height: | Size: 142 KiB |
After Width: | Height: | Size: 94 KiB |
After Width: | Height: | Size: 81 KiB |
After Width: | Height: | Size: 394 KiB |
After Width: | Height: | Size: 295 KiB |
After Width: | Height: | Size: 241 KiB |
After Width: | Height: | Size: 234 KiB |
After Width: | Height: | Size: 238 KiB |
After Width: | Height: | Size: 219 KiB |
After Width: | Height: | Size: 322 KiB |
After Width: | Height: | Size: 233 KiB |
After Width: | Height: | Size: 184 KiB |
After Width: | Height: | Size: 303 KiB |
After Width: | Height: | Size: 199 KiB |
After Width: | Height: | Size: 142 KiB |
After Width: | Height: | Size: 260 KiB |
After Width: | Height: | Size: 151 KiB |
After Width: | Height: | Size: 256 KiB |
After Width: | Height: | Size: 240 KiB |
After Width: | Height: | Size: 255 KiB |
After Width: | Height: | Size: 260 KiB |
After Width: | Height: | Size: 6.7 KiB |
@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<div class="footer">
|
||||
{{word}}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Footer',
|
||||
data() {
|
||||
return {
|
||||
word: '',
|
||||
wordList: [
|
||||
'悟已往之不谏,知来者之可追。实迷途其未远,觉今是而昨非。',
|
||||
'半山腰总是拥挤的,你要去山顶看看。',
|
||||
'只有对自己不将就,自己才会变得更优秀。',
|
||||
'拒绝摆烂,卷起来。',
|
||||
'每一种热爱,都值得全力以赴。',
|
||||
'人生如逆旅,我亦是行人。',
|
||||
'生而无畏,人类的最高品质便是勇气。',
|
||||
'少年心怀乌托邦,心仍向阳肆生长。',
|
||||
'平静的大海培养不出优秀的水手。',
|
||||
'长风破浪会有时,直挂云帆济沧海。',
|
||||
],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getWord() {
|
||||
this.word = this.wordList[Math.floor(Math.random() * this.wordList.length)];
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
$route() {
|
||||
this.getWord();
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.footer {
|
||||
text-align: center;
|
||||
line-height: 50px;
|
||||
font-size: 16px;
|
||||
background-color: #1abc9c;
|
||||
height: 50px;
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,104 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-row class="row_box">
|
||||
<el-col :span="15" :offset="2">
|
||||
<div class="title" @click="toHome">网上书店系统</div>
|
||||
</el-col>
|
||||
<el-col :span="3">
|
||||
<el-menu :default-active="activeIndex" mode="horizontal" @select="handleSelect"
|
||||
background-color="#1abc9c" text-color="#fff" active-text-color="#ffd04b">
|
||||
<el-menu-item index="home">书城</el-menu-item>
|
||||
<el-menu-item index="shoppingCart">购物车</el-menu-item>
|
||||
</el-menu>
|
||||
</el-col>
|
||||
<el-col :span="2">
|
||||
<el-dropdown @command="handleDownBoxCommand">
|
||||
<span class="el-dropdown-link">
|
||||
{{username}}
|
||||
<i class="el-icon-arrow-down el-icon--right"></i>
|
||||
</span>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item command="logout">退出登录</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
mapState
|
||||
} from "vuex";
|
||||
export default {
|
||||
name: 'Header',
|
||||
data() {
|
||||
return {
|
||||
activeIndex: 'home'
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
handleSelect(key, keyPath) {
|
||||
this.$router.push(key)
|
||||
},
|
||||
handleDownBoxCommand(command) {
|
||||
if (command === 'logout') {
|
||||
this.logout();
|
||||
}
|
||||
},
|
||||
logout() {
|
||||
this.$message({
|
||||
type:'success',
|
||||
message:'退出成功'
|
||||
})
|
||||
this.$router.push('/welcome')
|
||||
},
|
||||
toHome() {
|
||||
this.$router.push('/manager/home')
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
username: (state) => state.username,
|
||||
}),
|
||||
},
|
||||
watch: {
|
||||
$route(to,from) {
|
||||
if (to.name == 'Home') {
|
||||
this.activeIndex = 'home'
|
||||
} else if(to.name == 'ShoppingCart') {
|
||||
this.activeIndex = 'shoppingCart'
|
||||
} else {
|
||||
this.activeIndex = ''
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.row_box {
|
||||
background-color: #1abc9c;
|
||||
}
|
||||
|
||||
.title {
|
||||
letter-spacing: 2px;
|
||||
font-size: 25px;
|
||||
color: #F6F6F6;
|
||||
float: left;
|
||||
font-weight: 600;
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
line-height: 61px;
|
||||
}
|
||||
|
||||
.el-dropdown-link {
|
||||
cursor: pointer;
|
||||
color: #fff;
|
||||
font-size: 15px;
|
||||
line-height: 61px;
|
||||
}
|
||||
|
||||
.el-icon-arrow-down {
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<div class="manage-layout">
|
||||
<Header></Header>
|
||||
<div class="router">
|
||||
<router-view></router-view>
|
||||
</div>
|
||||
<Footer></Footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import Header from "./components/header/index.vue";
|
||||
import Footer from "./components/footer/index.vue";
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.manage-layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,21 @@
|
||||
<!--src/components/Footer.vue-->
|
||||
<template>
|
||||
<footer class="footer">
|
||||
<p>©2024 商家模块</p>
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.footer {
|
||||
background-color: #1abc9c;
|
||||
color: white;
|
||||
text-align: center;
|
||||
padding: 10px 0;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<div class="seller-layout">
|
||||
<Header></Header>
|
||||
<div class="router">
|
||||
<router-view></router-view>
|
||||
</div>
|
||||
<Footer></Footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import Header from "./components/header/index.vue";
|
||||
import Footer from "./components/footer/index.vue";
|
||||
</script>
|
@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<div class="footer">
|
||||
{{word}}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Footer',
|
||||
data() {
|
||||
return {
|
||||
word: '',
|
||||
wordList: [
|
||||
'悟已往之不谏,知来者之可追。实迷途其未远,觉今是而昨非。',
|
||||
'半山腰总是拥挤的,你要去山顶看看。',
|
||||
'只有对自己不将就,自己才会变得更优秀。',
|
||||
'拒绝摆烂,卷起来。',
|
||||
'每一种热爱,都值得全力以赴。',
|
||||
'人生如逆旅,我亦是行人。',
|
||||
'生而无畏,人类的最高品质便是勇气。',
|
||||
'少年心怀乌托邦,心仍向阳肆生长。',
|
||||
'平静的大海培养不出优秀的水手。',
|
||||
'长风破浪会有时,直挂云帆济沧海。',
|
||||
],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getWord() {
|
||||
this.word = this.wordList[Math.floor(Math.random() * this.wordList.length)];
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
$route() {
|
||||
this.getWord();
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.footer {
|
||||
text-align: center;
|
||||
line-height: 50px;
|
||||
font-size: 16px;
|
||||
background-color: #1abc9c;
|
||||
height: 50px;
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,104 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-row class="row_box">
|
||||
<el-col :span="15" :offset="2">
|
||||
<div class="title" @click="toHome">网上书店系统</div>
|
||||
</el-col>
|
||||
<el-col :span="3">
|
||||
<el-menu :default-active="activeIndex" mode="horizontal" @select="handleSelect"
|
||||
background-color="#1abc9c" text-color="#fff" active-text-color="#ffd04b">
|
||||
<el-menu-item index="/user/home">书城</el-menu-item>
|
||||
<el-menu-item index="/user/shoppingCart">购物车</el-menu-item>
|
||||
</el-menu>
|
||||
</el-col>
|
||||
<el-col :span="2">
|
||||
<el-dropdown @command="handleDownBoxCommand">
|
||||
<span class="el-dropdown-link">
|
||||
{{username}}
|
||||
<i class="el-icon-arrow-down el-icon--right"></i>
|
||||
</span>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item command="logout">退出登录</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
mapState
|
||||
} from "vuex";
|
||||
export default {
|
||||
name: 'Header',
|
||||
data() {
|
||||
return {
|
||||
activeIndex: 'home'
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
handleSelect(key, keyPath) {
|
||||
this.$router.push(key)
|
||||
},
|
||||
handleDownBoxCommand(command) {
|
||||
if (command === 'logout') {
|
||||
this.logout();
|
||||
}
|
||||
},
|
||||
logout() {
|
||||
this.$message({
|
||||
type:'success',
|
||||
message:'退出成功'
|
||||
})
|
||||
this.$router.push('/welcome')
|
||||
},
|
||||
toHome() {
|
||||
this.$router.push('/user/home')
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
username: (state) => state.username,
|
||||
}),
|
||||
},
|
||||
watch: {
|
||||
$route(to,from) {
|
||||
if (to.name == 'Home') {
|
||||
this.activeIndex = '/user/home'
|
||||
} else if(to.name == 'ShoppingCart') {
|
||||
this.activeIndex = '/user/shoppingCart'
|
||||
} else {
|
||||
this.activeIndex = ''
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.row_box {
|
||||
background-color: #1abc9c;
|
||||
}
|
||||
|
||||
.title {
|
||||
letter-spacing: 2px;
|
||||
font-size: 25px;
|
||||
color: #F6F6F6;
|
||||
float: left;
|
||||
font-weight: 600;
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
line-height: 61px;
|
||||
}
|
||||
|
||||
.el-dropdown-link {
|
||||
cursor: pointer;
|
||||
color: #fff;
|
||||
font-size: 15px;
|
||||
line-height: 61px;
|
||||
}
|
||||
|
||||
.el-icon-arrow-down {
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<div class="manage-layout">
|
||||
<Header></Header>
|
||||
<div class="router">
|
||||
<router-view></router-view>
|
||||
</div>
|
||||
<Footer></Footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import Header from "./components/header/index.vue";
|
||||
import Footer from "./components/footer/index.vue";
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.manage-layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,17 @@
|
||||
import Vue from "vue";
|
||||
import App from "./App.vue";
|
||||
import router from "./router";
|
||||
import store from "./store";
|
||||
// import "./plugins/element";
|
||||
import "./assets/css/reset.css";
|
||||
import "animate.css";
|
||||
import ElementUI from "element-ui";
|
||||
import "element-ui/lib/theme-chalk/index.css";
|
||||
|
||||
Vue.config.productionTip = false;
|
||||
Vue.use(ElementUI)
|
||||
new Vue({
|
||||
router,
|
||||
store,
|
||||
render: (h) => h(App),
|
||||
}).$mount("#app");
|
@ -0,0 +1,186 @@
|
||||
<template>
|
||||
<div class="bookList">
|
||||
<div class="bookList_box">
|
||||
<div class="category_list">
|
||||
<span @click="categoryChange(index,$event)" :class="{'active':activeIndex == index}"
|
||||
v-for="(category,index) in categoryList" :key="index" class="category">{{category}}</span>
|
||||
</div>
|
||||
<div class="book_box">
|
||||
<el-row>
|
||||
<el-col :span="6" v-for="(book,index) in bookPage" :key="index" class="boox_col">
|
||||
<div class="item">
|
||||
<img :src="book.cover" @click="detail(book)" alt="图片加载出错" class="cover">
|
||||
<div class="information">
|
||||
<span>{{book.bookName}}</span>
|
||||
<span>{{book.author}}</span>
|
||||
</div>
|
||||
<div class="information">
|
||||
<el-tag type="success">{{book.price}}元</el-tag>
|
||||
<el-tooltip effect="light" content="加入购物车" placement="right">
|
||||
<el-button type="success" @click="addCart(book)" size="mini"
|
||||
icon="el-icon-shopping-cart-2"></el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<el-pagination class="pagination" :current-page="page.currentPage" :page-sizes="[8, 16, 24]"
|
||||
:page-size="page.pageSize" @size-change="handleSizeChange" @current-change="handleCurrentChange"
|
||||
layout="prev, pager, next, jumper, ->, sizes, total" :total="page.total">
|
||||
</el-pagination>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
mapState
|
||||
} from "vuex";
|
||||
export default {
|
||||
name: 'BookList',
|
||||
data() {
|
||||
return {
|
||||
bookPage: [],
|
||||
page: {
|
||||
currentPage: 1,
|
||||
pageSize: 8,
|
||||
total: 0,
|
||||
},
|
||||
activeIndex: 0,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getBookPage() {
|
||||
let start = (this.page.currentPage - 1) * (this.page.pageSize);
|
||||
let end = this.page.pageSize * this.page.currentPage;
|
||||
this.bookPage = this.bookList.slice(start, end);
|
||||
},
|
||||
getTotal() {
|
||||
this.page.total = this.bookList.length;
|
||||
},
|
||||
handleSizeChange(pageSize) {
|
||||
this.page.pageSize = pageSize;
|
||||
this.getBookPage();
|
||||
},
|
||||
handleCurrentChange(currentPage) {
|
||||
this.page.currentPage = currentPage;
|
||||
this.getBookPage();
|
||||
},
|
||||
categoryChange(index, event) {
|
||||
this.activeIndex = index
|
||||
let type = event.target.innerHTML;
|
||||
if (type == '全部分类') {
|
||||
this.getBookPage();
|
||||
this.getTotal();
|
||||
} else {
|
||||
let newBookList = this.bookList.filter((item) => {
|
||||
if (item.type == type) return true
|
||||
})
|
||||
this.page.total = newBookList.length;
|
||||
this.bookPage = newBookList;
|
||||
}
|
||||
},
|
||||
detail(book) {
|
||||
this.$router.push('/user/book')
|
||||
this.$store.commit('DETAIL', book)
|
||||
},
|
||||
search(key) {
|
||||
this.bookPage = this.bookList.filter((item) => {
|
||||
if (item.bookName.indexOf(key) == 0) return true
|
||||
})
|
||||
this.page.total = this.bookPage.length;
|
||||
},
|
||||
addCart(book) {
|
||||
this.$store.commit('ADDCART', book);
|
||||
this.$message({
|
||||
type: 'success',
|
||||
message: '添加购物车成功'
|
||||
});
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getBookPage();
|
||||
this.getTotal();
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
bookList: (state) => state.bookList,
|
||||
categoryList: (state) => state.categoryList,
|
||||
}),
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.bookList {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
.bookList_box {
|
||||
background: white;
|
||||
}
|
||||
|
||||
.category_list {
|
||||
width: 1150px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.category {
|
||||
font-size: 16px;
|
||||
padding: 10px 30px;
|
||||
line-height: 50px;
|
||||
}
|
||||
|
||||
.category:hover {
|
||||
color: #27ae60;
|
||||
border-bottom: 2px solid #2ecc71;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.active {
|
||||
color: #27ae60;
|
||||
border-bottom: 2px solid #2ecc71;
|
||||
}
|
||||
|
||||
.book_box {
|
||||
width: 1150px;
|
||||
}
|
||||
|
||||
.boox_col {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.item {
|
||||
padding: 10px 15px;
|
||||
border: 2px #eeeeee solid;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.item:hover {
|
||||
box-shadow: 0 0 10px #ddd;
|
||||
}
|
||||
|
||||
.cover {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
.information {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 16px;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
text-align: center;
|
||||
padding: 30px 100px;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<div class="home">
|
||||
<Search @search="search"></Search>
|
||||
<BookList ref="bookList"></BookList>
|
||||
<el-backtop>
|
||||
<svg t="1652548555126" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||
p-id="2205" width="32" height="32">
|
||||
<path
|
||||
d="M528 67.5l-16-16.7-15.9 16.7c-7.3 7.7-179.9 190.6-179.9 420.8 0 112 40 210.1 73.5 272.7l6.2 11.6H627l5.9-13c3.1-6.8 75-167.8 75-271.3 0-230.2-172.6-413.1-179.9-420.8z m-16 48.8c19 22.9 51.9 66.1 82.3 122.5H429.8c30.3-56.4 63.3-99.6 82.2-122.5z m86.3 612.2H422.5c-25.7-50.6-62.2-140.1-62.2-240.2 0-75 20.8-145.5 47.7-205.4h208.2c26.8 59.9 47.6 130.3 47.6 205.4-0.1 78.3-48.7 200.4-65.5 240.2z"
|
||||
fill="#1E59E4" p-id="2206"></path>
|
||||
<path
|
||||
d="M834.7 623.9H643.3l6.7-27.3c9.1-37 13.7-73.4 13.7-108.2 0-44.8-7.7-92-22.9-140.3l-17-54 49.1 28.3c99.8 57.6 161.8 164.7 161.8 279.5v22z m-135.9-44.2h90.9c-5.7-71-38.8-137.2-91.3-184.6 6.3 31.7 9.4 62.9 9.4 93.2 0.1 29.7-3 60.3-9 91.4zM380.1 623.9H189.3v-22.1c0-114.8 62-221.9 161.8-279.5l49.1-28.3-17 54c-15.2 48.3-22.9 95.5-22.9 140.3 0 34.5 4.5 71 13.4 108.4l6.4 27.2z m-145.8-44.2H325c-5.9-31.3-8.8-61.9-8.8-91.4 0-30.3 3.2-61.5 9.4-93.2-52.5 47.5-85.6 113.6-91.3 184.6zM512 529.5c-45 0-81.6-36.6-81.6-81.6s36.6-81.6 81.6-81.6 81.6 36.6 81.6 81.6-36.6 81.6-81.6 81.6z m0-119c-20.7 0-37.5 16.8-37.5 37.5s16.8 37.5 37.5 37.5 37.5-16.8 37.5-37.5-16.8-37.5-37.5-37.5z"
|
||||
fill="#1E59E4" p-id="2207"></path>
|
||||
<path
|
||||
d="M512 999.7l-20.3-20.3c-28.8-28.6-68.3-67.9-68.3-111.6 0-48.9 39.8-88.6 88.6-88.6 48.9 0 88.6 39.8 88.6 88.6 0 43.6-24.4 67.9-64.8 108.2L512 999.7z m0-176.4c-24.5 0-44.5 20-44.5 44.5 0 21.5 23.8 48.4 44.5 69.5 33.6-33.7 44.4-47 44.4-69.5 0.1-24.6-19.9-44.5-44.4-44.5z"
|
||||
fill="#FF5A06" p-id="2208"></path>
|
||||
</svg>
|
||||
</el-backtop>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Search from './search'
|
||||
import BookList from './bookList'
|
||||
export default {
|
||||
name: 'Home',
|
||||
components: {
|
||||
Search,
|
||||
BookList
|
||||
},
|
||||
methods: {
|
||||
search(key) {
|
||||
if (key) {
|
||||
this.$refs.bookList.search(key)
|
||||
} else {
|
||||
this.$message.error('输入不能为空!');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.goTop {
|
||||
height: 100vh;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
</style>
|
After Width: | Height: | Size: 247 KiB |
@ -0,0 +1,105 @@
|
||||
<template>
|
||||
<div class="search">
|
||||
<div class="search_box">
|
||||
<div class="title">Pick Your Perfect Literature</div>
|
||||
<el-form :model="serachForm" class="search_form">
|
||||
<el-form-item class="search_item">
|
||||
<el-row>
|
||||
<el-col :span="21">
|
||||
<el-input v-model="serachForm.key" @keyup.enter.native="search" clearable placeholder="请输入书籍关键字"></el-input>
|
||||
</el-col>
|
||||
<el-col :span="3">
|
||||
<el-button type="success" @click="search" icon="el-icon-search"></el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="hotSearch">
|
||||
<span class="active" @click="detail(book)" v-for="(book,index) in hotSearch"
|
||||
:key="index">{{book.bookName}} </span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
mapState
|
||||
} from "vuex";
|
||||
export default {
|
||||
name: 'Search',
|
||||
data() {
|
||||
return {
|
||||
serachForm: {
|
||||
key: ''
|
||||
},
|
||||
hotSearch: [],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
search() {
|
||||
this.$emit('search',this.serachForm.key);
|
||||
this.serachForm.key = '';
|
||||
},
|
||||
getHotSearch() {
|
||||
this.hotSearch = [];
|
||||
for (let index = 0; index < 4; index++) {
|
||||
let book = this.bookList[Math.floor(Math.random() * this.bookList.length)]
|
||||
this.hotSearch.push(book)
|
||||
}
|
||||
},
|
||||
detail(book) {
|
||||
this.$router.push('/user/book')
|
||||
this.$store.commit('DETAIL', book)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getHotSearch();
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
bookList: (state) => state.bookList,
|
||||
}),
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.search {
|
||||
height: 350px;
|
||||
background: url(./images/beijing.jpg);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.search_box {
|
||||
width: 450px;
|
||||
}
|
||||
|
||||
.title {
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
font-weight: 800;
|
||||
font-size: 20px;
|
||||
letter-spacing: 1.5px;
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.search_item {
|
||||
margin: 0px 0px;
|
||||
}
|
||||
|
||||
.hotSearch {
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
font-size: 15px;
|
||||
padding-top: 15px;
|
||||
}
|
||||
|
||||
.active:hover {
|
||||
color: #ffd04b
|
||||
}
|
||||
</style>
|
@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>管理员首页</h1>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
</script>
|
@ -0,0 +1,140 @@
|
||||
<template>
|
||||
<div class="shoppingCart">
|
||||
<el-card class="shopping_card">
|
||||
<div class="title">
|
||||
<span>购物车</span>
|
||||
<i class="el-icon-shopping-cart-full"></i>
|
||||
</div>
|
||||
<el-table class="shopping_table" :data="cart" border style="width: 100%" @selection-change="selectChange">
|
||||
<el-table-column type="selection" label="全部" width="80" align="center"></el-table-column>
|
||||
<el-table-column prop="cover" label="商品" width="width" align="center">
|
||||
<template slot-scope="{row,$index}">
|
||||
<img :src="row.cover" alt="图片加载错误" class="cover">
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="price" label="单价(元)" width="width" align="center"></el-table-column>
|
||||
<el-table-column prop="number" label="数量" width="width" align="center">
|
||||
<template slot-scope="{row,$index}">
|
||||
<el-button plain icon="el-icon-minus" @click="subNumber(row)" size="mini"></el-button>
|
||||
<span class="number">{{row.number}}</span>
|
||||
<el-button plain icon="el-icon-plus" @click="addNumber(row)" size="mini"></el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="prop" label="小计(元)" width="width" align="center">
|
||||
<template slot-scope="{row,$index}">
|
||||
{{row.number*row.price}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="date" label="操作" width="width" align="center">
|
||||
<template slot-scope="{row,$index}">
|
||||
<el-button type="success" @click="detail(row)">详情</el-button>
|
||||
<el-tooltip effect="light" content="删除商品" placement="right">
|
||||
<el-button type="danger" icon="el-icon-delete" @click="deleteBook(row)"></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="bottom">
|
||||
<el-button type="success" plain @click="pay">结算</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
mapState
|
||||
} from "vuex";
|
||||
export default {
|
||||
name: 'ShoppingCart',
|
||||
data() {
|
||||
return {
|
||||
selectNumber: 0,
|
||||
total: 0,
|
||||
selectBook: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
cart: (state) => state.cart,
|
||||
}),
|
||||
},
|
||||
methods: {
|
||||
selectChange(selection) {
|
||||
this.selectBook = selection;
|
||||
this.selectNumber = selection.length;
|
||||
let total = 0;
|
||||
selection.forEach(item => {
|
||||
total += item.number * item.price
|
||||
});
|
||||
this.total = total;
|
||||
},
|
||||
deleteBook(book) {
|
||||
this.$store.commit('DELETE', book);
|
||||
},
|
||||
detail(book) {
|
||||
this.$router.push('/user/book')
|
||||
this.$store.commit('DETAIL', book)
|
||||
},
|
||||
addNumber(book) {
|
||||
this.$store.commit('ADD', book)
|
||||
},
|
||||
subNumber(book) {
|
||||
if (book.number == 1) {
|
||||
this.deleteBook(book);
|
||||
} else {
|
||||
this.$store.commit('SUB', book)
|
||||
}
|
||||
},
|
||||
pay() {
|
||||
this.$store.commit('PAY', this.selectBook)
|
||||
this.$message({
|
||||
type: 'success',
|
||||
message: '结算成功!',
|
||||
})
|
||||
this.$router.push('/home')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.shoppingCart {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
.shopping_card {
|
||||
margin: 30px 0px;
|
||||
width: 1000px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
padding-left: 10px;
|
||||
padding-bottom: 10px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.cover {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.number {
|
||||
padding: 0px 10px;
|
||||
}
|
||||
|
||||
.bottom {
|
||||
margin-top: 20px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.tips {
|
||||
font-size: 16px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-col :span="12" class="col_box">
|
||||
<div class="title_1">{{title_1}}</div>
|
||||
<div class="title_2">{{title_2}}</div>
|
||||
<div class="title_3">
|
||||
<span class="title_3_1">{{title_3_1}}</span>
|
||||
<span class="title_3_2">{{title_3_2}}</span>
|
||||
</div>
|
||||
<img class="cover" :src="cover">
|
||||
<div>
|
||||
<el-button round class="to_login" @click="change" type="success" plain>{{buttonTitle}}</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Bookmark',
|
||||
props: ['title_1', 'title_2', 'title_3_1', 'title_3_2', 'cover', 'buttonTitle'],
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
change() {
|
||||
this.$emit('change');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.col_box {
|
||||
text-align: center;
|
||||
padding: 30px 0px;
|
||||
}
|
||||
|
||||
.title_1 {
|
||||
line-height: 27px;
|
||||
color: #62C989;
|
||||
font-size: 27px;
|
||||
letter-spacing: 2px;
|
||||
font-family: 'Times New Roman', Times, serif;
|
||||
}
|
||||
|
||||
.title_2 {
|
||||
line-height: 27px;
|
||||
color: #8E9AAF;
|
||||
font-size: 27px;
|
||||
letter-spacing: 2px;
|
||||
font-family: 'Times New Roman', Times, serif;
|
||||
}
|
||||
|
||||
.title_3 {
|
||||
margin-top: 15px;
|
||||
font-size: 15px;
|
||||
letter-spacing: 2px;
|
||||
font-family: 'Times New Roman', Times, serif;
|
||||
}
|
||||
|
||||
.title_3_1 {
|
||||
color: #8E9AAF;
|
||||
}
|
||||
|
||||
.title_3_2 {
|
||||
color: #62C989;
|
||||
}
|
||||
|
||||
.cover {
|
||||
margin-top: 15px;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
|
||||
.to_login {
|
||||
margin-top: 15px;
|
||||
}
|
||||
</style>
|
After Width: | Height: | Size: 718 KiB |
After Width: | Height: | Size: 269 KiB |
After Width: | Height: | Size: 245 KiB |
@ -0,0 +1,92 @@
|
||||
<template>
|
||||
<div class="welcome">
|
||||
<el-card class="background_box">
|
||||
<BookMark ref="login" :title_1="leftTitle.title_1" :title_2="leftTitle.title_2"
|
||||
:title_3_1="leftTitle.title_3_1" :title_3_2="leftTitle.title_3_2" :cover="leftTitle.cover"
|
||||
:buttonTitle="leftTitle.buttonTitle" @change=toLogin>
|
||||
</BookMark>
|
||||
<BookMark ref="register" :title_1="rightTitle.title_1" :title_2="rightTitle.title_2"
|
||||
:title_3_1="rightTitle.title_3_1" :title_3_2="rightTitle.title_3_2" :cover="rightTitle.cover"
|
||||
:buttonTitle="rightTitle.buttonTitle" @change=toRegister>
|
||||
</BookMark>
|
||||
<Login ref="loginRef"></Login>
|
||||
<Register ref="registerRef" @toLogin="toLogin"></Register>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import BookMark from './bookmark'
|
||||
import Login from './login'
|
||||
import Register from './register'
|
||||
export default {
|
||||
name: 'Welcome',
|
||||
components: {
|
||||
BookMark,
|
||||
Login,
|
||||
Register
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
leftTitle: {
|
||||
title_1: 'BOOKS&',
|
||||
title_2: 'LITERARY',
|
||||
title_3_1: 'Pick your perfect ',
|
||||
title_3_2: 'literature',
|
||||
cover: require('./images/cover1.jpg'),
|
||||
buttonTitle: 'SIGN IN'
|
||||
},
|
||||
rightTitle: {
|
||||
title_1: 'BOOKS&',
|
||||
title_2: 'LITERARY',
|
||||
title_3_1: 'Pick your perfect ',
|
||||
title_3_2: 'literature',
|
||||
cover: require('./images/cover2.jpg'),
|
||||
buttonTitle: 'SIGN UP'
|
||||
},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toLogin() {
|
||||
this.$refs.loginRef.show = true
|
||||
this.$refs.registerRef.show = false
|
||||
setTimeout(() => {
|
||||
this.$refs.loginRef.moveStyle = {
|
||||
transform: 'translateX(0px)'
|
||||
}
|
||||
this.$refs.registerRef.moveStyle = {
|
||||
transform: 'translateX(0px)'
|
||||
}
|
||||
}, 10)
|
||||
},
|
||||
toRegister() {
|
||||
this.$refs.loginRef.show = false
|
||||
this.$refs.registerRef.show = true
|
||||
setTimeout(() => {
|
||||
this.$refs.loginRef.moveStyle = {
|
||||
transform: 'translateX(300px)'
|
||||
}
|
||||
this.$refs.registerRef.moveStyle = {
|
||||
transform: 'translateX(300px)'
|
||||
}
|
||||
}, 10)
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.welcome {
|
||||
height: 100%;
|
||||
background: url(./images/beijing.png);
|
||||
}
|
||||
|
||||
.background_box {
|
||||
width: 660px;
|
||||
height: 420px;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
overflow: visible;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,215 @@
|
||||
<template>
|
||||
<div class="login_background" v-show="show" :style="moveStyle">
|
||||
<div class="login_box">
|
||||
<div class="title">登录</div>
|
||||
<el-form
|
||||
class="login_form"
|
||||
:model="loginForm"
|
||||
:rules="loginRules"
|
||||
ref="loginRef"
|
||||
>
|
||||
<el-form-item prop="username">
|
||||
<el-input
|
||||
placeholder="用户名"
|
||||
v-model="loginForm.username"
|
||||
prefix-icon="el-icon-user"
|
||||
clearable
|
||||
>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
show-password
|
||||
clearable
|
||||
v-model="loginForm.password"
|
||||
prefix-icon="el-icon-lock"
|
||||
placeholder="密码"
|
||||
>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="userType">
|
||||
<el-radio-group v-model="loginForm.userType">
|
||||
<el-radio label="user">用户</el-radio>
|
||||
<el-radio label="seller">商家</el-radio>
|
||||
<el-radio label="manager">管理员</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item class="login_button">
|
||||
<el-button type="success" round plain @click="login">登录</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from "vuex";
|
||||
export default {
|
||||
name: "Login",
|
||||
data() {
|
||||
return {
|
||||
loginForm: {
|
||||
username: "",
|
||||
password: "",
|
||||
userType: "user",
|
||||
},
|
||||
loginRules: {
|
||||
username: [
|
||||
{
|
||||
required: true,
|
||||
message: "请输入用户名",
|
||||
trigger: "blur",
|
||||
},
|
||||
],
|
||||
password: [
|
||||
{
|
||||
required: true,
|
||||
message: "请输入密码",
|
||||
trigger: "blur",
|
||||
},
|
||||
],
|
||||
userType: [
|
||||
{
|
||||
required: true,
|
||||
message: "请选择用户类型",
|
||||
trigger: "change",
|
||||
},
|
||||
],
|
||||
},
|
||||
show: true,
|
||||
moveStyle: {},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
username: (state) => state.username,
|
||||
registerUser: (state) => state.registerUser,
|
||||
}),
|
||||
},
|
||||
methods: {
|
||||
login() {
|
||||
this.$refs.loginRef.validate(async (valid) => {
|
||||
if (!valid) return;
|
||||
if (this.username != this.loginForm.username) {
|
||||
this.$store.commit("CLEAR");
|
||||
}
|
||||
|
||||
// 保存当前的 userType
|
||||
const currentUserType = this.loginForm.userType;
|
||||
|
||||
// 存储登录信息
|
||||
this.$store.commit("LOGIN", {
|
||||
...this.loginForm,
|
||||
userType: currentUserType,
|
||||
});
|
||||
|
||||
// 根据用户类型跳转到不同的路由
|
||||
const routeMap = {
|
||||
user: "/user/home",
|
||||
seller: "/seller/home",
|
||||
manager: "/manager/home",
|
||||
};
|
||||
const targetRoute = routeMap[currentUserType] || "/user/home";
|
||||
if (currentUserType == "seller") {
|
||||
if (
|
||||
this.loginForm.username != "seller" ||
|
||||
this.loginForm.password != "123456"
|
||||
) {
|
||||
this.$message({
|
||||
type: "error",
|
||||
message: "用户名或密码错误",
|
||||
});
|
||||
return;
|
||||
}
|
||||
} else if (currentUserType == "manager") {
|
||||
if (
|
||||
this.loginForm.username != "manager" ||
|
||||
this.loginForm.password != "123456"
|
||||
) {
|
||||
this.$message({
|
||||
type: "error",
|
||||
message: "用户名或密码错误",
|
||||
});
|
||||
return;
|
||||
}
|
||||
} else if (currentUserType == "user") {
|
||||
const targetUser = this.registerUser.find((item) => {
|
||||
return item.username == this.loginForm.username;
|
||||
});
|
||||
if (!targetUser) {
|
||||
this.$message({
|
||||
type: "error",
|
||||
message: "该用户未注册",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const target = this.registerUser.find((item) => {
|
||||
return (
|
||||
item.username == this.loginForm.username &&
|
||||
item.password == this.loginForm.password
|
||||
);
|
||||
});
|
||||
if (!target) {
|
||||
this.$message({
|
||||
type: "error",
|
||||
message: "用户名或密码错误",
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.$message({
|
||||
type: "success",
|
||||
message: "登陆成功",
|
||||
});
|
||||
this.$refs["loginRef"]?.resetFields();
|
||||
this.$router.push(targetRoute);
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.login_background {
|
||||
background-color: #89d8a7;
|
||||
border-radius: 7px;
|
||||
width: 320px;
|
||||
height: 500px;
|
||||
position: fixed;
|
||||
margin-top: -60px;
|
||||
box-shadow: 2px 0 10px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.5s;
|
||||
}
|
||||
|
||||
.login_box {
|
||||
padding: 101px 0px;
|
||||
}
|
||||
|
||||
.title {
|
||||
text-align: center;
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
color: #f6f6f6;
|
||||
letter-spacing: 10px;
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
|
||||
.login_form {
|
||||
padding: 0px 30px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.login_button {
|
||||
padding-top: 30px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.el-radio-group {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,164 @@
|
||||
<template>
|
||||
<div class="register_background" v-show="show" :style="moveStyle">
|
||||
<div class="register_box">
|
||||
<div class="title">注册</div>
|
||||
<el-form
|
||||
class="register_form"
|
||||
:model="registerForm"
|
||||
:rules="registerRules"
|
||||
ref="registerRef"
|
||||
>
|
||||
<el-form-item prop="username">
|
||||
<el-input
|
||||
placeholder="用户名"
|
||||
v-model="registerForm.username"
|
||||
prefix-icon="el-icon-user"
|
||||
clearable
|
||||
>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
show-password
|
||||
clearable
|
||||
v-model="registerForm.password"
|
||||
prefix-icon="el-icon-lock"
|
||||
placeholder="密码"
|
||||
>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="confirmPassword">
|
||||
<el-input
|
||||
show-password
|
||||
clearable
|
||||
v-model="registerForm.confirmPassword"
|
||||
prefix-icon="el-icon-lock"
|
||||
placeholder="确认密码"
|
||||
>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item class="register_button">
|
||||
<el-button type="success" round plain @click="register()"
|
||||
>注册</el-button
|
||||
>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from "vuex";
|
||||
export default {
|
||||
name: "Register",
|
||||
data() {
|
||||
var confirmPassword = (rule, value, callback) => {
|
||||
if (value !== this.registerForm.password) {
|
||||
callback(new Error("两次输入密码不一致!"));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
return {
|
||||
registerForm: {
|
||||
username: "",
|
||||
password: "",
|
||||
confirmPassword: "",
|
||||
},
|
||||
registerRules: {
|
||||
username: [
|
||||
{
|
||||
required: true,
|
||||
message: "请输入用户名",
|
||||
trigger: "blur",
|
||||
},
|
||||
],
|
||||
password: [
|
||||
{
|
||||
required: true,
|
||||
message: "请输入密码",
|
||||
trigger: "blur",
|
||||
},
|
||||
],
|
||||
confirmPassword: [
|
||||
{
|
||||
required: true,
|
||||
message: "请再次输入密码",
|
||||
trigger: "blur",
|
||||
},
|
||||
{
|
||||
validator: confirmPassword,
|
||||
trigger: "blur",
|
||||
},
|
||||
],
|
||||
},
|
||||
show: false,
|
||||
moveStyle: {},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(["registerUser"]),
|
||||
},
|
||||
methods: {
|
||||
register() {
|
||||
this.$refs.registerRef.validate(async (valid) => {
|
||||
if (!valid) return;
|
||||
const target = this.registerUser.find((item) => {
|
||||
return item.username == this.registerForm.username;
|
||||
});
|
||||
if (target) {
|
||||
this.$message({
|
||||
type: "error",
|
||||
message: "该用户已注册",
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.$store.commit("REGISTER", this.registerForm);
|
||||
this.$message({
|
||||
type: "success",
|
||||
message: "注册成功",
|
||||
});
|
||||
this.$refs["registerRef"].resetFields();
|
||||
this.$emit("toLogin");
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.register_background {
|
||||
box-shadow: 2px 0 10px rgba(0, 0, 0, 0.1);
|
||||
background-color: #89d8a7;
|
||||
border-radius: 7px;
|
||||
width: 320px;
|
||||
height: 500px;
|
||||
position: fixed;
|
||||
margin-top: -60px;
|
||||
transition: all 0.5s;
|
||||
}
|
||||
|
||||
.title {
|
||||
text-align: center;
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
color: #f6f6f6;
|
||||
letter-spacing: 10px;
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
|
||||
.register_box {
|
||||
padding: 72px 0px;
|
||||
}
|
||||
|
||||
.register_form {
|
||||
padding: 0px 30px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.register_button {
|
||||
padding-top: 30px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,53 @@
|
||||
import Vue from 'vue'
|
||||
import {
|
||||
Button,
|
||||
Row,
|
||||
Col,
|
||||
Form,
|
||||
FormItem,
|
||||
Input,
|
||||
Message,
|
||||
Table,
|
||||
TableColumn,
|
||||
Pagination,
|
||||
Card,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Dropdown,
|
||||
DropdownMenu,
|
||||
DropdownItem,
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
Tag,
|
||||
Backtop,
|
||||
Divider,
|
||||
Tabs,
|
||||
TabPane,
|
||||
Tooltip,
|
||||
} from 'element-ui'
|
||||
|
||||
Vue.use(Button)
|
||||
Vue.use(Row)
|
||||
Vue.use(Col)
|
||||
Vue.use(Form)
|
||||
Vue.use(FormItem)
|
||||
Vue.use(Input)
|
||||
Vue.use(Table)
|
||||
Vue.use(TableColumn)
|
||||
Vue.use(Pagination)
|
||||
Vue.use(Card)
|
||||
Vue.use(Menu)
|
||||
Vue.use(MenuItem)
|
||||
Vue.use(Dropdown)
|
||||
Vue.use(DropdownMenu)
|
||||
Vue.use(DropdownItem)
|
||||
Vue.use(Breadcrumb)
|
||||
Vue.use(BreadcrumbItem)
|
||||
Vue.use(Tag)
|
||||
Vue.use(Backtop)
|
||||
Vue.use(Divider)
|
||||
Vue.use(Tabs)
|
||||
Vue.use(TabPane)
|
||||
Vue.use(Tooltip)
|
||||
|
||||
Vue.prototype.$message = Message
|
@ -0,0 +1,103 @@
|
||||
import VueRouter from "vue-router";
|
||||
import Vue from "vue";
|
||||
// import ManageLayout from "@/components/manageLayout";
|
||||
import UserLayout from "@/components/userLayout/index.vue";
|
||||
import SellerLayout from "@/components/sellerLayout/index.vue";
|
||||
Vue.use(VueRouter);
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: "/",
|
||||
redirect: "/welcome",
|
||||
},
|
||||
{
|
||||
path: "/welcome",
|
||||
name: "Welcome",
|
||||
meta: { show: true },
|
||||
component: () => import("@/pages/welcome"),
|
||||
},
|
||||
{
|
||||
path: "/user",
|
||||
component: () => import("@/components/userLayout/index.vue"),
|
||||
redirect: "/user/home",
|
||||
children: [
|
||||
{
|
||||
path: "/user/home",
|
||||
name: "Home",
|
||||
component: () => import("@/pages/home"),
|
||||
},
|
||||
{
|
||||
path: "/user/book",
|
||||
name: "Book",
|
||||
component: () => import("@/pages/book"),
|
||||
},
|
||||
{
|
||||
path: "/user/shoppingCart",
|
||||
name: "ShoppingCart",
|
||||
component: () => import("@/pages/shoppingCart"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/seller",
|
||||
name: "Seller",
|
||||
component: () => import("@/components/sellerLayout/index.vue"),
|
||||
redirect: "/seller/home",
|
||||
children: [
|
||||
{
|
||||
path: "/seller/home",
|
||||
name: "Home",
|
||||
component: () => import("@/views/index.vue"),
|
||||
},
|
||||
{
|
||||
path: "/seller/book-management",
|
||||
name: "BookManagement",
|
||||
component: () => import("@/views/BookManagement.vue"),
|
||||
},
|
||||
{
|
||||
path: "/seller/order-management",
|
||||
name: "OrderManagement",
|
||||
component: () => import("@/views/OrderManagement.vue"),
|
||||
},
|
||||
{
|
||||
path: "/seller/customer-feedback",
|
||||
name: "CustomerFeedback",
|
||||
component: () => import("@/views/CustomerFeedback.vue"),
|
||||
},
|
||||
{
|
||||
path: "/seller/promotion-management",
|
||||
name: "PromotionManagement",
|
||||
component: () => import("@/views/PromotionManagement.vue"),
|
||||
},
|
||||
{
|
||||
path: "/seller/data-analytics",
|
||||
name: "DataAnalytics",
|
||||
component: () => import("@/views/DataAnalytics.vue"),
|
||||
},
|
||||
{
|
||||
path: "/seller/merchant-info",
|
||||
name: "MerchantInfo",
|
||||
component: () => import("@/views/MerchantInfo.vue"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/manager",
|
||||
name: "Manager",
|
||||
component: () => import("@/components/managerLayout/index.vue"),
|
||||
redirect: "/manager/home",
|
||||
children: [
|
||||
{
|
||||
path: "/manager/home",
|
||||
name: "Home",
|
||||
component: () => import("@/pages/manager/home"),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const router = new VueRouter({
|
||||
routes,
|
||||
});
|
||||
|
||||
export default router;
|
@ -0,0 +1,15 @@
|
||||
let categoryList = [
|
||||
'全部分类',
|
||||
'文学',
|
||||
'教育',
|
||||
'绘画',
|
||||
'摄影',
|
||||
'历史',
|
||||
'哲学',
|
||||
'科学',
|
||||
'语言',
|
||||
'军事',
|
||||
'医药',
|
||||
]
|
||||
|
||||
export default categoryList;
|
@ -0,0 +1,82 @@
|
||||
import Vuex from "vuex";
|
||||
import Vue from "vue";
|
||||
import VuexPersist from "vuex-persist";
|
||||
import bookList from "./bookList";
|
||||
import categoryList from "./categoryList";
|
||||
import { Message } from "element-ui";
|
||||
Vue.use(Vuex);
|
||||
export default new Vuex.Store({
|
||||
state: {
|
||||
username: "",
|
||||
bookList: bookList,
|
||||
categoryList: categoryList,
|
||||
book: {},
|
||||
cart: [],
|
||||
registerUser: [],
|
||||
},
|
||||
mutations: {
|
||||
REGISTER(state, { username, password }) {
|
||||
state.registerUser.push({
|
||||
username,
|
||||
password,
|
||||
});
|
||||
},
|
||||
LOGIN(state, { username }) {
|
||||
state.username = username;
|
||||
},
|
||||
DETAIL(state, book) {
|
||||
state.book = book;
|
||||
},
|
||||
ADDCART(state, book) {
|
||||
if (state.cart.length == 0) {
|
||||
Vue.set(book, "number", 1);
|
||||
state.cart.push(book);
|
||||
return;
|
||||
}
|
||||
for (let index = 0; index < state.cart.length; index++) {
|
||||
if (state.cart[index].bookName == book.bookName) {
|
||||
state.cart[index].number++;
|
||||
return;
|
||||
}
|
||||
}
|
||||
Vue.set(book, "number", 1);
|
||||
state.cart.push(book);
|
||||
},
|
||||
ADD(state, book) {
|
||||
state.cart.forEach((item) => {
|
||||
if (item.bookName == book.bookName) item.number++;
|
||||
});
|
||||
},
|
||||
SUB(state, book) {
|
||||
state.cart.forEach((item) => {
|
||||
if (item.bookName == book.bookName) item.number--;
|
||||
});
|
||||
},
|
||||
DELETE(state, book) {
|
||||
state.cart = state.cart.filter((item) => {
|
||||
if (item.bookName != book.bookName) return true;
|
||||
});
|
||||
},
|
||||
PAY(state, selectBook) {
|
||||
state.cart = state.cart.filter((item) => {
|
||||
let flag = false;
|
||||
for (let index = 0; index < selectBook.length; index++) {
|
||||
if (item.bookName == selectBook[index].bookName) {
|
||||
flag = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (flag) return false;
|
||||
else return true;
|
||||
});
|
||||
},
|
||||
CLEAR(state) {
|
||||
state.cart = [];
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
new VuexPersist({
|
||||
storage: window.localStorage,
|
||||
}).plugin,
|
||||
],
|
||||
});
|
@ -0,0 +1,225 @@
|
||||
<template>
|
||||
<div class="book-management">
|
||||
<!-- 页面头部 -->
|
||||
<header class="header">
|
||||
<h2>图书管理</h2>
|
||||
<button @click="showAddForm = true" class="btn add-btn">添加图书</button>
|
||||
</header>
|
||||
|
||||
<!-- 添加图书表单弹窗 -->
|
||||
<div v-if="showAddForm" class="modal-overlay">
|
||||
<div class="form-modal">
|
||||
<h3>{{ editMode ? '编辑图书' : '添加图书' }}</h3>
|
||||
<form @submit.prevent="handleSubmit">
|
||||
<label for="bookTitle">图书标题</label>
|
||||
<input id="bookTitle" v-model="bookForm.title" type="text" required />
|
||||
|
||||
<label for="bookAuthor">作者</label>
|
||||
<input id="bookAuthor" v-model="bookForm.author" type="text" required />
|
||||
|
||||
<label for="bookPrice">价格</label>
|
||||
<input id="bookPrice" v-model="bookForm.price" type="number" required />
|
||||
|
||||
<label for="bookQuantity">库存</label>
|
||||
<input id="bookQuantity" v-model="bookForm.quantity" type="number" required />
|
||||
|
||||
<div class="form-buttons">
|
||||
<button type="submit" class="btn confirm-btn">{{ editMode ? '确认修改' : '确认添加' }}</button>
|
||||
<button type="button" class="btn cancel-btn" @click="closeForm">取消</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 图书列表 -->
|
||||
<div class="book-list">
|
||||
<div class="book-card" v-for="book in books" :key="book.id">
|
||||
<div class="book-details">
|
||||
<h3>{{ book.title }}</h3>
|
||||
<p>作者: {{ book.author }}</p>
|
||||
<p>价格: ¥{{ book.price }}</p>
|
||||
<p>库存: {{ book.quantity }}</p>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<button @click="editBook(book)" class="btn edit-btn">编辑</button>
|
||||
<button @click="deleteBook(book.id)" class="btn delete-btn">删除</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
|
||||
// 图书数据
|
||||
const books = ref([
|
||||
{ id: 1, title: 'Vue.js 实战', author: '李白', price: 59, quantity: 100 },
|
||||
{ id: 2, title: 'React 快速入门', author: '张三', price: 45, quantity: 200 },
|
||||
]);
|
||||
|
||||
// 控制表单的显示状态
|
||||
const showAddForm = ref(false);
|
||||
const editMode = ref(false);
|
||||
|
||||
// 新增或编辑图书的表单数据
|
||||
const bookForm = ref({ title: '', author: '', price: 0, quantity: 0 });
|
||||
|
||||
// 关闭表单
|
||||
const closeForm = () => {
|
||||
showAddForm.value = false;
|
||||
bookForm.value = { title: '', author: '', price: 0, quantity: 0 };
|
||||
editMode.value = false;
|
||||
};
|
||||
|
||||
// 添加图书或编辑图书
|
||||
const handleSubmit = () => {
|
||||
if (editMode.value) {
|
||||
const index = books.value.findIndex(book => book.id === bookForm.value.id);
|
||||
books.value[index] = { ...bookForm.value };
|
||||
} else {
|
||||
books.value.push({
|
||||
id: Date.now(),
|
||||
title: bookForm.value.title,
|
||||
author: bookForm.value.author,
|
||||
price: bookForm.value.price,
|
||||
quantity: bookForm.value.quantity,
|
||||
});
|
||||
}
|
||||
closeForm();
|
||||
};
|
||||
|
||||
// 编辑图书
|
||||
const editBook = (book) => {
|
||||
bookForm.value = { ...book };
|
||||
editMode.value = true;
|
||||
showAddForm.value = true;
|
||||
};
|
||||
|
||||
// 删除图书
|
||||
const deleteBook = (id) => {
|
||||
books.value = books.value.filter(book => book.id !== id);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.book-management {
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.header h2 {
|
||||
font-size: 2rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.book-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.book-card {
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
padding: 20px;
|
||||
width: 240px;
|
||||
transition: transform 0.3s, box-shadow 0.3s;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.book-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.book-details h3 {
|
||||
font-size: 1.2rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.card-actions button {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.form-modal {
|
||||
background-color: white;
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
width: 400px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.form-modal input {
|
||||
width: 100%;
|
||||
padding-left: 10px;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
padding-right: 0px;
|
||||
margin-bottom: 15px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.form-buttons {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 10px 20px;
|
||||
font-size: 1rem;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
background-color: #1abc9c;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.edit-btn {
|
||||
background-color: #3498db;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
background-color: #e74c3c;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.confirm-btn {
|
||||
background-color: #27ae60;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.cancel-btn {
|
||||
background-color: #95a5a6;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 999;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,200 @@
|
||||
<template>
|
||||
<div class="customer-feedback">
|
||||
<header class="header">
|
||||
<h1>客户反馈</h1>
|
||||
<p>查看客户的反馈内容并及时处理。</p>
|
||||
</header>
|
||||
|
||||
<div class="toolbar">
|
||||
<input
|
||||
type="text"
|
||||
v-model="searchQuery"
|
||||
class="search-bar"
|
||||
placeholder="搜索反馈..."
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="filteredFeedbacks.length > 0" class="feedback-list">
|
||||
<div
|
||||
class="feedback-card"
|
||||
v-for="feedback in filteredFeedbacks"
|
||||
:key="feedback.id"
|
||||
>
|
||||
<div class="feedback-info">
|
||||
<h3>反馈人: {{ feedback.name }}</h3>
|
||||
<p>反馈内容: {{ feedback.content }}</p>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<button class="btn delete-btn" @click="deleteFeedback(feedback.id)">删除</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="empty-state">
|
||||
<p>没有找到匹配的反馈。</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
|
||||
// 数据声明
|
||||
const feedbacks = ref([]);
|
||||
const searchQuery = ref('');
|
||||
|
||||
// 模拟加载反馈数据
|
||||
const loadFeedbackData = () => {
|
||||
feedbacks.value = [
|
||||
{ id: 1, name: '张三', content: '非常满意!服务很周到。' },
|
||||
{ id: 2, name: '李四', content: '产品质量不错,但物流有点慢。' },
|
||||
{ id: 3, name: '王五', content: '客服态度很好,问题解决得很快。' },
|
||||
];
|
||||
};
|
||||
|
||||
// 搜索过滤
|
||||
const filteredFeedbacks = computed(() =>
|
||||
feedbacks.value.filter(
|
||||
(feedback) =>
|
||||
feedback.name.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
|
||||
feedback.content.toLowerCase().includes(searchQuery.value.toLowerCase())
|
||||
)
|
||||
);
|
||||
|
||||
// 删除反馈
|
||||
const deleteFeedback = (id) => {
|
||||
feedbacks.value = feedbacks.value.filter((feedback) => feedback.id !== id);
|
||||
};
|
||||
|
||||
// 生命周期钩子
|
||||
onMounted(() => {
|
||||
loadFeedbackData();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 全局背景和字体设置 */
|
||||
body {
|
||||
font-family: 'Roboto', Arial, sans-serif;
|
||||
background: linear-gradient(135deg, #f0f8ff, #e0f7fa);
|
||||
margin: 0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.customer-feedback {
|
||||
padding: 20px;
|
||||
max-width: 900px;
|
||||
margin: 40px auto;
|
||||
background: white;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
/* 页头样式 */
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 2.5rem;
|
||||
color: #007bff;
|
||||
margin-bottom: 10px;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.header p {
|
||||
font-size: 1rem;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
/* 工具栏样式 */
|
||||
.toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
flex: 1;
|
||||
padding: 10px 15px;
|
||||
font-size: 1rem;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 30px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
transition: box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
.search-bar:focus {
|
||||
box-shadow: 0 4px 8px rgba(0, 123, 255, 0.3);
|
||||
border-color: #007bff;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 30px;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease, box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
/* 反馈列表样式 */
|
||||
.feedback-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.feedback-card {
|
||||
background: #ffffff;
|
||||
border-radius: 15px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
.feedback-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.feedback-info h3 {
|
||||
margin: 0;
|
||||
font-size: 1.2rem;
|
||||
color: #343a40;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.feedback-info p {
|
||||
margin: 0;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.card-actions {
|
||||
margin-top: 15px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
background-color: #e74c3c;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.delete-btn:hover {
|
||||
background-color: #c0392b;
|
||||
box-shadow: 0 4px 8px rgba(231, 76, 60, 0.2);
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
color: #6c757d;
|
||||
font-size: 1.2rem;
|
||||
margin-top: 30px;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,5 @@
|
||||
const { defineConfig } = require('@vue/cli-service')
|
||||
module.exports = defineConfig({
|
||||
transpileDependencies: true,
|
||||
lintOnSave: false,
|
||||
})
|