@agile-team/wl-skills-kit 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 (112) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +328 -0
  3. package/bin/wl-skills.js +104 -0
  4. package/files/.github/copilot-instructions.md +211 -0
  5. package/files/.github/docs/SYS_MENU_INFO.md +247 -0
  6. package/files/.github/docs/menu-sync-design.md +265 -0
  7. package/files/.github/docs/use-skill.md +379 -0
  8. package/files/.github/docs/wl-skills-kit.md +266 -0
  9. package/files/.github/skills/api-contract/SKILL.md +247 -0
  10. package/files/.github/skills/convention-extract/SKILL.md +355 -0
  11. package/files/.github/skills/menu-sync/SKILL.md +255 -0
  12. package/files/.github/skills/menu-sync/env/guide.md +73 -0
  13. package/files/.github/skills/page-codegen/SKILL.md +825 -0
  14. package/files/.github/skills/page-codegen/TPL-CHANGE-HISTORY.md +281 -0
  15. package/files/.github/skills/page-codegen/TPL-DETAIL-TABS.md +1112 -0
  16. package/files/.github/skills/page-codegen/TPL-DRIVEN.md +124 -0
  17. package/files/.github/skills/page-codegen/TPL-FORM-ROUTE.md +441 -0
  18. package/files/.github/skills/page-codegen/TPL-LIST.md +196 -0
  19. package/files/.github/skills/page-codegen/TPL-MASTER-DETAIL.md +153 -0
  20. package/files/.github/skills/page-codegen/TPL-OPERATION-STATION.md +442 -0
  21. package/files/.github/skills/page-codegen/TPL-RECORD-FORM.md +376 -0
  22. package/files/.github/skills/page-codegen/TPL-TREE-LIST.md +191 -0
  23. package/files/.github/skills/prototype-scan/SKILL.md +414 -0
  24. package/files/demo/README.md +44 -0
  25. package/files/demo/produce/aiflow/mmwr-customer-apply-add/api.md +54 -0
  26. package/files/demo/produce/aiflow/mmwr-customer-apply-add/data.ts +346 -0
  27. package/files/demo/produce/aiflow/mmwr-customer-apply-add/index.scss +1 -0
  28. package/files/demo/produce/aiflow/mmwr-customer-apply-add/index.vue +28 -0
  29. package/files/demo/produce/aiflow/mmwr-customer-apply-add-form/data.ts +115 -0
  30. package/files/demo/produce/aiflow/mmwr-customer-apply-add-form/index.scss +44 -0
  31. package/files/demo/produce/aiflow/mmwr-customer-apply-add-form/index.vue +43 -0
  32. package/files/demo/produce/aiflow/mmwr-customer-apply-change/data.ts +338 -0
  33. package/files/demo/produce/aiflow/mmwr-customer-apply-change/index.scss +1 -0
  34. package/files/demo/produce/aiflow/mmwr-customer-apply-change/index.vue +28 -0
  35. package/files/demo/produce/aiflow/mmwr-customer-apply-change-form/data.ts +115 -0
  36. package/files/demo/produce/aiflow/mmwr-customer-apply-change-form/index.scss +44 -0
  37. package/files/demo/produce/aiflow/mmwr-customer-apply-change-form/index.vue +43 -0
  38. package/files/demo/produce/aiflow/mmwr-customer-apply-change-history/data.ts +196 -0
  39. package/files/demo/produce/aiflow/mmwr-customer-apply-change-history/index.scss +150 -0
  40. package/files/demo/produce/aiflow/mmwr-customer-apply-change-history/index.vue +79 -0
  41. package/files/demo/produce/aiflow/mmwr-customer-archive/api.md +88 -0
  42. package/files/demo/produce/aiflow/mmwr-customer-archive/data.ts +601 -0
  43. package/files/demo/produce/aiflow/mmwr-customer-archive/index.scss +1 -0
  44. package/files/demo/produce/aiflow/mmwr-customer-archive/index.vue +64 -0
  45. package/files/demo/produce/aiflow/mmwr-customer-detail/api.md +67 -0
  46. package/files/demo/produce/aiflow/mmwr-customer-detail/data.ts +286 -0
  47. package/files/demo/produce/aiflow/mmwr-customer-detail/index.scss +139 -0
  48. package/files/demo/produce/aiflow/mmwr-customer-detail/index.vue +318 -0
  49. package/files/demo/produce/aiflow/mmwr-temp-customer-archive/api.md +98 -0
  50. package/files/demo/produce/aiflow/mmwr-temp-customer-archive/data.ts +543 -0
  51. package/files/demo/produce/aiflow/mmwr-temp-customer-archive/index.scss +1 -0
  52. package/files/demo/produce/aiflow/mmwr-temp-customer-archive/index.vue +52 -0
  53. package/files/demo/sale/demo/add-demo/data.ts +518 -0
  54. package/files/demo/sale/demo/add-demo/index.scss +207 -0
  55. package/files/demo/sale/demo/add-demo/index.vue +167 -0
  56. package/files/demo/sale/demo/billet-flame-cut-plan/data.ts +524 -0
  57. package/files/demo/sale/demo/billet-flame-cut-plan/index.scss +155 -0
  58. package/files/demo/sale/demo/billet-flame-cut-plan/index.vue +117 -0
  59. package/files/demo/sale/demo/domestic-trade-order/data.ts +308 -0
  60. package/files/demo/sale/demo/domestic-trade-order/index.scss +99 -0
  61. package/files/demo/sale/demo/domestic-trade-order/index.vue +77 -0
  62. package/files/demo/sale/demo/heat-batch-return/data.ts +367 -0
  63. package/files/demo/sale/demo/heat-batch-return/index.scss +100 -0
  64. package/files/demo/sale/demo/heat-batch-return/index.vue +170 -0
  65. package/files/demo/sale/demo/heat-batch-return/meltDialog.vue +320 -0
  66. package/files/demo/sale/demo/metallurgical-spec/data.ts +825 -0
  67. package/files/demo/sale/demo/metallurgical-spec/index.scss +264 -0
  68. package/files/demo/sale/demo/metallurgical-spec/index.vue +309 -0
  69. package/files/docs/jh-date-range.md +257 -0
  70. package/files/docs/jh-date.md +222 -0
  71. package/files/docs/jh-dept-picker.md +190 -0
  72. package/files/docs/jh-drag-row.md +590 -0
  73. package/files/docs/jh-file-upload.md +216 -0
  74. package/files/docs/jh-pagination.md +505 -0
  75. package/files/docs/jh-picker.md +218 -0
  76. package/files/docs/jh-select.md +148 -0
  77. package/files/docs/jh-text.md +248 -0
  78. package/files/docs/jh-user-picker.md +197 -0
  79. package/files/docs/page-query-hook-best-practices.md +362 -0
  80. package/files/docs/request.md +925 -0
  81. package/files/src/components/global/C_ParentView/index.vue +3 -0
  82. package/files/src/components/global/C_RightToolbar/index.vue +459 -0
  83. package/files/src/components/global/C_Splitter/index.vue +195 -0
  84. package/files/src/components/global/C_SvgIcon/index.vue +61 -0
  85. package/files/src/components/global/C_SvgIcon/svgicon.js +10 -0
  86. package/files/src/components/global/C_TagStatus/README.md +264 -0
  87. package/files/src/components/global/C_TagStatus/config.ts +192 -0
  88. package/files/src/components/global/C_TagStatus/index.vue +127 -0
  89. package/files/src/components/global/C_TagStatus/types.ts +64 -0
  90. package/files/src/components/global/C_Tree/README.md +153 -0
  91. package/files/src/components/global/C_Tree/index.scss +42 -0
  92. package/files/src/components/global/C_Tree/index.vue +119 -0
  93. package/files/src/components/global/C_Tree/types.ts +59 -0
  94. package/files/src/components/local/c_formModal/README.md +235 -0
  95. package/files/src/components/local/c_formModal/data.ts +95 -0
  96. package/files/src/components/local/c_formModal/index.scss +8 -0
  97. package/files/src/components/local/c_formModal/index.vue +107 -0
  98. package/files/src/components/local/c_formSections/README.md +496 -0
  99. package/files/src/components/local/c_formSections/data.ts +175 -0
  100. package/files/src/components/local/c_formSections/index.scss +280 -0
  101. package/files/src/components/local/c_formSections/index.vue +429 -0
  102. package/files/src/components/local/c_listModal/data.ts +41 -0
  103. package/files/src/components/local/c_listModal/index.vue +136 -0
  104. package/files/src/components/local/c_spliterTitle/index.scss +25 -0
  105. package/files/src/components/local/c_spliterTitle/index.vue +21 -0
  106. package/files/src/components/remote/AGGrid/README.md +530 -0
  107. package/files/src/components/remote/BaseForm/README.md +508 -0
  108. package/files/src/components/remote/BaseQuery/README.md +865 -0
  109. package/files/src/components/remote/BaseTable/README.md +941 -0
  110. package/files/src/components/remote/BaseToolbar/README.md +496 -0
  111. package/files/src/types/page.ts +24 -0
  112. package/package.json +31 -0
