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.
Files changed (57) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/LICENSE +21 -0
  3. package/README.md +88 -0
  4. package/bin/create-vue3-pc.mjs +545 -0
  5. package/package.json +68 -0
  6. package/template/base/.dockerignore +12 -0
  7. package/template/base/.env +5 -0
  8. package/template/base/.env.production +5 -0
  9. package/template/base/.eslintrc.cjs +22 -0
  10. package/template/base/.husky/commit-msg +1 -0
  11. package/template/base/.husky/pre-commit +1 -0
  12. package/template/base/.lintstagedrc +7 -0
  13. package/template/base/.prettierrc +5 -0
  14. package/template/base/.stylelintrc.cjs +6 -0
  15. package/template/base/.vscode/settings.json +26 -0
  16. package/template/base/CHANGELOG.md +6 -0
  17. package/template/base/Dockerfile +19 -0
  18. package/template/base/README.md +87 -0
  19. package/template/base/commitlint.config.cjs +1 -0
  20. package/template/base/index.html +15 -0
  21. package/template/base/mock/user.js +393 -0
  22. package/template/base/nginx.conf +27 -0
  23. package/template/base/package.json +47 -0
  24. package/template/base/public/favicon.svg +9 -0
  25. package/template/base/public/logo.svg +9 -0
  26. package/template/base/src/App.vue +20 -0
  27. package/template/base/src/assets/index.css +83 -0
  28. package/template/base/src/assets/logo.png +0 -0
  29. package/template/base/src/components/LanguageSwitch.vue +65 -0
  30. package/template/base/src/components/basic-layout.vue +484 -0
  31. package/template/base/src/composables/useCrud.ts +172 -0
  32. package/template/base/src/env.d.ts +28 -0
  33. package/template/base/src/env.ts +24 -0
  34. package/template/base/src/locales/en.json +153 -0
  35. package/template/base/src/locales/index.ts +32 -0
  36. package/template/base/src/locales/zh.json +153 -0
  37. package/template/base/src/main.ts +27 -0
  38. package/template/base/src/router/index.ts +91 -0
  39. package/template/base/src/services/http.ts +64 -0
  40. package/template/base/src/services/user.ts +23 -0
  41. package/template/base/src/store/modules/user.ts +45 -0
  42. package/template/base/src/views/Admin.vue +326 -0
  43. package/template/base/src/views/Home.vue +382 -0
  44. package/template/base/src/views/Login.vue +1252 -0
  45. package/template/base/src/views/Role.vue +269 -0
  46. package/template/base/src/views/User.vue +332 -0
  47. package/template/base/src/views/error/Forbidden.vue +62 -0
  48. package/template/base/src/views/error/NotFound.vue +60 -0
  49. package/template/base/src/views/error/ServerError.vue +62 -0
  50. package/template/base/tests/e2e/example.spec.ts +7 -0
  51. package/template/base/tests/unit/user.test.ts +15 -0
  52. package/template/base/vite.config.ts +52 -0
  53. package/template/cicd-github/.github/workflows/ci.yml +123 -0
  54. package/template/cicd-gitlab/.gitlab-ci.yml +103 -0
  55. package/template/cicd-jenkins/Jenkinsfile +107 -0
  56. package/template/ts/shims-vue.d.ts +5 -0
  57. 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,12 @@
1
+ node_modules
2
+ dist
3
+ .git
4
+ .gitignore
5
+ *.log
6
+ .DS_Store
7
+ .vscode
8
+ .idea
9
+ coverage
10
+ tests
11
+ mock
12
+ *.md
@@ -0,0 +1,5 @@
1
+ VITE_APP_TITLE=Vue3 PC Template
2
+ VITE_APP_LOCALE=zh-CN
3
+ VITE_APP_MOCK=true
4
+ VITE_APP_PORT=3000
5
+ VITE_APP_API_BASE_URL=http://localhost:8080
@@ -0,0 +1,5 @@
1
+ VITE_APP_TITLE=Vue3 PC Template
2
+ VITE_APP_LOCALE=zh-CN
3
+ VITE_APP_MOCK=false
4
+ VITE_APP_PORT=80
5
+ VITE_APP_API_BASE_URL=/api
@@ -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,7 @@
1
+ {
2
+ "*.js": ["eslint --fix"],
3
+ "*.vue": ["eslint --fix"],
4
+ "*.ts": ["eslint --fix"],
5
+ "*.css": ["stylelint --fix"],
6
+ "*.json": ["prettier --write"]
7
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "singleQuote": true,
3
+ "semi": true,
4
+ "trailingComma": "all"
5
+ }
@@ -0,0 +1,6 @@
1
+ module.exports = {
2
+ extends: [
3
+ 'stylelint-config-standard',
4
+ 'stylelint-config-prettier',
5
+ ],
6
+ };
@@ -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,6 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ ### Added
6
+ - Initial project setup
@@ -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
+ ];