Compare commits
No commits in common. 'admin' and 'main' have entirely different histories.
@ -1,3 +0,0 @@
|
||||
{
|
||||
"recommendations": ["Vue.volar"]
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
D:\workplace\Vue.js\final work\.gitignore
|
||||
D:\workplace\Vue.js\final work\.idea
|
||||
D:\workplace\Vue.js\final work\.vscode
|
||||
D:\workplace\Vue.js\final work\index.html
|
||||
D:\workplace\Vue.js\final work\LIST.TXT
|
||||
D:\workplace\Vue.js\final work\logo.png
|
||||
D:\workplace\Vue.js\final work\node_modules
|
||||
D:\workplace\Vue.js\final work\package-lock.json
|
||||
D:\workplace\Vue.js\final work\package.json
|
||||
D:\workplace\Vue.js\final work\public
|
||||
D:\workplace\Vue.js\final work\README.md
|
||||
D:\workplace\Vue.js\final work\src
|
||||
D:\workplace\Vue.js\final work\vite.config.js
|
||||
D:\workplace\Vue.js\final work\yarn.lock
|
||||
D:\workplace\Vue.js\final work\新建文本文档 (3).bat
|
||||
D:\workplace\Vue.js\final work\.idea\.gitignore
|
||||
D:\workplace\Vue.js\final work\.idea\final work.iml
|
||||
D:\workplace\Vue.js\final work\.idea\libraries
|
||||
D:\workplace\Vue.js\final work\.idea\misc.xml
|
||||
D:\workplace\Vue.js\final work\.idea\modules.xml
|
||||
D:\workplace\Vue.js\final work\.idea\vcs.xml
|
||||
D:\workplace\Vue.js\final work\.idea\workspace.xml
|
||||
D:\workplace\Vue.js\final work\.idea\libraries\212206253.xml
|
||||
D:\workplace\Vue.js\final work\.vscode\extensions.json
|
||||
D:\workplace\Vue.js\final work\public\arrow.png
|
||||
D:\workplace\Vue.js\final work\public\logo.png
|
||||
D:\workplace\Vue.js\final work\public\user.png
|
||||
D:\workplace\Vue.js\final work\public\vite.svg
|
||||
D:\workplace\Vue.js\final work\public\箭头.png
|
||||
D:\workplace\Vue.js\final work\src\App.vue
|
||||
D:\workplace\Vue.js\final work\src\assets
|
||||
D:\workplace\Vue.js\final work\src\components
|
||||
D:\workplace\Vue.js\final work\src\express.js
|
||||
D:\workplace\Vue.js\final work\src\main.js
|
||||
D:\workplace\Vue.js\final work\src\request.js
|
||||
D:\workplace\Vue.js\final work\src\router
|
||||
D:\workplace\Vue.js\final work\src\store
|
||||
D:\workplace\Vue.js\final work\src\style.css
|
||||
D:\workplace\Vue.js\final work\src\view
|
||||
D:\workplace\Vue.js\final work\src\assets\arrow.png
|
||||
D:\workplace\Vue.js\final work\src\assets\vue.svg
|
||||
D:\workplace\Vue.js\final work\src\router\router.js
|
||||
D:\workplace\Vue.js\final work\src\store\expressStore.ts
|
||||
D:\workplace\Vue.js\final work\src\store\user.js
|
||||
D:\workplace\Vue.js\final work\src\store\user.ts
|
||||
D:\workplace\Vue.js\final work\src\view\index.vue
|
||||
D:\workplace\Vue.js\final work\src\view\logo.png
|
||||
D:\workplace\Vue.js\final work\src\view\user
|
||||
D:\workplace\Vue.js\final work\src\view\user\212206253叶卓林.zip
|
||||
D:\workplace\Vue.js\final work\src\view\user\Elist.vue
|
||||
D:\workplace\Vue.js\final work\src\view\user\ExpressForm.vue
|
||||
D:\workplace\Vue.js\final work\src\view\user\ExpressList.vue
|
||||
D:\workplace\Vue.js\final work\src\view\user\Get.vue
|
||||
D:\workplace\Vue.js\final work\src\view\user\login.vue
|
||||
D:\workplace\Vue.js\final work\src\view\user\MyInfo.vue
|
||||
D:\workplace\Vue.js\final work\src\view\user\register.vue
|
||||
D:\workplace\Vue.js\final work\src\view\user\Send.vue
|
||||
D:\workplace\Vue.js\final work\src\view\user\test.vue
|
@ -1,22 +1,21 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
.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*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
@ -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,56 @@
|
||||
{
|
||||
"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",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"chart.js": "^4.4.7",
|
||||
"core-js": "^3.8.3",
|
||||
"cors": "^2.8.5",
|
||||
"dayjs": "^1.11.6",
|
||||
"element-ui": "^2.15.14",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"mysql2": "^3.11.5",
|
||||
"sequelize": "^6.37.5",
|
||||
"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,39 @@
|
||||
<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,89 @@
|
||||
// app.js
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
const bcrypt = require('bcryptjs');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const sequelize = require('./config/database'); // 调整路径为相对路径
|
||||
const { User, Book, CartItem } = require('./models/index');
|
||||
|
||||
const cartRoutes = require('./router/cart'); // 引入购物车路由模块
|
||||
|
||||
const app = express();
|
||||
|
||||
// 中间件
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
app.use(cors({
|
||||
origin: 'http://localhost:8081', // 替换为你的前端地址
|
||||
credentials: true
|
||||
}));
|
||||
|
||||
// 数据库同步
|
||||
async function syncDatabase() {
|
||||
try {
|
||||
await sequelize.sync(); // 不使用 force 选项
|
||||
console.log('Database & tables created!');
|
||||
} catch (error) {
|
||||
console.error('Error syncing database:', error);
|
||||
process.exit(1); // 如果数据库同步失败,退出进程
|
||||
}
|
||||
}
|
||||
|
||||
syncDatabase();
|
||||
|
||||
// 设置关联关系(确保所有模型都被加载)
|
||||
User.hasMany(CartItem, { foreignKey: 'user_id' });
|
||||
CartItem.belongsTo(User, { foreignKey: 'user_id' });
|
||||
|
||||
Book.hasMany(CartItem, { foreignKey: 'book_id' });
|
||||
CartItem.belongsTo(Book, { foreignKey: 'book_id' });
|
||||
|
||||
// 注册用户
|
||||
app.post('/api/register', async (req, res) => {
|
||||
try {
|
||||
const { username, password } = req.body;
|
||||
if (!username || !password) {
|
||||
return res.status(400).json({ error: '缺少用户名或密码' });
|
||||
}
|
||||
const hashedPassword = await bcrypt.hash(password, 10);
|
||||
const user = await User.create({ username, password: hashedPassword });
|
||||
res.status(201).json(user);
|
||||
} catch (error) {
|
||||
console.error('Error registering user:', error);
|
||||
res.status(500).json({ error: '服务器错误' });
|
||||
}
|
||||
});
|
||||
|
||||
// 登录用户
|
||||
app.post('/api/login', async (req, res) => {
|
||||
try {
|
||||
const { username, password } = req.body;
|
||||
if (!username || !password) {
|
||||
return res.status(400).json({ error: '缺少用户名或密码' });
|
||||
}
|
||||
const user = await User.findOne({ where: { username } });
|
||||
if (!user) {
|
||||
return res.status(400).json({ error: '用户不存在' });
|
||||
}
|
||||
const isMatch = await bcrypt.compare(password, user.password);
|
||||
if (!isMatch) {
|
||||
return res.status(400).json({ error: '密码错误' });
|
||||
}
|
||||
const token = jwt.sign({ userId: user.id }, 'your_jwt_secret', { expiresIn: '24h' }); // 增加Token的有效期
|
||||
|
||||
// 返回用户的完整信息
|
||||
res.json({ token, user: { id: user.id, username: user.username } });
|
||||
} catch (error) {
|
||||
console.error('Error logging in user:', error);
|
||||
res.status(500).json({ error: '服务器错误' });
|
||||
}
|
||||
});
|
||||
|
||||
// 使用购物车路由模块
|
||||
app.use('/api/cart', cartRoutes); // 注意这里使用了 '/api/cart' 前缀
|
||||
|
||||
// 启动服务器
|
||||
const PORT = process.env.PORT || 3000;
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Server is running on port ${PORT}`);
|
||||
});
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.7 KiB |
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 |
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 7.1 KiB |
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.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,23 @@
|
||||
<!--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;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
|
||||
}
|
||||
</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,25 @@
|
||||
// config/database.js
|
||||
const { Sequelize } = require('sequelize');
|
||||
|
||||
console.log('开始创建 sequelize 实例');
|
||||
const sequelize = new Sequelize('bs', 'root', '123456', {
|
||||
host: 'localhost',
|
||||
dialect: 'mysql',
|
||||
dialectModule: require('mysql2'), // 使用 mysql2 模块
|
||||
logging: (msg) => {
|
||||
console.log('数据库操作日志:', msg);
|
||||
}
|
||||
});
|
||||
|
||||
// 测试连接
|
||||
(async () => {
|
||||
try {
|
||||
await sequelize.authenticate();
|
||||
console.log('Connection has been established successfully.');
|
||||
} catch (error) {
|
||||
console.error('Unable to connect to the database:', error);
|
||||
}
|
||||
})();
|
||||
console.log('sequelize 实例创建成功');
|
||||
|
||||
module.exports = sequelize;
|
@ -0,0 +1,18 @@
|
||||
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);
|
||||
/*Vue.prototype.$message = Message;*/
|
||||
new Vue({
|
||||
router,
|
||||
store,
|
||||
render: (h) => h(App),
|
||||
}).$mount("#app");
|
@ -0,0 +1,38 @@
|
||||
const { DataTypes } = require('sequelize');
|
||||
const sequelize = require('../config/database');
|
||||
/* const CartItem = require('./CartItem'); */
|
||||
const Books = sequelize.define('Books', {
|
||||
bookName: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: false
|
||||
},
|
||||
cover: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: true
|
||||
},
|
||||
type: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true
|
||||
},
|
||||
author: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: true
|
||||
},
|
||||
price: {
|
||||
type: DataTypes.DECIMAL(10, 2),
|
||||
allowNull: false
|
||||
},
|
||||
stock: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0
|
||||
},
|
||||
description: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true
|
||||
}
|
||||
}, {
|
||||
tableName: 'books'
|
||||
});
|
||||
/* Books.hasMany(CartItem, { foreignKey: 'book_id' }); */
|
||||
module.exports = Books;
|
@ -0,0 +1,45 @@
|
||||
// models/CartItem.js
|
||||
const { DataTypes } = require('sequelize');
|
||||
const sequelize = require('../config/database');
|
||||
const User = require('./User');
|
||||
const Book = require('./Book');
|
||||
|
||||
const CartItem = sequelize.define('CartItem', {
|
||||
user_id: { // 明确指定外键名称
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: User,
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
book_id: { // 明确指定外键名称
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: Book,
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
quantity: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 1
|
||||
},
|
||||
price_at_purchase: { // 确保字段名与数据库表一致
|
||||
type: DataTypes.DECIMAL(10, 2),
|
||||
allowNull: false,
|
||||
field: 'price_at_purchase' // 如果数据库中字段名为 price_at_purchase
|
||||
}
|
||||
}, {
|
||||
tableName: 'cartitems',
|
||||
timestamps: true, // 启用时间戳
|
||||
createdAt: 'created_at', // 自定义创建时间戳字段名
|
||||
updatedAt: 'updated_at' // 自定义更新时间戳字段名
|
||||
});
|
||||
|
||||
// 关联模型
|
||||
CartItem.belongsTo(User, { foreignKey: 'user_id' });
|
||||
CartItem.belongsTo(Book, { foreignKey: 'book_id' });
|
||||
|
||||
module.exports = CartItem;
|
@ -0,0 +1,19 @@
|
||||
const { DataTypes } = require('sequelize');
|
||||
const sequelize = require('../config/database');
|
||||
/* const CartItem = require('./CartItem');
|
||||
*/
|
||||
const User = sequelize.define('User', {
|
||||
username: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
unique: true
|
||||
},
|
||||
password: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: false
|
||||
}
|
||||
}, {
|
||||
tableName: 'user'
|
||||
});
|
||||
/* User.hasMany(CartItem, { foreignKey: 'user_id' }); */
|
||||
module.exports = User;
|
@ -0,0 +1,17 @@
|
||||
//const sequelize = require('../config/database');
|
||||
const User = require('./User');
|
||||
const Book = require('./Book');
|
||||
const CartItem = require('./CartItem');
|
||||
|
||||
// 设置关联关系
|
||||
User.hasMany(CartItem, { foreignKey: 'user_id' });
|
||||
CartItem.belongsTo(User, { foreignKey: 'user_id' });
|
||||
|
||||
Book.hasMany(CartItem, { foreignKey: 'book_id' });
|
||||
CartItem.belongsTo(Book, { foreignKey: 'book_id' });
|
||||
|
||||
module.exports = {
|
||||
User,
|
||||
Book,
|
||||
CartItem
|
||||
};
|
@ -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,224 @@
|
||||
<template>
|
||||
<div class="book-container">
|
||||
<!-- 搜索栏 -->
|
||||
<div class="search-bar">
|
||||
<el-input
|
||||
v-model="searchQuery"
|
||||
placeholder="搜索书籍(书名、作者)"
|
||||
suffix-icon="el-icon-search"
|
||||
@input="handleSearch"
|
||||
clearable
|
||||
style="width: 300px;"
|
||||
/>
|
||||
<el-button type="primary" @click="openAddDialog" style="margin-left: 10px;">新增书籍</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 书籍信息展示 -->
|
||||
<el-table :data="currentPageBooks" stripe style="width: 100%">
|
||||
<el-table-column label="书名" prop="title"></el-table-column>
|
||||
<el-table-column label="作者" prop="author"></el-table-column>
|
||||
<el-table-column label="ISBN" prop="isbn"></el-table-column>
|
||||
<el-table-column label="价格" prop="price"></el-table-column>
|
||||
<el-table-column label="状态" prop="status"></el-table-column>
|
||||
<el-table-column label="操作">
|
||||
<template slot-scope="{ row }">
|
||||
<el-button @click="editBook(row)" type="text" size="small">编辑</el-button>
|
||||
<el-button @click="deleteBook(row)" type="text" size="small" class="delete-btn">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<el-pagination
|
||||
v-if="totalBooks > 10"
|
||||
:current-page="currentPage"
|
||||
:page-size="pageSize"
|
||||
:total="totalBooks"
|
||||
layout="prev, pager, next, jumper"
|
||||
@current-change="handlePageChange"
|
||||
></el-pagination>
|
||||
|
||||
<!-- 增加书籍的表单 -->
|
||||
<el-dialog
|
||||
:visible.sync="dialogVisible"
|
||||
title="新增/编辑书籍"
|
||||
@close="resetForm"
|
||||
>
|
||||
<el-form :model="newBook" label-width="120px">
|
||||
<el-form-item label="书名" prop="title" :rules="[{ required: true, message: '书名不能为空', trigger: 'blur' }]">
|
||||
<el-input v-model="newBook.title" placeholder="请输入书名"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="作者" prop="author" :rules="[{ required: true, message: '作者不能为空', trigger: 'blur' }]">
|
||||
<el-input v-model="newBook.author" placeholder="请输入作者"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="ISBN" prop="isbn" :rules="[{ required: true, message: 'ISBN不能为空', trigger: 'blur' }]">
|
||||
<el-input v-model="newBook.isbn" placeholder="请输入ISBN"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="价格" prop="price" :rules="[{ required: true, message: '价格不能为空', trigger: 'blur' }]">
|
||||
<el-input v-model="newBook.price" placeholder="请输入价格"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status" :rules="[{ required: true, message: '状态不能为空', trigger: 'blur' }]">
|
||||
<el-select v-model="newBook.status" placeholder="请选择状态">
|
||||
<el-option label="上架" value="上架"></el-option>
|
||||
<el-option label="下架" value="下架"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="resetForm">取消</el-button>
|
||||
<el-button type="primary" @click="saveBook">保存</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/*import { ref, onMounted, watch } from 'vue';*/
|
||||
/*import { ElMessage } from 'element-ui';*/
|
||||
|
||||
// 假数据
|
||||
const dummyBooks = [
|
||||
{ id: 1, title: '书籍 A', author: '作者 A', isbn: '978-1-23456-789-0', price: '20.00', status: '上架' },
|
||||
{ id: 2, title: '书籍 B', author: '作者 B', isbn: '978-1-23456-789-1', price: '25.00', status: '下架' },
|
||||
// ... 其他书籍数据
|
||||
];
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
books: [...dummyBooks], // 全部书籍数据
|
||||
currentPageBooks: [], // 当前页显示的数据
|
||||
currentPage: 1, // 当前页数
|
||||
pageSize: 10, // 每页显示书籍的数量
|
||||
totalBooks: 0, // 书籍总数
|
||||
searchQuery: '', // 搜索输入框的内容
|
||||
dialogVisible: false, // 是否显示新增书籍的对话框
|
||||
newBook: {
|
||||
id: null,
|
||||
title: '',
|
||||
author: '',
|
||||
isbn: '',
|
||||
price: '',
|
||||
status: '上架',
|
||||
},
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
// 初始化书籍数据
|
||||
fetchBooks() {
|
||||
// 根据搜索查询进行过滤
|
||||
const filteredBooks = this.books.filter((book) => {
|
||||
return (
|
||||
book.title.includes(this.searchQuery) || book.author.includes(this.searchQuery)
|
||||
);
|
||||
});
|
||||
|
||||
// 分页处理
|
||||
const startIndex = (this.currentPage - 1) * this.pageSize;
|
||||
const endIndex = this.currentPage * this.pageSize;
|
||||
this.currentPageBooks = filteredBooks.slice(startIndex, endIndex);
|
||||
this.totalBooks = filteredBooks.length;
|
||||
},
|
||||
|
||||
// 搜索处理
|
||||
handleSearch() {
|
||||
this.currentPage = 1; // 重置页码为第一页
|
||||
this.fetchBooks(); // 根据搜索查询更新数据
|
||||
},
|
||||
|
||||
// 分页切换
|
||||
handlePageChange(page) {
|
||||
this.currentPage = page;
|
||||
this.fetchBooks();
|
||||
},
|
||||
|
||||
// 新增书籍
|
||||
openAddDialog() {
|
||||
this.resetForm();
|
||||
this.dialogVisible = true;
|
||||
},
|
||||
|
||||
// 保存书籍
|
||||
saveBook() {
|
||||
if (this.newBook.id === null) {
|
||||
// 新增书籍
|
||||
this.newBook.id = this.books.length + 1;
|
||||
this.books.push(this.newBook);
|
||||
} else {
|
||||
// 编辑书籍
|
||||
const index = this.books.findIndex((book) => book.id === this.newBook.id);
|
||||
if (index !== -1) {
|
||||
this.books[index] = { ...this.newBook };
|
||||
}
|
||||
}
|
||||
|
||||
this.$message.success('操作成功');
|
||||
this.dialogVisible = false;
|
||||
this.fetchBooks(); // 刷新书籍列表
|
||||
},
|
||||
|
||||
// 编辑书籍
|
||||
editBook(book) {
|
||||
this.newBook = { ...book };
|
||||
this.dialogVisible = true;
|
||||
},
|
||||
|
||||
// 删除书籍
|
||||
deleteBook(book) {
|
||||
const index = this.books.findIndex((b) => b.id === book.id);
|
||||
if (index !== -1) {
|
||||
this.books.splice(index, 1);
|
||||
}
|
||||
|
||||
this.$message.success('删除成功');
|
||||
this.fetchBooks(); // 刷新书籍列表
|
||||
},
|
||||
|
||||
// 重置表单
|
||||
resetForm() {
|
||||
this.newBook = {
|
||||
id: null,
|
||||
title: '',
|
||||
author: '',
|
||||
isbn: '',
|
||||
price: '',
|
||||
status: '上架',
|
||||
};
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.fetchBooks();
|
||||
},
|
||||
watch: {
|
||||
searchQuery: 'fetchBooks',
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.book-container {
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
color: red;
|
||||
font-size: 14px;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.delete-btn:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,178 @@
|
||||
<template>
|
||||
<div class="user-container">
|
||||
<!-- 新增用户按钮 -->
|
||||
<el-button type="primary" @click="openAddDialog" style="margin-bottom: 20px;">新增用户</el-button>
|
||||
|
||||
<!-- 用户信息展示 -->
|
||||
<el-table :data="allUsers" stripe style="width: 100%">
|
||||
<el-table-column label="用户名" prop="username"></el-table-column>
|
||||
<el-table-column label="邮箱" prop="email"></el-table-column>
|
||||
<el-table-column label="电话" prop="phone"></el-table-column>
|
||||
<el-table-column label="状态" prop="status"></el-table-column>
|
||||
<el-table-column label="操作">
|
||||
<template slot-scope="{ row }">
|
||||
<el-button @click="editUser(row)" type="text" size="small">编辑</el-button>
|
||||
<el-button @click="deleteUser(row)" type="text" size="small" class="delete-btn">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<el-pagination
|
||||
v-if="totalUsers > 10"
|
||||
:current-page="currentPage"
|
||||
:page-size="pageSize"
|
||||
:total="totalUsers"
|
||||
layout="prev, pager, next, jumper"
|
||||
@current-change="handlePageChange"
|
||||
></el-pagination>
|
||||
|
||||
<!-- 增加用户的表单 -->
|
||||
<el-dialog
|
||||
:visible.sync="dialogVisible"
|
||||
title="新增/编辑用户"
|
||||
@close="resetForm"
|
||||
>
|
||||
<el-form :model="newUser" label-width="120px">
|
||||
<el-form-item label="用户名" prop="username" :rules="[{ required: true, message: '用户名不能为空', trigger: 'blur' }]">
|
||||
<el-input v-model="newUser.username" placeholder="请输入用户名"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="邮箱" prop="email" :rules="[{ required: true, message: '邮箱不能为空', trigger: 'blur' }]">
|
||||
<el-input v-model="newUser.email" placeholder="请输入邮箱"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="电话" prop="phone" :rules="[{ required: true, message: '电话不能为空', trigger: 'blur' }]">
|
||||
<el-input v-model="newUser.phone" placeholder="请输入电话"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status" :rules="[{ required: true, message: '状态不能为空', trigger: 'blur' }]">
|
||||
<el-select v-model="newUser.status" placeholder="请选择状态">
|
||||
<el-option label="激活" value="激活"></el-option>
|
||||
<el-option label="冻结" value="冻结"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="resetForm">取消</el-button>
|
||||
<el-button type="primary" @click="saveUser">保存</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
users: [
|
||||
/*{ id: 1, username: '用户 A', email: 'usera@example.com', phone: '1234567890', status: '激活' },*/
|
||||
// ... 其他用户
|
||||
],
|
||||
currentPageUsers: [],
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
totalUsers: 0,
|
||||
dialogVisible: false,
|
||||
newUser: {
|
||||
id: null,
|
||||
username: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
status: '激活',
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(['registerUser']),
|
||||
allUsers() {
|
||||
// 合并本地用户和注册用户
|
||||
return [...this.users, ...this.registerUser];
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.fetchUsers();
|
||||
},
|
||||
watch: {
|
||||
allUsers: {
|
||||
handler() {
|
||||
this.fetchUsers();
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
fetchUsers() {
|
||||
const startIndex = (this.currentPage - 1) * this.pageSize;
|
||||
const endIndex = this.currentPage * this.pageSize;
|
||||
const pageData = this.allUsers.slice(startIndex, endIndex);
|
||||
|
||||
this.currentPageUsers = pageData;
|
||||
this.totalUsers = this.allUsers.length; // 更新用户的总数
|
||||
},
|
||||
handlePageChange(page) {
|
||||
this.currentPage = page;
|
||||
this.fetchUsers();
|
||||
},
|
||||
openAddDialog() {
|
||||
this.newUser = { id: null, username: '', email: '', phone: '', status: '激活' }; // 清空表单
|
||||
this.dialogVisible = true;
|
||||
},
|
||||
saveUser() {
|
||||
if (this.newUser.username && this.newUser.email && this.newUser.phone && this.newUser.status) {
|
||||
const newId = this.allUsers.length + 1; // 用于模拟新增的用户ID
|
||||
this.newUser.id = newId;
|
||||
|
||||
// 将新的用户添加到用户数组中
|
||||
this.$store.commit('REGISTER', this.newUser);
|
||||
|
||||
// 关闭对话框
|
||||
this.dialogVisible = false;
|
||||
|
||||
// 提示信息
|
||||
this.$message.success('用户信息保存成功');
|
||||
this.fetchUsers(); // 刷新用户列表
|
||||
this.resetForm(); // 重置表单
|
||||
} else {
|
||||
this.$message.error('请填写完整信息');
|
||||
}
|
||||
},
|
||||
editUser(row) {
|
||||
this.newUser = { ...row }; // 将用户信息传递给表单
|
||||
this.dialogVisible = true; // 打开对话框
|
||||
},
|
||||
deleteUser(row) {
|
||||
this.$store.commit('DELETE_USER', row.id); // 删除用户
|
||||
this.$message.success('用户删除成功');
|
||||
this.fetchUsers(); // 刷新用户列表
|
||||
},
|
||||
resetForm() {
|
||||
this.newUser = {
|
||||
id: null,
|
||||
username: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
status: '激活',
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
.user-container {
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
color: red;
|
||||
font-size: 14px;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.delete-btn:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
</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,239 @@
|
||||
<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="handleLogin">登录</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions, mapState } from 'vuex';
|
||||
|
||||
export default {
|
||||
name: 'LoginPage',
|
||||
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: {
|
||||
...mapActions(['login']),
|
||||
async handleLogin() {
|
||||
this.$refs.loginRef.validate(async valid => {
|
||||
if (!valid) return;
|
||||
|
||||
const currentUserType = this.loginForm.userType;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('http://localhost:3000/api/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(this.loginForm),
|
||||
});
|
||||
const data = await response.json();
|
||||
if (response.ok) {
|
||||
localStorage.setItem('token', data.token);
|
||||
const user = data.user;
|
||||
if (user && user.id && user.username) {
|
||||
this.login(user);
|
||||
console.log('User logged in:', this.$store.state.user);
|
||||
this.$refs['loginRef'].resetFields();
|
||||
const routeMap = {
|
||||
user: '/user/home',
|
||||
seller: '/seller/home',
|
||||
manager: '/manager/home',
|
||||
};
|
||||
const targetRoute = routeMap[currentUserType] || '/user/home';
|
||||
this.$router.push(targetRoute);
|
||||
this.$message({
|
||||
type: 'success',
|
||||
message: '登录成功',
|
||||
});
|
||||
} else {
|
||||
this.$message({
|
||||
type: 'error',
|
||||
message: '用户信息不完整',
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.$message({
|
||||
type: 'error',
|
||||
message: data.error,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
this.$message({
|
||||
type: 'error',
|
||||
message: '登录失败',
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</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,160 @@
|
||||
<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: 'RegisterPage',
|
||||
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: {
|
||||
async 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;
|
||||
}
|
||||
try {
|
||||
const response = await fetch('http://localhost:3000/api/register', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(this.registerForm)
|
||||
});
|
||||
const data = await response.json();
|
||||
if (response.ok) {
|
||||
this.$store.commit("REGISTER", this.registerForm);
|
||||
this.$message({
|
||||
type: 'success',
|
||||
message: '注册成功'
|
||||
});
|
||||
this.$refs['registerRef'].resetFields();
|
||||
this.$emit('toLogin');
|
||||
} else {
|
||||
this.$message({
|
||||
type: 'error',
|
||||
message: data.error
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
this.$message({
|
||||
type: 'error',
|
||||
message: '注册失败'
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</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,20 @@
|
||||
// router/cart.js
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const cartController = require('../controllers/cartController'); // 引入控制器
|
||||
|
||||
console.log('进入添加购物车路由配置');
|
||||
|
||||
// 添加商品到购物车的 POST 请求
|
||||
router.post('/add', cartController.addToCart);
|
||||
|
||||
// 更新购物车商品数量的 POST 请求
|
||||
router.post('/updateQuantity', cartController.updateQuantity);
|
||||
|
||||
// 删除购物车商品的 POST 请求
|
||||
router.post('/delete', cartController.deleteCartItem);
|
||||
|
||||
// 支付购物车的 POST 请求
|
||||
router.post('/pay', cartController.payCart);
|
||||
|
||||
module.exports = router;
|
@ -0,0 +1,169 @@
|
||||
// router/index.js
|
||||
import VueRouter from "vue-router";
|
||||
import Vue from "vue";
|
||||
import UserLayout from "@/components/userLayout/index.vue";
|
||||
import SellerLayout from "@/components/sellerLayout/index.vue";
|
||||
import ManagerLayout from "@/components/managerLayout/index.vue";
|
||||
|
||||
Vue.use(VueRouter);
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: "/",
|
||||
redirect: "/welcome",
|
||||
},
|
||||
{
|
||||
path: "/welcome",
|
||||
name: "Welcome",
|
||||
meta: { show: true },
|
||||
component: () => import("@/pages/welcome"),
|
||||
},
|
||||
{
|
||||
path: "/user",
|
||||
component: UserLayout,
|
||||
redirect: "/user/home",
|
||||
children: [
|
||||
{
|
||||
path: "home",
|
||||
name: "UserHome",
|
||||
component: () => import("@/pages/home"),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: "book",
|
||||
name: "UserBook",
|
||||
component: () => import("@/pages/book"),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: "shoppingCart",
|
||||
name: "UserShoppingCart",
|
||||
component: () => import("@/pages/shoppingCart"),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/seller",
|
||||
component: SellerLayout,
|
||||
redirect: "/seller/home",
|
||||
children: [
|
||||
{
|
||||
path: "home",
|
||||
name: "SellerHome",
|
||||
component: () => import("@/views/index.vue"),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: "book-management",
|
||||
name: "BookManagement",
|
||||
component: () => import("@/views/BookManagement.vue"),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: "order-management",
|
||||
name: "OrderManagement",
|
||||
component: () => import("@/views/OrderManagement.vue"),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: "customer-feedback",
|
||||
name: "CustomerFeedback",
|
||||
component: () => import("@/views/CustomerFeedback.vue"),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: "promotion-management",
|
||||
name: "PromotionManagement",
|
||||
component: () => import("@/views/PromotionManagement.vue"),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: "data-analytics",
|
||||
name: "DataAnalytics",
|
||||
component: () => import("@/views/DataAnalytics.vue"),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: "merchant-info",
|
||||
name: "MerchantInfo",
|
||||
component: () => import("@/views/MerchantInfo.vue"),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/manager",
|
||||
component: ManagerLayout,
|
||||
redirect: "/manager/home",
|
||||
children: [
|
||||
{
|
||||
path: "home",
|
||||
name: "ManagerHome",
|
||||
component: () => import("@/pages/manager/home/ManagerHome.vue"),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: "myInfo",
|
||||
name: "AdminMyInfo",
|
||||
component: () => import("@/pages/manager/AdminMyInfo.vue"),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: "accopass",
|
||||
name: "AdminAccoPass",
|
||||
component: () => import("@/pages/manager/AdminAccopass.vue"),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: "merchant",
|
||||
name: "AdminMerchant",
|
||||
component: () => import("@/pages/manager/AdminMerchant.vue"),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: "client",
|
||||
name: "AdminClient",
|
||||
component: () => import("@/pages/manager/AdminClient.vue"),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: "bookinfo",
|
||||
name: "AdminBookInfo",
|
||||
component: () => import("@/pages/manager/AdminBookinfo.vue"),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: "books",
|
||||
name: "AdminBooks",
|
||||
component: () => import("@/pages/manager/AdminBooks.vue"),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: "orders",
|
||||
name: "AdminOrders",
|
||||
component: () => import("@/pages/manager/AdminOrders.vue"),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const router = new VueRouter({
|
||||
routes,
|
||||
});
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
const isAuthenticated = localStorage.getItem('token') !== null;
|
||||
if (to.matched.some(record => record.meta.requiresAuth)) {
|
||||
if (!isAuthenticated) {
|
||||
next('/welcome'); // 未登录时重定向到欢迎页
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
@ -0,0 +1,15 @@
|
||||
let categoryList = [
|
||||
'全部分类',
|
||||
'文学',
|
||||
'教育',
|
||||
'绘画',
|
||||
'摄影',
|
||||
'历史',
|
||||
'哲学',
|
||||
'科学',
|
||||
'语言',
|
||||
'军事',
|
||||
'医药',
|
||||
]
|
||||
|
||||
export default categoryList;
|
@ -0,0 +1,290 @@
|
||||
<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="bookName">图书标题</label>
|
||||
<input id="bookName" v-model="bookForm.bookName" 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 />
|
||||
|
||||
<label for="bookCover">封面图片路径</label>
|
||||
<input id="bookCover" v-model="bookForm.cover" type="text" required />
|
||||
|
||||
<label for="bookType">类型</label>
|
||||
<input id="bookType" v-model="bookForm.type" type="text" required />
|
||||
|
||||
<label for="bookDescription">描述</label>
|
||||
<textarea id="bookDescription" v-model="bookForm.description" required></textarea>
|
||||
|
||||
<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 bookList" :key="book.book_id">
|
||||
<div class="book-details">
|
||||
<h3>{{ book.bookName }}</h3>
|
||||
<p>作者: {{ book.author }}</p>
|
||||
<p>价格: ¥{{ book.price }}</p>
|
||||
<p>库存: {{ book.quantity }}</p>
|
||||
<p>类型: {{ book.type }}</p>
|
||||
<p>描述: {{ book.description }}</p>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<button @click="editBook(book)" class="btn edit-btn">编辑</button>
|
||||
<button @click="deleteBook(book.book_id)" class="btn delete-btn">删除</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState, mapMutations } from 'vuex';
|
||||
|
||||
export default {
|
||||
name: 'BookManagement',
|
||||
data() {
|
||||
return {
|
||||
showAddForm: false,
|
||||
editMode: false,
|
||||
bookForm: {
|
||||
bookName: '',
|
||||
author: '',
|
||||
price: 0,
|
||||
quantity: 0,
|
||||
cover: '',
|
||||
type: '',
|
||||
description: ''
|
||||
}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(['bookList'])
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(['ADDBOOK', 'UPDATE_BOOK', 'DELETE_BOOK']),
|
||||
closeForm() {
|
||||
this.showAddForm = false;
|
||||
this.bookForm = {
|
||||
bookName: '',
|
||||
author: '',
|
||||
price: 0,
|
||||
quantity: 0,
|
||||
cover: '',
|
||||
type: '',
|
||||
description: ''
|
||||
};
|
||||
this.editMode = false;
|
||||
},
|
||||
handleSubmit() {
|
||||
if (this.editMode) {
|
||||
const index = this.bookList.findIndex(book => book.book_id === this.bookForm.book_id);
|
||||
this.UPDATE_BOOK({ index, book: { ...this.bookForm } });
|
||||
} else {
|
||||
this.ADDBOOK({ ...this.bookForm, book_id: Date.now() });
|
||||
}
|
||||
this.closeForm();
|
||||
},
|
||||
editBook(book) {
|
||||
this.bookForm = { ...book };
|
||||
this.editMode = true;
|
||||
this.showAddForm = true;
|
||||
},
|
||||
deleteBook(book_id) {
|
||||
this.DELETE_BOOK(book_id);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
console.log('showAddForm:', this.showAddForm);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.book-management {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 40px 20px;
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #f4f4f9;
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.header h2 {
|
||||
font-size: 2.5rem;
|
||||
margin: 0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.book-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 30px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.book-card {
|
||||
background-color: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
|
||||
padding: 30px;
|
||||
width: 300px;
|
||||
transition: transform 0.3s, box-shadow 0.3s;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.book-card:hover {
|
||||
transform: translateY(-10px);
|
||||
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.book-details h3 {
|
||||
font-size: 1.5rem;
|
||||
margin: 0 0 10px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.book-details p {
|
||||
margin: 5px 0;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.card-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.card-actions button {
|
||||
margin: 0;
|
||||
padding: 10px 20px;
|
||||
font-size: 1rem;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.card-actions button:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
background-color: #1abc9c;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.edit-btn {
|
||||
background-color: #3498db;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
background-color: #e74c3c;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.form-modal {
|
||||
background-color: white;
|
||||
border-radius: 12px;
|
||||
padding: 40px;
|
||||
width: 500px;
|
||||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.form-modal h3 {
|
||||
font-size: 1.8rem;
|
||||
margin-bottom: 30px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.form-modal label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.form-modal input,
|
||||
.form-modal textarea {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
margin-bottom: 20px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 6px;
|
||||
font-size: 1rem;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.form-modal textarea {
|
||||
resize: vertical;
|
||||
min-height: 100px;
|
||||
}
|
||||
|
||||
.form-buttons {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.form-buttons button {
|
||||
padding: 12px 24px;
|
||||
font-size: 1rem;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.form-buttons button:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.confirm-btn {
|
||||
background-color: #27ae60;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.cancel-btn {
|
||||
background-color: #95a5a6;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.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,24 @@
|
||||
/*
|
||||
const { defineConfig } = require('@vue/cli-service')
|
||||
module.exports = defineConfig({
|
||||
transpileDependencies: true,
|
||||
lintOnSave: false,
|
||||
})
|
||||
*/
|
||||
const { defineConfig } = require('@vue/cli-service');
|
||||
|
||||
module.exports = defineConfig({
|
||||
transpileDependencies: true,
|
||||
lintOnSave: false,
|
||||
devServer: {
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://localhost:3000',
|
||||
changeOrigin: true,
|
||||
pathRewrite: {
|
||||
'^/api': '/api'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
@ -1,13 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite + Vue</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
@ -1,895 +0,0 @@
|
||||
{
|
||||
"name": "bookstore",
|
||||
"version": "0.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "bookstore",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"axios": "^1.7.9",
|
||||
"element-plus": "^2.8.8",
|
||||
"pinia": "2.0.27",
|
||||
"pinia-plugin-persist": "^1.0.0",
|
||||
"vue": "^3.4.37",
|
||||
"vue-router": "^4.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.1.2",
|
||||
"vite": "^5.4.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-string-parser": {
|
||||
"version": "7.24.8",
|
||||
"resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz",
|
||||
"integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-validator-identifier": {
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz",
|
||||
"integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.25.6",
|
||||
"resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.25.6.tgz",
|
||||
"integrity": "sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.25.6"
|
||||
},
|
||||
"bin": {
|
||||
"parser": "bin/babel-parser.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.25.6",
|
||||
"resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.25.6.tgz",
|
||||
"integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": "^7.24.8",
|
||||
"@babel/helper-validator-identifier": "^7.24.7",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@ctrl/tinycolor": {
|
||||
"version": "3.6.1",
|
||||
"resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz",
|
||||
"integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@element-plus/icons-vue": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.3.1.tgz",
|
||||
"integrity": "sha512-XxVUZv48RZAd87ucGS48jPf6pKu0yV5UCg9f4FFwtrYxXOwWuVJo6wOvSLKEoMQKjv8GsX/mhP6UsC1lRwbUWg==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"vue": "^3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
|
||||
"integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/core": {
|
||||
"version": "1.6.8",
|
||||
"resolved": "https://registry.npmmirror.com/@floating-ui/core/-/core-1.6.8.tgz",
|
||||
"integrity": "sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@floating-ui/utils": "^0.2.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/dom": {
|
||||
"version": "1.6.12",
|
||||
"resolved": "https://registry.npmmirror.com/@floating-ui/dom/-/dom-1.6.12.tgz",
|
||||
"integrity": "sha512-NP83c0HjokcGVEMeoStg317VD9W7eDlGK7457dMBANbKA6GJZdc7rjujdgqzTaz93jkGgc5P/jeWbaCHnMNc+w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@floating-ui/core": "^1.6.0",
|
||||
"@floating-ui/utils": "^0.2.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/utils": {
|
||||
"version": "0.2.8",
|
||||
"resolved": "https://registry.npmmirror.com/@floating-ui/utils/-/utils-0.2.8.tgz",
|
||||
"integrity": "sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
|
||||
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@popperjs/core": {
|
||||
"name": "@sxzz/popperjs-es",
|
||||
"version": "2.11.7",
|
||||
"resolved": "https://registry.npmmirror.com/@sxzz/popperjs-es/-/popperjs-es-2.11.7.tgz",
|
||||
"integrity": "sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/popperjs"
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||
"version": "4.21.2",
|
||||
"resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.2.tgz",
|
||||
"integrity": "sha512-6UUxd0+SKomjdzuAcp+HAmxw1FlGBnl1v2yEPSabtx4lBfdXHDVsW7+lQkgz9cNFJGY3AWR7+V8P5BqkD9L9nA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.5.tgz",
|
||||
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/lodash": {
|
||||
"version": "4.17.13",
|
||||
"resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.13.tgz",
|
||||
"integrity": "sha512-lfx+dftrEZcdBPczf9d0Qv0x+j/rfNCMuC6OcfXmO8gkfeNAY88PgKUbvG56whcN23gc27yenwF6oJZXGFpYxg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/lodash-es": {
|
||||
"version": "4.17.12",
|
||||
"resolved": "https://registry.npmmirror.com/@types/lodash-es/-/lodash-es-4.17.12.tgz",
|
||||
"integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/lodash": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/web-bluetooth": {
|
||||
"version": "0.0.16",
|
||||
"resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz",
|
||||
"integrity": "sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@vitejs/plugin-vue": {
|
||||
"version": "5.1.3",
|
||||
"resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-5.1.3.tgz",
|
||||
"integrity": "sha512-3xbWsKEKXYlmX82aOHufFQVnkbMC/v8fLpWwh6hWOUrK5fbbtBh9Q/WWse27BFgSy2/e2c0fz5Scgya9h2GLhw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^18.0.0 || >=20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vite": "^5.0.0",
|
||||
"vue": "^3.2.25"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-core": {
|
||||
"version": "3.5.1",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.1.tgz",
|
||||
"integrity": "sha512-WdjF+NSgFYdWttHevHw5uaJFtKPalhmxhlu2uREj8cLP0uyKKIR60/JvSZNTp0x+NSd63iTiORQTx3+tt55NWQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.25.3",
|
||||
"@vue/shared": "3.5.1",
|
||||
"entities": "^4.5.0",
|
||||
"estree-walker": "^2.0.2",
|
||||
"source-map-js": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-dom": {
|
||||
"version": "3.5.1",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.1.tgz",
|
||||
"integrity": "sha512-Ao23fB1lINo18HLCbJVApvzd9OQe8MgmQSgyY5+umbWj2w92w9KykVmJ4Iv2US5nak3ixc2B+7Km7JTNhQ8kSQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/compiler-core": "3.5.1",
|
||||
"@vue/shared": "3.5.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-sfc": {
|
||||
"version": "3.5.1",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.1.tgz",
|
||||
"integrity": "sha512-DFizMNH8eDglLhlfwJ0+ciBsztaYe3fY/zcZjrqL1ljXvUw/UpC84M1d7HpBTCW68SNqZyIxrs1XWmf+73Y65w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.25.3",
|
||||
"@vue/compiler-core": "3.5.1",
|
||||
"@vue/compiler-dom": "3.5.1",
|
||||
"@vue/compiler-ssr": "3.5.1",
|
||||
"@vue/shared": "3.5.1",
|
||||
"estree-walker": "^2.0.2",
|
||||
"magic-string": "^0.30.11",
|
||||
"postcss": "^8.4.44",
|
||||
"source-map-js": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-ssr": {
|
||||
"version": "3.5.1",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.1.tgz",
|
||||
"integrity": "sha512-C1hpSHQgRM8bg+5XWWD7CkFaVpSn9wZHCLRd10AmxqrH17d4EMP6+XcZpwBOM7H1jeStU5naEapZZWX0kso1tQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/compiler-dom": "3.5.1",
|
||||
"@vue/shared": "3.5.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/devtools-api": {
|
||||
"version": "6.6.4",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
|
||||
"integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@vue/reactivity": {
|
||||
"version": "3.5.1",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.1.tgz",
|
||||
"integrity": "sha512-aFE1nMDfbG7V+U5vdOk/NXxH/WX78XuAfX59vWmCM7Ao4lieoc83RkzOAWun61sQXlzNZ4IgROovFBHg+Iz1+Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/shared": "3.5.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/runtime-core": {
|
||||
"version": "3.5.1",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.1.tgz",
|
||||
"integrity": "sha512-Ce92CCholNRHR3ZtzpRp/7CDGIPFxQ7ElXt9iH91ilK5eOrUv3Z582NWJesuM3aYX71BujVG5/4ypUxigGNxjA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/reactivity": "3.5.1",
|
||||
"@vue/shared": "3.5.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/runtime-dom": {
|
||||
"version": "3.5.1",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.1.tgz",
|
||||
"integrity": "sha512-B/fUJfBLp5PwE0EWNfBYnA4JUea8Yufb3wN8fN0/HzaqBdkiRHh4sFHOjWqIY8GS75gj//8VqeEqhcU6yUjIkA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/reactivity": "3.5.1",
|
||||
"@vue/runtime-core": "3.5.1",
|
||||
"@vue/shared": "3.5.1",
|
||||
"csstype": "^3.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/server-renderer": {
|
||||
"version": "3.5.1",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.1.tgz",
|
||||
"integrity": "sha512-C5V/fjQTitgVaRNH5wCoHynaWysjZ+VH68drNsAvQYg4ArHsZUQNz0nHoEWRj41nzqkVn2RUlnWaEOTl2o1Ppg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/compiler-ssr": "3.5.1",
|
||||
"@vue/shared": "3.5.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "3.5.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/shared": {
|
||||
"version": "3.5.1",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.1.tgz",
|
||||
"integrity": "sha512-NdcTRoO4KuW2RSFgpE2c+E/R/ZHaRzWPxAGxhmxZaaqLh6nYCXx7lc9a88ioqOCxCaV2SFJmujkxbUScW7dNsQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@vueuse/core": {
|
||||
"version": "9.13.0",
|
||||
"resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-9.13.0.tgz",
|
||||
"integrity": "sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/web-bluetooth": "^0.0.16",
|
||||
"@vueuse/metadata": "9.13.0",
|
||||
"@vueuse/shared": "9.13.0",
|
||||
"vue-demi": "*"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/@vueuse/metadata": {
|
||||
"version": "9.13.0",
|
||||
"resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-9.13.0.tgz",
|
||||
"integrity": "sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/@vueuse/shared": {
|
||||
"version": "9.13.0",
|
||||
"resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-9.13.0.tgz",
|
||||
"integrity": "sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"vue-demi": "*"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/async-validator": {
|
||||
"version": "4.2.5",
|
||||
"resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz",
|
||||
"integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.7.9",
|
||||
"resolved": "https://registry.npmmirror.com/axios/-/axios-1.7.9.tgz",
|
||||
"integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz",
|
||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/dayjs": {
|
||||
"version": "1.11.13",
|
||||
"resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.13.tgz",
|
||||
"integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/element-plus": {
|
||||
"version": "2.8.8",
|
||||
"resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.8.8.tgz",
|
||||
"integrity": "sha512-MLAH1x2PGTnOT7Iwqh9ASgfZhvgqQqrdbxuJH0w2fGjzE4ZjryyLQj24HXoQO7Zon66U3lrYxbdLI57M6OX0qw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ctrl/tinycolor": "^3.4.1",
|
||||
"@element-plus/icons-vue": "^2.3.1",
|
||||
"@floating-ui/dom": "^1.0.1",
|
||||
"@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7",
|
||||
"@types/lodash": "^4.14.182",
|
||||
"@types/lodash-es": "^4.17.6",
|
||||
"@vueuse/core": "^9.1.0",
|
||||
"async-validator": "^4.2.5",
|
||||
"dayjs": "^1.11.13",
|
||||
"escape-html": "^1.0.3",
|
||||
"lodash": "^4.17.21",
|
||||
"lodash-es": "^4.17.21",
|
||||
"lodash-unified": "^1.0.2",
|
||||
"memoize-one": "^6.0.0",
|
||||
"normalize-wheel-es": "^1.2.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/entities": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz",
|
||||
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.21.5.tgz",
|
||||
"integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/aix-ppc64": "0.21.5",
|
||||
"@esbuild/android-arm": "0.21.5",
|
||||
"@esbuild/android-arm64": "0.21.5",
|
||||
"@esbuild/android-x64": "0.21.5",
|
||||
"@esbuild/darwin-arm64": "0.21.5",
|
||||
"@esbuild/darwin-x64": "0.21.5",
|
||||
"@esbuild/freebsd-arm64": "0.21.5",
|
||||
"@esbuild/freebsd-x64": "0.21.5",
|
||||
"@esbuild/linux-arm": "0.21.5",
|
||||
"@esbuild/linux-arm64": "0.21.5",
|
||||
"@esbuild/linux-ia32": "0.21.5",
|
||||
"@esbuild/linux-loong64": "0.21.5",
|
||||
"@esbuild/linux-mips64el": "0.21.5",
|
||||
"@esbuild/linux-ppc64": "0.21.5",
|
||||
"@esbuild/linux-riscv64": "0.21.5",
|
||||
"@esbuild/linux-s390x": "0.21.5",
|
||||
"@esbuild/linux-x64": "0.21.5",
|
||||
"@esbuild/netbsd-x64": "0.21.5",
|
||||
"@esbuild/openbsd-x64": "0.21.5",
|
||||
"@esbuild/sunos-x64": "0.21.5",
|
||||
"@esbuild/win32-arm64": "0.21.5",
|
||||
"@esbuild/win32-ia32": "0.21.5",
|
||||
"@esbuild/win32-x64": "0.21.5"
|
||||
}
|
||||
},
|
||||
"node_modules/escape-html": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz",
|
||||
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/estree-walker": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz",
|
||||
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.9",
|
||||
"resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.9.tgz",
|
||||
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.1.tgz",
|
||||
"integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash-es": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.21.tgz",
|
||||
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash-unified": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/lodash-unified/-/lodash-unified-1.0.3.tgz",
|
||||
"integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/lodash-es": "*",
|
||||
"lodash": "*",
|
||||
"lodash-es": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/magic-string": {
|
||||
"version": "0.30.11",
|
||||
"resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.11.tgz",
|
||||
"integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/sourcemap-codec": "^1.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/memoize-one": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/memoize-one/-/memoize-one-6.0.0.tgz",
|
||||
"integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.7",
|
||||
"resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.7.tgz",
|
||||
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"nanoid": "bin/nanoid.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/normalize-wheel-es": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz",
|
||||
"integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.0.tgz",
|
||||
"integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/pinia": {
|
||||
"version": "2.0.27",
|
||||
"resolved": "https://registry.npmmirror.com/pinia/-/pinia-2.0.27.tgz",
|
||||
"integrity": "sha512-nOnXP0OFeL8R4WjAHsterU+11vptda643gH02xKNtSCDPiRzVfRYodOLihLDoa0gL1KKuQKV+KOzEgdt3YvqEw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/devtools-api": "^6.4.5",
|
||||
"vue-demi": "*"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/posva"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@vue/composition-api": "^1.4.0",
|
||||
"typescript": ">=4.4.4",
|
||||
"vue": "^2.6.14 || ^3.2.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@vue/composition-api": {
|
||||
"optional": true
|
||||
},
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/pinia-plugin-persist": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/pinia-plugin-persist/-/pinia-plugin-persist-1.0.0.tgz",
|
||||
"integrity": "sha512-M4hBBd8fz/GgNmUPaaUsC29y1M09lqbXrMAHcusVoU8xlQi1TqgkWnnhvMikZwr7Le/hVyMx8KUcumGGrR6GVw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"vue-demi": "^0.12.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@vue/composition-api": "^1.0.0",
|
||||
"pinia": "^2.0.0",
|
||||
"vue": "^2.0.0 || >=3.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@vue/composition-api": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/pinia-plugin-persist/node_modules/vue-demi": {
|
||||
"version": "0.12.5",
|
||||
"resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.12.5.tgz",
|
||||
"integrity": "sha512-BREuTgTYlUr0zw0EZn3hnhC3I6gPWv+Kwh4MCih6QcAeaTlaIX0DwOVN0wHej7hSvDPecz4jygy/idsgKfW58Q==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"vue-demi-fix": "bin/vue-demi-fix.js",
|
||||
"vue-demi-switch": "bin/vue-demi-switch.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@vue/composition-api": "^1.0.0-rc.1",
|
||||
"vue": "^3.0.0-0 || ^2.6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@vue/composition-api": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.45",
|
||||
"resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.4.45.tgz",
|
||||
"integrity": "sha512-7KTLTdzdZZYscUc65XmjFiB73vBhBfbPztCYdUNvlaso9PrzjzcmjqBPR0lNGkcVlcO4BjiO5rK/qNz+XAen1Q==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/postcss/"
|
||||
},
|
||||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/postcss"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.7",
|
||||
"picocolors": "^1.0.1",
|
||||
"source-map-js": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.21.2",
|
||||
"resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.21.2.tgz",
|
||||
"integrity": "sha512-e3TapAgYf9xjdLvKQCkQTnbTKd4a6jwlpQSJJFokHGaX2IVjoEqkIIhiQfqsi0cdwlOD+tQGuOd5AJkc5RngBw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "1.0.5"
|
||||
},
|
||||
"bin": {
|
||||
"rollup": "dist/bin/rollup"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0",
|
||||
"npm": ">=8.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rollup/rollup-android-arm-eabi": "4.21.2",
|
||||
"@rollup/rollup-android-arm64": "4.21.2",
|
||||
"@rollup/rollup-darwin-arm64": "4.21.2",
|
||||
"@rollup/rollup-darwin-x64": "4.21.2",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.21.2",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.21.2",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.21.2",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.21.2",
|
||||
"@rollup/rollup-linux-powerpc64le-gnu": "4.21.2",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.21.2",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.21.2",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.21.2",
|
||||
"@rollup/rollup-linux-x64-musl": "4.21.2",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.21.2",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.21.2",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.21.2",
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.0.tgz",
|
||||
"integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/to-fast-properties": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
|
||||
"integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "5.4.3",
|
||||
"resolved": "https://registry.npmmirror.com/vite/-/vite-5.4.3.tgz",
|
||||
"integrity": "sha512-IH+nl64eq9lJjFqU+/yrRnrHPVTlgy42/+IzbOdaFDVlyLgI/wDlf+FCobXLX1cT0X5+7LMyH1mIy2xJdLfo8Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"esbuild": "^0.21.3",
|
||||
"postcss": "^8.4.43",
|
||||
"rollup": "^4.20.0"
|
||||
},
|
||||
"bin": {
|
||||
"vite": "bin/vite.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.0.0 || >=20.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/vitejs/vite?sponsor=1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/node": "^18.0.0 || >=20.0.0",
|
||||
"less": "*",
|
||||
"lightningcss": "^1.21.0",
|
||||
"sass": "*",
|
||||
"sass-embedded": "*",
|
||||
"stylus": "*",
|
||||
"sugarss": "*",
|
||||
"terser": "^5.4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/node": {
|
||||
"optional": true
|
||||
},
|
||||
"less": {
|
||||
"optional": true
|
||||
},
|
||||
"lightningcss": {
|
||||
"optional": true
|
||||
},
|
||||
"sass": {
|
||||
"optional": true
|
||||
},
|
||||
"sass-embedded": {
|
||||
"optional": true
|
||||
},
|
||||
"stylus": {
|
||||
"optional": true
|
||||
},
|
||||
"sugarss": {
|
||||
"optional": true
|
||||
},
|
||||
"terser": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vue": {
|
||||
"version": "3.5.1",
|
||||
"resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.1.tgz",
|
||||
"integrity": "sha512-k4UNnbPOEskodSxMtv+B9GljdB0C9ubZDOmW6vnXVGIfMqmEsY2+ohasjGguhGkMkrcP/oOrbH0dSD41x5JQFw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/compiler-dom": "3.5.1",
|
||||
"@vue/compiler-sfc": "3.5.1",
|
||||
"@vue/runtime-dom": "3.5.1",
|
||||
"@vue/server-renderer": "3.5.1",
|
||||
"@vue/shared": "3.5.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vue-demi": {
|
||||
"version": "0.14.10",
|
||||
"resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz",
|
||||
"integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"vue-demi-fix": "bin/vue-demi-fix.js",
|
||||
"vue-demi-switch": "bin/vue-demi-switch.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@vue/composition-api": "^1.0.0-rc.1",
|
||||
"vue": "^3.0.0-0 || ^2.6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@vue/composition-api": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vue-router": {
|
||||
"version": "4.4.5",
|
||||
"resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.4.5.tgz",
|
||||
"integrity": "sha512-4fKZygS8cH1yCyuabAXGUAsyi1b2/o/OKgu/RUb+znIYOxPRxdkytJEx+0wGcpBE1pX6vUgh5jwWOKRGvuA/7Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/devtools-api": "^6.6.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/posva"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.2.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
{
|
||||
"name": "bookstore",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.7.9",
|
||||
"element-plus": "^2.8.8",
|
||||
"pinia": "2.0.27",
|
||||
"pinia-plugin-persist": "^1.0.0",
|
||||
"vue": "^3.4.37",
|
||||
"vue-router": "^4.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.1.2",
|
||||
"vite": "^5.4.1"
|
||||
}
|
||||
}
|