@agile-team/wl-skills-kit 2.4.2 → 2.5.1
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 +44 -1
- package/README.md +68 -45
- package/bin/wl-skills.js +196 -12
- package/files/.github/copilot-instructions.md +361 -322
- package/files/.github/guides/architecture.md +1 -1
- package/files/.github/guides/usage.md +32 -10
- package/files/.github/skills/_registry.md +18 -16
- package/files/.github/skills/core/page-codegen/SKILL.md +200 -97
- package/files/.github/skills/core/page-codegen/USAGE.md +33 -12
- package/files/.github/skills/core/page-codegen/templates/universal/TPL-DETAIL-TABS.md +80 -48
- package/files/.github/skills/core/page-codegen/templates/universal/TPL-FORM-ROUTE.md +183 -55
- package/files/.github/skills/core/page-codegen/templates/universal/TPL-LIST.md +110 -21
- package/files/.github/skills/core/page-codegen/templates/universal/TPL-MASTER-DETAIL.md +29 -9
- package/files/.github/skills/core/page-codegen/templates/universal/TPL-RECORD-FORM.md +93 -48
- package/files/.github/skills/core/page-codegen/templates/universal/TPL-TREE-LIST.md +49 -29
- package/files/.github/skills/core/prototype-scan/SKILL.md +16 -0
- package/files/.github/skills/sync/menu-sync/SKILL.md +27 -13
- package/mcp/server.js +279 -195
- package/mcp/tools/menuSync.js +416 -96
- package/mcp/tools/projectTools.js +336 -124
- package/package.json +5 -2
|
@@ -46,6 +46,7 @@ npx @agile-team/wl-skills-kit
|
|
|
46
46
|
"审计 src/views/sale 这个目录的代码规范"
|
|
47
47
|
"提取 mmwr-rolling-management 这个页面作为模板"
|
|
48
48
|
"同步菜单到后端"
|
|
49
|
+
"帮我做个台账页面,后端没好先 mock,菜单也加上"
|
|
49
50
|
```
|
|
50
51
|
|
|
51
52
|
AI 会自动识别意图,触发对应的 Skill。
|
|
@@ -54,17 +55,17 @@ AI 会自动识别意图,触发对应的 Skill。
|
|
|
54
55
|
|
|
55
56
|
## 9 个 Skill 速览
|
|
56
57
|
|
|
57
|
-
| Skill | 触发关键词 | 用途
|
|
58
|
-
| ------------------ | ------------------------------ |
|
|
59
|
-
| `prototype-scan` | 扫描原型 / 解析原型 / 口述需求 | 原型 / 详设 → page-spec JSON
|
|
60
|
-
| `api-contract` | 接口约定 / api.md / 字段定义 | 生成接口约定文档
|
|
61
|
-
| `page-codegen` | 生成页面 / 帮我生成 | 生成页面骨架 + 菜单注册
|
|
58
|
+
| Skill | 触发关键词 | 用途 |
|
|
59
|
+
| ------------------ | ------------------------------ | ---------------------------------------------------- |
|
|
60
|
+
| `prototype-scan` | 扫描原型 / 解析原型 / 口述需求 | 原型 / 详设 → page-spec JSON |
|
|
61
|
+
| `api-contract` | 接口约定 / api.md / 字段定义 | 生成接口约定文档 |
|
|
62
|
+
| `page-codegen` | 生成页面 / 帮我生成 | 生成页面骨架 + 菜单注册 |
|
|
62
63
|
| `menu-sync` | 创建菜单 / 同步菜单 | 菜单数据同步到后端(MCP 自动 / prompt 手动两种模式) |
|
|
63
|
-
| `dict-sync` | 同步字典 / 创建字典 / 字典审计
|
|
64
|
-
| `convention-audit` | 规范审计 / 代码审计 | 13 条规范扫描 + 偏差报告
|
|
65
|
-
| `template-extract` | 提取模板 / 抄取模板 | 从现有页面沉淠领域专属模板
|
|
66
|
-
| `permission-sync` | 创建角色 / 角色授权 / 同步权限
|
|
67
|
-
| `code-fix` | 自动修复 / 整改偏差 / 规范整改
|
|
64
|
+
| `dict-sync` | 同步字典 / 创建字典 / 字典审计 | 字典基线同步到后端(MCP 自动 / prompt 手动两种模式) |
|
|
65
|
+
| `convention-audit` | 规范审计 / 代码审计 | 13 条规范扫描 + 偏差报告 |
|
|
66
|
+
| `template-extract` | 提取模板 / 抄取模板 | 从现有页面沉淠领域专属模板 |
|
|
67
|
+
| `permission-sync` | 创建角色 / 角色授权 / 同步权限 | 角色+授权+动作权限同步(MCP) |
|
|
68
|
+
| `code-fix` | 自动修复 / 整改偏差 / 规范整改 | 受控自动修复审计报告中的偏差 |
|
|
68
69
|
|
|
69
70
|
完整调度规则见 `.github/skills/_registry.md`。
|
|
70
71
|
|
|
@@ -105,9 +106,28 @@ AI 会自动识别意图,触发对应的 Skill。
|
|
|
105
106
|
```
|
|
106
107
|
|
|
107
108
|
> **说明**:每一步都可以单独触发,也可以按用户意图自动接续。
|
|
109
|
+
>
|
|
108
110
|
> - `dict-sync`:首次使用先跑 **pull 模式**(「刷新字典基线」)建立本地基线,再跑 push 模式同步差异。
|
|
109
111
|
> - `code-fix`:只修复 🟡/🟢 偏差;🔴 严重偏差必须人工或 page-codegen 处理。每条修复前强制 diff 预览确认。
|
|
110
112
|
|
|
113
|
+
### 页面生成最终标准
|
|
114
|
+
|
|
115
|
+
`page-codegen` 生成的业务列表/台账/主从/树表页面必须满足:
|
|
116
|
+
|
|
117
|
+
- `AbstractPageQueryHook + BaseQuery + BaseToolbar + BaseTable + jh-pagination`
|
|
118
|
+
- `BaseTable` 必须 `render-type="agGrid"`
|
|
119
|
+
- 每个 `BaseTable` 必须有全局唯一 `cid`
|
|
120
|
+
- 列定义必须使用 `@agile-team/wk-skills-ui/runtime` 的 `defineColumns()`
|
|
121
|
+
- 操作列必须使用 `renderOps()`,禁止旧式 `operations: []`
|
|
122
|
+
- mock-first:后端未就绪时生成 `mock/*.ts`,关闭 mock 后业务代码不需要改
|
|
123
|
+
|
|
124
|
+
生成后建议运行:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
wl-skills validate-page src/views/<页面目录>
|
|
128
|
+
wl-skills doctor-ui
|
|
129
|
+
```
|
|
130
|
+
|
|
111
131
|
---
|
|
112
132
|
|
|
113
133
|
## 常见问题
|
|
@@ -146,6 +166,8 @@ A: 执行 `npx @agile-team/wl-skills-kit clean`。会移除所有 AI 辅助文
|
|
|
146
166
|
```bash
|
|
147
167
|
npx @agile-team/wl-skills-kit check # 环境/MCP/manifest 体检
|
|
148
168
|
npx @agile-team/wl-skills-kit diff # 查看与当前包版本差异
|
|
169
|
+
npx @agile-team/wl-skills-kit validate-page src/views/xxx # 单页/目录强校验
|
|
170
|
+
npx @agile-team/wl-skills-kit doctor-ui # wk-skills-ui 接入体检
|
|
149
171
|
npx @agile-team/wl-skills-kit update # 增量更新
|
|
150
172
|
npx @agile-team/wl-skills-kit clean # 清理 AI 文件,保留 src/components + src/types
|
|
151
173
|
npx @agile-team/wl-skills-kit export # 导出菜单/字典/权限基线 xlsx
|
|
@@ -38,17 +38,17 @@ skills/
|
|
|
38
38
|
|
|
39
39
|
## 启用 Skill 路由表
|
|
40
40
|
|
|
41
|
-
| Skill 名 | 状态
|
|
42
|
-
| ---------------- |
|
|
43
|
-
| prototype-scan | ✅ 启用
|
|
44
|
-
| api-contract | ✅ 启用
|
|
45
|
-
| page-codegen | ✅ 启用
|
|
46
|
-
| convention-audit | ✅ 启用
|
|
47
|
-
| template-extract | ✅ 启用
|
|
48
|
-
| menu-sync | ✅ 启用
|
|
49
|
-
| dict-sync | ✅ 启用
|
|
50
|
-
| permission-sync | ✅ 启用
|
|
51
|
-
| code-fix | ✅ 启用
|
|
41
|
+
| Skill 名 | 状态 | 路径 | 触发关键词 |
|
|
42
|
+
| ---------------- | ------- | --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
|
|
43
|
+
| prototype-scan | ✅ 启用 | `skills/core/prototype-scan/SKILL.md` | 扫描原型 / 解析原型 / 页面清单 / 详设文档 / 口述需求 / 建个页面 / 写个页面 / 根据截图 / 根据原型 |
|
|
44
|
+
| api-contract | ✅ 启用 | `skills/core/api-contract/SKILL.md` | 接口约定 / api.md / 字段定义 / 前后端对齐 / 接口设计 |
|
|
45
|
+
| page-codegen | ✅ 启用 | `skills/core/page-codegen/SKILL.md` | 生成页面 / 创建页面 / 代码生成 / vue页面 / 按原型生成 / 帮我生成 / 列表页 / 管理页 / 台账 / mock / 假数据 / 先能跑 / AGGrid / skills-ui |
|
|
46
|
+
| convention-audit | ✅ 启用 | `skills/core/convention-audit/SKILL.md` | 规范审计 / 代码审计 / 规范检查 / 对齐规范 / 规范偏差 / 接手新项目 / 存量代码分析 / 项目体检 |
|
|
47
|
+
| template-extract | ✅ 启用 | `skills/core/template-extract/SKILL.md` | 提取模板 / 抽取模板 / 沉淀模板 / 模板贡献 |
|
|
48
|
+
| menu-sync | ✅ 启用 | `skills/sync/menu-sync/SKILL.md` | 创建菜单 / 注册菜单 / 同步菜单 / 补菜单 / 页面点击进不来 / 菜单打不开 |
|
|
49
|
+
| dict-sync | ✅ 启用 | `skills/sync/dict-sync/SKILL.md` | 同步字典 / 创建字典 / 刷新字典基线 / 字典对比 / 字典审计 |
|
|
50
|
+
| permission-sync | ✅ 启用 | `skills/sync/permission-sync/SKILL.md` | 创建角色 / 角色管理 / 角色授权 / 给角色分配菜单 / 挂动作 / 添加动作按钮 / 同步权限 / 权限码注册 |
|
|
51
|
+
| code-fix | ✅ 启用 | `skills/ops/code-fix/SKILL.md` | 自动修复 / 整改偏差 / 修复报告 / 规范整改 / 修复偏差 / code fix |
|
|
52
52
|
|
|
53
53
|
---
|
|
54
54
|
|
|
@@ -58,6 +58,8 @@ skills/
|
|
|
58
58
|
2. SKILL.md 中标注的 `必读 standards` 按 `standards/index.md` 任务类型映射加载
|
|
59
59
|
3. 如用户表达连续交付/智能体/全流程意图,同时读取 `_pipeline.md` 获取 Skill I/O 与 `next_suggest`
|
|
60
60
|
4. 在 SKILL.md 指示下输出 **Pre-flight 声明**(强制约定式输出)
|
|
61
|
+
5. 页面生成任务必须额外读取 `standards/12-base-table.md`,并执行 `BaseTable + render-type="agGrid" + cid + defineColumns + renderOps` 硬约束
|
|
62
|
+
6. 若消息包含“风格 / skills-ui / 状态标签 / 操作列 / AGGrid”,同时建议运行 `wl-skills doctor-ui`
|
|
61
63
|
|
|
62
64
|
---
|
|
63
65
|
|
|
@@ -75,9 +77,9 @@ skills/
|
|
|
75
77
|
|
|
76
78
|
每个启用 Skill 同目录下都有 `USAGE.md`:
|
|
77
79
|
|
|
78
|
-
| 文件 | 读者
|
|
79
|
-
| --------------- |
|
|
80
|
-
| `SKILL.md` | AI 编辑器/模型
|
|
81
|
-
| `USAGE.md` | 团队成员
|
|
80
|
+
| 文件 | 读者 | 用途 |
|
|
81
|
+
| --------------- | ------------------------- | -------------------------------------------------------- |
|
|
82
|
+
| `SKILL.md` | AI 编辑器/模型 | 触发规则、流程步骤、Pre-flight 声明 |
|
|
83
|
+
| `USAGE.md` | 团队成员 | 示例对话、踩坑、FAQ、快速上手 |
|
|
82
84
|
| `_pipeline.md` | AI 编辑器/模型 + 团队成员 | Skill 间 I/O 契约、next_suggest、Agent Pipeline 调度协议 |
|
|
83
|
-
| `*.MAINTAIN.md` | kit 维护者
|
|
85
|
+
| `*.MAINTAIN.md` | kit 维护者 | 维护要点(在 `kit-internal/skills/` 目录) |
|
|
@@ -46,8 +46,9 @@ description: "Use when: generating complete Vue 3 page code (index.vue + data.ts
|
|
|
46
46
|
────────────────────────────────────────────────
|
|
47
47
|
📌 后续步骤:
|
|
48
48
|
1. 在 router/pages.ts 注册路由
|
|
49
|
-
2.
|
|
50
|
-
3.
|
|
49
|
+
2. 若本页 hiddenMenu=true → 在 src/util/navigate-hidden.ts 的 HIDDEN_ROUTE_MAP 追加一行
|
|
50
|
+
3. 提交:git cz(禁止直接 git commit)
|
|
51
|
+
4. 可选:触发 convention-audit 扫描本次生成文件
|
|
51
52
|
────────────────────────────────────────────────
|
|
52
53
|
```
|
|
53
54
|
|
|
@@ -119,6 +120,15 @@ src/views/[域]/[模块]/[子模块]/[kebab-case-目录名]/
|
|
|
119
120
|
18. **按钮必须可交互**:所有按钮的 `onClick` 必须有真实处理逻辑,禁止空函数 `() => {}`。通用交互实现见下方 §按钮交互实现规则
|
|
120
121
|
19. **未知交互兜底**:当原型未提供交互细节、且无法从通用模式推断时,`onClick` 中使用 `ElMessage.info("需业务确认交互逻辑")` 作为占位
|
|
121
122
|
20. **生成后依赖自检**:代码生成完成后,检查 `package.json` 是否已安装生成代码所需的依赖(`mockjs`、`vite-plugin-mock`、`lodash-es`、`xlsx` 等),若缺失则提示用户执行安装命令。同时检查 `vite.config.ts` 是否已注册 `viteMockServe`、`mock/` 目录是否存在
|
|
123
|
+
21. **默认 Mock First**:新生成页面默认必须走 `vite-plugin-mock`。必须生成 `mock/[页面kebab-name].ts`,并确保 `API_CONFIG` 中每个 URL 都有对应 mock 端点;只有当用户明确要求关闭 mock 或 `.env.dev` 中 `ENV_MOCK=false` 时,才允许直接联调真实接口。
|
|
124
|
+
22. **Mock URL 必须匹配真实请求**:`API_CONFIG` 保持真实接口路径(如 `/mdata/mdataModel/list`),mock 文件端点必须带 Vite 代理前缀(如 `/dev-api/mdata/mdataModel/list`),这样关闭 mock 后无需修改业务代码。
|
|
125
|
+
23. **页面初始数据必须由 mock 提供**:列表页 `onMounted(() => select())` 后必须能显示模拟数据,不允许生成空白页等待后端接口;`list` 端点返回 `{ code: 2000, data: { records, total, size, current } }`。
|
|
126
|
+
24. **必须使用 wk-skills-ui runtime 风格**:当项目安装了 `@agile-team/wk-skills-ui` 时,列表列定义必须使用 `defineColumns()`,操作列必须使用 `renderOps()`,状态/字典列优先使用 runtime 渲染器或 `logicType=dict` 自动映射;不可退回默认纯文本/空函数风格。
|
|
127
|
+
25. **wk-skills-ui 接入自检**:生成页面前检查项目是否已接入 `@agile-team/wk-skills-ui` 样式与 runtime。若未接入,先提示并补齐:`@use '@agile-team/wk-skills-ui/styles' as *;`、`installCommonPreset()`、必要的 design tokens 引入;否则页面风格不会自动生效。
|
|
128
|
+
26. **pages.ts 分组注册**:多页面模块必须按当前业务目录分组写入 `vite/plugins/shared/pages.ts`,使用 `gProd(module, { subModule: [[page, label]] })` 结构,不允许把所有页面扁平追加到一个数组。
|
|
129
|
+
27. **BaseTable 强制 AGGrid**:所有业务主列表/台账/主从表/树表/详情子表的 `BaseTable` 必须显式写 `render-type="agGrid"`,并绑定全局唯一 `cid`。弹窗小表格可豁免,但必须在生成摘要中说明理由。
|
|
130
|
+
28. **cid 必须可追踪**:每个页面导出 `TABLE_CID = "{pageAbbr}-{base36Timestamp}"`;多表页面使用 `BOTTOM_TABLE_CID` / `ITEM_TABLE_CID`,列级 `cid` 必须使用 `${TABLE_CID}-fieldName` 前缀。
|
|
131
|
+
29. **skills-ui 只能融合,不可生搬硬套**:不得照搬 `wk-skills-ui/templates/list-page` 中的原生 `el-form/usePageHook/el-pagination` 通用写法;本项目必须保留 `AbstractPageQueryHook + BaseQuery + BaseToolbar + BaseTable + jh-pagination` 平台骨架,只融合 `defineColumns/renderOps/tokens/preset`。
|
|
122
132
|
|
|
123
133
|
### 禁止事项(严格遵守)
|
|
124
134
|
|
|
@@ -135,6 +145,11 @@ src/views/[域]/[模块]/[子模块]/[kebab-case-目录名]/
|
|
|
135
145
|
11. **❌ 禁止表单控件宽度不统一**:`jh-select`、`jh-date`、`el-input-number`、`jh-file-upload` 默认宽度可能与 `el-input` 不一致,必须在 scoped style 中用 `:deep()` 统一设为 `width: 100%`(详见 §表单页 UI 细节规范)
|
|
136
146
|
12. **❌ 禁止表单页无滚动**:独立路由表单页内容超出视口时必须可滚动,`.app-page-container` 须设 `overflow-y: auto`(**不要加 `height: 100%`,全局已有 `height: calc(100vh - 100px)`,叠加会导致双滚动条**)
|
|
137
147
|
13. **❌ 禁止内联 style 散落**:所有页面/组件样式统一写在 `index.scss` 中(便于复用和移动),不可在 template 中大量使用内联 `style="..."`
|
|
148
|
+
14. **❌ 禁止生成无 mock 的页面**:只写 `API_CONFIG` 但不写 `mock/*.ts` 属于生成失败。
|
|
149
|
+
15. **❌ 禁止生成空 onClick**:`onClick: () => {}` 属于生成失败;未知逻辑也必须用 `ElMessage.info(...)` 明示。
|
|
150
|
+
16. **❌ 禁止忽略 wk-skills-ui**:项目已安装 `@agile-team/wk-skills-ui` 时,不使用 `defineColumns/renderOps` 属于生成失败。
|
|
151
|
+
17. **❌ 禁止 BaseTable 非 AGGrid**:业务列表中 `<BaseTable>` 未写 `render-type="agGrid"` 或缺少 `cid/:cid` 属于生成失败。
|
|
152
|
+
18. **❌ 禁止列缺 cid**:AGGrid 表格的数据列/操作列缺少列级 `cid` 属于生成失败。
|
|
138
153
|
|
|
139
154
|
### c_formModal 使用规范
|
|
140
155
|
|
|
@@ -245,9 +260,10 @@ function handleCodeClick(row: any) {
|
|
|
245
260
|
>
|
|
246
261
|
> | 场景 | 方式 | 原因 |
|
|
247
262
|
> |---|---|---|
|
|
248
|
-
> | **菜单页 →
|
|
249
|
-
> | **隐藏页 → 隐藏页**(如表单→变更历史) | `envConfig()?.router` + `location.href` | `router.push()` 跳过 shell 的 `generateCurrentRoute`,导致 "Invalid route component" 报错 |
|
|
263
|
+
> | **菜单页 → 隐藏页 / 隐藏页 → 隐藏页** | `navigateHidden(path, query?)` from `src/util/navigate-hidden.ts` | 懒注册 + router.push,无整页刷新;内部自动兜底 location.href 防白屏 |
|
|
250
264
|
> | **返回上一页** | `useRouter().back()` | 任何页面均可用 |
|
|
265
|
+
>
|
|
266
|
+
> ⚠️ `navigateHidden` 依赖 `src/util/navigate-hidden.ts` 的 `HIDDEN_ROUTE_MAP`。**每新增一个隐藏页,必须在该 Map 里追加一行**,否则兜底会退化为整页刷新。
|
|
251
267
|
|
|
252
268
|
#### 路由路径命名规则
|
|
253
269
|
|
|
@@ -260,66 +276,94 @@ function handleCodeClick(row: any) {
|
|
|
260
276
|
- 子模块名取 pages.ts 的 key,如 `aiflow`
|
|
261
277
|
- 页面目录名整体转 PascalCase(含 `mmwr` 前缀),如 `mmwr-customer-apply-add-form` → `mmwrCustomerApplyAddForm`
|
|
262
278
|
|
|
263
|
-
####
|
|
279
|
+
#### navigate-hidden.ts 标准实现(首次使用时创建,后续只追加 Map 条目)
|
|
264
280
|
|
|
265
281
|
```typescript
|
|
266
|
-
//
|
|
282
|
+
// src/util/navigate-hidden.ts
|
|
267
283
|
import envConfig from "@jhlc/common-core/src/store/env-config";
|
|
284
|
+
import { ElMessage } from "element-plus";
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* 隐藏页路由懒注册表
|
|
288
|
+
* 每新增一个 hiddenMenu=true 的页面,在此追加一行
|
|
289
|
+
*/
|
|
290
|
+
const HIDDEN_ROUTE_MAP: Record<string, () => Promise<any>> = {
|
|
291
|
+
// "/aiflow/mmwrCustomerApplyAddForm": () => import("@/views/produce/aiflow/mmwr-customer-apply-add-form/index.vue"),
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
export async function navigateHidden(path: string, query?: Record<string, string>) {
|
|
295
|
+
const router = envConfig()?.router;
|
|
296
|
+
if (!router) { ElMessage.error("路由未初始化,请刷新页面重试"); return; }
|
|
297
|
+
|
|
298
|
+
const matched = router.resolve({ path }).matched;
|
|
299
|
+
if (!matched.length || matched[0].path === "/:pathMatch(.*)*") {
|
|
300
|
+
const loader = HIDDEN_ROUTE_MAP[path];
|
|
301
|
+
if (!loader) {
|
|
302
|
+
// 降级兜底:路由 Map 未配置时整页跳转,不白屏
|
|
303
|
+
location.href = router.resolve({ path, query } as any).href;
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
router.addRoute({ path, component: loader });
|
|
307
|
+
}
|
|
308
|
+
await router.push({ path, query } as any);
|
|
309
|
+
}
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
#### 调用侧标准实现(data.ts)
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
// ✅ 正确:用 navigateHidden
|
|
316
|
+
import { navigateHidden } from "@/util/navigate-hidden";
|
|
268
317
|
|
|
269
318
|
// 在 createPage() 外部定义,避免每次调用都重新创建
|
|
270
319
|
const FORM_ROUTE = "/aiflow/mmwrCustomerApplyAddForm";
|
|
271
320
|
|
|
272
321
|
function navigateToForm(query?: Record<string, string>) {
|
|
273
|
-
|
|
274
|
-
if (!router) {
|
|
275
|
-
ElMessage.error("路由未初始化,请刷新页面重试");
|
|
276
|
-
return;
|
|
277
|
-
}
|
|
278
|
-
const target: any = { path: FORM_ROUTE };
|
|
279
|
-
if (query) target.query = query;
|
|
280
|
-
location.href = router.resolve(target).href;
|
|
322
|
+
navigateHidden(FORM_ROUTE, query);
|
|
281
323
|
}
|
|
282
324
|
|
|
283
325
|
export function createPage() {
|
|
284
|
-
// ... 不在 createPage 内部声明 router
|
|
285
326
|
const Page = new (class extends AbstractPageQueryHook {
|
|
286
|
-
// ...
|
|
287
327
|
toolbarDef(): ActionButtonDesc[] {
|
|
288
328
|
return [
|
|
289
329
|
{
|
|
290
330
|
name: "primary",
|
|
291
331
|
label: "新增申请",
|
|
292
|
-
onClick: () => navigateToForm()
|
|
332
|
+
onClick: () => navigateToForm() // 无参:新增
|
|
293
333
|
}
|
|
294
334
|
];
|
|
295
335
|
}
|
|
296
336
|
columnsDef(): TableColumnDesc<any>[] {
|
|
297
|
-
return [
|
|
337
|
+
return defineColumns([
|
|
298
338
|
// ...
|
|
299
339
|
{
|
|
300
340
|
label: "操作",
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
341
|
+
name: "_action",
|
|
342
|
+
cid: `${TABLE_CID}-action`,
|
|
343
|
+
fixed: "right",
|
|
344
|
+
align: "center",
|
|
345
|
+
defaultSlot: ({ row }: any) =>
|
|
346
|
+
renderOps([
|
|
347
|
+
{ type: "edit", onClick: () => navigateToForm({ id: row.id }) } // 带 id:编辑
|
|
348
|
+
])
|
|
307
349
|
}
|
|
308
|
-
];
|
|
350
|
+
] as any) as TableColumnDesc<any>[];
|
|
309
351
|
}
|
|
310
352
|
})();
|
|
311
353
|
return Page.create() as any;
|
|
312
354
|
}
|
|
313
355
|
```
|
|
314
356
|
|
|
315
|
-
>
|
|
316
|
-
> - `
|
|
317
|
-
> -
|
|
357
|
+
> **✅ 正确做法**:
|
|
358
|
+
> - 跳转隐藏页 → `navigateHidden(path, query?)`(懒注册 + router.push,无刷新,内部兜底防白屏)
|
|
359
|
+
> - 返回上一页 → `useRouter().back()`
|
|
318
360
|
>
|
|
319
|
-
>
|
|
320
|
-
> - `
|
|
361
|
+
> **❌ 禁止**:
|
|
362
|
+
> - 直接 `router.push({ path: "..." })` — 主应用过滤了 hidden 路由,路由未注册直接 push 会白屏或报 "Invalid route component"
|
|
363
|
+
> - 直接 `location.href = router.resolve(...).href` — 触发整页重载,有加载动画刷新感;`navigateHidden` 内部已兜底,**外部调用侧禁止直接使用**
|
|
364
|
+
> - kebab-case 路径(`/mmwr-xxx-form`)— 路由路径必须是 camelCase
|
|
321
365
|
>
|
|
322
|
-
> ⚠️
|
|
366
|
+
> ⚠️ **新增隐藏页时必须同步维护 `src/util/navigate-hidden.ts` 的 `HIDDEN_ROUTE_MAP`**,否则 `navigateHidden` 降级为整页刷新,失去无刷新优势。
|
|
323
367
|
|
|
324
368
|
---
|
|
325
369
|
|
|
@@ -401,34 +445,38 @@ const ids = rows.map((r: any) => r.id);
|
|
|
401
445
|
```typescript
|
|
402
446
|
{
|
|
403
447
|
label: "操作",
|
|
448
|
+
name: "_action",
|
|
449
|
+
cid: `${TABLE_CID}-action`,
|
|
404
450
|
width: 140,
|
|
405
451
|
fixed: "right",
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
452
|
+
align: "center",
|
|
453
|
+
defaultSlot: ({ row }: any) =>
|
|
454
|
+
renderOps([
|
|
455
|
+
{
|
|
456
|
+
type: "edit",
|
|
457
|
+
label: "修改",
|
|
458
|
+
show: () => row.verifyStatus === "已核实",
|
|
459
|
+
onClick: () => _editModalRef?.value?.edit(row.id)
|
|
460
|
+
},
|
|
461
|
+
{
|
|
462
|
+
type: "danger",
|
|
463
|
+
label: "作废",
|
|
464
|
+
show: () => row.verifyStatus === "已核实",
|
|
465
|
+
onClick: () => handleCancel(row)
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
type: "edit",
|
|
469
|
+
label: "编辑",
|
|
470
|
+
show: () => row.verifyStatus !== "已核实",
|
|
471
|
+
onClick: () => _editModalRef?.value?.edit(row.id)
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
type: "del",
|
|
475
|
+
label: "删除",
|
|
476
|
+
show: () => row.verifyStatus !== "已核实",
|
|
477
|
+
onClick: () => Page?.remove(row.id)
|
|
478
|
+
}
|
|
479
|
+
])
|
|
432
480
|
}
|
|
433
481
|
```
|
|
434
482
|
|
|
@@ -497,15 +545,24 @@ function renderStatusTag(row: any, field: string) {
|
|
|
497
545
|
let Page: any = null;
|
|
498
546
|
|
|
499
547
|
export function managementColumns(): TableColumnDesc<any>[] {
|
|
500
|
-
return [
|
|
548
|
+
return defineColumns([
|
|
501
549
|
// ...
|
|
502
|
-
{
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
550
|
+
{
|
|
551
|
+
label: "操作",
|
|
552
|
+
name: "_action",
|
|
553
|
+
cid: `${TABLE_CID}-management-action`,
|
|
554
|
+
fixed: "right",
|
|
555
|
+
width: 100,
|
|
556
|
+
align: "center",
|
|
557
|
+
defaultSlot: ({ row }: any) =>
|
|
558
|
+
renderOps([
|
|
559
|
+
{ type: "del", label: "删除", onClick: () => Page?.remove(row.id) }
|
|
560
|
+
])
|
|
561
|
+
}
|
|
562
|
+
] as any) as TableColumnDesc<any>[];
|
|
506
563
|
}
|
|
507
564
|
export function usageColumns(): TableColumnDesc<any>[] {
|
|
508
|
-
return [ /* 使用视角列... */ ];
|
|
565
|
+
return defineColumns([ /* 使用视角列... */ ] as any) as TableColumnDesc<any>[];
|
|
509
566
|
}
|
|
510
567
|
|
|
511
568
|
export function createPage(editModalRef?: any) {
|
|
@@ -866,6 +923,50 @@ import { BaseQueryItemDesc } from "@jhlc/common-core/src/components/form/base-qu
|
|
|
866
923
|
|
|
867
924
|
page-codegen 生成页面代码后,**必须追加写入菜单配置信息到 `reports/SYS_MENU_INFO.md`**。
|
|
868
925
|
|
|
926
|
+
## pages.ts 分组注册规则(Module Federation 子应用)
|
|
927
|
+
|
|
928
|
+
多页面子应用必须使用与 `wl-mdata` 验证一致的分组模式,禁止扁平追加:
|
|
929
|
+
|
|
930
|
+
```typescript
|
|
931
|
+
import type { SharedPageItem } from "./utils";
|
|
932
|
+
|
|
933
|
+
type PageTuple = [string, string];
|
|
934
|
+
type SubModuleMap = Record<string, PageTuple[]>;
|
|
935
|
+
|
|
936
|
+
const gProd = (module: string, subModules: SubModuleMap): SharedPageItem[] =>
|
|
937
|
+
Object.entries(subModules).flatMap(([subModule, pages]) =>
|
|
938
|
+
pages.map(([page, label]) => ({
|
|
939
|
+
name: `${module}/${subModule}/${page}/index.vue`,
|
|
940
|
+
label
|
|
941
|
+
}))
|
|
942
|
+
);
|
|
943
|
+
|
|
944
|
+
const mdataModule = gProd("mdata", {
|
|
945
|
+
model: [
|
|
946
|
+
["mdata-model-config", "主数据模型配置"],
|
|
947
|
+
["mdata-attr-mapping", "属性映射管理"]
|
|
948
|
+
],
|
|
949
|
+
integration: [
|
|
950
|
+
["mdata-integ-system", "集成系统管理"]
|
|
951
|
+
]
|
|
952
|
+
});
|
|
953
|
+
|
|
954
|
+
export const list: SharedPageItem[] = mdataModule;
|
|
955
|
+
export default list;
|
|
956
|
+
```
|
|
957
|
+
|
|
958
|
+
生成规则:
|
|
959
|
+
|
|
960
|
+
| 字段 | 规则 |
|
|
961
|
+
| --- | --- |
|
|
962
|
+
| `module` | 来自项目启动参数 `--module=xxx`,如 `mdata` |
|
|
963
|
+
| `subModule` | 来自页面目录第二级,如 `src/views/mdata/model/...` → `model` |
|
|
964
|
+
| `page` | 页面 kebab 目录名,如 `mdata-model-config` |
|
|
965
|
+
| `name` | `${module}/${subModule}/${page}/index.vue` |
|
|
966
|
+
| `label` | 页面中文名 |
|
|
967
|
+
|
|
968
|
+
> menu-sync 的 `component` 必须与 `pages.ts` 生成的 `name` 完全一致,否则菜单能创建但点击无法加载页面。
|
|
969
|
+
|
|
869
970
|
### 写入策略(默认追加,不覆盖)
|
|
870
971
|
|
|
871
972
|
- **默认为追加模式**:保留已有内容,在末尾追加本次生成的菜单。避免覆盖团队之前累积的菜单记录。
|
|
@@ -875,24 +976,27 @@ page-codegen 生成页面代码后,**必须追加写入菜单配置信息到 `
|
|
|
875
976
|
|
|
876
977
|
### 生成模板
|
|
877
978
|
|
|
878
|
-
|
|
979
|
+
多级菜单必须先写目录(`type=M`),再在目录下写页面菜单(`type=C`)。格式如下:
|
|
879
980
|
|
|
880
981
|
```markdown
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
|
889
|
-
|
|
|
890
|
-
|
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
982
|
+
# 系统菜单配置 — [项目名] [业务模块]
|
|
983
|
+
|
|
984
|
+
> **module 命名**:`[module]`
|
|
985
|
+
> **父级菜单**:来自 `.github/skills/sync/env.local.json` 的 `menu.parentMenuId`
|
|
986
|
+
|
|
987
|
+
## 一级目录(type=M)
|
|
988
|
+
|
|
989
|
+
| # | 菜单名 | path | orderNum | 备注 |
|
|
990
|
+
| - | ------ | ---- | -------- | ---- |
|
|
991
|
+
| 1 | [目录中文名] | `[目录pathCamel]` | 1 | 含 N 个子菜单 |
|
|
992
|
+
|
|
993
|
+
## 二级菜单(type=C)
|
|
994
|
+
|
|
995
|
+
### 1. [目录中文名] 子菜单
|
|
996
|
+
|
|
997
|
+
| 菜单名 | path | component | permission |
|
|
998
|
+
| ------ | ---- | --------- | ---------- |
|
|
999
|
+
| [页面中文名] | `[pageCamel]` | `[module]/[subModule]/[pageKebab]/index.vue` | `[module]:[pageCamel]:list` |
|
|
896
1000
|
```
|
|
897
1001
|
|
|
898
1002
|
### 字段生成规则
|
|
@@ -901,8 +1005,8 @@ page-codegen 生成页面代码后,**必须追加写入菜单配置信息到 `
|
|
|
901
1005
|
|------|------|------|
|
|
902
1006
|
| 菜单路径 | page-spec.kebabName | kebab-case → camelCase(`mmwr-customer-archive` → `mmwrCustomerArchive`) |
|
|
903
1007
|
| 菜单名称 | page-spec.pageName | 直接使用中文名 |
|
|
904
|
-
| 组件路径 | pages.ts 注册路径 | `[
|
|
905
|
-
| 权限标识 |
|
|
1008
|
+
| 组件路径 | pages.ts 注册路径 | `[module]/[subModule]/[kebab-目录名]/index.vue` |
|
|
1009
|
+
| 权限标识 | module + 菜单路径 | `[module]:[pageCamel]:list` |
|
|
906
1010
|
| 是否隐藏 | page-spec.features.hiddenMenu | `true` → 是,`false` → 否 |
|
|
907
1011
|
| 上级目录 | 用户指定 / page-spec 推断 | 如果用户在原型扫描阶段指定了上级目录,使用该值 |
|
|
908
1012
|
| 应用选择 | pages.ts 域名 | `produce` → 生产,`sale` → 销售 |
|
|
@@ -919,29 +1023,27 @@ page-codegen 生成页面代码后,**必须追加写入菜单配置信息到 `
|
|
|
919
1023
|
### SYS_MENU_INFO.md 文件结构
|
|
920
1024
|
|
|
921
1025
|
```markdown
|
|
922
|
-
# 系统菜单配置 — [
|
|
1026
|
+
# 系统菜单配置 — [项目名] [模块名]
|
|
923
1027
|
|
|
924
|
-
>
|
|
925
|
-
>
|
|
926
|
-
>
|
|
927
|
-
> **pages.ts 注册位置**:`vite/plugins/shared/pages.ts` → `[模块变量名]` → `[子模块key]`
|
|
1028
|
+
> **父级菜单**:`[parentMenuId]`
|
|
1029
|
+
> **应用编码**:`[sysAppNo]`
|
|
1030
|
+
> **module 命名**:`[module]`
|
|
928
1031
|
|
|
929
|
-
##
|
|
1032
|
+
## 一级目录(type=M)
|
|
930
1033
|
|
|
931
|
-
|
|
|
932
|
-
| -------- |
|
|
933
|
-
|
|
|
934
|
-
| 菜单名称 | `[目录名]` |
|
|
935
|
-
| 显示排序 | `[序号]` |
|
|
1034
|
+
| # | 菜单名 | path | orderNum | 备注 |
|
|
1035
|
+
| - | ------ | ---- | -------- | ---- |
|
|
1036
|
+
| 1 | [目录名] | `[目录pathCamel]` | 1 | 含 N 个子菜单 |
|
|
936
1037
|
|
|
937
|
-
##
|
|
1038
|
+
## 二级菜单(type=C)
|
|
938
1039
|
|
|
939
|
-
[
|
|
1040
|
+
### 1. [目录名] 子菜单
|
|
940
1041
|
|
|
941
|
-
|
|
1042
|
+
| 菜单名 | path | component | permission |
|
|
1043
|
+
| ------ | ---- | --------- | ---------- |
|
|
1044
|
+
| [页面名称] | `[pageCamel]` | `[module]/[subModule]/[pageKebab]/index.vue` | `[module]:[pageCamel]:list` |
|
|
942
1045
|
|
|
943
|
-
|
|
944
|
-
...
|
|
1046
|
+
> pages.ts 对应:`gProd("[module]", { [subModule]: [["[pageKebab]", "[页面名称]"]] })`
|
|
945
1047
|
```
|
|
946
1048
|
|
|
947
1049
|
### 与 menu-sync 的衔接
|
|
@@ -950,6 +1052,7 @@ SYS_MENU_INFO.md 是 menu-sync Skill 的输入数据源:
|
|
|
950
1052
|
- **自动创建**:用户说"帮我创建菜单" → menu-sync 读取 SYS_MENU_INFO.md → 调 API 逐条创建
|
|
951
1053
|
- **手动创建**:用户也可直接按 SYS_MENU_INFO.md 的表格在系统管理后台手动创建菜单
|
|
952
1054
|
- 两种方式等价,菜单创建后通过 `组件路径` 字段与 pages.ts 注册的文件路径关联
|
|
1055
|
+
- **自动创建顺序**:必须先调用 `wls_menu_query` 获取当前 domain 菜单树,再 `wls_menu_upsert` 创建/更新一级目录(type=M),拿到目录 id 后再创建二级菜单(type=C)。不得把二级页面全部直接挂到根 `parentMenuId`。
|
|
953
1056
|
|
|
954
1057
|
---
|
|
955
1058
|
|