user1
张新星 8 months ago
parent ef19f0f263
commit 10ac4be007

8
.idea/.gitignore vendored

@ -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>

23
UML/.gitignore vendored

@ -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"
]
}
}

11439
UML/package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -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"
]
}

Binary file not shown.

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>

@ -0,0 +1,79 @@
/* 清除内外边距 */
body, h1, h2, h3, h4, h5, h6, hr, p, blockquote,
dl, dt, dd, ul, ol, li,
pre,
fieldset, lengend, button, input, textarea,
th, td {
margin: 0;
padding: 0;
}
html,body,#app {
height: 100%;
}
/* 设置默认字体 */
body,
button, input, select, textarea { /* for ie */
/*font: 12px/1 Tahoma, Helvetica, Arial, "宋体", sans-serif;*/
font: 12px/1.3 "Microsoft YaHei",Tahoma, Helvetica, Arial, "\5b8b\4f53", sans-serif; /* 用 ascii 字符表示,使得在任何编码下都无问题 */
color: #333;
}
h1 { font-size: 18px; /* 18px / 12px = 1.5 */ }
h2 { font-size: 16px; }
h3 { font-size: 14px; }
h4, h5, h6 { font-size: 100%; }
address, cite, dfn, em, var, i{ font-style: normal; } /* 将斜体扶正 */
b, strong{ font-weight: normal; } /* 将粗体扶细 */
code, kbd, pre, samp, tt { font-family: "Courier New", Courier, monospace; } /* 统一等宽字体 */
small { font-size: 12px; } /* 小于 12px 的中文很难阅读,让 small 正常化 */
/* 重置列表元素 */
ul, ol { list-style: none; }
/* 重置文本格式元素 */
a { text-decoration: none; color: #666;}
/* 重置表单元素 */
legend { color: #000; } /* for ie6 */
fieldset, img { border: none; }
button, input, select, textarea {
font-size: 100%; /* 使得表单元素在 ie 下能继承字体大小 */
}
/* 重置表格元素 */
table {
border-collapse: collapse;
border-spacing: 0;
}
/* 重置 hr */
hr {
border: none;
height: 1px;
}
.clearFix::after{
content:"";
display: block;
clear:both;
}
/* 让非ie浏览器默认也显示垂直滚动条防止因滚动条引起的闪烁 */
html { overflow-y: scroll; }
a:link:hover{
color : rgb(79, 76, 212) !important;
text-decoration: underline;
}
/* 清除浮动 */
.clearfix::after {
display: block;
height: 0;
content: "";
clear: both;
visibility: hidden;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 334 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 394 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 241 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 322 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 KiB

Binary file not shown.

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>&copy;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,59 @@
<!--src/components/Header.vue-->
<template>
<header class="header">
<div class="logo">
<h1>商家管理系统</h1>
</div>
<nav>
<ul class="nav-links">
<li><router-link to="/seller/home">首页</router-link></li>
<li><router-link to="/seller/book-management">图书管理</router-link></li>
<li><router-link to="/seller/order-management">订单管理</router-link></li>
<li><router-link to="/seller/promotion-management">促销活动</router-link></li>
<li><router-link to="/seller/customer-feedback">客户反馈</router-link></li>
<li><router-link to="/seller/data-analytics">数据分析</router-link></li>
<li><router-link to="/seller/merchant-info">商家信息</router-link></li>
</ul>
</nav>
</header>
</template>
<script setup>
//
</script>
<style scoped>
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 20px;
background-color: #1abc9c;
color: white;
}
.logo h1 {
margin: 0;
}
.nav-links {
list-style-type: none;
margin: 0;
padding: 0;
display: flex;
}
.nav-links li {
margin: 0 10px;
}
.nav-links li a {
text-decoration: none;
color: white;
font-size: 16px;
}
.nav-links li a:hover {
color: #333;
}
</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,102 @@
<template>
<div class="book">
<el-card class="book_card">
<el-divider content-position="left">{{book.bookName}}</el-divider>
<el-row>
<el-col :span="5">
<img :src="book.cover" alt="图片加载出错" class="cover">
</el-col>
<el-col :span="19">
<div class="information">类型{{book.type}}</div>
<div class="information">作者{{book.author}}</div>
<div class="information">价格{{book.price}}</div>
<div class="information">库存32</div>
</el-col>
</el-row>
<el-card class="description_box">
<div class="title">
内容简介
</div>
<div class="description">
{{book.description}}
</div>
</el-card>
<el-button type="success" class="shoppingCart" @click="addCart" plain icon="el-icon-shopping-cart-2">加入购物车</el-button>
</el-card>
</div>
</template>
<script>
import {
mapState
} from "vuex";
export default {
name: 'Book',
data() {
return {
}
},
methods: {
addCart() {
this.$store.commit('ADDCART',this.book)
this.$message({
type:'success',
message:'添加购物车成功'
});
}
},
computed: {
...mapState({
book: (state) => state.book,
}),
},
}
</script>
<style scoped>
.book {
background-color: #eee;
display: flex;
justify-content: center;
}
.book_card {
margin: 30px 0px;
width: 900px;
}
.el-divider__text {
font-size: 20px;
font-weight: 600;
}
.cover {
width: 100%;
}
.information {
font-size: 16px;
padding: 16px 0px;
padding-left: 25px;
}
.description_box {
margin-top: 30px;
}
.description {
margin-top: 20px;
font-size: 15px;
line-height: 30px;
}
.title {
font-size: 20px;
color: #27ae60;
}
.shoppingCart {
margin: 25px 0px;
float: right;
}
</style>

@ -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>

Binary file not shown.

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}}&nbsp;&nbsp;&nbsp;</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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 718 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 KiB

