@agile-team/wl-skills-kit 1.2.0 → 2.1.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 (86) hide show
  1. package/CHANGELOG.md +130 -0
  2. package/README.md +169 -366
  3. package/bin/wl-skills.js +149 -43
  4. package/files/.github/copilot-instructions.md +105 -42
  5. package/files/.github/guides/README.md +13 -0
  6. package/files/.github/guides/architecture.md +555 -0
  7. package/files/.github/guides/usage.md +166 -0
  8. package/files/.github/reports/README.md +65 -0
  9. package/files/.github/reports/SYS_DICT_INFO.md +19 -0
  10. package/files/.github/reports/SYS_PERMISSION_INFO.md +20 -0
  11. package/files/.github/reports//347/273/204/344/273/266/346/217/220/345/217/226/345/273/272/350/256/256.md +33 -0
  12. package/files/.github/reports//350/247/204/350/214/203/345/256/241/346/237/245/346/212/245/345/221/212.md +44 -0
  13. package/files/.github/skills/_compat/README.md +108 -0
  14. package/files/.github/skills/_compat/editors.json +61 -0
  15. package/files/.github/skills/_compat/headers/agents.txt +8 -0
  16. package/files/.github/skills/_compat/headers/claude-code.txt +7 -0
  17. package/files/.github/skills/_compat/headers/cline.txt +7 -0
  18. package/files/.github/skills/_compat/headers/cursor-mdc.txt +16 -0
  19. package/files/.github/skills/_compat/headers/cursor-rules.txt +7 -0
  20. package/files/.github/skills/_compat/headers/github-copilot.txt +1 -0
  21. package/files/.github/skills/_compat/headers/kiro.txt +10 -0
  22. package/files/.github/skills/_compat/headers/trae.txt +11 -0
  23. package/files/.github/skills/_compat/headers/windsurf.txt +7 -0
  24. package/files/.github/skills/_registry.md +81 -0
  25. package/files/.github/skills/{api-contract → core/api-contract}/SKILL.md +126 -29
  26. package/files/.github/skills/core/api-contract/USAGE.md +110 -0
  27. package/files/.github/skills/core/convention-audit/SKILL.md +189 -0
  28. package/files/.github/skills/core/convention-audit/USAGE.md +99 -0
  29. package/files/.github/skills/{page-codegen → core/page-codegen}/SKILL.md +67 -22
  30. package/files/.github/skills/core/page-codegen/USAGE.md +102 -0
  31. package/files/.github/skills/core/page-codegen/templates/_index.md +46 -0
  32. package/files/.github/skills/core/page-codegen/templates/domains/_CONTRIBUTING.md +107 -0
  33. package/files/.github/skills/{page-codegen → core/page-codegen/templates/domains/produce}/TPL-OPERATION-STATION.md +442 -442
  34. package/files/.github/skills/core/page-codegen/templates/domains/sale/README.md +26 -0
  35. package/files/.github/skills/{page-codegen → core/page-codegen/templates/universal}/TPL-DETAIL-TABS.md +94 -39
  36. package/files/.github/skills/{page-codegen → core/page-codegen/templates/universal}/TPL-DRIVEN.md +124 -124
  37. package/files/.github/skills/{prototype-scan → core/prototype-scan}/SKILL.md +87 -3
  38. package/files/.github/skills/core/prototype-scan/USAGE.md +95 -0
  39. package/files/.github/skills/core/template-extract/SKILL.md +139 -0
  40. package/files/.github/skills/core/template-extract/USAGE.md +93 -0
  41. package/files/.github/skills/domain/README.md +51 -0
  42. package/files/.github/skills/ops/code-fix/SKILL.draft.md +108 -0
  43. package/files/.github/skills/sync/dict-sync/SKILL.draft.md +100 -0
  44. package/files/.github/skills/{menu-sync → sync/menu-sync}/SKILL.md +258 -258
  45. package/files/.github/skills/sync/menu-sync/USAGE.md +104 -0
  46. package/files/.github/skills/{menu-sync → sync/menu-sync}/env/guide.md +83 -83
  47. package/files/.github/skills/sync/permission-sync/SKILL.draft.md +91 -0
  48. package/files/.github/standards/01-toolchain.md +57 -0
  49. package/files/.github/standards/02-code-structure.md +111 -0
  50. package/files/.github/standards/03-comments.md +53 -0
  51. package/files/.github/standards/04-coding-basics.md +33 -0
  52. package/files/.github/standards/05-logging.md +38 -0
  53. package/files/.github/standards/06-security.md +44 -0
  54. package/files/.github/standards/07-config.md +52 -0
  55. package/files/.github/standards/08-git.md +60 -0
  56. package/files/.github/standards/09-typescript.md +71 -0
  57. package/files/.github/standards/10-pinia.md +57 -0
  58. package/files/.github/standards/11-form-validation.md +81 -0
  59. package/files/.github/standards/12-base-table.md +116 -0
  60. package/files/.github/standards/13-platform-components.md +123 -0
  61. package/files/.github/standards/index.md +89 -0
  62. package/files/demo/produce/aiflow/mmwr-customer-apply-change-history/data.ts +196 -196
  63. package/files/demo/produce/aiflow/mmwr-customer-apply-change-history/index.scss +150 -150
  64. package/files/demo/produce/aiflow/mmwr-customer-apply-change-history/index.vue +79 -79
  65. package/files/docs/jh-date-range.md +257 -257
  66. package/files/docs/jh-date.md +222 -222
  67. package/files/docs/jh-dept-picker.md +190 -190
  68. package/files/docs/jh-drag-row.md +590 -590
  69. package/files/docs/jh-file-upload.md +216 -216
  70. package/files/docs/jh-picker.md +218 -218
  71. package/files/docs/jh-select.md +148 -148
  72. package/files/docs/jh-text.md +248 -248
  73. package/files/docs/jh-user-picker.md +197 -197
  74. package/package.json +4 -1
  75. package/files/.github/docs/menu-sync-design.md +0 -264
  76. package/files/.github/docs/use-skill.md +0 -382
  77. package/files/.github/docs/wl-skills-kit.md +0 -266
  78. package/files/.github/skills/convention-extract/SKILL.md +0 -236
  79. /package/files/.github/{docs → reports}/SYS_MENU_INFO.md +0 -0
  80. /package/files/.github/skills/{page-codegen → core/page-codegen/templates/universal}/TPL-CHANGE-HISTORY.md +0 -0
  81. /package/files/.github/skills/{page-codegen → core/page-codegen/templates/universal}/TPL-FORM-ROUTE.md +0 -0
  82. /package/files/.github/skills/{page-codegen → core/page-codegen/templates/universal}/TPL-LIST.md +0 -0
  83. /package/files/.github/skills/{page-codegen → core/page-codegen/templates/universal}/TPL-MASTER-DETAIL.md +0 -0
  84. /package/files/.github/skills/{page-codegen → core/page-codegen/templates/universal}/TPL-RECORD-FORM.md +0 -0
  85. /package/files/.github/skills/{page-codegen → core/page-codegen/templates/universal}/TPL-TREE-LIST.md +0 -0
  86. /package/files/.github/skills/{menu-sync → sync/menu-sync}/env/env.local.json +0 -0
