gridsum-vue3-pc 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/LICENSE +21 -0
- package/README.md +88 -0
- package/bin/create-vue3-pc.mjs +545 -0
- package/package.json +68 -0
- package/template/base/.dockerignore +12 -0
- package/template/base/.env +5 -0
- package/template/base/.env.production +5 -0
- package/template/base/.eslintrc.cjs +22 -0
- package/template/base/.husky/commit-msg +1 -0
- package/template/base/.husky/pre-commit +1 -0
- package/template/base/.lintstagedrc +7 -0
- package/template/base/.prettierrc +5 -0
- package/template/base/.stylelintrc.cjs +6 -0
- package/template/base/.vscode/settings.json +26 -0
- package/template/base/CHANGELOG.md +6 -0
- package/template/base/Dockerfile +19 -0
- package/template/base/README.md +87 -0
- package/template/base/commitlint.config.cjs +1 -0
- package/template/base/index.html +15 -0
- package/template/base/mock/user.js +393 -0
- package/template/base/nginx.conf +27 -0
- package/template/base/package.json +47 -0
- package/template/base/public/favicon.svg +9 -0
- package/template/base/public/logo.svg +9 -0
- package/template/base/src/App.vue +20 -0
- package/template/base/src/assets/index.css +83 -0
- package/template/base/src/assets/logo.png +0 -0
- package/template/base/src/components/LanguageSwitch.vue +65 -0
- package/template/base/src/components/basic-layout.vue +484 -0
- package/template/base/src/composables/useCrud.ts +172 -0
- package/template/base/src/env.d.ts +28 -0
- package/template/base/src/env.ts +24 -0
- package/template/base/src/locales/en.json +153 -0
- package/template/base/src/locales/index.ts +32 -0
- package/template/base/src/locales/zh.json +153 -0
- package/template/base/src/main.ts +27 -0
- package/template/base/src/router/index.ts +91 -0
- package/template/base/src/services/http.ts +64 -0
- package/template/base/src/services/user.ts +23 -0
- package/template/base/src/store/modules/user.ts +45 -0
- package/template/base/src/views/Admin.vue +326 -0
- package/template/base/src/views/Home.vue +382 -0
- package/template/base/src/views/Login.vue +1252 -0
- package/template/base/src/views/Role.vue +269 -0
- package/template/base/src/views/User.vue +332 -0
- package/template/base/src/views/error/Forbidden.vue +62 -0
- package/template/base/src/views/error/NotFound.vue +60 -0
- package/template/base/src/views/error/ServerError.vue +62 -0
- package/template/base/tests/e2e/example.spec.ts +7 -0
- package/template/base/tests/unit/user.test.ts +15 -0
- package/template/base/vite.config.ts +52 -0
- package/template/cicd-github/.github/workflows/ci.yml +123 -0
- package/template/cicd-gitlab/.gitlab-ci.yml +103 -0
- package/template/cicd-jenkins/Jenkinsfile +107 -0
- package/template/ts/shims-vue.d.ts +5 -0
- package/template/ts/tsconfig.json +23 -0
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "gridsum-vue3-pc",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Gridsum Vue3 Vite PC Template Generator - 快速生成基于 Vue3 + Vite + TypeScript 的企业级 PC 端项目",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"bin": {
|
|
8
|
+
"gridsum-vue3-pc": "./bin/create-vue3-pc.mjs",
|
|
9
|
+
"gsvue": "./bin/create-vue3-pc.mjs"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"bin",
|
|
13
|
+
"template",
|
|
14
|
+
"README.md",
|
|
15
|
+
"CHANGELOG.md",
|
|
16
|
+
"LICENSE"
|
|
17
|
+
],
|
|
18
|
+
"keywords": [
|
|
19
|
+
"vue",
|
|
20
|
+
"vite",
|
|
21
|
+
"vue3",
|
|
22
|
+
"typescript",
|
|
23
|
+
"template",
|
|
24
|
+
"scaffolding",
|
|
25
|
+
"generator",
|
|
26
|
+
"element-plus",
|
|
27
|
+
"pinia",
|
|
28
|
+
"gridsum"
|
|
29
|
+
],
|
|
30
|
+
"author": "",
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"homepage": "https://github.com/gridsum/vue3-pc-template#readme",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "git+https://github.com/gridsum/vue3-pc-template.git"
|
|
36
|
+
},
|
|
37
|
+
"bugs": {
|
|
38
|
+
"url": "https://github.com/gridsum/vue3-pc-template/issues"
|
|
39
|
+
},
|
|
40
|
+
"funding": {
|
|
41
|
+
"type": "github",
|
|
42
|
+
"url": "https://github.com/sponsors/gridsum"
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=18.0.0"
|
|
47
|
+
},
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"@clack/prompts": "^0.9.0",
|
|
50
|
+
"cross-spawn": "^7.0.6",
|
|
51
|
+
"mri": "^1.2.0",
|
|
52
|
+
"picocolors": "^1.1.1"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@types/cross-spawn": "^6.0.6",
|
|
56
|
+
"eslint": "^10.5.0"
|
|
57
|
+
},
|
|
58
|
+
"scripts": {
|
|
59
|
+
"lint": "eslint bin/ --ext .mjs --fix",
|
|
60
|
+
"test": "node bin/create-vue3-pc.mjs --help",
|
|
61
|
+
"test:create": "node bin/create-vue3-pc.mjs test-project --name test-project --title \"Test Project\" --no-interactive && npm --prefix test-project install --legacy-peer-deps && npm --prefix test-project run lint && npm --prefix test-project run typecheck && npm --prefix test-project run build",
|
|
62
|
+
"posttest:create": "node -e \"const fs = require('fs'); if (fs.existsSync('test-project')) fs.rmSync('test-project', { recursive: true, force: true }); console.log('cleaned up test-project');\"",
|
|
63
|
+
"prepublishOnly": "npm test",
|
|
64
|
+
"version:patch": "npm version patch",
|
|
65
|
+
"version:minor": "npm version minor",
|
|
66
|
+
"version:major": "npm version major"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
root: true,
|
|
3
|
+
env: {
|
|
4
|
+
node: true,
|
|
5
|
+
browser: true,
|
|
6
|
+
es2021: true,
|
|
7
|
+
},
|
|
8
|
+
extends: [
|
|
9
|
+
'eslint:recommended',
|
|
10
|
+
'plugin:vue/vue3-recommended',
|
|
11
|
+
'prettier',
|
|
12
|
+
],
|
|
13
|
+
parserOptions: {
|
|
14
|
+
ecmaVersion: 2021,
|
|
15
|
+
sourceType: 'module',
|
|
16
|
+
},
|
|
17
|
+
plugins: ['vue'],
|
|
18
|
+
rules: {
|
|
19
|
+
'vue/multi-word-component-names': 'off',
|
|
20
|
+
'no-unused-vars': 'warn',
|
|
21
|
+
},
|
|
22
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
npx --no -- commitlint --edit $1
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
npx lint-staged
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"editor.codeActionsOnSave": {
|
|
3
|
+
"source.fixAll.eslint": "explicit",
|
|
4
|
+
"source.fixAll.stylelint": "explicit"
|
|
5
|
+
},
|
|
6
|
+
"editor.formatOnSave": true,
|
|
7
|
+
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
|
8
|
+
"editor.quickSuggestions": {
|
|
9
|
+
"strings": true
|
|
10
|
+
},
|
|
11
|
+
"files.associations": {
|
|
12
|
+
"*.vue": "vue"
|
|
13
|
+
},
|
|
14
|
+
"[json]": {
|
|
15
|
+
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
|
16
|
+
},
|
|
17
|
+
"[javascript]": {
|
|
18
|
+
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
|
19
|
+
},
|
|
20
|
+
"[typescript]": {
|
|
21
|
+
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
|
22
|
+
},
|
|
23
|
+
"[vue]": {
|
|
24
|
+
"editor.defaultFormatter": "Vue.volar"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
FROM node:20-alpine AS builder
|
|
2
|
+
|
|
3
|
+
WORKDIR /app
|
|
4
|
+
|
|
5
|
+
COPY package.json package-lock.json* ./
|
|
6
|
+
RUN npm ci
|
|
7
|
+
|
|
8
|
+
COPY . .
|
|
9
|
+
RUN npm run build
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
FROM nginx:alpine AS production
|
|
13
|
+
|
|
14
|
+
COPY --from=builder /app/dist /usr/share/nginx/html
|
|
15
|
+
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
|
16
|
+
|
|
17
|
+
EXPOSE 80
|
|
18
|
+
|
|
19
|
+
CMD ["nginx", "-g", "daemon off;"]
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Vue3 PC Template
|
|
2
|
+
|
|
3
|
+
基于 Vite + Vue3 + TypeScript + Pinia 的 PC 端项目模板。
|
|
4
|
+
|
|
5
|
+
## 技术栈
|
|
6
|
+
|
|
7
|
+
- **构建工具**: Vite 5
|
|
8
|
+
- **框架**: Vue 3.5
|
|
9
|
+
- **语言**: TypeScript
|
|
10
|
+
- **状态管理**: Pinia
|
|
11
|
+
- **路由**: Vue Router 4
|
|
12
|
+
- **UI 组件库**: Element Plus
|
|
13
|
+
- **国际化**: vue-i18n
|
|
14
|
+
- **网络请求**: Axios
|
|
15
|
+
- **样式**: SCSS
|
|
16
|
+
- **代码规范**: ESLint + Prettier + Stylelint
|
|
17
|
+
- **提交规范**: Husky + Commitlint
|
|
18
|
+
- **测试**: Vitest + Playwright
|
|
19
|
+
|
|
20
|
+
## 功能特性
|
|
21
|
+
|
|
22
|
+
- ✅ Vite + Vue3 + TypeScript 完整配置
|
|
23
|
+
- ✅ Pinia 状态管理
|
|
24
|
+
- ✅ Element Plus 组件库
|
|
25
|
+
- ✅ 国际化支持 (vue-i18n)
|
|
26
|
+
- ✅ Mock 数据支持
|
|
27
|
+
- ✅ 权限路由守卫
|
|
28
|
+
- ✅ 环境变量配置
|
|
29
|
+
- ✅ Docker 构建支持
|
|
30
|
+
- ✅ CI/CD 配置
|
|
31
|
+
|
|
32
|
+
## 快速开始
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# 安装依赖
|
|
36
|
+
yarn install
|
|
37
|
+
|
|
38
|
+
# 开发模式
|
|
39
|
+
yarn dev
|
|
40
|
+
|
|
41
|
+
# 构建生产
|
|
42
|
+
yarn build
|
|
43
|
+
|
|
44
|
+
# 预览构建
|
|
45
|
+
yarn preview
|
|
46
|
+
|
|
47
|
+
# 类型检查
|
|
48
|
+
yarn typecheck
|
|
49
|
+
|
|
50
|
+
# 代码检查
|
|
51
|
+
yarn lint
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## 环境变量
|
|
55
|
+
|
|
56
|
+
| 变量名 | 说明 | 默认值 |
|
|
57
|
+
|--------|------|--------|
|
|
58
|
+
| VITE_APP_TITLE | 项目标题 | Vue3 PC Template |
|
|
59
|
+
| VITE_APP_LOCALE | 默认语言 | zh-CN |
|
|
60
|
+
| VITE_APP_MOCK | 是否启用 Mock | true |
|
|
61
|
+
| VITE_APP_PORT | 开发服务器端口 | 3000 |
|
|
62
|
+
| VITE_APP_API_BASE_URL | API 基础路径 | http://localhost:8080 |
|
|
63
|
+
|
|
64
|
+
## 目录结构
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
src/
|
|
68
|
+
├── api/ # API 接口
|
|
69
|
+
├── assets/ # 静态资源
|
|
70
|
+
├── components/ # 公共组件
|
|
71
|
+
├── locales/ # 国际化文件
|
|
72
|
+
├── router/ # 路由配置
|
|
73
|
+
├── services/ # 网络请求
|
|
74
|
+
├── store/ # Pinia 状态管理
|
|
75
|
+
├── utils/ # 工具函数
|
|
76
|
+
└── views/ # 页面组件
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Docker 构建
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
# 构建镜像
|
|
83
|
+
docker build -t vue3-pc .
|
|
84
|
+
|
|
85
|
+
# 运行容器
|
|
86
|
+
docker run -p 80:80 vue3-pc
|
|
87
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = { extends: ['@commitlint/config-conventional'] };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<meta name="description" content="Vue3 PC Template - Enterprise Admin Dashboard">
|
|
7
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
8
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
|
9
|
+
<title>Vue3 PC Template</title>
|
|
10
|
+
</head>
|
|
11
|
+
<body>
|
|
12
|
+
<div id="app"></div>
|
|
13
|
+
<script type="module" src="/src/main.ts"></script>
|
|
14
|
+
</body>
|
|
15
|
+
</html>
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
const users = [
|
|
2
|
+
{
|
|
3
|
+
id: '1',
|
|
4
|
+
username: 'admin',
|
|
5
|
+
name: '管理员',
|
|
6
|
+
email: 'admin@example.com',
|
|
7
|
+
phone: '13800138000',
|
|
8
|
+
role: 'admin',
|
|
9
|
+
status: 1,
|
|
10
|
+
createTime: '2024-01-01 10:00:00',
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
id: '2',
|
|
14
|
+
username: 'zhangsan',
|
|
15
|
+
name: '张三',
|
|
16
|
+
email: 'zhangsan@example.com',
|
|
17
|
+
phone: '13800138001',
|
|
18
|
+
role: 'user',
|
|
19
|
+
status: 1,
|
|
20
|
+
createTime: '2024-01-15 14:30:00',
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
id: '3',
|
|
24
|
+
username: 'lisi',
|
|
25
|
+
name: '李四',
|
|
26
|
+
email: 'lisi@example.com',
|
|
27
|
+
phone: '13800138002',
|
|
28
|
+
role: 'user',
|
|
29
|
+
status: 1,
|
|
30
|
+
createTime: '2024-02-01 09:15:00',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: '4',
|
|
34
|
+
username: 'wangwu',
|
|
35
|
+
name: '王五',
|
|
36
|
+
email: 'wangwu@example.com',
|
|
37
|
+
phone: '13800138003',
|
|
38
|
+
role: 'user',
|
|
39
|
+
status: 0,
|
|
40
|
+
createTime: '2024-02-10 16:45:00',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: '5',
|
|
44
|
+
username: 'zhaoliu',
|
|
45
|
+
name: '赵六',
|
|
46
|
+
email: 'zhaoliu@example.com',
|
|
47
|
+
phone: '13800138004',
|
|
48
|
+
role: 'guest',
|
|
49
|
+
status: 1,
|
|
50
|
+
createTime: '2024-03-05 11:20:00',
|
|
51
|
+
},
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
const roles = [
|
|
55
|
+
{
|
|
56
|
+
id: '1',
|
|
57
|
+
name: '管理员',
|
|
58
|
+
code: 'admin',
|
|
59
|
+
description: '拥有所有权限',
|
|
60
|
+
permissions: ['user:view', 'user:add', 'user:edit', 'user:delete', 'role:view', 'role:add', 'role:edit', 'role:delete'],
|
|
61
|
+
createTime: '2024-01-01 10:00:00',
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: '2',
|
|
65
|
+
name: '普通用户',
|
|
66
|
+
code: 'user',
|
|
67
|
+
description: '普通用户权限',
|
|
68
|
+
permissions: ['user:view', 'role:view'],
|
|
69
|
+
createTime: '2024-01-01 10:00:00',
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
id: '3',
|
|
73
|
+
name: '访客',
|
|
74
|
+
code: 'guest',
|
|
75
|
+
description: '只读权限',
|
|
76
|
+
permissions: ['user:view'],
|
|
77
|
+
createTime: '2024-01-01 10:00:00',
|
|
78
|
+
},
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
const permissions = [
|
|
82
|
+
{ id: '1', name: '用户查看', code: 'user:view' },
|
|
83
|
+
{ id: '2', name: '用户新增', code: 'user:add' },
|
|
84
|
+
{ id: '3', name: '用户编辑', code: 'user:edit' },
|
|
85
|
+
{ id: '4', name: '用户删除', code: 'user:delete' },
|
|
86
|
+
{ id: '5', name: '角色查看', code: 'role:view' },
|
|
87
|
+
{ id: '6', name: '角色新增', code: 'role:add' },
|
|
88
|
+
{ id: '7', name: '角色编辑', code: 'role:edit' },
|
|
89
|
+
{ id: '8', name: '角色删除', code: 'role:delete' },
|
|
90
|
+
];
|
|
91
|
+
|
|
92
|
+
const credentials = {
|
|
93
|
+
admin: 'admin',
|
|
94
|
+
zhangsan: '123456',
|
|
95
|
+
lisi: '123456',
|
|
96
|
+
wangwu: '123456',
|
|
97
|
+
zhaoliu: '123456',
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
let nextUserId = 6;
|
|
101
|
+
let nextRoleId = 4;
|
|
102
|
+
|
|
103
|
+
function getId({ url, params }) {
|
|
104
|
+
return params?.id || url?.split('/').pop();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export default [
|
|
108
|
+
// 登录验证
|
|
109
|
+
{
|
|
110
|
+
url: '/api/auth/login',
|
|
111
|
+
method: 'post',
|
|
112
|
+
response: ({ body }) => {
|
|
113
|
+
const { username, password } = body;
|
|
114
|
+
const user = users.find(u => u.username === username && credentials[u.username] === password);
|
|
115
|
+
if (user) {
|
|
116
|
+
const roleObj = roles.find(r => r.code === user.role);
|
|
117
|
+
return {
|
|
118
|
+
code: 0,
|
|
119
|
+
data: {
|
|
120
|
+
id: user.id,
|
|
121
|
+
username: user.username,
|
|
122
|
+
displayName: user.name,
|
|
123
|
+
email: user.email,
|
|
124
|
+
role: user.role,
|
|
125
|
+
permissions: roleObj ? roleObj.permissions : [],
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
return { code: 401, message: '用户名或密码错误' };
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
// 用户列表
|
|
134
|
+
{
|
|
135
|
+
url: '/api/users',
|
|
136
|
+
method: 'get',
|
|
137
|
+
response: ({ query }) => {
|
|
138
|
+
const { page = 1, pageSize = 10, keyword = '' } = query;
|
|
139
|
+
let filteredUsers = [...users];
|
|
140
|
+
|
|
141
|
+
if (keyword) {
|
|
142
|
+
filteredUsers = filteredUsers.filter(u =>
|
|
143
|
+
u.username.includes(keyword) ||
|
|
144
|
+
u.name.includes(keyword) ||
|
|
145
|
+
u.email.includes(keyword)
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const start = (page - 1) * pageSize;
|
|
150
|
+
const end = start + parseInt(pageSize);
|
|
151
|
+
const list = filteredUsers.slice(start, end);
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
code: 0,
|
|
155
|
+
data: {
|
|
156
|
+
list,
|
|
157
|
+
total: filteredUsers.length,
|
|
158
|
+
page: parseInt(page),
|
|
159
|
+
pageSize: parseInt(pageSize),
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
// 导出用户
|
|
166
|
+
{
|
|
167
|
+
url: '/api/users/export',
|
|
168
|
+
method: 'get',
|
|
169
|
+
response: () => {
|
|
170
|
+
const exportData = users.map(u => {
|
|
171
|
+
const roleObj = roles.find(r => r.code === u.role);
|
|
172
|
+
return {
|
|
173
|
+
...u,
|
|
174
|
+
roleName: roleObj ? roleObj.name : u.role,
|
|
175
|
+
};
|
|
176
|
+
});
|
|
177
|
+
return { code: 0, data: { list: exportData, total: exportData.length } };
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
|
|
181
|
+
// 用户详情
|
|
182
|
+
{
|
|
183
|
+
url: '/api/users/:id',
|
|
184
|
+
method: 'get',
|
|
185
|
+
response: ({ params, url }) => {
|
|
186
|
+
const id = getId({ url, params });
|
|
187
|
+
const user = users.find(u => u.id === id);
|
|
188
|
+
return user
|
|
189
|
+
? { code: 0, data: user }
|
|
190
|
+
: { code: 404, message: '用户不存在' };
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
|
|
194
|
+
// 新增用户
|
|
195
|
+
{
|
|
196
|
+
url: '/api/users',
|
|
197
|
+
method: 'post',
|
|
198
|
+
response: ({ body }) => {
|
|
199
|
+
const newUser = {
|
|
200
|
+
...body,
|
|
201
|
+
id: String(nextUserId++),
|
|
202
|
+
createTime: new Date().toISOString().slice(0, 19).replace('T', ' '),
|
|
203
|
+
};
|
|
204
|
+
users.push(newUser);
|
|
205
|
+
return { code: 0, data: newUser };
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
|
|
209
|
+
// 编辑用户
|
|
210
|
+
{
|
|
211
|
+
url: '/api/users/:id',
|
|
212
|
+
method: 'put',
|
|
213
|
+
response: ({ params, body, url }) => {
|
|
214
|
+
const id = getId({ url, params });
|
|
215
|
+
const index = users.findIndex(u => u.id === id);
|
|
216
|
+
if (index > -1) {
|
|
217
|
+
users[index] = { ...users[index], ...body };
|
|
218
|
+
return { code: 0, data: users[index] };
|
|
219
|
+
}
|
|
220
|
+
return { code: 404, message: '用户不存在' };
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
|
|
224
|
+
// 批量删除用户
|
|
225
|
+
{
|
|
226
|
+
url: '/api/users/batch',
|
|
227
|
+
method: 'delete',
|
|
228
|
+
response: ({ body }) => {
|
|
229
|
+
const ids = body?.ids || [];
|
|
230
|
+
ids.forEach(id => {
|
|
231
|
+
const index = users.findIndex(u => u.id === id);
|
|
232
|
+
if (index > -1) users.splice(index, 1);
|
|
233
|
+
});
|
|
234
|
+
return { code: 0, message: '批量删除成功' };
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
|
|
238
|
+
// 删除用户
|
|
239
|
+
{
|
|
240
|
+
url: '/api/users/:id',
|
|
241
|
+
method: 'delete',
|
|
242
|
+
response: ({ params, url }) => {
|
|
243
|
+
const id = getId({ url, params });
|
|
244
|
+
const index = users.findIndex(u => u.id === id);
|
|
245
|
+
if (index > -1) {
|
|
246
|
+
users.splice(index, 1);
|
|
247
|
+
return { code: 0, message: '删除成功' };
|
|
248
|
+
}
|
|
249
|
+
return { code: 404, message: '用户不存在' };
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
|
|
253
|
+
// 角色列表
|
|
254
|
+
{
|
|
255
|
+
url: '/api/roles',
|
|
256
|
+
method: 'get',
|
|
257
|
+
response: ({ query }) => {
|
|
258
|
+
const { page = 1, pageSize = 10, keyword = '', all } = query;
|
|
259
|
+
let filteredRoles = [...roles];
|
|
260
|
+
|
|
261
|
+
if (keyword) {
|
|
262
|
+
filteredRoles = filteredRoles.filter(r =>
|
|
263
|
+
r.name.includes(keyword) ||
|
|
264
|
+
r.code.includes(keyword)
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (all) {
|
|
269
|
+
return { code: 0, data: filteredRoles };
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const start = (page - 1) * pageSize;
|
|
273
|
+
const end = start + parseInt(pageSize);
|
|
274
|
+
const list = filteredRoles.slice(start, end);
|
|
275
|
+
|
|
276
|
+
return {
|
|
277
|
+
code: 0,
|
|
278
|
+
data: {
|
|
279
|
+
list,
|
|
280
|
+
total: filteredRoles.length,
|
|
281
|
+
page: parseInt(page),
|
|
282
|
+
pageSize: parseInt(pageSize),
|
|
283
|
+
},
|
|
284
|
+
};
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
|
|
288
|
+
// 角色详情
|
|
289
|
+
{
|
|
290
|
+
url: '/api/roles/:id',
|
|
291
|
+
method: 'get',
|
|
292
|
+
response: ({ params, url }) => {
|
|
293
|
+
const id = getId({ url, params });
|
|
294
|
+
const role = roles.find(r => r.id === id);
|
|
295
|
+
return role
|
|
296
|
+
? { code: 0, data: role }
|
|
297
|
+
: { code: 404, message: '角色不存在' };
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
|
|
301
|
+
// 新增角色
|
|
302
|
+
{
|
|
303
|
+
url: '/api/roles',
|
|
304
|
+
method: 'post',
|
|
305
|
+
response: ({ body }) => {
|
|
306
|
+
const existingCode = roles.find(r => r.code === body.code);
|
|
307
|
+
if (existingCode) {
|
|
308
|
+
return { code: 400, message: '角色编码已存在' };
|
|
309
|
+
}
|
|
310
|
+
const newRole = {
|
|
311
|
+
...body,
|
|
312
|
+
id: String(nextRoleId++),
|
|
313
|
+
createTime: new Date().toISOString().slice(0, 19).replace('T', ' '),
|
|
314
|
+
};
|
|
315
|
+
roles.push(newRole);
|
|
316
|
+
return { code: 0, data: newRole };
|
|
317
|
+
},
|
|
318
|
+
},
|
|
319
|
+
|
|
320
|
+
// 编辑角色
|
|
321
|
+
{
|
|
322
|
+
url: '/api/roles/:id',
|
|
323
|
+
method: 'put',
|
|
324
|
+
response: ({ params, body, url }) => {
|
|
325
|
+
const id = getId({ url, params });
|
|
326
|
+
const index = roles.findIndex(r => r.id === id);
|
|
327
|
+
if (index > -1) {
|
|
328
|
+
const existingCode = roles.find((r, i) => i !== index && r.code === body.code);
|
|
329
|
+
if (existingCode) {
|
|
330
|
+
return { code: 400, message: '角色编码已存在' };
|
|
331
|
+
}
|
|
332
|
+
roles[index] = { ...roles[index], ...body };
|
|
333
|
+
return { code: 0, data: roles[index] };
|
|
334
|
+
}
|
|
335
|
+
return { code: 404, message: '角色不存在' };
|
|
336
|
+
},
|
|
337
|
+
},
|
|
338
|
+
|
|
339
|
+
// 批量删除角色
|
|
340
|
+
{
|
|
341
|
+
url: '/api/roles/batch',
|
|
342
|
+
method: 'delete',
|
|
343
|
+
response: ({ body }) => {
|
|
344
|
+
const ids = body?.ids || [];
|
|
345
|
+
const errors = [];
|
|
346
|
+
ids.forEach(id => {
|
|
347
|
+
const index = roles.findIndex(r => r.id === id);
|
|
348
|
+
if (index > -1) {
|
|
349
|
+
const roleCode = roles[index].code;
|
|
350
|
+
const hasUsers = users.some(u => u.role === roleCode);
|
|
351
|
+
if (hasUsers) {
|
|
352
|
+
errors.push(`角色 "${roles[index].name}" 下还有用户,无法删除`);
|
|
353
|
+
} else {
|
|
354
|
+
roles.splice(index, 1);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
if (errors.length > 0) {
|
|
359
|
+
return { code: 400, message: errors.join(';') };
|
|
360
|
+
}
|
|
361
|
+
return { code: 0, message: '批量删除成功' };
|
|
362
|
+
},
|
|
363
|
+
},
|
|
364
|
+
|
|
365
|
+
// 删除角色
|
|
366
|
+
{
|
|
367
|
+
url: '/api/roles/:id',
|
|
368
|
+
method: 'delete',
|
|
369
|
+
response: ({ params, url }) => {
|
|
370
|
+
const id = getId({ url, params });
|
|
371
|
+
const index = roles.findIndex(r => r.id === id);
|
|
372
|
+
if (index > -1) {
|
|
373
|
+
const roleCode = roles[index].code;
|
|
374
|
+
const hasUsers = users.some(u => u.role === roleCode);
|
|
375
|
+
if (hasUsers) {
|
|
376
|
+
return { code: 400, message: '该角色下还有用户,无法删除' };
|
|
377
|
+
}
|
|
378
|
+
roles.splice(index, 1);
|
|
379
|
+
return { code: 0, message: '删除成功' };
|
|
380
|
+
}
|
|
381
|
+
return { code: 404, message: '角色不存在' };
|
|
382
|
+
},
|
|
383
|
+
},
|
|
384
|
+
|
|
385
|
+
// 权限列表
|
|
386
|
+
{
|
|
387
|
+
url: '/api/permissions',
|
|
388
|
+
method: 'get',
|
|
389
|
+
response: () => {
|
|
390
|
+
return { code: 0, data: permissions };
|
|
391
|
+
},
|
|
392
|
+
},
|
|
393
|
+
];
|