Binary file not shown.

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,192 @@
let bookList = [{
cover: require('../assets/images/1.png'),
bookName: '三体',
author: '刘慈欣',
price: 50,
type: '文学',
description: '《三体》的幻想源于经典物理中的三体问题,即三个体积质量相当的天体,在远离其它星系以致其它星系的引力影响可忽略不计的情况下,三个天体在互相引力 的作用下互相围绕运行,其运行轨迹将产生不可预测的混沌。很多年来,数学家们一直希望能建立三体乃至多体问题的数学模型,可遗憾的是,得到的结果仅仅是三 体问题在非限制条件下的不可解。'
}, {
cover: require('../assets/images/2.png'),
bookName: '云边有个小卖部',
author: '张嘉佳',
price: 28.7,
type: '文学',
description: '1. 张嘉佳全新作品。畅销千万现象级作品《从你的全世界路过》后暌违五年写给离开我们的人写给陪伴我们的人写给每个人心中的山和海。2. 精美手绘插画。陪你度过云边镇的日与夜。3. 惊喜彩蛋。张嘉佳作词单曲《刘十三》带你来到书中现场。4. 希望和悲伤,都是一缕光。总有一天,我们会再相遇。'
}, {
cover: require('../assets/images/3.png'),
bookName: '我们仨',
author: '杨绛',
price: 29.8,
type: '文学',
description: '《我们仨》是钱钟书夫人杨绛撰写的家庭生活回忆录。1998年钱钟书逝世而他和杨绛专享的女儿钱瑗已于此前1997年先他们而去。在人生的伴侣离去四年后杨绛在92岁高龄的时候用心记述了他们这个特殊家庭63年的风风雨雨、点点滴滴结成回忆录《我们仨》。'
}, {
cover: require('../assets/images/4.png'),
bookName: '教养在生活的细节里',
author: '洪兰',
price: 39,
type: '教育',
description: '《教养在生活的细节里》洪兰与蔡颖卿对父母、老师关注的27个典型问题进行了温柔而智慧的对谈我们从中可以看到教养不是方法论而是生活本身大人与孩子相处的分分秒秒里都是对孩子潜移默化的教育。关注细节用平等、尊重、温柔、爱、智慧与孩子相处彼此关照共同成长。'
}, {
cover: require('../assets/images/5.png'),
bookName: '教育常识',
author: '李政涛',
price: 36,
type: '教育',
description: '教育常识的原点,就是人性常识,也就是对人的天性的认识。衡量一位教师和教育理论研究者是否进入了智慧的境界,不仅要看他有没有与教育有关的实践智慧和思想智慧,更要看他有没有直捣人心、洞察人性的智慧,有没有形成自己独特的对人性的理解。'
}, {
cover: require('../assets/images/6.png'),
bookName: '博赞儿童思维导图',
author: '东尼·博赞',
price: 60,
type: '教育',
description: ' 描述孩子成长过程中、在理想状态下会喜欢的东西,帮助孩子完全开发自己思维、身体和精神的潜能。只要给予孩子们合适的激励和正确的培养环境,每一个孩子都可以成为非凡的天才。而重要的是,他们一定会成长为一个内心丰富而幸福的人。这是一本能够带给所有孩子成功与幸福的书,也是给予家长的教育指南!'
}, {
cover: require('../assets/images/7.png'),
bookName: '花卉动物速写描摹本',
author: '孔祥彦',
price: 50,
type: '绘画',
description: '零基础画画入门新手自学教程书籍线描花卉临摹手绘初学者学绘画教材铅笔画素描速写画稿美院名师范画,具有各种花卉图片供给临摹和观赏。'
}, {
cover: require('../assets/images/8.png'),
bookName: 'A Bigger Book',
author: 'Hockney',
price: 95,
type: '绘画',
description: '这本SUMO出版的《A Bigger Book》是对David Hockney艺术的一次宏大的视觉考察。在这本书中Hockney回顾了他60多年的作品从他十几岁在艺术学校的日子到他最近大量的肖像画、iPad绘画和约克郡风景画系列。Hockney的作品从来没有以如此大的范围、如此大的投入、如此惊人的规模出版。'
}, {
cover: require('../assets/images/9.png'),
bookName: '绘画之道',
author: 'Susan Herbert',
price: 91,
type: '绘画',
description: '猫咪以沉思沉思的表情完美地重塑了印象派大师的伟大作品无论是漫步在克劳德·莫奈的野生罂粟丛中坐在玛丽·卡萨特的歌剧中还是在奥古斯特·雷诺阿的Bougival享受周日舞蹈。他们可能会陷入沉闷绝望低俗或母亲资产阶级或知识分子的迷人或沉迷但它们总是印象派的猫自发地毫无准备地被照相机抓住。印象派猫是艺术猫系列中的绝妙补充一定会吸引任何猫迷。'
}, {
cover: require('../assets/images/10.png'),
bookName: '摄影的艺术',
author: '杨建飞',
price: 49.9,
type: '摄影',
description: '这部著作被认为是最具可读性、最易理解和最全面的摄影教科书。现在它已经更新了数码摄影的内容。这本书有超过100幅黑白和彩色摄影的插图还有各种图表可以帮助初学者、进阶者乃至高级摄影师找到自己的摄影表达方式。Bruce没有故作高深也没有以高人一等的姿态进行论述而是以清晰、简明、易懂的方式介绍了数码和传统摄影的创作技术。他还超越了技术层面深入地讨论了有关摄影的哲学、表达方式以及创意的内容这往往是其他摄影书避而不谈的内容。'
}, {
cover: require('../assets/images/11.png'),
bookName: '摄影基础教程',
author: '杨建飞',
price: 108,
type: '摄影',
description: '数码单反手机摄影书 人物风景静物艺术旅行婚礼儿童成长记录摄影构图取景光线后期处理摄影学院级入门自学教材,本教材着重于艺术角度的阐释。此外,随着近年来 数码摄影的崛起,本教材也顺应了这一趋势,偏重在数码摄影方面,偶尔涉及传统的胶片摄影,也是为了做比较之用。编写过程中,参考了很多前辈的相关著述,也选取了许多著名摄影师的作品作为图例。'
}, {
cover: require('../assets/images/12.png'),
bookName: '今日摄影',
author: '马克·德登',
price: 45.8,
type: '摄影',
description: '作为一名拥有多重学术背景作者系艺术学士艺术史与理论硕士以及摄影研究博士的作家和艺术家马克·德登Mark Durden对摄影写作的兴趣并不仅仅局限于上述某一类的讨论之上 而是以其丰富的知识结构作为依托,将当代艺术摄影、摄影理论和哲学的复杂话题融汇贯通于摄影史的书写之中。《今日摄影》全书以十一个主题章节作为框架,包含了街拍、肖像、风景和纪实摄影等相关主题,重现了摄影在每个类别中的非线性发展过程,同时通过介绍各个摄影类别之间的联系,描述了这一时期重要艺术家和摄影师多样的实践方法和形式。'
}, {
cover: require('../assets/images/13.png'),
bookName: '中国通史',
author: '中国通史',
price: 128,
type: '历史',
description: '傅乐成《中国通史》上起旧石器时代下讫1912年清帝退位台湾版还包括《民国的忧患》和《中国的现代化》两章大陆版则无凡六十余万言。文字浅近平易不做繁征博引叙说清晰见解持正数千年中国史事之此伏彼起重要节点前因后果俱在目前。*初由台湾大中国图书公司于1960年出版1968年增订1977年再次增订。自增订以来已出版近四十次在台湾出版的中国通史之中被大家公认为很好的一部作品。'
}, {
cover: require('../assets/images/14.png'),
bookName: '春秋战国',
author: '梁启超',
price: 56,
type: '历史',
description: '《春秋战国:争霸九州》内容简介:春秋时代,大国争霸,小国争胜,强者存,弱者亡。善良和邪恶明争暗斗,英雄和美女惺惺相惜。作者提炼古今史料,以写实的手法,生动地描绘了齐桓公、宋襄公、晋文公、秦穆公、楚庄王五位春秋霸主励精图治,广招贤才,争霸天下的雄略,塑造了管仲、鲍叔牙、百里奚、孙叔敖等人满腹谋略,勤政爱民的光辉形象,同时也刻画了卫姬、隗后、弄玉、樊姬等一系列宫廷女性的不同命运……《春秋战国(上卷)争霸九州》以其宏大的结构,深刻的内涵,艺术地再现了这个被视为中国历史上最具活力、最伟大的时代。'
}, {
cover: require('../assets/images/15.png'),
bookName: '中国史纲',
author: '张荫麟',
price: 99,
type: '历史',
description: '文辞优美,能给读者带来阅读享受的历史作品:别人写历史都引用史籍,张荫麟却不囿于史籍,他甚至把《诗经》《楚辞》《论语》词句用的神出鬼没,给一部严肃的学术作品,披上了华丽丽的文学外衣,使得这部《中国史纲》华彩奕奕。'
}, {
cover: require('../assets/images/16.png'),
bookName: '格局',
author: '何权峰',
price: 36,
type: '哲学',
description: '格局指一个人的眼界、胸襟、胆识等心理要素的内在布局。再大的烙饼也大不过烙它的锅。只会盯着树皮里的虫子不放的鸟儿是不可能飞到白云之上的,只有眼里和心中装满了山河天地的雄鹰才能自由地在天地间翱翔!'
}, {
cover: require('../assets/images/17.png'),
bookName: '了凡四训',
author: '袁了凡',
price: 24,
type: '哲学',
description: '《了凡四训》为明代袁了凡所著训子善书,阐明“命自我立,福自己求”的思想,指出一切祸福休咎皆自当人掌握,行善则积福,作恶则招祸;并现身说法,结合儒释道三家思想以自身经历体会阐明此理,鼓励向善立身,慎独立品,自求多福,远避祸殃。该书自明末以来流行甚广,影响较大,此白话绘图本的出版当有助于阅读了解,于个人品德修养与世道人心改善或许不无小补。'
}, {
cover: require('../assets/images/18.png'),
bookName: '宇宙',
author: '卡尔·萨根',
price: 68,
type: '科学',
description: ' 宇宙一直是这个样子,过去如此,将来也如此。对宇宙不够有力的思考唤醒我们对周围的感觉——脊柱传来的一阵刺痛,捕捉到的一个声音,好似来自遥远的记忆,我们常常有一种从高空坠落的微弱感觉。我们知道我们正接近那个最终的秘密。'
}, {
cover: require('../assets/images/19.png'),
bookName: '认识星座',
author: '阿斯达',
price: 268,
type: '科学',
description: '星座是占星学中不可或缺的组成部分之一,由天上的恒星组合而成。星座是占星学中不可或缺的组成部分之一,由天上的恒星组合而成。在西方文化中,星座的地位几乎相当于生肖在我国的地位,就像我们利用自己的生辰测算自己一生的运数一样,西方世界则利用星座来测算吉凶。”'
}, {
cover: require('../assets/images/20.png'),
bookName: '学会表达',
author: '丁艳丽',
price: 88,
type: '语言',
description: '卡耐基魅力口才与说话技巧提高情商语言能力高情商训练图书籍,不能完全表达言者的本意,要透过语言揣摩本质,也要提高表达能力,能让自己表达得更准确完备。自从明确地提出:“要会表达”。'
}, {
cover: require('../assets/images/21.png'),
bookName: '高情商聊天术',
author: '张超',
price: 36.8,
type: '语言',
description: '让你一开口就能说到对方心里去 有礼有节才能说话周全口才说话技巧书籍畅销书 职场生活口才训练书好好说话情商99%的人际问题出在不会说话上,本书教你做一个真正会聊天的人!听说两种能力训练+3大场景模拟对话+10年表达经验锤炼。影响说服沟通谈判谈笑间搞定'
}, {
cover: require('../assets/images/22.png'),
bookName: '口才三绝',
author: '歌诗达',
price: 158,
type: '语言',
description: '本书是一剂向大家介绍提高口才能力和说话技巧的良方!主要从口才必备的三个方面来阐述提升口才的技巧:第一篇主要介绍了敏捷的思维是措辞的关键;第二篇主要阐述了赞美的话要说到点子上;第三篇与读者一起分享了不同场景下与不同人沟通、交流、说话的技巧等。 本书口才的基础入手,将科学性与技巧性、理论指导与实际场景融于一体。只要你精心阅读,坚持训练,本书定会逐渐引导你走向捷径,最后轻松带你步入成功口才的殿堂。'
}, {
cover: require('../assets/images/23.png'),
bookName: '孙子兵法',
author: '孙武',
price: 49.8,
type: '军事',
description: '《孙子兵法》是中国古代汉族军事文化遗产中的璀璨瑰宝汉族优秀传统文化的重要组成部分其内容博大精深思想精邃富赡逻辑缜密严谨是古代汉族军事思想精华的集中体现。作者为春秋时祖籍齐国乐安的吴国将军孙武。《孙子兵法》被称为镇国之宝在中国被奉为兵家经典。诞生至今已有2500年历史历代都有研究。李世民说“观诸兵书无出孙武”。兵法是谋略谋略不是小花招而是大战略、大智慧。如今孙子兵法已经走向世界。它也被翻译成多种语言在世界军事史上也具有重要的地位。'
}, {
cover: require('../assets/images/24.png'),
bookName: '世界战列舰全史',
author: '罗伯特',
price: 68,
type: '军事',
description: '作为曾经辉煌的日不落帝国,英国依靠强大的皇家海军控制着整个海洋。进入铁甲蒸汽时代之后,拥有大口径主炮、厚重装甲和高功率发动机的战列舰成为英国海上力量的象征。在战列舰建造领域,英国不断推出具有跨时代意义的作品,其中包括“君权”级、“无畏”号、“伊丽莎白女王”级等,这些强大的战舰成为引领世界海军发展的风向标。'
}, {
cover: require('../assets/images/25.png'),
bookName: '本草纲目',
author: '李时珍',
price: 48,
type: '医药',
description: ' 《本草纲目》是李时珍所著的集中国16世纪前本草学大成的中医典籍属《四库全书》子部医家类。该典籍于明代万历六年定稿万历二十四年在南京正式刊行。《本草纲目》共有52卷载有药物1892种其中载有新药374种收集医方11096个约190万字分为16部、60类。'
}, {
cover: require('../assets/images/26.png'),
bookName: '奇效偏方',
author: '李春深',
price: 68,
type: '医药',
description: '古人说用药如用兵,如果把那些出于名医之手和载入各种医典之中的医药方剂视为正规部队就可以把偏方看作是游击队,同样为病人解除痛苦。偏方是原生态,是现代方剂和新生药物取之不尽的源泉,是祖国医药宝库中光彩夺目的明珠。'
}, {
cover: require('../assets/images/27.png'),
bookName: '中医急诊学',
author: '姜良铎',
price: 78,
type: '医药',
description: '是新中国成立以来第yi次对中医急诊学科发展进行的全面、系统总结。 《中医急诊学(精)》由上篇总论、下篇各论和附篇三部分组成。首次梳理了中医急诊科学术发展史,系统阐述了中医急诊科基本理论、临证思路和学习方法,介绍急诊科病症的诊疗方法、临床经验和研究新进展'
}, ]
export default bookList