@@ -1,197 +1,197 @@
1
- # jh-user-picker - 用户选择组件
2
-
3
- > 平台统一的用户挑选组件,用于选择单个或多个用户,内置用户数据加载与回显逻辑
4
-
5
- ## 📦 组件位置
6
-
7
- ```ts
8
- import "@jhlc/common-core";
9
- ```
10
-
11
- 组件已全局注册,可直接在模板中使用 `<jh-user-picker />`。
12
-
13
- ---
14
-
15
- ## 基本用法
16
-
17
- ### 1️⃣ 单选用户(最常用)
18
-
19
- ```vue
20
- <template>
21
- <jh-user-picker v-model="form.userId" placeholder="请选择负责人" />
22
- </template>
23
-
24
- <script setup lang="ts">
25
- import { ref } from "vue";
26
-
27
- const form = ref({
28
- userId: ""
29
- });
30
- </script>
31
- ```
32
-
33
- ---
34
-
35
- ### 2️⃣ 多选用户
36
-
37
- ```vue
38
- <jh-user-picker v-model="form.userIds" multiple placeholder="请选择相关人员" />
39
- ```
40
-
41
- ---
42
-
43
- ## Props 属性
44
-
45
- | 参数 | 说明 | 类型 | 默认值 |
46
- | -------------------- | ---------------------- | --------------------- | ---------------- |
47
- | modelValue / v-model | 绑定值 | `string \| string[]` | - |
48
- | multiple | 是否多选 | `boolean` | `false` |
49
- | placeholder | 占位提示 | `string` | `"请选择用户"` |
50
- | disabled | 是否禁用 | `boolean` | `false` |
51
- | clearable | 是否可清空 | `boolean` | `true` |
52
- | dataType | 返回数据类型(多选时) | `"array" \| "string"` | `"array"` |
53
- | dialogTitle | 弹窗标题 | `string` | `"选择用户"` |
54
- | dialogWidth | 弹窗宽度 | `string` | `"800px"` |
55
- | searchPlaceholder | 搜索框占位文本 | `string` | `"请输入用户名"` |
56
-
57
- > **重点**: 多选时,`dataType="string"` 会返回逗号分隔的字符串,`dataType="array"` 返回数组。
58
-
59
- ---
60
-
61
- ## Events 事件
62
-
63
- | 事件名 | 说明 | 回调参数 |
64
- | ----------------- | ------------------------ | ------------------------------------- |
65
- | update:modelValue | v-model 更新 | `(value: string \| string[]) => void` |
66
- | confirm | 确认选择时触发 | `() => void` |
67
- | clear | 清空时触发 | `() => void` |
68
- | blur | 失去焦点时触发 | `() => void` |
69
- | closed | 弹窗关闭时触发 | `() => void` |
70
- | remove | 移除选中项时触发(多选) | `() => void` |
71
-
72
- ---
73
-
74
- ## 常见场景
75
-
76
- ### 场景 1:表单负责人选择
77
-
78
- ```vue
79
- <jh-user-picker v-model="form.ownerId" placeholder="请选择负责人" />
80
- ```
81
-
82
- ---
83
-
84
- ### 场景 2:查询条件(多选)
85
-
86
- ```vue
87
- <jh-user-picker v-model="query.userIds" multiple placeholder="请选择用户" />
88
- ```
89
-
90
- ---
91
-
92
- ### 场景 3:详情页只读展示(配合 jh-text)
93
-
94
- ```vue
95
- <jh-text type="user" :value="detail.userId" />
96
- ```
97
-
98
- > ⚠️ `jh-user-picker` 仅用于选择,展示请使用 `jh-text`
99
-
100
- ---
101
-
102
- ## 与手动实现对比
103
-
104
- ### 使用 jh-user-picker(推荐)
105
-
106
- ```vue
107
- <jh-user-picker v-model="form.userId" />
108
- ```
109
-
110
- ### 手动实现(不推荐)
111
-
112
- ```vue
113
- <el-select v-model="form.userId">
114
- <el-option
115
- v-for="user in userList"
116
- :key="user.id"
117
- :label="user.name"
118
- :value="user.id"
119
- />
120
- </el-select>
121
- ```
122
-
123
- ❌ 需要自己加载用户列表
124
- ❌ 需要处理回显
125
- ❌ 每个页面重复实现
126
-
127
- ---
128
-
129
- ## 最佳实践
130
-
131
- ### 1️⃣ 编辑 & 展示分离
132
-
133
- | 场景 | 编辑 | 展示 |
134
- | ---- | -------------- | ------- |
135
- | 用户 | jh-user-picker | jh-text |
136
-
137
- ---
138
-
139
- ### 2️⃣ 表单中直接使用 v-model
140
-
141
- ```vue
142
- <jh-user-picker v-model="form.userId" />
143
- ```
144
-
145
- 避免手动监听 `change` 事件
146
-
147
- ---
148
-
149
- ### 3️⃣ 多选返回值说明
150
-
151
- ```ts
152
- // 单选
153
- userId: "u001";
154
-
155
- // 多选
156
- userIds: ["u001", "u002"];
157
- ```
158
-
159
- ---
160
-
161
- ## 注意事项
162
-
163
- 1. **组件内部已处理用户数据加载**
164
- - 不需要手动请求接口
165
- - 支持自动回显
166
-
167
- 2. **仅返回用户 ID**
168
- - 展示用户名称请使用 `jh-text`
169
-
170
- 3. **多选时注意字段类型**
171
- - 必须使用数组接收
172
-
173
- ---
174
-
175
- ## 🎯 真实项目示例
176
-
177
- ### 示例 1:新增页面
178
-
179
- ```vue
180
- <jh-user-picker v-model="form.createUserId" />
181
- ```
182
-
183
- ### 示例 2:查询条件
184
-
185
- ```vue
186
- <jh-user-picker v-model="query.userIds" multiple />
187
- ```
188
-
189
- ---
190
-
191
- ## 🚀 快速开始
192
-
193
- - **单选**:直接使用 v-model
194
- - **多选**:添加 `multiple`
195
- - **展示**:统一使用 `jh-text type="user"`
196
-
197
- **推荐作为平台统一的用户选择组件使用!**
1
+ # jh-user-picker - 用户选择组件
2
+
3
+ > 平台统一的用户挑选组件,用于选择单个或多个用户,内置用户数据加载与回显逻辑
4
+
5
+ ## 📦 组件位置
6
+
7
+ ```ts
8
+ import "@jhlc/common-core";
9
+ ```
10
+
11
+ 组件已全局注册,可直接在模板中使用 `<jh-user-picker />`。
12
+
13
+ ---
14
+
15
+ ## 基本用法
16
+
17
+ ### 1️⃣ 单选用户(最常用)
18
+
19
+ ```vue
20
+ <template>
21
+ <jh-user-picker v-model="form.userId" placeholder="请选择负责人" />
22
+ </template>
23
+
24
+ <script setup lang="ts">
25
+ import { ref } from "vue";
26
+
27
+ const form = ref({
28
+ userId: ""
29
+ });
30
+ </script>
31
+ ```
32
+
33
+ ---
34
+
35
+ ### 2️⃣ 多选用户
36
+
37
+ ```vue
38
+ <jh-user-picker v-model="form.userIds" multiple placeholder="请选择相关人员" />
39
+ ```
40
+
41
+ ---
42
+
43
+ ## Props 属性
44
+
45
+ | 参数 | 说明 | 类型 | 默认值 |
46
+ | -------------------- | ---------------------- | --------------------- | ---------------- |
47
+ | modelValue / v-model | 绑定值 | `string \| string[]` | - |
48
+ | multiple | 是否多选 | `boolean` | `false` |
49
+ | placeholder | 占位提示 | `string` | `"请选择用户"` |
50
+ | disabled | 是否禁用 | `boolean` | `false` |
51
+ | clearable | 是否可清空 | `boolean` | `true` |
52
+ | dataType | 返回数据类型(多选时) | `"array" \| "string"` | `"array"` |
53
+ | dialogTitle | 弹窗标题 | `string` | `"选择用户"` |
54
+ | dialogWidth | 弹窗宽度 | `string` | `"800px"` |
55
+ | searchPlaceholder | 搜索框占位文本 | `string` | `"请输入用户名"` |
56
+
57
+ > **重点**: 多选时,`dataType="string"` 会返回逗号分隔的字符串,`dataType="array"` 返回数组。
58
+
59
+ ---
60
+
61
+ ## Events 事件
62
+
63
+ | 事件名 | 说明 | 回调参数 |
64
+ | ----------------- | ------------------------ | ------------------------------------- |
65
+ | update:modelValue | v-model 更新 | `(value: string \| string[]) => void` |
66
+ | confirm | 确认选择时触发 | `() => void` |
67
+ | clear | 清空时触发 | `() => void` |
68
+ | blur | 失去焦点时触发 | `() => void` |
69
+ | closed | 弹窗关闭时触发 | `() => void` |
70
+ | remove | 移除选中项时触发(多选) | `() => void` |
71
+
72
+ ---
73
+
74
+ ## 常见场景
75
+
76
+ ### 场景 1:表单负责人选择
77
+
78
+ ```vue
79
+ <jh-user-picker v-model="form.ownerId" placeholder="请选择负责人" />
80
+ ```
81
+
82
+ ---
83
+
84
+ ### 场景 2:查询条件(多选)
85
+
86
+ ```vue
87
+ <jh-user-picker v-model="query.userIds" multiple placeholder="请选择用户" />
88
+ ```
89
+
90
+ ---
91
+
92
+ ### 场景 3:详情页只读展示(配合 jh-text)
93
+
94
+ ```vue
95
+ <jh-text type="user" :value="detail.userId" />
96
+ ```
97
+
98
+ > ⚠️ `jh-user-picker` 仅用于选择,展示请使用 `jh-text`
99
+
100
+ ---
101
+
102
+ ## 与手动实现对比
103
+
104
+ ### 使用 jh-user-picker(推荐)
105
+
106
+ ```vue
107
+ <jh-user-picker v-model="form.userId" />
108
+ ```
109
+
110
+ ### 手动实现(不推荐)
111
+
112
+ ```vue
113
+ <el-select v-model="form.userId">
114
+ <el-option
115
+ v-for="user in userList"
116
+ :key="user.id"
117
+ :label="user.name"
118
+ :value="user.id"
119
+ />
120
+ </el-select>
121
+ ```
122
+
123
+ ❌ 需要自己加载用户列表
124
+ ❌ 需要处理回显
125
+ ❌ 每个页面重复实现
126
+
127
+ ---
128
+
129
+ ## 最佳实践
130
+
131
+ ### 1️⃣ 编辑 & 展示分离
132
+
133
+ | 场景 | 编辑 | 展示 |
134
+ | ---- | -------------- | ------- |
135
+ | 用户 | jh-user-picker | jh-text |
136
+
137
+ ---
138
+
139
+ ### 2️⃣ 表单中直接使用 v-model
140
+
141
+ ```vue
142
+ <jh-user-picker v-model="form.userId" />
143
+ ```
144
+
145
+ 避免手动监听 `change` 事件
146
+
147
+ ---
148
+
149
+ ### 3️⃣ 多选返回值说明
150
+
151
+ ```ts
152
+ // 单选
153
+ userId: "u001";
154
+
155
+ // 多选
156
+ userIds: ["u001", "u002"];
157
+ ```
158
+
159
+ ---
160
+
161
+ ## 注意事项
162
+
163
+ 1. **组件内部已处理用户数据加载**
164
+ - 不需要手动请求接口
165
+ - 支持自动回显
166
+
167
+ 2. **仅返回用户 ID**
168
+ - 展示用户名称请使用 `jh-text`
169
+
170
+ 3. **多选时注意字段类型**
171
+ - 必须使用数组接收
172
+
173
+ ---
174
+
175
+ ## 🎯 真实项目示例
176
+
177
+ ### 示例 1:新增页面
178
+
179
+ ```vue
180
+ <jh-user-picker v-model="form.createUserId" />
181
+ ```
182
+
183
+ ### 示例 2:查询条件
184
+
185
+ ```vue
186
+ <jh-user-picker v-model="query.userIds" multiple />
187
+ ```
188
+
189
+ ---
190
+
191
+ ## 🚀 快速开始
192
+
193
+ - **单选**:直接使用 v-model
194
+ - **多选**:添加 `multiple`
195
+ - **展示**:统一使用 `jh-text type="user"`
196
+
197
+ **推荐作为平台统一的用户选择组件使用!**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agile-team/wl-skills-kit",
3
- "version": "1.2.0",
3
+ "version": "2.1.0",
4
4
  "description": "AI Skill 模板包 — 一键导入 AI 指令 + 组件文档 + 通用组件 + 领域样例,覆盖 Copilot/Cursor/Windsurf/Kiro 等主流 AI 编辑器",
