first commit
This commit is contained in:
commit
8a0801b2ab
73
.gitignore
vendored
Normal file
73
.gitignore
vendored
Normal file
@ -0,0 +1,73 @@
|
||||
# 依赖包
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# 构建输出
|
||||
/dist
|
||||
/build
|
||||
|
||||
# 测试覆盖率
|
||||
/coverage
|
||||
|
||||
# 本地环境文件
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# 日志文件
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
logs
|
||||
*.log
|
||||
|
||||
# 编辑器目录和文件
|
||||
.idea
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
!.vscode/settings.json
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# 缓存文件
|
||||
.cache
|
||||
.npm
|
||||
.eslintcache
|
||||
.stylelintcache
|
||||
.temp
|
||||
.cache-loader
|
||||
|
||||
# 临时文件
|
||||
.tmp
|
||||
temp
|
||||
tmp
|
||||
|
||||
# 打包文件
|
||||
*.tgz
|
||||
*.tar.gz
|
||||
*.zip
|
||||
|
||||
# 自动生成的文件
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
desktop.ini
|
||||
|
||||
# 本地配置文件
|
||||
.env
|
||||
.env.development
|
||||
.env.test
|
||||
.env.production
|
||||
|
||||
# 其他
|
||||
.history
|
||||
.sass-cache
|
||||
69
README.md
Normal file
69
README.md
Normal file
@ -0,0 +1,69 @@
|
||||
# 蜂快·运营商平台
|
||||
|
||||
基于Vue 3和Ant Design Vue的蜂快·运营商平台。
|
||||
|
||||
## 功能特点
|
||||
|
||||
- 基于Vue 3、Vuex 4和Vue Router 4
|
||||
- 使用Ant Design Vue 3作为UI组件库
|
||||
- 响应式布局,适配不同屏幕尺寸
|
||||
- 包含常见的后台功能模块:
|
||||
- 用户管理
|
||||
- 系统设置
|
||||
- 仪表盘数据展示
|
||||
|
||||
## 开发环境
|
||||
|
||||
- Node.js >= 18.0.0
|
||||
- npm >= 8.6.0
|
||||
|
||||
## 安装与运行
|
||||
|
||||
1. 安装依赖
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
2. 开发模式运行
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
3. 构建生产版本
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
├── public/ # 静态资源
|
||||
├── src/ # 源代码
|
||||
│ ├── assets/ # 资源文件
|
||||
│ ├── components/ # 公共组件
|
||||
│ ├── layouts/ # 布局组件
|
||||
│ ├── router/ # 路由配置
|
||||
│ ├── store/ # Vuex存储
|
||||
│ ├── utils/ # 工具函数
|
||||
│ ├── views/ # 页面组件
|
||||
│ ├── App.vue # 根组件
|
||||
│ └── main.js # 入口文件
|
||||
├── .babelrc # Babel配置
|
||||
├── package.json # 项目依赖
|
||||
├── webpack.config.js # Webpack配置
|
||||
└── README.md # 项目说明
|
||||
```
|
||||
|
||||
## 登录信息
|
||||
|
||||
默认用户名和密码:
|
||||
|
||||
- 用户名:admin
|
||||
- 密码:admin123
|
||||
|
||||
## 许可证
|
||||
|
||||
ISC
|
||||
5
babel.config.js
Normal file
5
babel.config.js
Normal file
@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@babel/preset-env'
|
||||
]
|
||||
};
|
||||
39
package.json
Normal file
39
package.json
Normal file
@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "partner-admin",
|
||||
"version": "1.0.0",
|
||||
"description": "基于Ant Design Vue的蜂快·运营商平台",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"dev": "webpack serve --mode development",
|
||||
"build": "webpack --mode production",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [
|
||||
"vue",
|
||||
"ant-design-vue",
|
||||
"admin",
|
||||
"dashboard"
|
||||
],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"ant-design-vue": "^3.0.0",
|
||||
"axios": "^1.6.0",
|
||||
"vue": "^3.0.0",
|
||||
"vue-router": "^4.0.0",
|
||||
"vuex": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.0.0",
|
||||
"@babel/preset-env": "^7.0.0",
|
||||
"@vue/compiler-sfc": "^3.0.0",
|
||||
"babel-loader": "^10.0.0",
|
||||
"css-loader": "^7.1.2",
|
||||
"html-webpack-plugin": "^5.0.0",
|
||||
"style-loader": "^4.0.0",
|
||||
"vue-loader": "^17.0.0",
|
||||
"webpack": "^5.0.0",
|
||||
"webpack-cli": "^6.0.1",
|
||||
"webpack-dev-server": "^5.2.0"
|
||||
}
|
||||
}
|
||||
12
public/index.html
Normal file
12
public/index.html
Normal file
@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>蜂快·运营商平台</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ant-design/icons-svg@4.2.1/lib/asn/index.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
</html>
|
||||
24
src/App.vue
Normal file
24
src/App.vue
Normal file
@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<router-view></router-view>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'App'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif;
|
||||
}
|
||||
|
||||
#app {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
BIN
src/assets/logo.png
Normal file
BIN
src/assets/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.3 KiB |
BIN
src/assets/sub_logo.png
Normal file
BIN
src/assets/sub_logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.5 KiB |
141
src/layouts/AdminLayout.vue
Normal file
141
src/layouts/AdminLayout.vue
Normal file
@ -0,0 +1,141 @@
|
||||
<template>
|
||||
<a-layout style="min-height: 100vh">
|
||||
<!-- 侧边栏 -->
|
||||
<a-layout-sider v-model:collapsed="collapsed" collapsible>
|
||||
<div class="logo">
|
||||
<h1 v-if="!collapsed">蜂快·运营商平台</h1>
|
||||
<h1 v-else>蜂快</h1>
|
||||
</div>
|
||||
<a-menu
|
||||
v-model:selectedKeys="selectedKeys"
|
||||
theme="dark"
|
||||
mode="inline"
|
||||
>
|
||||
<a-menu-item key="dashboard" @click="() => $router.push('/dashboard')">
|
||||
<template #icon><dashboard-outlined /></template>
|
||||
<span>仪表盘</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="user" @click="() => $router.push('/user')">
|
||||
<template #icon><user-outlined /></template>
|
||||
<span>用户管理</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="settings" @click="() => $router.push('/settings')">
|
||||
<template #icon><setting-outlined /></template>
|
||||
<span>系统设置</span>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</a-layout-sider>
|
||||
|
||||
<a-layout>
|
||||
<!-- 头部 -->
|
||||
<a-layout-header style="background: #fff; padding: 0">
|
||||
<div class="header-right">
|
||||
<a-dropdown>
|
||||
<a class="ant-dropdown-link" @click.prevent>
|
||||
<a-avatar :src="userInfo?.avatar" />
|
||||
<span style="margin-left: 8px">{{ userInfo?.name || '用户' }}</span>
|
||||
</a>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item key="0">
|
||||
<a href="javascript:;" @click="handleLogout">退出登录</a>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</div>
|
||||
</a-layout-header>
|
||||
|
||||
<!-- 内容区 -->
|
||||
<a-layout-content style="margin: 16px">
|
||||
<div :style="{ padding: '24px', background: '#fff', minHeight: '360px' }">
|
||||
<router-view></router-view>
|
||||
</div>
|
||||
</a-layout-content>
|
||||
|
||||
<!-- 底部 -->
|
||||
<a-layout-footer style="text-align: center">
|
||||
蜂快·运营商平台 ©2025 Created by Admin
|
||||
</a-layout-footer>
|
||||
</a-layout>
|
||||
</a-layout>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, computed, watch, onMounted } from 'vue';
|
||||
import { useStore } from 'vuex';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import {
|
||||
DashboardOutlined,
|
||||
UserOutlined,
|
||||
SettingOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
|
||||
export default {
|
||||
name: 'AdminLayout',
|
||||
components: {
|
||||
DashboardOutlined,
|
||||
UserOutlined,
|
||||
SettingOutlined
|
||||
},
|
||||
setup() {
|
||||
const store = useStore();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const collapsed = computed({
|
||||
get: () => store.getters.sidebarCollapsed,
|
||||
set: (value) => store.commit('TOGGLE_SIDEBAR')
|
||||
});
|
||||
|
||||
const userInfo = computed(() => store.getters.userInfo);
|
||||
const selectedKeys = ref([]);
|
||||
|
||||
// 根据当前路由设置选中的菜单项
|
||||
watch(() => route.path, (path) => {
|
||||
const key = path.split('/')[1] || 'dashboard';
|
||||
selectedKeys.value = [key];
|
||||
}, { immediate: true });
|
||||
|
||||
// 检查是否已登录
|
||||
onMounted(() => {
|
||||
if (!store.getters.isAuthenticated) {
|
||||
router.push('/login');
|
||||
}
|
||||
});
|
||||
|
||||
const handleLogout = () => {
|
||||
store.dispatch('logout');
|
||||
router.push('/login');
|
||||
};
|
||||
|
||||
return {
|
||||
collapsed,
|
||||
selectedKeys,
|
||||
userInfo,
|
||||
handleLogout
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.logo {
|
||||
height: 32px;
|
||||
margin: 16px;
|
||||
color: white;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.logo h1 {
|
||||
color: white;
|
||||
font-size: 18px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
float: right;
|
||||
margin-right: 24px;
|
||||
}
|
||||
</style>
|
||||
14
src/main.js
Normal file
14
src/main.js
Normal file
@ -0,0 +1,14 @@
|
||||
import { createApp } from 'vue';
|
||||
import Antd from 'ant-design-vue';
|
||||
import App from './App.vue';
|
||||
import router from './router';
|
||||
import store from './store';
|
||||
import 'ant-design-vue/dist/antd.css';
|
||||
|
||||
const app = createApp(App);
|
||||
|
||||
app.use(Antd);
|
||||
app.use(router);
|
||||
app.use(store);
|
||||
|
||||
app.mount('#app');
|
||||
54
src/router/index.js
Normal file
54
src/router/index.js
Normal file
@ -0,0 +1,54 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router';
|
||||
import AdminLayout from '../layouts/AdminLayout.vue';
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
component: AdminLayout,
|
||||
redirect: '/dashboard',
|
||||
children: [
|
||||
{
|
||||
path: 'dashboard',
|
||||
name: 'Dashboard',
|
||||
component: () => import('../views/Dashboard.vue'),
|
||||
meta: { title: '仪表盘', icon: 'dashboard' }
|
||||
},
|
||||
{
|
||||
path: 'user',
|
||||
name: 'User',
|
||||
component: () => import('../views/User.vue'),
|
||||
meta: { title: '用户管理', icon: 'user' }
|
||||
},
|
||||
{
|
||||
path: 'settings',
|
||||
name: 'Settings',
|
||||
component: () => import('../views/Settings.vue'),
|
||||
meta: { title: '系统设置', icon: 'setting' }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
name: 'Login',
|
||||
component: () => import('../views/Login.vue')
|
||||
},
|
||||
{
|
||||
path: '/:pathMatch(.*)*',
|
||||
name: 'NotFound',
|
||||
component: () => import('../views/NotFound.vue')
|
||||
}
|
||||
];
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes
|
||||
});
|
||||
|
||||
// 路由守卫
|
||||
router.beforeEach((to, from, next) => {
|
||||
// 这里可以添加身份验证逻辑
|
||||
document.title = to.meta.title ? `${to.meta.title} - 蜂快·运营商平台` : '蜂快·运营商平台';
|
||||
next();
|
||||
});
|
||||
|
||||
export default router;
|
||||
52
src/store/index.js
Normal file
52
src/store/index.js
Normal file
@ -0,0 +1,52 @@
|
||||
import { createStore } from 'vuex';
|
||||
|
||||
export default createStore({
|
||||
state: {
|
||||
user: null,
|
||||
token: localStorage.getItem('token') || '',
|
||||
sidebar: {
|
||||
collapsed: false
|
||||
}
|
||||
},
|
||||
mutations: {
|
||||
SET_TOKEN(state, token) {
|
||||
state.token = token;
|
||||
localStorage.setItem('token', token);
|
||||
},
|
||||
CLEAR_TOKEN(state) {
|
||||
state.token = '';
|
||||
localStorage.removeItem('token');
|
||||
},
|
||||
SET_USER(state, user) {
|
||||
state.user = user;
|
||||
},
|
||||
TOGGLE_SIDEBAR(state) {
|
||||
state.sidebar.collapsed = !state.sidebar.collapsed;
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
login({ commit }, userInfo) {
|
||||
// 这里应该有实际的登录API调用
|
||||
return new Promise((resolve) => {
|
||||
// 模拟登录成功
|
||||
const token = 'mock-token-' + Date.now();
|
||||
commit('SET_TOKEN', token);
|
||||
commit('SET_USER', {
|
||||
name: userInfo.username,
|
||||
avatar: 'https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png',
|
||||
roles: ['admin']
|
||||
});
|
||||
resolve();
|
||||
});
|
||||
},
|
||||
logout({ commit }) {
|
||||
commit('CLEAR_TOKEN');
|
||||
commit('SET_USER', null);
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
isAuthenticated: state => !!state.token,
|
||||
userInfo: state => state.user,
|
||||
sidebarCollapsed: state => state.sidebar.collapsed
|
||||
}
|
||||
});
|
||||
145
src/views/Dashboard.vue
Normal file
145
src/views/Dashboard.vue
Normal file
@ -0,0 +1,145 @@
|
||||
<template>
|
||||
<div class="dashboard">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<template #title>
|
||||
<span><user-outlined /> 用户总数</span>
|
||||
</template>
|
||||
<div class="card-content">
|
||||
<h2>1,286</h2>
|
||||
<p>较上周 <a-tag color="green">+12%</a-tag></p>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<template #title>
|
||||
<span><shopping-outlined /> 订单总数</span>
|
||||
</template>
|
||||
<div class="card-content">
|
||||
<h2>4,389</h2>
|
||||
<p>较上周 <a-tag color="green">+8%</a-tag></p>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<template #title>
|
||||
<span><dollar-outlined /> 销售额</span>
|
||||
</template>
|
||||
<div class="card-content">
|
||||
<h2>¥ 126,560</h2>
|
||||
<p>较上周 <a-tag color="green">+23%</a-tag></p>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card>
|
||||
<template #title>
|
||||
<span><team-outlined /> 新增会员</span>
|
||||
</template>
|
||||
<div class="card-content">
|
||||
<h2>128</h2>
|
||||
<p>较上周 <a-tag color="red">-5%</a-tag></p>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16" style="margin-top: 16px">
|
||||
<a-col :span="16">
|
||||
<a-card title="销售趋势">
|
||||
<div style="height: 300px; display: flex; justify-content: center; align-items: center;">
|
||||
<p>这里将显示销售趋势图表</p>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-card title="最近活动">
|
||||
<a-list item-layout="horizontal" :data-source="activities">
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item>
|
||||
<a-list-item-meta>
|
||||
<template #avatar>
|
||||
<a-avatar :src="item.avatar" />
|
||||
</template>
|
||||
<template #title>{{ item.title }}</template>
|
||||
<template #description>{{ item.description }}</template>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue';
|
||||
import {
|
||||
UserOutlined,
|
||||
ShoppingOutlined,
|
||||
DollarOutlined,
|
||||
TeamOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
|
||||
export default {
|
||||
name: 'Dashboard',
|
||||
components: {
|
||||
UserOutlined,
|
||||
ShoppingOutlined,
|
||||
DollarOutlined,
|
||||
TeamOutlined
|
||||
},
|
||||
setup() {
|
||||
const activities = ref([
|
||||
{
|
||||
title: '张三',
|
||||
avatar: 'https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png',
|
||||
description: '刚刚完成了一笔订单'
|
||||
},
|
||||
{
|
||||
title: '李四',
|
||||
avatar: 'https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png',
|
||||
description: '刚刚注册成为新用户'
|
||||
},
|
||||
{
|
||||
title: '王五',
|
||||
avatar: 'https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png',
|
||||
description: '提交了一个工单'
|
||||
},
|
||||
{
|
||||
title: '系统通知',
|
||||
avatar: 'https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png',
|
||||
description: '服务器将于今晚23:00进行维护'
|
||||
}
|
||||
]);
|
||||
|
||||
return {
|
||||
activities
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dashboard {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.card-content h2 {
|
||||
font-size: 24px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.card-content p {
|
||||
margin: 0;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
</style>
|
||||
280
src/views/Login.vue
Normal file
280
src/views/Login.vue
Normal file
@ -0,0 +1,280 @@
|
||||
<template>
|
||||
<div class="login-container">
|
||||
<div class="login-box">
|
||||
<div class="login-left">
|
||||
<div class="logo-container">
|
||||
<img src="../assets/logo.png" alt="蜂快到家" class="logo" />
|
||||
<div class="logo-text">蜂快到家</div>
|
||||
</div>
|
||||
<div class="feature-list">
|
||||
<div class="feature-item">
|
||||
<check-circle-outlined />
|
||||
<span>高效管理配送</span>
|
||||
</div>
|
||||
<div class="feature-item">
|
||||
<check-circle-outlined />
|
||||
<span>实时监控订单</span>
|
||||
</div>
|
||||
<div class="feature-item">
|
||||
<check-circle-outlined />
|
||||
<span>便捷数据分析</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="login-right">
|
||||
<div class="welcome-text">
|
||||
<h1>欢迎使用 蜂快·运营商平台</h1>
|
||||
<p>请使用您的手机号和密码登录</p>
|
||||
</div>
|
||||
<a-form
|
||||
:model="formState"
|
||||
name="login"
|
||||
@finish="onFinish"
|
||||
autocomplete="off"
|
||||
class="login-form"
|
||||
>
|
||||
<a-form-item
|
||||
name="username"
|
||||
:rules="[{ required: true, message: '请输入手机号!' }]"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="formState.username"
|
||||
placeholder="请输入手机号"
|
||||
size="large"
|
||||
class="login-input"
|
||||
>
|
||||
<template #prefix><user-outlined /></template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
name="password"
|
||||
:rules="[{ required: true, message: '请输入密码!' }]"
|
||||
>
|
||||
<a-input-password
|
||||
v-model:value="formState.password"
|
||||
placeholder="请输入密码"
|
||||
size="large"
|
||||
class="login-input"
|
||||
>
|
||||
<template #prefix><lock-outlined /></template>
|
||||
</a-input-password>
|
||||
</a-form-item>
|
||||
|
||||
<div class="login-options">
|
||||
<a-checkbox v-model:checked="formState.remember">记住我</a-checkbox>
|
||||
</div>
|
||||
|
||||
<a-form-item>
|
||||
<a-button type="primary" html-type="submit" class="login-button" :loading="loading" size="large">
|
||||
登 录
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<div class="login-footer">
|
||||
<p>© 2025 蜂快到家. 保留所有权利</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { reactive, ref } from 'vue';
|
||||
import { useStore } from 'vuex';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { UserOutlined, LockOutlined, CheckCircleOutlined } from '@ant-design/icons-vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
export default {
|
||||
name: 'Login',
|
||||
components: {
|
||||
UserOutlined,
|
||||
LockOutlined,
|
||||
CheckCircleOutlined
|
||||
},
|
||||
setup() {
|
||||
const store = useStore();
|
||||
const router = useRouter();
|
||||
const loading = ref(false);
|
||||
|
||||
const formState = reactive({
|
||||
username: 'admin',
|
||||
password: 'admin123',
|
||||
remember: true
|
||||
});
|
||||
|
||||
const onFinish = async (values) => {
|
||||
loading.value = true;
|
||||
try {
|
||||
await store.dispatch('login', {
|
||||
username: formState.username,
|
||||
password: formState.password
|
||||
});
|
||||
message.success('登录成功');
|
||||
router.push('/');
|
||||
} catch (error) {
|
||||
message.error('登录失败: ' + error.message);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
formState,
|
||||
loading,
|
||||
onFinish
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.login-container {
|
||||
height: 100vh;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: #f0f8ff;
|
||||
}
|
||||
|
||||
.login-box {
|
||||
width: 900px;
|
||||
height: 600px;
|
||||
display: flex;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.login-left {
|
||||
width: 40%;
|
||||
background-color: #2b7aee;
|
||||
padding: 40px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.logo-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-top: 60px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 100px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.logo-text {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.feature-list {
|
||||
margin-bottom: 100px;
|
||||
}
|
||||
|
||||
.feature-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.feature-item .anticon {
|
||||
margin-right: 12px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.login-right {
|
||||
width: 60%;
|
||||
background-color: white;
|
||||
padding: 40px 60px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.welcome-text {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.welcome-text h1 {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.welcome-text p {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.login-input {
|
||||
height: 50px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.login-options {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.login-button {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
font-size: 16px;
|
||||
border-radius: 4px;
|
||||
background-color: #2b7aee;
|
||||
border-color: #2b7aee;
|
||||
}
|
||||
|
||||
.login-button:hover {
|
||||
background-color: #1e6cd8;
|
||||
border-color: #1e6cd8;
|
||||
}
|
||||
|
||||
.login-footer {
|
||||
text-align: center;
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.login-box {
|
||||
width: 90%;
|
||||
height: auto;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.login-left {
|
||||
width: 100%;
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.login-right {
|
||||
width: 100%;
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.logo-container {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.feature-list {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
31
src/views/NotFound.vue
Normal file
31
src/views/NotFound.vue
Normal file
@ -0,0 +1,31 @@
|
||||
<template>
|
||||
<div class="not-found">
|
||||
<a-result
|
||||
status="404"
|
||||
title="404"
|
||||
sub-title="抱歉,您访问的页面不存在。"
|
||||
>
|
||||
<template #extra>
|
||||
<a-button type="primary" @click="$router.push('/')">
|
||||
返回首页
|
||||
</a-button>
|
||||
</template>
|
||||
</a-result>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'NotFound'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.not-found {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
background-color: #f0f2f5;
|
||||
}
|
||||
</style>
|
||||
253
src/views/Settings.vue
Normal file
253
src/views/Settings.vue
Normal file
@ -0,0 +1,253 @@
|
||||
<template>
|
||||
<div class="settings">
|
||||
<a-card title="系统设置" :bordered="false">
|
||||
<a-tabs default-active-key="1">
|
||||
<a-tab-pane key="1" tab="基本设置">
|
||||
<a-form
|
||||
:model="basicForm"
|
||||
:label-col="{ span: 4 }"
|
||||
:wrapper-col="{ span: 14 }"
|
||||
@finish="onBasicFormFinish"
|
||||
>
|
||||
<a-form-item label="系统名称" name="systemName" :rules="[{ required: true, message: '请输入系统名称' }]">
|
||||
<a-input v-model:value="basicForm.systemName" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="系统描述" name="description">
|
||||
<a-textarea v-model:value="basicForm.description" :rows="4" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="系统Logo" name="logo">
|
||||
<a-upload
|
||||
name="logo"
|
||||
list-type="picture-card"
|
||||
class="logo-uploader"
|
||||
:show-upload-list="false"
|
||||
action="https://www.mocky.io/v2/5cc8019d300000980a055e76"
|
||||
:before-upload="beforeUpload"
|
||||
@change="handleLogoChange"
|
||||
>
|
||||
<img v-if="basicForm.logoUrl" :src="basicForm.logoUrl" alt="logo" style="width: 100%" />
|
||||
<div v-else>
|
||||
<plus-outlined />
|
||||
<div style="margin-top: 8px">上传</div>
|
||||
</div>
|
||||
</a-upload>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="备案信息" name="icp">
|
||||
<a-input v-model:value="basicForm.icp" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="版权信息" name="copyright">
|
||||
<a-input v-model:value="basicForm.copyright" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item :wrapper-col="{ span: 14, offset: 4 }">
|
||||
<a-button type="primary" html-type="submit">保存设置</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-tab-pane>
|
||||
|
||||
<a-tab-pane key="2" tab="安全设置">
|
||||
<a-form
|
||||
:model="securityForm"
|
||||
:label-col="{ span: 4 }"
|
||||
:wrapper-col="{ span: 14 }"
|
||||
@finish="onSecurityFormFinish"
|
||||
>
|
||||
<a-form-item label="密码策略" name="passwordPolicy">
|
||||
<a-select v-model:value="securityForm.passwordPolicy">
|
||||
<a-select-option value="simple">简单 (至少6位)</a-select-option>
|
||||
<a-select-option value="medium">中等 (至少8位,包含字母和数字)</a-select-option>
|
||||
<a-select-option value="strong">强 (至少10位,包含大小写字母、数字和特殊字符)</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="登录失败锁定" name="loginLockEnabled">
|
||||
<a-switch v-model:checked="securityForm.loginLockEnabled" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="锁定阈值" name="loginLockThreshold" v-if="securityForm.loginLockEnabled">
|
||||
<a-input-number v-model:value="securityForm.loginLockThreshold" :min="1" :max="10" />
|
||||
<span style="margin-left: 8px">次</span>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="锁定时间" name="loginLockTime" v-if="securityForm.loginLockEnabled">
|
||||
<a-input-number v-model:value="securityForm.loginLockTime" :min="1" :max="1440" />
|
||||
<span style="margin-left: 8px">分钟</span>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="会话超时" name="sessionTimeout">
|
||||
<a-input-number v-model:value="securityForm.sessionTimeout" :min="1" :max="1440" />
|
||||
<span style="margin-left: 8px">分钟</span>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item :wrapper-col="{ span: 14, offset: 4 }">
|
||||
<a-button type="primary" html-type="submit">保存设置</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-tab-pane>
|
||||
|
||||
<a-tab-pane key="3" tab="邮件设置">
|
||||
<a-form
|
||||
:model="emailForm"
|
||||
:label-col="{ span: 4 }"
|
||||
:wrapper-col="{ span: 14 }"
|
||||
@finish="onEmailFormFinish"
|
||||
>
|
||||
<a-form-item label="SMTP服务器" name="smtpServer" :rules="[{ required: true, message: '请输入SMTP服务器' }]">
|
||||
<a-input v-model:value="emailForm.smtpServer" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="SMTP端口" name="smtpPort" :rules="[{ required: true, message: '请输入SMTP端口' }]">
|
||||
<a-input-number v-model:value="emailForm.smtpPort" :min="1" :max="65535" style="width: 100%" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="发件人邮箱" name="senderEmail" :rules="[{ required: true, message: '请输入发件人邮箱' }, { type: 'email', message: '请输入有效的邮箱地址' }]">
|
||||
<a-input v-model:value="emailForm.senderEmail" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="发件人名称" name="senderName">
|
||||
<a-input v-model:value="emailForm.senderName" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="SMTP用户名" name="smtpUsername" :rules="[{ required: true, message: '请输入SMTP用户名' }]">
|
||||
<a-input v-model:value="emailForm.smtpUsername" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="SMTP密码" name="smtpPassword" :rules="[{ required: true, message: '请输入SMTP密码' }]">
|
||||
<a-input-password v-model:value="emailForm.smtpPassword" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="启用SSL" name="smtpSsl">
|
||||
<a-switch v-model:checked="emailForm.smtpSsl" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item :wrapper-col="{ span: 14, offset: 4 }">
|
||||
<a-button type="primary" html-type="submit" style="margin-right: 10px">保存设置</a-button>
|
||||
<a-button @click="testEmailSettings">测试连接</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { reactive } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { PlusOutlined } from '@ant-design/icons-vue';
|
||||
|
||||
export default {
|
||||
name: 'Settings',
|
||||
components: {
|
||||
PlusOutlined
|
||||
},
|
||||
setup() {
|
||||
// 基本设置表单
|
||||
const basicForm = reactive({
|
||||
systemName: '蜂快·运营商平台',
|
||||
description: '基于Ant Design Vue的蜂快·运营商平台',
|
||||
logoUrl: 'https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg',
|
||||
icp: '京ICP备12345678号',
|
||||
copyright: '©2025 蜂快·运营商平台'
|
||||
});
|
||||
|
||||
// 安全设置表单
|
||||
const securityForm = reactive({
|
||||
passwordPolicy: 'medium',
|
||||
loginLockEnabled: true,
|
||||
loginLockThreshold: 5,
|
||||
loginLockTime: 30,
|
||||
sessionTimeout: 120
|
||||
});
|
||||
|
||||
// 邮件设置表单
|
||||
const emailForm = reactive({
|
||||
smtpServer: 'smtp.example.com',
|
||||
smtpPort: 465,
|
||||
senderEmail: 'admin@example.com',
|
||||
senderName: '系统管理员',
|
||||
smtpUsername: 'admin@example.com',
|
||||
smtpPassword: 'password',
|
||||
smtpSsl: true
|
||||
});
|
||||
|
||||
// 基本设置保存
|
||||
const onBasicFormFinish = (values) => {
|
||||
console.log('基本设置表单提交:', values);
|
||||
message.success('基本设置已保存');
|
||||
};
|
||||
|
||||
// 安全设置保存
|
||||
const onSecurityFormFinish = (values) => {
|
||||
console.log('安全设置表单提交:', values);
|
||||
message.success('安全设置已保存');
|
||||
};
|
||||
|
||||
// 邮件设置保存
|
||||
const onEmailFormFinish = (values) => {
|
||||
console.log('邮件设置表单提交:', values);
|
||||
message.success('邮件设置已保存');
|
||||
};
|
||||
|
||||
// 测试邮件设置
|
||||
const testEmailSettings = () => {
|
||||
message.loading('正在测试邮件设置...', 2.5)
|
||||
.then(() => message.success('邮件设置测试成功'));
|
||||
};
|
||||
|
||||
// 上传Logo前的校验
|
||||
const beforeUpload = (file) => {
|
||||
const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png';
|
||||
if (!isJpgOrPng) {
|
||||
message.error('只能上传JPG或PNG格式的图片!');
|
||||
}
|
||||
const isLt2M = file.size / 1024 / 1024 < 2;
|
||||
if (!isLt2M) {
|
||||
message.error('图片必须小于2MB!');
|
||||
}
|
||||
return isJpgOrPng && isLt2M;
|
||||
};
|
||||
|
||||
// 处理Logo上传状态变化
|
||||
const handleLogoChange = (info) => {
|
||||
if (info.file.status === 'uploading') {
|
||||
return;
|
||||
}
|
||||
if (info.file.status === 'done') {
|
||||
// 获取上传后的URL
|
||||
basicForm.logoUrl = info.file.response.url;
|
||||
message.success('Logo上传成功');
|
||||
} else if (info.file.status === 'error') {
|
||||
message.error('Logo上传失败');
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
basicForm,
|
||||
securityForm,
|
||||
emailForm,
|
||||
onBasicFormFinish,
|
||||
onSecurityFormFinish,
|
||||
onEmailFormFinish,
|
||||
testEmailSettings,
|
||||
beforeUpload,
|
||||
handleLogoChange
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.settings {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.logo-uploader {
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
}
|
||||
</style>
|
||||
383
src/views/User.vue
Normal file
383
src/views/User.vue
Normal file
@ -0,0 +1,383 @@
|
||||
<template>
|
||||
<div class="user-management">
|
||||
<a-card title="用户管理" :bordered="false">
|
||||
<!-- 搜索和操作区域 -->
|
||||
<div class="table-operations">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="6">
|
||||
<a-input-search
|
||||
v-model:value="searchValue"
|
||||
placeholder="搜索用户名/邮箱"
|
||||
@search="onSearch"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="18" style="text-align: right">
|
||||
<a-button type="primary" @click="showModal">
|
||||
<template #icon><plus-outlined /></template>
|
||||
添加用户
|
||||
</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
|
||||
<!-- 用户表格 -->
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="userData"
|
||||
:pagination="pagination"
|
||||
:loading="loading"
|
||||
@change="handleTableChange"
|
||||
style="margin-top: 16px"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag :color="record.status === 'active' ? 'green' : 'red'">
|
||||
{{ record.status === 'active' ? '正常' : '禁用' }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a @click="editUser(record)">编辑</a>
|
||||
<a-divider type="vertical" />
|
||||
<a-popconfirm
|
||||
title="确定要删除此用户吗?"
|
||||
ok-text="确定"
|
||||
cancel-text="取消"
|
||||
@confirm="deleteUser(record)"
|
||||
>
|
||||
<a>删除</a>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
|
||||
<!-- 添加/编辑用户对话框 -->
|
||||
<a-modal
|
||||
v-model:visible="modalVisible"
|
||||
:title="modalTitle"
|
||||
@ok="handleModalOk"
|
||||
@cancel="handleModalCancel"
|
||||
>
|
||||
<a-form
|
||||
:model="formState"
|
||||
:rules="rules"
|
||||
ref="formRef"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 16 }"
|
||||
>
|
||||
<a-form-item label="用户名" name="username">
|
||||
<a-input v-model:value="formState.username" />
|
||||
</a-form-item>
|
||||
<a-form-item label="邮箱" name="email">
|
||||
<a-input v-model:value="formState.email" />
|
||||
</a-form-item>
|
||||
<a-form-item label="手机号" name="phone">
|
||||
<a-input v-model:value="formState.phone" />
|
||||
</a-form-item>
|
||||
<a-form-item label="角色" name="role">
|
||||
<a-select v-model:value="formState.role">
|
||||
<a-select-option value="admin">管理员</a-select-option>
|
||||
<a-select-option value="user">普通用户</a-select-option>
|
||||
<a-select-option value="guest">访客</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="状态" name="status">
|
||||
<a-switch
|
||||
v-model:checked="formState.statusActive"
|
||||
checked-children="正常"
|
||||
un-checked-children="禁用"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { PlusOutlined } from '@ant-design/icons-vue';
|
||||
|
||||
export default {
|
||||
name: 'UserManagement',
|
||||
components: {
|
||||
PlusOutlined
|
||||
},
|
||||
setup() {
|
||||
const searchValue = ref('');
|
||||
const loading = ref(false);
|
||||
const modalVisible = ref(false);
|
||||
const modalTitle = ref('添加用户');
|
||||
const formRef = ref(null);
|
||||
const isEdit = ref(false);
|
||||
const currentUserId = ref(null);
|
||||
|
||||
// 表格列定义
|
||||
const columns = [
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
title: '用户名',
|
||||
dataIndex: 'username',
|
||||
key: 'username'
|
||||
},
|
||||
{
|
||||
title: '邮箱',
|
||||
dataIndex: 'email',
|
||||
key: 'email'
|
||||
},
|
||||
{
|
||||
title: '手机号',
|
||||
dataIndex: 'phone',
|
||||
key: 'phone'
|
||||
},
|
||||
{
|
||||
title: '角色',
|
||||
dataIndex: 'role',
|
||||
key: 'role',
|
||||
filters: [
|
||||
{ text: '管理员', value: 'admin' },
|
||||
{ text: '普通用户', value: 'user' },
|
||||
{ text: '访客', value: 'guest' }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
filters: [
|
||||
{ text: '正常', value: 'active' },
|
||||
{ text: '禁用', value: 'inactive' }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
key: 'createTime',
|
||||
sorter: true
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 150
|
||||
}
|
||||
];
|
||||
|
||||
// 分页设置
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showTotal: (total) => `共 ${total} 条记录`
|
||||
});
|
||||
|
||||
// 表单状态
|
||||
const formState = reactive({
|
||||
username: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
role: 'user',
|
||||
statusActive: true
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = {
|
||||
username: [
|
||||
{ required: true, message: '请输入用户名', trigger: 'blur' },
|
||||
{ min: 3, max: 20, message: '用户名长度必须在3-20个字符之间', trigger: 'blur' }
|
||||
],
|
||||
email: [
|
||||
{ required: true, message: '请输入邮箱', trigger: 'blur' },
|
||||
{ type: 'email', message: '请输入有效的邮箱地址', trigger: 'blur' }
|
||||
],
|
||||
phone: [
|
||||
{ required: true, message: '请输入手机号', trigger: 'blur' },
|
||||
{ pattern: /^1[3-9]\d{9}$/, message: '请输入有效的手机号码', trigger: 'blur' }
|
||||
],
|
||||
role: [
|
||||
{ required: true, message: '请选择角色', trigger: 'change' }
|
||||
]
|
||||
};
|
||||
|
||||
// 模拟用户数据
|
||||
const userData = ref([
|
||||
{
|
||||
id: 1,
|
||||
username: 'admin',
|
||||
email: 'admin@example.com',
|
||||
phone: '13800138000',
|
||||
role: '管理员',
|
||||
status: 'active',
|
||||
createTime: '2025-01-01 12:00:00'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
username: 'user1',
|
||||
email: 'user1@example.com',
|
||||
phone: '13800138001',
|
||||
role: '普通用户',
|
||||
status: 'active',
|
||||
createTime: '2025-01-02 12:00:00'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
username: 'user2',
|
||||
email: 'user2@example.com',
|
||||
phone: '13800138002',
|
||||
role: '普通用户',
|
||||
status: 'inactive',
|
||||
createTime: '2025-01-03 12:00:00'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
username: 'guest',
|
||||
email: 'guest@example.com',
|
||||
phone: '13800138003',
|
||||
role: '访客',
|
||||
status: 'active',
|
||||
createTime: '2025-01-04 12:00:00'
|
||||
}
|
||||
]);
|
||||
|
||||
// 加载用户数据
|
||||
const loadUserData = () => {
|
||||
loading.value = true;
|
||||
// 这里应该是实际的API调用
|
||||
setTimeout(() => {
|
||||
pagination.total = userData.value.length;
|
||||
loading.value = false;
|
||||
}, 500);
|
||||
};
|
||||
|
||||
// 搜索用户
|
||||
const onSearch = (value) => {
|
||||
console.log('搜索:', value);
|
||||
// 实际应用中应该调用API进行搜索
|
||||
loadUserData();
|
||||
};
|
||||
|
||||
// 表格变化处理
|
||||
const handleTableChange = (pag, filters, sorter) => {
|
||||
console.log('表格变化:', pag, filters, sorter);
|
||||
pagination.current = pag.current;
|
||||
pagination.pageSize = pag.pageSize;
|
||||
// 实际应用中应该调用API获取数据
|
||||
loadUserData();
|
||||
};
|
||||
|
||||
// 显示添加用户对话框
|
||||
const showModal = () => {
|
||||
resetForm();
|
||||
isEdit.value = false;
|
||||
modalTitle.value = '添加用户';
|
||||
modalVisible.value = true;
|
||||
};
|
||||
|
||||
// 编辑用户
|
||||
const editUser = (record) => {
|
||||
isEdit.value = true;
|
||||
currentUserId.value = record.id;
|
||||
modalTitle.value = '编辑用户';
|
||||
|
||||
// 填充表单数据
|
||||
formState.username = record.username;
|
||||
formState.email = record.email;
|
||||
formState.phone = record.phone;
|
||||
formState.role = record.role === '管理员' ? 'admin' : record.role === '普通用户' ? 'user' : 'guest';
|
||||
formState.statusActive = record.status === 'active';
|
||||
|
||||
modalVisible.value = true;
|
||||
};
|
||||
|
||||
// 删除用户
|
||||
const deleteUser = (record) => {
|
||||
console.log('删除用户:', record);
|
||||
// 实际应用中应该调用API删除用户
|
||||
message.success(`用户 ${record.username} 已删除`);
|
||||
loadUserData();
|
||||
};
|
||||
|
||||
// 对话框确认
|
||||
const handleModalOk = () => {
|
||||
formRef.value.validate().then(() => {
|
||||
// 表单验证通过
|
||||
const userData = {
|
||||
...formState,
|
||||
status: formState.statusActive ? 'active' : 'inactive'
|
||||
};
|
||||
|
||||
if (isEdit.value) {
|
||||
console.log('更新用户:', userData);
|
||||
message.success('用户信息已更新');
|
||||
} else {
|
||||
console.log('添加用户:', userData);
|
||||
message.success('用户已添加');
|
||||
}
|
||||
modalVisible.value = false;
|
||||
loadUserData();
|
||||
}).catch(error => {
|
||||
console.log('表单验证失败:', error);
|
||||
});
|
||||
};
|
||||
|
||||
// 对话框取消
|
||||
const handleModalCancel = () => {
|
||||
modalVisible.value = false;
|
||||
};
|
||||
|
||||
// 重置表单
|
||||
const resetForm = () => {
|
||||
formState.username = '';
|
||||
formState.email = '';
|
||||
formState.phone = '';
|
||||
formState.role = 'user';
|
||||
formState.statusActive = true;
|
||||
if (formRef.value) {
|
||||
formRef.value.resetFields();
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loadUserData();
|
||||
});
|
||||
|
||||
return {
|
||||
searchValue,
|
||||
loading,
|
||||
columns,
|
||||
userData,
|
||||
pagination,
|
||||
modalVisible,
|
||||
modalTitle,
|
||||
formState,
|
||||
rules,
|
||||
formRef,
|
||||
onSearch,
|
||||
handleTableChange,
|
||||
showModal,
|
||||
editUser,
|
||||
deleteUser,
|
||||
handleModalOk,
|
||||
handleModalCancel
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.user-management {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.table-operations {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
</style>
|
||||
54
webpack.config.js
Normal file
54
webpack.config.js
Normal file
@ -0,0 +1,54 @@
|
||||
const path = require('path');
|
||||
const { VueLoaderPlugin } = require('vue-loader');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
|
||||
module.exports = {
|
||||
mode: 'development',
|
||||
entry: './src/main.js',
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
filename: 'bundle.js',
|
||||
publicPath: '/'
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.vue$/,
|
||||
loader: 'vue-loader'
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
loader: 'babel-loader',
|
||||
exclude: /node_modules/
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: ['style-loader', 'css-loader']
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpg|gif|svg)$/,
|
||||
type: 'asset/resource'
|
||||
}
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new VueLoaderPlugin(),
|
||||
new HtmlWebpackPlugin({
|
||||
template: './public/index.html'
|
||||
})
|
||||
],
|
||||
resolve: {
|
||||
extensions: ['.js', '.vue', '.json'],
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, 'src')
|
||||
}
|
||||
},
|
||||
devServer: {
|
||||
static: {
|
||||
directory: path.join(__dirname, 'public'),
|
||||
},
|
||||
historyApiFallback: true,
|
||||
port: 8080,
|
||||
hot: true
|
||||
}
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user