@ -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,218 @@
<template>
<div class="data-analytics">
<header class="header">
<h2>数据分析</h2>
<p>实时监控订单概况帮助商家做出明智决策</p>
</header>
<div class="analytics-summary">
<div class="summary-card">
<h3>订单总数</h3>
<p class="value">{{ totalOrders }}</p>
</div>
<div class="summary-card">
<h3>订单总金额</h3>
<p class="value">¥{{ totalAmount.toFixed(2) }}</p>
</div>
</div>
<!-- 订单状态分布图 -->
<div class="order-status">
<h3>订单状态分布</h3>
<div class="status-cards">
<div v-for="(count, status) in orderStatusCounts" :key="status" class="status-card">
<div class="status-name">{{ status }}</div>
<div class="status-count">{{ count }}</div>
</div>
</div>
</div>
<!-- 订单趋势图 -->
<div class="order-trends">
<h3>订单趋势</h3>
<!-- Chart.js 渲染区域 -->
<canvas id="orderTrendsChart" width="400" height="200"></canvas>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue';
import { Chart } from 'chart.js';
// 使 API
const orders = ref([
{ id: 1, status: '待发货', totalPrice: 150, date: '2024-01-01' },
{ id: 2, status: '已发货', totalPrice: 300, date: '2024-01-02' },
{ id: 3, status: '待发货', totalPrice: 450, date: '2024-01-03' },
{ id: 4, status: '已完成', totalPrice: 200, date: '2024-01-04' },
{ id: 5, status: '已发货', totalPrice: 350, date: '2024-01-05' },
{ id: 6, status: '已取消', totalPrice: 100, date: '2024-01-06' },
]);
//
const totalOrders = computed(() => orders.value.length);
//
const totalAmount = computed(() => orders.value.reduce((sum, order) => sum + order.totalPrice, 0));
//
const orderStatusCounts = computed(() => {
return orders.value.reduce((counts, order) => {
counts[order.status] = (counts[order.status] || 0) + 1;
return counts;
}, {});
});
//
const orderTrendsData = computed(() => {
const dates = orders.value.map(order => order.date);
const amounts = orders.value.map(order => order.totalPrice);
return { labels: dates, data: amounts };
});
// Chart.js
onMounted(() => {
const ctx = document.getElementById('orderTrendsChart').getContext('2d');
new Chart(ctx, {
type: 'line', // 线
data: {
labels: orderTrendsData.value.labels,
datasets: [{
label: '订单总金额',
data: orderTrendsData.value.data,
borderColor: '#27ae60', // 线
backgroundColor: 'rgba(39, 174, 96, 0.2)', // 线
fill: true, //
tension: 0.3, //
}]
},
options: {
responsive: true,
scales: {
x: {
title: {
display: true,
text: '日期'
}
},
y: {
title: {
display: true,
text: '金额 (¥)'
},
beginAtZero: true
}
}
}
});
});
</script>
<style scoped>
/* 数据分析页面样式 */
.data-analytics {
max-width: 1000px;
margin: 0 auto;
padding: 20px;
font-family: 'Arial', sans-serif;
}
.header {
text-align: center;
margin-bottom: 40px;
}
.header h2 {
font-size: 2rem;
color: #2c3e50;
margin: 0;
}
.header p {
font-size: 1rem;
color: #7f8c8d;
margin-top: 10px;
}
.analytics-summary {
display: flex;
justify-content: space-between;
margin-bottom: 40px;
}
.summary-card {
background-color: #fff;
border-radius: 8px;
padding: 20px;
width: 45%;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
text-align: center;
}
.summary-card h3 {
font-size: 1.2rem;
color: #34495e;
margin-bottom: 10px;
}
.summary-card .value {
font-size: 2rem;
color: #27ae60;
}
.order-status {
margin-bottom: 40px;
}
.order-status h3 {
font-size: 1.5rem;
color: #34495e;
margin-bottom: 20px;
}
.status-cards {
display: flex;
justify-content: space-between;
gap: 20px;
}
.status-card {
background-color: #fff;
border-radius: 8px;
padding: 15px;
width: 20%;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
text-align: center;
}
.status-name {
font-size: 1.1rem;
color: #34495e;
margin-bottom: 10px;
}
.status-count {
font-size: 1.5rem;
color: #2980b9;
}
.order-trends {
margin-top: 40px;
}
.order-trends h3 {
font-size: 1.5rem;
color: #34495e;
margin-bottom: 20px;
}
canvas {
width: 100%;
height: 300px;
max-width: 800px;
margin: 0 auto;
border-radius: 8px;
}
</style>