@@ -0,0 +1,247 @@
1
+ # 系统菜单配置 — 客户档案模块(produce / production-mmwr / aiflow)
2
+
3
+ > 对应系统管理 → 菜单管理 → 新增菜单,每栏直接复制粘贴。
4
+ > **操作顺序:先建目录(第 0 步),再逐个添加菜单(第 1-5 步)。**
5
+ >
6
+ > **pages.ts 注册位置**:`vite/plugins/shared/pages.ts` → `steelPerformanceSubModule` → `aiflow`
7
+ >
8
+ > **命名规范**:
9
+ >
10
+ > | 字段 | 规则 | 示例 |
11
+ > | -------- | ----------------------------------------------------------- | ---------------------------------------------------------------- |
12
+ > | 菜单路径 | 页面 kebab-name 转 camelCase | `mmwr-customer-archive` → `mmwrCustomerArchive` |
13
+ > | 组件路径 | `produce/production-mmwr/aiflow/{页面kebab-name}/index.vue` | `produce/production-mmwr/aiflow/mmwr-customer-archive/index.vue` |
14
+ > | 权限标识 | 与菜单路径一致(camelCase) | `mmwrCustomerArchive` |
15
+ > | 应用选择 | 生产 | |
16
+
17
+ ---
18
+
19
+ ## 第 0 步:新建目录
20
+
21
+ > Tab 选 **目录**
22
+
23
+ | 字段 | 值 |
24
+ | -------- | ---------------------------------- |
25
+ | 上级目录 | `生产棒线材`(按实际上级目录选择) |
26
+ | 菜单名称 | `客户档案管理` |
27
+ | 显示排序 | `6` |
28
+
29
+ ---
30
+
31
+ ## 第 1 步:客户档案
32
+
33
+ > Tab 选 **菜单**
34
+
35
+ | 字段 | 值 |
36
+ | ------------ | ---------------------------------------------------------------- |
37
+ | 上级目录 | `客户档案管理` |
38
+ | 应用选择 | `生产` |
39
+ | 使用缓存 | ◉ 使用 |
40
+ | 显示排序 | `1` |
41
+ | 菜单路径 | `mmwrCustomerArchive` |
42
+ | 菜单名称 | `客户档案` |
43
+ | 名称编码后缀 | `kehudangan` |
44
+ | 组件路径 | `produce/production-mmwr/aiflow/mmwr-customer-archive/index.vue` |
45
+ | 权限标识 | `mmwrCustomerArchive` |
46
+ | 是否隐藏 | 否 |
47
+
48
+ > pages.ts 对应:`["mmwr-customer-archive", "客户档案"]`
49
+
50
+ ---
51
+
52
+ ## 第 2 步:临时客户档案
53
+
54
+ > Tab 选 **菜单**
55
+
56
+ | 字段 | 值 |
57
+ | ------------ | --------------------------------------------------------------------- |
58
+ | 上级目录 | `客户档案管理` |
59
+ | 应用选择 | `生产` |
60
+ | 使用缓存 | ◉ 使用 |
61
+ | 显示排序 | `2` |
62
+ | 菜单路径 | `mmwrTempCustomerArchive` |
63
+ | 菜单名称 | `临时客户档案` |
64
+ | 名称编码后缀 | `linshikehudangan` |
65
+ | 组件路径 | `produce/production-mmwr/aiflow/mmwr-temp-customer-archive/index.vue` |
66
+ | 权限标识 | `mmwrTempCustomerArchive` |
67
+ | 是否隐藏 | 否 |
68
+
69
+ > pages.ts 对应:`["mmwr-temp-customer-archive", "临时客户档案"]`
70
+
71
+ ---
72
+
73
+ ## 第 3 步:客户申请新增
74
+
75
+ > Tab 选 **菜单**
76
+
77
+ | 字段 | 值 |
78
+ | ------------ | ------------------------------------------------------------------ |
79
+ | 上级目录 | `客户档案管理` |
80
+ | 应用选择 | `生产` |
81
+ | 使用缓存 | ◉ 使用 |
82
+ | 显示排序 | `3` |
83
+ | 菜单路径 | `mmwrCustomerApplyAdd` |
84
+ | 菜单名称 | `客户申请新增` |
85
+ | 名称编码后缀 | `kehushenqingxinzeng` |
86
+ | 组件路径 | `produce/production-mmwr/aiflow/mmwr-customer-apply-add/index.vue` |
87
+ | 权限标识 | `mmwrCustomerApplyAdd` |
88
+ | 是否隐藏 | 否 |
89
+
90
+ > pages.ts 对应:`["mmwr-customer-apply-add", "客户申请新增"]`
91
+
92
+ ---
93
+
94
+ ## 第 4 步:客户申请变更
95
+
96
+ > Tab 选 **菜单**
97
+
98
+ | 字段 | 值 |
99
+ | ------------ | --------------------------------------------------------------------- |
100
+ | 上级目录 | `客户档案管理` |
101
+ | 应用选择 | `生产` |
102
+ | 使用缓存 | ◉ 使用 |
103
+ | 显示排序 | `4` |
104
+ | 菜单路径 | `mmwrCustomerApplyChange` |
105
+ | 菜单名称 | `客户申请变更` |
106
+ | 名称编码后缀 | `kehushenqingbiangeng` |
107
+ | 组件路径 | `produce/production-mmwr/aiflow/mmwr-customer-apply-change/index.vue` |
108
+ | 权限标识 | `mmwrCustomerApplyChange` |
109
+ | 是否隐藏 | 否 |
110
+
111
+ > pages.ts 对应:`["mmwr-customer-apply-change", "客户申请变更"]`
112
+
113
+ ---
114
+
115
+ ## 第 5 步:客户详情(隐藏菜单)
116
+
117
+ > Tab 选 **菜单**
118
+
119
+ | 字段 | 值 |
120
+ | ------------ | --------------------------------------------------------------- |
121
+ | 上级目录 | `客户档案管理` |
122
+ | 应用选择 | `生产` |
123
+ | 使用缓存 | ◉ 使用 |
124
+ | 显示排序 | `5` |
125
+ | 菜单路径 | `mmwrCustomerDetail` |
126
+ | 菜单名称 | `客户详情` |
127
+ | 名称编码后缀 | `kehuxiangqing` |
128
+ | 组件路径 | `produce/production-mmwr/aiflow/mmwr-customer-detail/index.vue` |
129
+ | 权限标识 | `mmwrCustomerDetail` |
130
+ | 是否隐藏 | **是** |
131
+
132
+ > pages.ts 对应:`["mmwr-customer-detail", "客户详情"]`
133
+
134
+ ---
135
+
136
+ ## 第 6 步:客户申请新增表单(隐藏菜单)
137
+
138
+ > Tab 选 **菜单**
139
+
140
+ | 字段 | 值 |
141
+ | ------------ | ----------------------------------------------------------------------- |
142
+ | 上级目录 | `客户档案管理` |
143
+ | 应用选择 | `生产` |
144
+ | 使用缓存 | ◉ 使用 |
145
+ | 显示排序 | `6` |
146
+ | 菜单路径 | `mmwrCustomerApplyAddForm` |
147
+ | 菜单名称 | `客户申请新增表单` |
148
+ | 名称编码后缀 | `kehushenqingxinzengbiaodan` |
149
+ | 组件路径 | `produce/production-mmwr/aiflow/mmwr-customer-apply-add-form/index.vue` |
150
+ | 权限标识 | `mmwrCustomerApplyAddForm` |
151
+ | 是否隐藏 | **是** |
152
+
153
+ > pages.ts 对应:`["mmwr-customer-apply-add-form", "客户申请新增表单"]`
154
+
155
+ ---
156
+
157
+ ## 第 7 步:客户申请变更表单(隐藏菜单)
158
+
159
+ > Tab 选 **菜单**
160
+
161
+ | 字段 | 值 |
162
+ | ------------ | -------------------------------------------------------------------------- |
163
+ | 上级目录 | `客户档案管理` |
164
+ | 应用选择 | `生产` |
165
+ | 使用缓存 | ◉ 使用 |
166
+ | 显示排序 | `7` |
167
+ | 菜单路径 | `mmwrCustomerApplyChangeForm` |
168
+ | 菜单名称 | `客户申请变更表单` |
169
+ | 名称编码后缀 | `kehushenqingbiangengbiaodan` |
170
+ | 组件路径 | `produce/production-mmwr/aiflow/mmwr-customer-apply-change-form/index.vue` |
171
+ | 权限标识 | `mmwrCustomerApplyChangeForm` |
172
+ | 是否隐藏 | **是** |
173
+
174
+ > pages.ts 对应:`["mmwr-customer-apply-change-form", "客户申请变更表单"]`
175
+
176
+ ---
177
+
178
+ ## 第 8 步:变更历史查询(隐藏菜单)
179
+
180
+ > Tab 选 **菜单**
181
+
182
+ | 字段 | 值 |
183
+ | ------------ | --------------------------------------------------------------------------------------- |
184
+ | 上级目录 | `客户档案管理` |
185
+ | 应用选择 | `生产` |
186
+ | 使用缓存 | ◉ 使用 |
187
+ | 显示排序 | `8` |
188
+ | 菜单路径 | `mmwrCustomerApplyChangeHistory` |
189
+ | 菜单名称 | `变更历史查询` |
190
+ | 名称编码后缀 | `biangenglishipinshi` |
191
+ | 组件路径 | `produce/production-mmwr/aiflow/mmwr-customer-apply-change-history/index.vue` |
192
+ | 权限标识 | `mmwrCustomerApplyChangeHistory` |
193
+ | 是否隐藏 | **是** |
194
+
195
+ > pages.ts 对应:`["mmwr-customer-apply-change-history", "变更历史查询"]`
196
+ > 入口:客户申请新增表单 / 客户申请变更表单 顶部工具栏「变更历史查询」按钮,传入 `query.id = 当前申请ID`
197
+
198
+ ---
199
+
200
+ ## 汇总对照表
201
+
202
+ | # | 菜单名称 | 菜单路径 | 组件路径(views/下) | pages.ts 注册名 | 隐藏 |
203
+ | --- | ---------------- | ----------------------------- | -------------------------------------------------------------------------- | --------------------------------- | ------ |
204
+ | 1 | 客户档案 | `mmwrCustomerArchive` | `produce/production-mmwr/aiflow/mmwr-customer-archive/index.vue` | `mmwr-customer-archive` | 否 |
205
+ | 2 | 临时客户档案 | `mmwrTempCustomerArchive` | `produce/production-mmwr/aiflow/mmwr-temp-customer-archive/index.vue` | `mmwr-temp-customer-archive` | 否 |
206
+ | 3 | 客户申请新增 | `mmwrCustomerApplyAdd` | `produce/production-mmwr/aiflow/mmwr-customer-apply-add/index.vue` | `mmwr-customer-apply-add` | 否 |
207
+ | 4 | 客户申请变更 | `mmwrCustomerApplyChange` | `produce/production-mmwr/aiflow/mmwr-customer-apply-change/index.vue` | `mmwr-customer-apply-change` | 否 |
208
+ | 5 | 客户详情 | `mmwrCustomerDetail` | `produce/production-mmwr/aiflow/mmwr-customer-detail/index.vue` | `mmwr-customer-detail` | **是** |
209
+ | 6 | 客户申请新增表单 | `mmwrCustomerApplyAddForm` | `produce/production-mmwr/aiflow/mmwr-customer-apply-add-form/index.vue` | `mmwr-customer-apply-add-form` | **是** |
210
+ | 7 | 客户申请变更表单 | `mmwrCustomerApplyChangeForm` | `produce/production-mmwr/aiflow/mmwr-customer-apply-change-form/index.vue` | `mmwr-customer-apply-change-form` | **是** |
211
+ | 8 | 变更历史查询 | `mmwrCustomerApplyChangeHistory` | `produce/production-mmwr/aiflow/mmwr-customer-apply-change-history/index.vue` | `mmwr-customer-apply-change-history` | **是** |
212
+
213
+ ---
214
+
215
+ ## pages.ts 对应关系
216
+
217
+ ```typescript
218
+ // 生产棒线材 → 客户档案子模块
219
+ aiflow: [
220
+ ["mmwr-customer-archive", "客户档案"],
221
+ ["mmwr-temp-customer-archive", "临时客户档案"],
222
+ ["mmwr-customer-apply-add", "客户申请新增"],
223
+ ["mmwr-customer-apply-change", "客户申请变更"],
224
+ ["mmwr-customer-apply-add-form", "客户申请新增表单"],
225
+ ["mmwr-customer-apply-change-form", "客户申请变更表单"],
226
+ ["mmwr-customer-detail", "客户详情"],
227
+ ["mmwr-customer-apply-change-history", "变更历史查询"]
228
+ ];
229
+ ```
230
+
231
+ ## 详情交互说明
232
+
233
+ | 列表页 | 详情入口 | 实现方式 |
234
+ | ------------ | ------------------ | ------------------------------------------------------------------------------------- |
235
+ | 客户档案 | 操作列「详情」按钮 | 页内 detailDialog 弹窗(8 个 Tab:基本/资质/联系人/地址/银行/发票/适用范围/跟进记录) |
236
+ | 临时客户档案 | 客户编号可点击列 | 路由跳转 → `mmwrCustomerDetail`(平铺详情页:基本/联系/送货/银行/发票/跟进记录) |
237
+ | 客户申请新增 | 操作列「详情」按钮 | 共享组件 `c_applyDetailDialog`(7 个 Tab) |
238
+ | 客户申请变更 | 操作列「详情」按钮 | 共享组件 `c_applyDetailDialog`(7 个 Tab) |
239
+
240
+ ## 备注
241
+
242
+ - 客户档案详情通过页内 `c_formModal` 弹窗查看;临时客户详情通过路由跳转 `mmwrCustomerDetail` 查看
243
+ - 新增/编辑弹窗复用平台 `c_formModal` 组件
244
+ - 客户申请新增与客户申请变更共享 `src/components/local/c_applyDetailDialog/index.vue` 详情弹窗
245
+ - 变更历史查询通过路由跳转 `mmwrCustomerApplyChangeHistory` 查看,入口在申请表单页工具栏
246
+ - **显示排序**:目录排 `6`(接在实绩统计查询后面),菜单从 `1` 开始
247
+ - **mmwr- 前缀**:生产棒线材模块下所有页面统一使用 `mmwr-` 前缀(kebab)/ `mmwr` 前缀(camelCase)
@@ -0,0 +1,265 @@
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
+ → 自动生成 menu-config.md(含菜单字段)
247
+ → 追加更新 SYS_MENU_INFO.md
248
+ → 对 AI 说"帮我创建菜单"
249
+ → AI 读取 SYS_MENU_INFO.md + env.local.json → 调 /system/menu/save
250
+ → 输出 created/skipped 结果表
251
+ ```
252
+
253
+ ---
254
+
255
+ ### ⏳ Phase 2(待后端接口):`pnpm run menu:push` 脚本
256
+
257
+ 方案 D 的批量推送脚本需要后端提供 `POST /system/menu/batchPush` 接口。在接口就绪前,继续使用 Phase 1(AI 调接口)。
258
+
259
+ **过渡路径**:
260
+
261
+ 1. ✅ page-codegen Skill 自动生成 `menu-config.md`(已实现)
262
+ 2. ✅ menu-sync Skill Phase 1:AI 调 `/system/menu/save`(已实现)
263
+ 3. ⏳ 后端实现 `POST /system/menu/batchPush` 接口
264
+ 4. ⏳ 前端 `scripts/menu-push.ts` 读 `pages.ts` → POST batchPush
265
+ 5. ⏳ 切换到 `pnpm run menu:push`,废弃手动/AI 调接口流程