5
5
  "bin": {
6
6
  "wl-skills-kit": "./bin/wl-skills.js"
@@ -31,5 +31,8 @@
31
31
  },
32
32
  "engines": {
33
33
  "node": ">=16.0.0"
34
+ },
35
+ "devDependencies": {
36
+ "xlsx": "^0.18.5"
34
37
  }
35
38
  }
@@ -1,264 +0,0 @@
1
- # 菜单配置方案设计
2
-
3
- ## 背景
4
-
5
- 本项目为 Module Federation 子应用,页面渲染链路如下:
6
-
7
- ```
8
- 后端菜单表 → 主应用加载菜单树 → Module Federation 分发路由 → 子应用渲染页面
9
-
10
- pages.ts 注册组件(纯构建配置)
11
- ```
12
-
13
- `pages.ts` 和菜单表职责不同:
14
-
15
- - **pages.ts**:告诉 Vite "这个路径有个 Vue 组件"(构建时)
16
- - **菜单表**:告诉系统 "页面叫什么名字、在哪个目录下、谁能访问"(运行时)
17
-
18
- 新增页面后必须在菜单表中注册,否则系 统无法路由到该页面。以下对比四种方案。
19
-
20
- ---
21
-
22
- ## 方案对比
23
-
24
- ### 方案 A:纯手动配置
25
-
26
- 前端在 `pages.ts` 注册组件后,开发者登录系统管理后台手动创建菜单。
27
-
28
- ```
29
- 开发者写 pages.ts → 登录后台 → 菜单管理 → 逐个新增 → 配权限
30
- ```
31
-
32
- | 优点 | 缺点 |
33
- |------|------|
34
- | 零开发成本 | 每新增一个页面都要切后台手动填 12 个字段 |
35
- | 零副作用 | 批量新增(如 AI 生成 20 个页面)效率极低 |
36
- | 后端无改动 | 容易填错组件路径导致菜单打不开 |
37
-
38
- ### 方案 B:双向同步脚本(已否决)
39
-
40
- 前端维护 `pnpm run menu:sync`,读取 `pages.ts` 和菜单表做双向 diff,增删改菜单。
41
-
42
- ```
43
- pages.ts ←→ 菜单表 (双向 diff & sync)
44
- ```
45
-
46
- | 优点 | 缺点 |
47
- |------|------|
48
- | 全自动 | **改名/改路径** → 旧记录匹配不到 → 一删一增 → 权限丢失 |
49
- | | **删页面** → 自动删菜单 → 可能误删已配好权限的菜单 |
50
- | | **父级冲突** → 路径推算的父级名(如 `production-plan`)和手动建的(如"生产计划")不一致 |
51
- | | **双数据源** → pages.ts 和菜单表永远存在不一致风险 |
52
-
53
- > 社区主流框架(Vben Admin、Pure Admin、RuoYi)无一采用双数据源同步方案。**此方案已否决。**
54
-
55
- ### 方案 C:只增不删的批量导入
56
-
57
- 后端提供 `POST /system/menu/batchImport`,按 `componentPath` 判断:存在则跳过,不存在则新增。
58
-
59
- ```
60
- 前端生成 JSON → POST batchImport → 只增不改不删
61
- ```
62
-
63
- | 优点 | 缺点 |
64
- |------|------|
65
- | 不碰已有数据 | 页面改名后重新导入会**产生重复记录**(旧的还在) |
66
- | 后端逻辑简单 | 只能用于首次导入,后续维护仍需手动操作 |
67
- | 无权限丢失风险 | 改名/调整排序/改父级目录 → 都要手动改 |
68
-
69
- ### 方案 D:前端推送覆盖(推荐)
70
-
71
- 前端是**菜单结构的唯一源**,后端是**权限配置的唯一源**,通过一个推送接口实现职责分离。
72
-
73
- ```
74
- pages.ts + menuMeta ──POST──→ /system/menu/batchPush ──→ 菜单表
75
- (结构定义) (upsert by componentPath)
76
- 仅写结构字段,不碰权限字段
77
- ```
78
-
79
- | 优点 | 缺点 |
80
- |------|------|
81
- | 一条命令完成菜单创建 | 后端需新增 1 个接口 |
82
- | 改名/改排序 → 再推一次即覆盖 | 首次需约定字段分区规则 |
83
- | **不删除**后端多余的菜单 | — |
84
- | **不碰**权限/角色绑定字段 | — |
85
- | 幂等 — 推多少次结果一致 | — |
86
-
87
- ---
88
-
89
- ## 四方案横评
90
-
91
- | 维度 | A 手动 | B 双向同步 | C 只增不删 | D 推送覆盖 |
92
- |------|--------|-----------|-----------|-----------|
93
- | 后端改动 | 无 | 大(双向 diff) | 小(1 接口) | 小(1 接口) |
94
- | 日常效率 | ❌ 低 | ✅ 高 | ⚠️ 仅首次 | ✅ 高 |
95
- | 批量场景 | ❌ 逐个填 | ✅ 自动 | ✅ 自动 | ✅ 自动 |
96
- | 改名安全 | ✅ 手动改 | ❌ 权限丢失 | ⚠️ 产生重复 | ✅ 覆盖更新 |
97
- | 删除安全 | ✅ 手动删 | ❌ 可能误删 | ✅ 不删 | ✅ 不删 |
98
- | 权限影响 | 无 | ❌ 可能丢失 | 无 | 无 |
99
- | 幂等性 | — | ❌ | ⚠️ 重复则跳过 | ✅ 完全幂等 |
100
- | 副作用 | 无 | **有** | 无 | 无 |
101
-
102
- ---
103
-
104
- ## 推荐:方案 D — 前端推送覆盖
105
-
106
- ### 核心原则
107
-
108
- - **菜单结构**(名称、路径、排序、父级、图标、隐藏状态)→ 前端定义,推送覆盖
109
- - **菜单权限**(角色绑定、按钮权限、数据权限)→ 后端管理,推送不碰
110
- - **不删除** → 前端只管"我有什么",后端多出来的菜单(手动创建的)不受影响
111
-
112
- ### 前端侧:扩展 pages.ts
113
-
114
- 在 `SharedPageItem` 中增加可选的菜单元数据:
115
-
116
- ```typescript
117
- // vite/plugins/shared/pages.ts
118
- export interface SharedPageItem {
119
- name: string; // 组件路径(已有)
120
- label: string; // 菜单名称(已有)
121
- // ↓ 菜单推送用(可选,仅需推送的页面填写)
122
- menuMeta?: {
123
- parentName: string; // 上级目录名称(后端按名称匹配)
124
- menuPath: string; // 菜单路径(camelCase)
125
- sortOrder: number; // 显示排序
126
- appCode: string; // 应用选择("produce" | "sale")
127
- hidden?: boolean; // 是否隐藏(默认 false)
128
- icon?: string; // 菜单图标
129
- permCode?: string; // 权限标识(默认取 menuPath)
130
- };
131
- }
132
- ```
133
-
134
- 使用示例:
135
-
136
- ```typescript
137
- const saleModule = gSale("demo", {
138
- khda: [
139
- ["customer-archive", "客户档案", {
140
- parentName: "客户档案管理", menuPath: "customerArchive",
141
- sortOrder: 1, appCode: "sale"
142
- }],
143
- ["customer-detail", "客户详情", {
144
- parentName: "客户档案管理", menuPath: "customerDetail",
145
- sortOrder: 5, appCode: "sale", hidden: true
146
- }]
147
- ]
148
- });
149
- ```
150
-
151
- ### 推送脚本
152
-
153
- ```bash
154
- pnpm run menu:push # 推送所有含 menuMeta 的页面到后端
155
- pnpm run menu:push --dry # 预览模式,仅打印将要推送的内容
156
- ```
157
-
158
- 脚本逻辑(`scripts/menu-push.ts`,vite-node 执行):
159
- 1. 读取 pages.ts 导出的 `SharedPageItem[]`
160
- 2. 过滤出有 `menuMeta` 的条目
161
- 3. 组装请求体,POST 到后端接口
162
-
163
- ### 后端侧:1 个接口
164
-
165
- ```
166
- POST /system/menu/batchPush
167
- ```
168
-
169
- **Request**:
170
-
171
- ```json
172
- {
173
- "items": [
174
- {
175
- "componentPath": "sale/demo/khda/customer-archive/index.vue",
176
- "menuName": "客户档案",
177
- "menuPath": "customerArchive",
178
- "parentName": "客户档案管理",
179
- "sortOrder": 1,
180
- "appCode": "sale",
181
- "hidden": false,
182
- "icon": "",
183
- "permCode": "customerArchive"
184
- }
185
- ]
186
- }
187
- ```
188
-
189
- **后端逻辑**(伪代码):
190
-
191
- ```
192
- for item in items:
193
- existing = SELECT * FROM sys_menu WHERE component_path = item.componentPath
194
- if existing:
195
- // 只更新结构字段,不动权限字段
196
- UPDATE sys_menu SET
197
- menu_name = item.menuName,
198
- menu_path = item.menuPath,
199
- parent_id = (SELECT id FROM sys_menu WHERE menu_name = item.parentName),
200
- sort_order = item.sortOrder,
201
- hidden = item.hidden,
202
- icon = item.icon
203
- WHERE id = existing.id
204
- // ⚠️ 不动: role_ids, perm_code(如已配置), button_perms, data_scope
205
- else:
206
- INSERT INTO sys_menu (component_path, menu_name, menu_path, parent_id,
207
- sort_order, app_code, hidden, icon, perm_code)
208
- VALUES (...)
209
- ```
210
-
211
- **字段分区规则**:
212
-
213
- | 分区 | 字段 | 推送覆盖? |
214
- |------|------|-----------|
215
- | 结构字段 | menu_name, menu_path, parent_id, sort_order, hidden, icon, component_path | ✅ 是 |
216
- | 权限字段 | role_menu 关联表, button_perms, data_scope | ❌ 否 |
217
- | 标识字段 | perm_code | 仅新增时写入,已存在则不覆盖 |
218
-
219
- ### 工作流
220
-
221
- ```
222
- 日常开发:
223
- AI生成页面 → pages.ts 注册(含 menuMeta) → pnpm run menu:push → 菜单出现 → 管理员配权限
224
-
225
- 改名/调整:
226
- 修改 pages.ts 的 label/menuMeta → pnpm run menu:push → 菜单自动更新,权限不受影响
227
-
228
- 删除页面:
229
- 删除 pages.ts 条目 → 手动去后台删菜单(推送不会自动删)
230
- ```
231
-
232
- ---
233
-
234
- ## 当前实现状态
235
-
236
- ### ✅ Phase 1(已实现):AI 直接调 `/system/menu/save`
237
-
238
- menu-sync Skill 已实现 Phase 1:AI 读取 `SYS_MENU_INFO.md` + `env.local.json`,逐条调用 `/system/menu/save` 接口创建菜单。
239
-
240
- **使用方式**:对 AI 说"帮我创建菜单" → AI 自动读取配置并调接口
241
-
242
- **当前工作流**:
243
-
244
- ```
245
- page-codegen 生成代码
246
- → 写入 SYS_MENU_INFO.md(覆盖/追加模式)
247
- → 对 AI 说"帮我创建菜单"
248
- → AI 读取 SYS_MENU_INFO.md + env.local.json → 调 /system/menu/save
249
- → 输出 created/skipped 结果表
250
- ```
251
-
252
- ---
253
-
254
- ### ⏳ Phase 2(待后端接口):`pnpm run menu:push` 脚本
255
-
256
- 方案 D 的批量推送脚本需要后端提供 `POST /system/menu/batchPush` 接口。在接口就绪前,继续使用 Phase 1(AI 调接口)。
257
-
258
- **过渡路径**:
259
-
260
- 1. ✅ page-codegen Skill 自动生成 `SYS_MENU_INFO.md`(已实现)
261
- 2. ✅ menu-sync Skill Phase 1:AI 调 `/system/menu/save`(已实现)
262
- 3. ⏳ 后端实现 `POST /system/menu/batchPush` 接口
263
- 4. ⏳ 前端 `scripts/menu-push.ts` 读 `pages.ts` → POST batchPush
264
- 5. ⏳ 切换到 `pnpm run menu:push`,废弃手动/AI 调接口流程