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