@ -0,0 +1,213 @@
<template>
<div id="app">
<!-- 商家信息卡片 -->
<div class="merchant-info">
<h1>商家信息</h1>
<p class="info-item"><strong>名称:</strong> {{ merchantInfo.name }}</p>
<p class="info-item"><strong>地址:</strong> {{ merchantInfo.address }}</p>
<p class="info-item"><strong>电话:</strong> {{ merchantInfo.phone }}</p>
<p class="info-item"><strong>邮箱:</strong> {{ merchantInfo.email }}</p>
<button @click="showUpdateForm = true" class="update-button">更新您的信息</button>
<!-- 弹出更新表单 -->
<div v-if="showUpdateForm" class="modal-overlay">
<div class="update-form-modal">
<form @submit.prevent="updateMerchantInfo">
<label for="name">名称</label>
<input id="name" v-model="updateForm.name" type="text" required placeholder="请输入商家名称" />
<label for="address">地址</label>
<input id="address" v-model="updateForm.address" type="text" required placeholder="请输入商家地址" />
<label for="phone">电话</label>
<input id="phone" v-model="updateForm.phone" type="text" required placeholder="请输入商家电话" pattern="^[0-9]{3}-[0-9]{8}$" />
<label for="email">邮箱</label>
<input id="email" v-model="updateForm.email" type="email" required placeholder="请输入商家邮箱" />
<div class="form-buttons">
<button type="submit" :disabled="!isFormValid" class="confirm-btn">确认更新</button>
<button type="button" @click="showUpdateForm = false" class="cancel-btn">取消</button>
</div>
</form>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
//
const merchantInfo = ref({
name: "商家001",
address: "福建省福州市",
phone: "123-45678901",
email: "example@example.com",
});
//
const showUpdateForm = ref(false);
//
const updateForm = ref({
name: merchantInfo.value.name,
address: merchantInfo.value.address,
phone: merchantInfo.value.phone,
email: merchantInfo.value.email,
});
//
const isFormValid = computed(() => {
return updateForm.value.name && updateForm.value.address && updateForm.value.phone && updateForm.value.email;
});
//
const updateMerchantInfo = () => {
merchantInfo.value = { ...updateForm.value };
showUpdateForm.value = false;
};
</script>
<style scoped>
#app {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #1abc9c, #fff); /* 渐变背景 */
min-height: 100vh;
display: flex;
flex-direction: column;
justify-content: flex-start; /* 向上对齐 */
align-items: center;
padding: 40px 20px;
}
.merchant-info {
padding: 40px;
background-color: #fff;
border-radius: 15px;
box-shadow: 0 8px 15px rgba(0, 0, 0, 0.1);
width: 450px;
text-align: left;
margin-top: 50px; /* 卡片稍微偏上 */
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.merchant-info:hover {
transform: translateY(-5px); /* 增加悬浮效果 */
box-shadow: 0 12px 20px rgba(0, 0, 0, 0.15);
}
.merchant-info h1 {
color: #2c3e50;
font-size: 30px;
margin-bottom: 20px;
text-align: center;
}
.info-item {
font-size: 18px;
margin: 10px 0;
color: #34495e;
}
.update-button {
margin-top: 20px;
padding: 14px 25px;
background-color: #1abc9c;
color: white;
border: none;
border-radius: 8px;
font-size: 18px;
cursor: pointer;
transition: background-color 0.3s ease, transform 0.2s;
}
.update-button:hover {
background-color: #16a085;
}
.update-button:active {
transform: scale(0.95);
}
.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;
}
.update-form-modal {
background-color: #fff;
padding: 40px;
border-radius: 12px;
width: 480px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
}
.update-form-modal label {
font-size: 16px;
color: #2c3e50;
margin-bottom: 5px;
display: block;
}
.update-form-modal input {
width: 100%;
padding: 12px;
margin-bottom: 15px;
border: 1px solid #ccc;
border-radius: 8px;
font-size: 16px;
transition: border-color 0.3s ease;
}
.update-form-modal input:focus {
border-color: #1abc9c;
outline: none;
}
.form-buttons {
display: flex;
justify-content: space-between;
}
.confirm-btn {
padding: 12px 25px;
background-color: #1abc9c;
color: white;
border: none;
border-radius: 8px;
font-size: 16px;
cursor: pointer;
transition: background-color 0.3s ease, transform 0.2s;
}
.confirm-btn:hover {
background-color: #16a085;
}
.confirm-btn:active {
transform: scale(0.95);
}
.cancel-btn {
padding: 12px 25px;
background-color: #e74c3c;
color: white;
border: none;
border-radius: 8px;
font-size: 16px;
cursor: pointer;
transition: background-color 0.3s ease, transform 0.2s;
}
.cancel-btn:hover {
background-color: #c0392b;
}
.cancel-btn:active {
transform: scale(0.95);
}
</style>

@ -0,0 +1,274 @@
<template>
<div class="order-management">
<header>
<h2>订单管理</h2>
</header>
<!-- 订单列表 -->
<div class="order-list">
<div class="order-card" v-for="order in orders" :key="order.id">
<div class="order-details">
<h3>订单ID: {{ order.id }}</h3>
<p><strong>客户:</strong> {{ order.customerName }}</p>
<p><strong>总价:</strong> ¥{{ order.totalPrice }}</p>
<!-- 发货人信息 -->
<p><strong>发货人:</strong> {{ order.senderName }}</p>
<!-- 发货人电话 -->
<p><strong>发货人电话:</strong> {{ order.senderPhone }}</p>
<!-- 发货地址 -->
<p><strong>发货地址:</strong> {{ order.shippingAddress }}</p>
<!-- 订单状态 -->
<p><strong>订单状态:</strong> {{ order.status }}</p>
</div>
<div class="card-actions">
<button @click="editOrder(order)" class="btn edit-btn">编辑</button>
<button @click="deleteOrder(order.id)" class="btn delete-btn">删除</button>
</div>
</div>
</div>
<!-- 编辑订单模态框 -->
<div v-if="editingOrder" class="modal-overlay">
<div class="modal-content">
<h3>编辑订单 {{ editingOrder.id }}</h3>
<!-- 发货人信息 -->
<label for="senderName">发货人</label>
<input v-model="editingOrder.senderName" type="text" id="senderName" placeholder="发货人姓名" />
<!-- 发货人电话 -->
<label for="senderPhone">发货人电话</label>
<input v-model="editingOrder.senderPhone" type="text" id="senderPhone" placeholder="发货人电话" />
<!-- 发货地址 -->
<label for="shippingAddress">发货地址</label>
<input v-model="editingOrder.shippingAddress" type="text" id="shippingAddress" placeholder="发货地址" />
<!-- 订单状态 -->
<label for="status">订单状态</label>
<select v-model="editingOrder.status" id="status">
<option v-for="status in orderStatuses" :key="status" :value="status">{{ status }}</option>
</select>
<!-- 保存与取消按钮 -->
<div class="modal-buttons">
<button @click="saveOrder" class="btn save-btn">保存</button>
<button @click="cancelEdit" class="btn cancel-btn">取消</button>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
//
const orderStatuses = ['待发货', '已发货', '已完成', '已取消'];
//
const orders = ref([
{ id: 1, customerName: '张三', totalPrice: 150, senderName: '李四', senderPhone: '13800000001', shippingAddress: '北京市朝阳区', status: '待发货' },
{ id: 2, customerName: '王五', totalPrice: 300, senderName: '赵六', senderPhone: '13900000002', shippingAddress: '上海市浦东新区', status: '已发货' },
]);
//
const editingOrder = ref(null);
//
const editOrder = (order) => {
editingOrder.value = { ...order }; //
};
//
const saveOrder = () => {
const index = orders.value.findIndex(order => order.id === editingOrder.value.id);
if (index !== -1) {
orders.value[index] = { ...editingOrder.value }; //
}
cancelEdit(); //
};
//
const cancelEdit = () => {
editingOrder.value = null; //
};
//
const deleteOrder = (id) => {
orders.value = orders.value.filter(order => order.id !== id);
};
</script>
<style scoped>
/* 样式 */
.order-management {
max-width: 1100px;
margin: 0 auto;
padding: 20px;
font-family: 'Roboto', sans-serif;
}
header {
text-align: center;
margin-bottom: 20px;
font-size: 1.8rem;
font-weight: bold;
color: #34495e;
}
.order-list {
display: flex;
flex-wrap: wrap;
gap: 30px;
justify-content: center;
}
.order-card {
background-color: #ffffff;
border-radius: 12px;
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1);
padding: 20px;
width: 280px;
transition: transform 0.3s, box-shadow 0.3s;
cursor: pointer;
}
.order-card:hover {
transform: translateY(-8px);
box-shadow: 0 15px 25px rgba(0, 0, 0, 0.2);
}
.order-details h3 {
font-size: 1.4rem;
font-weight: 600;
color: #2c3e50;
margin-bottom: 10px;
}
.order-details p {
color: #7f8c8d;
font-size: 1rem;
line-height: 1.5;
}
.card-actions {
margin-top: 20px;
display: flex;
justify-content: space-between;
}
.card-actions .btn {
padding: 12px 20px;
font-size: 1.1rem;
border-radius: 8px;
cursor: pointer;
transition: background-color 0.3s ease, transform 0.3s ease;
width: 48%;
box-sizing: border-box;
}
.edit-btn {
background: linear-gradient(135deg, #3498db, #2980b9);
color: white;
}
.delete-btn {
background: linear-gradient(135deg, #e74c3c, #c0392b);
color: white;
}
.card-actions .btn:hover {
transform: translateY(-2px);
}
.card-actions .btn:active {
transform: translateY(0);
}
.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;
opacity: 0;
animation: fadeIn 0.3s forwards;
}
.modal-content {
background-color: #ffffff;
border-radius: 12px;
padding: 30px;
width: 450px;
max-width: 80%;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
}
.modal-content h3 {
color: #34495e;
font-size: 1.5rem;
font-weight: bold;
margin-bottom: 20px;
}
input, select {
width: 100%;
padding: 12px;
margin: 12px 0;
border: 1px solid #ccc;
border-radius: 8px;
font-size: 1rem;
}
.modal-buttons {
margin-top: 20px;
display: flex;
justify-content: space-between;
}
.save-btn {
background: linear-gradient(135deg, #27ae60, #2ecc71);
color: white;
border-radius: 8px;
padding: 12px 20px;
font-size: 1.1rem;
transition: transform 0.3s ease, background-color 0.3s ease;
}
.cancel-btn {
background: linear-gradient(135deg, #e74c3c, #c0392b);
color: white;
border-radius: 8px;
padding: 12px 20px;
font-size: 1.1rem;
transition: transform 0.3s ease, background-color 0.3s ease;
}
.modal-buttons .btn:hover {
transform: translateY(-2px);
}
.modal-buttons .btn:active {
transform: translateY(0);
}
/* 动画 */
@keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
</style>

@ -0,0 +1,271 @@
<template>
<div class="promotion-management">
<header class="header">
<h1>促销活动管理</h1>
<p>管理您的促销活动添加编辑或删除促销信息</p>
</header>
<!-- 添加促销按钮 -->
<div class="toolbar">
<button @click="showAddForm = true" class="btn add-btn">添加促销活动</button>
</div>
<!-- 添加促销表单 -->
<div v-if="showAddForm" class="modal-overlay">
<div class="form-modal">
<h2>添加促销活动</h2>
<form @submit.prevent="addPromotionSubmit">
<label for="addTitle">促销标题</label>
<input id="addTitle" v-model="newPromotion.title" type="text" required />
<label for="addDiscount">折扣 (%)</label>
<input
id="addDiscount"
v-model="newPromotion.discount"
type="number"
min="0"
max="100"
required
/>
<div class="form-buttons">
<button type="submit" class="btn confirm-btn">确认添加</button>
<button type="button" class="btn cancel-btn" @click="showAddForm = false">取消</button>
</div>
</form>
</div>
</div>
<!-- 促销活动列表 -->
<div class="promotion-list">
<div class="promotion-card" v-for="promo in promotions" :key="promo.id">
<div class="promo-details">
<h3>{{ promo.title }}</h3>
<p>折扣: <strong>{{ promo.discount }}%</strong></p>
</div>
<div class="card-actions">
<button @click="startEditPromotion(promo)" class="btn edit-btn">编辑</button>
<button @click="showDeleteConfirm = promo.id" class="btn delete-btn">删除</button>
</div>
</div>
</div>
<!-- 编辑促销表单 -->
<div v-if="showEditForm" class="modal-overlay">
<div class="form-modal">
<h2>编辑促销活动</h2>
<form @submit.prevent="editPromotionSubmit">
<label for="editTitle">促销标题</label>
<input id="editTitle" v-model="editPromotionData.title" type="text" required />
<label for="editDiscount">折扣 (%)</label>
<input
id="editDiscount"
v-model="editPromotionData.discount"
type="number"
min="0"
max="100"
required
/>
<div class="form-buttons">
<button type="submit" class="btn confirm-btn">确认修改</button>
<button type="button" class="btn cancel-btn" @click="showEditForm = false">取消</button>
</div>
</form>
</div>
</div>
<!-- 删除确认提示 -->
<div v-if="showDeleteConfirm" class="modal-overlay">
<div class="confirm-modal">
<p>确定要删除促销活动 "<strong>{{ getPromoTitle(showDeleteConfirm) }}</strong>" </p>
<div class="form-buttons">
<button @click="deletePromotion(showDeleteConfirm)" class="btn confirm-btn">确定</button>
<button @click="showDeleteConfirm = false" class="btn cancel-btn">取消</button>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from "vue";
//
const promotions = ref([
{ id: 1, title: "双十一促销", discount: 50 },
{ id: 2, title: "年终特惠", discount: 30 },
]);
//
const showAddForm = ref(false);
const newPromotion = ref({ title: "", discount: 0 });
//
const showEditForm = ref(false);
const editPromotionData = ref({ title: "", discount: 0 });
//
const showDeleteConfirm = ref(false);
//
const addPromotionSubmit = () => {
promotions.value.push({
id: Date.now(),
title: newPromotion.value.title,
discount: newPromotion.value.discount,
});
newPromotion.value = { title: "", discount: 0 };
showAddForm.value = false;
};
//
const startEditPromotion = (promo) => {
editPromotionData.value = { ...promo };
showEditForm.value = true;
};
//
const editPromotionSubmit = () => {
const index = promotions.value.findIndex((p) => p.id === editPromotionData.value.id);
promotions.value[index] = { ...editPromotionData.value };
editPromotionData.value = { title: "", discount: 0 };
showEditForm.value = false;
};
//
const deletePromotion = (id) => {
promotions.value = promotions.value.filter((p) => p.id !== id);
showDeleteConfirm.value = false;
};
//
const getPromoTitle = (id) => {
const promo = promotions.value.find((p) => p.id === id);
return promo ? promo.title : "";
};
</script>
<style scoped>
/* 页面整体布局 */
.promotion-management {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
font-family: Arial, sans-serif;
}
.header {
text-align: center;
margin-bottom: 30px;
}
.header h1 {
font-size: 2rem;
margin-bottom: 10px;
color: #2c3e50;
}
.header p {
font-size: 1rem;
color: #7f8c8d;
}
.toolbar {
text-align: center;
margin-bottom: 20px;
}
.btn {
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
font-size: 1rem;
margin: 5px;
transition: background-color 0.3s ease;
}
.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;
}
/* 卡片样式 */
.promotion-list {
display: flex;
flex-wrap: wrap;
gap: 20px;
}
.promotion-card {
background: white;
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
padding: 20px;
flex: 1 1 calc(33.333% - 20px);
}
.promo-details h3 {
margin: 0 0 10px;
color: #34495e;
}
/* 弹窗样式 */
.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;
}
.form-modal,
.confirm-modal {
background-color: white;
border-radius: 10px;
padding: 20px;
width: 400px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
form label {
display: block;
margin-bottom: 5px;
color: #2c3e50;
}
form input {
width: 100%;
padding: 10px;
margin-bottom: 20px;
border: 1px solid #ccc;
border-radius: 5px;
}
</style>

@ -0,0 +1,165 @@
<template>
<div class="dashboard">
<!-- 顶部欢迎语 -->
<header class="dashboard-header">
<h1>欢迎商家</h1>
<p>高效管理您的商家业务从这里开始</p>
</header>
<!-- 快速访问模块 -->
<main class="quick-access">
<h2>快速访问功能</h2>
<div class="card-container">
<div
class="card"
v-for="link in links"
:key="link.name"
@click="navigateTo(link.path)"
>
<div class="card-icon">
<img :src="link.icon" :alt="link.name" />
</div>
<div class="card-content">
<h3>{{ link.name }}</h3>
<p>{{ link.description }}</p>
</div>
</div>
</div>
</main>
</div>
</template>
<script>
export default {
name: "Index",
data() {
return {
links: [
{
name: "图书管理",
path: "/seller/book-management",
description: "管理商店的图书库存",
icon: "/icons/book.png", //
},
{
name: "订单管理",
path: "/seller/order-management",
description: "查看和处理订单",
icon: "/icons/order.png",
},
{
name: "促销活动",
path: "/seller/promotion-management",
description: "策划并管理促销活动",
icon: "/icons/promotion.png",
},
{
name: "客户关系",
path: "/seller/customer-feedback",
description: "维护客户关系,提高满意度",
icon: "/icons/customer.png",
},
{
name: "数据分析",
path: "/seller/data-analytics",
description: "分析数据以驱动决策",
icon: "/icons/analytics.png",
},
{
name: "商家信息",
path: "/seller/merchant-info",
description: "管理您的商家信息",
icon: "/icons/merchant.png",
},
],
};
},
methods: {
navigateTo(path) {
this.$router.push(path);
},
},
};
</script>
<style scoped>
/* 通用样式 */
body {
margin: 0;
font-family: "Arial", sans-serif;
color: #333;
}
/* 顶部样式 */
.dashboard-header {
text-align: center;
padding: 20px;
background-color: #2c3e50;
color: white;
border-bottom: 5px solid #1abc9c;
}
.dashboard-header h1 {
font-size: 2.5rem;
margin: 0;
}
.dashboard-header p {
font-size: 1.2rem;
margin: 10px 0 0;
}
/* 快速访问模块 */
.quick-access {
text-align: center;
margin: 30px auto;
}
.quick-access h2 {
font-size: 1.8rem;
margin-bottom: 20px;
color: #34495e;
}
/* 卡片容器 */
.card-container {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 20px;
padding: 0 20px;
}
/* 卡片样式 */
.card {
width: 200px;
background: white;
border-radius: 10px;
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1);
transition: transform 0.3s, box-shadow 0.3s;
cursor: pointer;
padding: 20px;
text-align: center;
}
.card:hover {
transform: translateY(-5px);
box-shadow: 0px 6px 12px rgba(0, 0, 0, 0.2);
}
.card-icon img {
width: 60px;
height: 60px;
margin-bottom: 15px;
}
.card-content h3 {
font-size: 1.2rem;
color: #2c3e50;
}
.card-content p {
font-size: 0.9rem;
color: #7f8c8d;
}
</style>

@ -0,0 +1,5 @@
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
lintOnSave: false,
})
Loading…
Cancel
Save