@qilitt-mickey/vue3-temp-skill 1.1.12 → 1.1.14

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/SKILL.md CHANGED
@@ -24,11 +24,20 @@ tags: [vue3, typescript, element-plus, vant, pinia, vite, unocss, crud, componen
24
24
 
25
25
  1. 先读 `references/core-kernel.md`
26
26
  2. 复用仓库已有 `Re*` / Hook / utils;选型表指定库勿擅自替换
27
- 3. 按命中模块内完整示例编写
27
+ 3. 按命中模块内**强制完整样板**整段编写(禁止只抄 API 签名、丢掉 meta/配套段落)
28
28
  4. 无对应能力 → 标注「待确认」,未经同意不引入新依赖
29
29
  5. 真相源:`仓库已有封装` > `本 Skill 示例` > 通用 Vue 习惯
30
30
  6. 命中 addon / 缺依赖时再读 `project-inventory.md` 第九节;精确版本安装,禁止 `@latest`
31
31
 
32
+ ## 抗略读(全模块 · 提高命中率)
33
+
34
+ 按需模块 ≠ 可浅读。命中即须落到该文件的**完整样板 / Must-do**,生成后靠 `code-quality` 硬失败表兜底。
35
+
36
+ 1. **样板字段默认写出**:示例里写明的 `activePath` / `keepAlive` / `roles` / `responseType: "blob"` 等不得改成 `//` 注释再当可选项;仅业务明确不需要时才删除
37
+ 2. **关联能力同批落盘**:列表→详情 = `crud-pages` + `detail-page` 三件套;有 API = 同步 `http-api`;有删除 = 确认框;有编辑回显 = `Object.assign`
38
+ 3. **Hook ≠ 壳层自动**:`toDetail` 写 tag;侧栏高亮 / keepAlive 靠路由 meta;`http` 加解密靠环境开关,不靠单请求配置
39
+ 4. **大范围交付仍须逐模块核对硬失败表**,禁止「lint 过了就算完」
40
+
32
41
  ## 模块路由表
33
42
 
34
43
  ### 始终加载
@@ -112,7 +121,8 @@ tags: [vue3, typescript, element-plus, vant, pinia, vite, unocss, crud, componen
112
121
 
113
122
  - HTTP:`import { http } from '@/utils/http'`;方法跟后端(允许 GET/POST);`Result<T>`;`VITE_API_BASE_URL` → `http-api`
114
123
  - 列表:`onSearch` / `reset` + 黄金样板 → `crud-pages`
115
- - 独立详情:`toDetail` + 路由 meta → `detail-page`
124
+ - 独立详情:`toDetail` **不能替代** `activePath` / `keepAlive` / `moreTags`;三件套 → `detail-page`
125
+ - 权限:`meta.roles` + `v-auth`/`ReAuth` → `permission-auth`
116
126
  - 不二次挂 Layout;`script setup` + `defineOptions({ name })`;`$t('中文')`;样式契约 → `core-kernel`
117
127
 
118
128
  ## 工作流
@@ -134,6 +144,7 @@ tags: [vue3, typescript, element-plus, vant, pinia, vite, unocss, crud, componen
134
144
  ## 代码质量审查
135
145
  - 闸门:已执行 code-quality(生成后)
136
146
  - 清单:选型 / 类型 / 组件 / API / 安全 / 业务 / 列表→详情(如适用) / 列表布局(如适用) / 风格 → 通过 | 已修正:…
147
+ - 硬失败:无 | 已拦截并修正:…
137
148
  - 命令:`pnpm lint:fix` → …;`pnpm type-check` → …
138
149
  - 待确认:…
139
150
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qilitt-mickey/vue3-temp-skill",
3
- "version": "1.1.12",
3
+ "version": "1.1.14",
4
4
  "description": "Vue 3 企业级中后台项目开发规范技能包 — core-kernel 架构、按需功能模块",
5
5
  "bin": {
6
6
  "vue3-temp-skill": "./bin/cli.js"
@@ -9,6 +9,8 @@ tags: [api, backend, integration, contract, http, checklist, mock]
9
9
 
10
10
  写法与示例 → **`http-api`**。本文件只做联调核对。权威源:`types/global.d.ts`、`src/utils/http.ts`、`src/api/**`、`mock/**`、`useTableSearch`。
11
11
 
12
+ **抗略读**:联调阶段仍须核对「下载是否带 blob」「分页字段是否 content/totalCount」;契约不过 = API 未完成,禁止只靠 lint 放行。
13
+
12
14
  ## 基本信息
13
15
 
14
16
  - [ ] 业务 path 与后端文档一致;忌把 `/mock/...` 当生产约定
@@ -12,7 +12,7 @@ tags: [code-review, quality, checklist, lint, typescript, vue3, security, perfor
12
12
  ## 协议(按序)
13
13
 
14
14
  1. 重读本文件
15
- 2. 列出本轮改动路径,对照下方清单
15
+ 2. 列出本轮改动路径,对照下方清单;**硬失败表命中则先改代码再继续**
16
16
  3. 业务项目根目录必跑:`pnpm lint:fix` → `pnpm type-check`(无脚本则 `pnpm lint` / `pnpm exec eslint . --fix`;`pnpm exec vue-tsc --noEmit`)
17
17
  4. 失败则修代码并重跑,直至通过或写明阻塞
18
18
  5. 最终回复追加:
@@ -22,11 +22,29 @@ tags: [code-review, quality, checklist, lint, typescript, vue3, security, perfor
22
22
  - 闸门:已执行 code-quality(生成后)
23
23
  - 改动文件:`path/a`、`path/b`、…
24
24
  - 清单:选型 ✅ / 类型 ✅ / 组件 ✅ / API ✅ / 安全 ✅ / 业务 ✅ / 列表→详情 ✅或N/A / 列表布局 ✅或N/A / 风格 ✅
25
+ - 硬失败:无 | 已拦截并修正:…
25
26
  - 已修正:…(无则写「无」)
26
27
  - 命令:`pnpm lint:fix` → …;`pnpm type-check` → …
27
28
  - 待确认:…
28
29
  ```
29
30
 
31
+ ## 硬失败(对照落盘文件;命中即未通过)
32
+
33
+ lint / type-check **过了也不能放过**下列项。发现即改代码,禁止「已知问题后续再说」。
34
+
35
+ | 触发条件(改动里出现) | 未通过条件 | 正确做法 |
36
+ |------------------------|------------|----------|
37
+ | `toDetail` / 独立 `*Detail` 路由 | 路由无 `activePath`,或 `showLink` 非 `false` | `detail-page` 三件套 |
38
+ | 同上 | 默认场景缺 `keepAlive`/`moreTags`(业务未声明不要缓存) | 样板默认写出;不要缓存再删 |
39
+ | 同上 | 详情页无 `initToDetail`,或 `name` 与路由不一致 | 对齐 `defineOptions` |
40
+ | 列表页 | 无 `el-card.vts-page-card` / 无 `tableHeight` / 分页在 ReTableBar 外 | `crud-pages` L1–L8 |
41
+ | `src/api/**` | 绕过 `@/utils/http`,或虚构 `/api` 前缀,或响应当 `success`/`code` | `http-api` |
42
+ | 下载/导出 API | 无 `responseType: "blob"` | `http-api` / `download-export` |
43
+ | 删除/危险操作 | 无确认框 | `feedback-loading` |
44
+ | 编辑回显 | 整表 `formData = res.data` 破坏响应式 | `Object.assign`(`data-writeback`) |
45
+ | 按钮权限需求 | 裸 `v-if` 角色判断,未用 `v-auth`/`ReAuth` | `permission-auth` |
46
+ | 新菜单路由 | 父级无 `icon` 或业务页二次挂 `Layout` | `icons` + `core-kernel` |
47
+
30
48
  ## 选型与硬约束
31
49
 
32
50
  - [ ] 已读 `core-kernel`;addon/缺依赖时已读 `project-inventory`
@@ -34,8 +52,8 @@ tags: [code-review, quality, checklist, lint, typescript, vue3, security, perfor
34
52
  - [ ] addon 缺包按 inventory 第九节精确版本(禁止 `@latest`)
35
53
  - [ ] 列表方法名 `onSearch` / `reset`;HTTP 用 `@/utils/http` + `Result<T>`
36
54
  - [ ] 业务路由未二次挂 Layout
37
- - [ ] 独立详情:`showLink:false` + `activePath` + `toDetail` + `initToDetail`(细则 `detail-page`)
38
- - [ ] 列表布局按 `crud-pages` 黄金样板(`el-card`+`content`+固定 height、`tableHeight`、`otherRef`、分页在 ReTableBar 内)
55
+ - [ ] 独立详情:对照路由文件确认 `showLink:false` + `activePath` + `keepAlive`/`moreTags`(默认)+ `toDetail` + `initToDetail`
56
+ - [ ] 列表布局按 `crud-pages` 黄金样板(`el-card.vts-page-card`+`content`+固定 height、`tableHeight`、`otherRef`、分页在 ReTableBar 内;内容区无外层滚动条)
39
57
 
40
58
  ## TypeScript
41
59
 
@@ -88,13 +106,12 @@ tags: [code-review, quality, checklist, lint, typescript, vue3, security, perfor
88
106
  ## 业务
89
107
 
90
108
  - [ ] 提交前 `validate()`;删除有确认;提交有 loading;列表有 `v-loading`
91
- - [ ] 编辑回显正确;分页切换会拉数
109
+ - [ ] 编辑回显正确(`Object.assign`);分页切换会拉数
92
110
 
93
- ### 列表→详情(命中 detail-page 时)
111
+ ### 列表→详情(改动含 toDetail / *Detail 路由时强制,禁止 N/A 糊弄)
94
112
 
95
- - [ ] 兄弟绝对 path;`showLink:false`;`activePath`=列表 path;非空 `title`
96
- - [ ] 仅 `toDetail`;详情 `initToDetail`;`name` 对齐
97
- - [ ] 按需 `keepAlive` / `moreTags`
113
+ - [ ] **打开落盘路由文件**核对:兄弟绝对 path;`showLink:false`;`activePath`=列表 path;非空 `title`;默认 `keepAlive`+`moreTags`
114
+ - [ ] 仅 `toDetail`;详情 `initToDetail`;`name` 对齐;`toDetail` 第 3 参 title 可辨识
98
115
 
99
116
  ### 列表布局(命中 crud-pages 时)
100
117
 
@@ -18,13 +18,13 @@ tags: [vue3, kernel, conventions, layout, router, http, crud, inventory]
18
18
  | 3 | 方法跟后端文档,**允许 GET / POST**;POST → `data`,GET → `params` |
19
19
  | 4 | 列表用 `useTableSearch`,方法名 **`onSearch` / `reset`** |
20
20
  | 5 | 业务路由 **禁止** 再挂 `component: Layout` |
21
- | 6 | 独立详情/编辑:路由与跳转按 **`detail-page`**(`showLink` / `activePath` / `toDetail` / `initToDetail`) |
21
+ | 6 | 独立详情/编辑:按 **`detail-page` 三件套**(`showLink`+`activePath`+默认`keepAlive`/`moreTags` + `toDetail` + `initToDetail`);`toDetail`≠自动侧栏高亮/缓存 |
22
22
  | 7 | 页面权限 `meta.roles`;按钮 `v-auth` / `ReAuth` |
23
23
  | 8 | 环境只用 `import.meta.env.VITE_*` |
24
24
  | 9 | i18n:`$t('中文key')` |
25
25
  | 10 | 禁止用 `v-html` 渲染不可信内容 |
26
26
  | 11 | 新依赖须用户确认;已有封装须复用;选型表指定库勿擅自替换 |
27
- | 12 | 列表布局按 **`crud-pages` 黄金样板**(`el-card`+`content`+固定 height、`tableHeight`、`otherRef`、分页在 ReTableBar 内) |
27
+ | 12 | 列表布局按 **`crud-pages` 黄金样板**(`el-card.vts-page-card`+`content`+固定 height、`tableHeight`、`otherRef`、分页在 ReTableBar 内;仅表格体内滚动) |
28
28
  | 13 | addon 按 `project-inventory` 第九节精确版本安装;禁止 `@latest` |
29
29
  | 14 | 样式契约见下文「样式契约」 |
30
30
 
@@ -44,11 +44,15 @@ export default {
44
44
  component: () => import("@/views/xxx/list/index.vue"),
45
45
  meta: { title: "客户列表", roles: ["admin", "common"] },
46
46
  },
47
- // 独立详情:兄弟路由 + showLink/activePath,完整约定见 detail-page
47
+ // 独立详情:兄弟路由 + showLink + activePath + keepAlive/moreTags,见 detail-page 三件套
48
48
  ],
49
49
  } satisfies RouteConfigsTable;
50
50
  ```
51
51
 
52
+ ### 抗略读
53
+
54
+ 命中模块须照其**强制完整样板**落盘;样板中的字段默认写出。壳层行为(tag / 侧栏 / keep-alive)以路由 meta + 项目已有实现为准,不假设 Hook 会「全自动」。
55
+
52
56
  ### 权限
53
57
 
54
58
  ```vue
@@ -119,6 +123,8 @@ export function getCustomerList(data: CustomerQuery, url: string) {
119
123
 
120
124
  ### 独立功能页容器
121
125
 
126
+ 定高且内部滚动(与列表卡同类场景):
127
+
122
128
  ```vue
123
129
  <div
124
130
  :style="{ height: `calc(100vh - 2 * var(--vts-margin) - ${headerHeight}px)` }"
@@ -126,6 +132,7 @@ export function getCustomerList(data: CustomerQuery, url: string) {
126
132
  >
127
133
  ```
128
134
 
135
+ 查询列表定高卡 → `crud-pages`(`el-card.vts-page-card` + `tableHeight`)。
129
136
  ## 六、资产分层
130
137
 
131
138
  附加 Hook/组件按功能模块加载(WebSocket、微信、附件、图表等)→ 见各专项与 `project-inventory`。
@@ -9,6 +9,8 @@ tags: [vue3, crud, list, form, detail, table, useTableSearch, page, layout]
9
9
 
10
10
  遵守 `core-kernel` / `http-api`。独立详情路由 → 同时加载 `detail-page`。回显 → `data-writeback`。
11
11
 
12
+ **抗略读**:列表页必须整段照抄「列表黄金样板」;有独立详情时 **同批** 落 `detail-page` 三件套(禁止只写 `toDetail`)。方法名固定 `onSearch` / `reset`。
13
+
12
14
  ## useTableSearch
13
15
 
14
16
  ```typescript
@@ -21,20 +23,29 @@ useTableSearch(formQuery, searchFn, url)
21
23
  - 查询 / 重置:`onSearch()` / `reset()`
22
24
  - 列表:`tableData.value.tableList`
23
25
  - 分页请求:`pageNo` / `pageSize`;响应:`data.content` / `data.totalCount`
24
- - 高度:`tableHeight = content formRef paginationRef − otherRef − 50`
26
+ - 高度:`tableHeight` Hook 按「卡片内容区底边表格顶边分页」实测;左右分栏、`--vts-card-padding`、工具栏、`otherRef`(表上方)均自动消化;空数据仍保留表格占位高度。业务页只绑定,不手算魔法数
27
+
28
+ ## 内容区滚动约定
29
+
30
+ | 场景 | 做法 |
31
+ |------|------|
32
+ | 查询列表(定高) | `el-card.vts-page-card` + 固定 `height: calc(100vh - 2 * var(--vts-margin) - ${headerHeight}px)`;表格绑 `tableHeight`;**仅表格体内滚动**;分页在表格下方;整页无外层滚动条 |
33
+ | 树 + 表分栏 | 同上;左侧树、右侧表共用定高卡,高度仍走 `useTableSearch` 几何实测 |
34
+ | 长表单 / 详情 / 说明类 | 可用 `minHeight` 同公式;内容超出时由 `.app-main` 内 `el-scrollbar` 滚动 |
35
+ | Card 内边距 | 一律走 `--vts-card-padding`(桥接 `--card-padding`);换规范只改令牌,列表高度由 Hook 跟随 |
25
36
 
26
37
  ## 列表黄金样板(唯一完整示例)
27
38
 
28
39
  | # | 必须 |
29
40
  |---|------|
30
- | L1 | `el-card` + `ref="content"`(禁止挂 `div`) |
41
+ | L1 | `el-card` + `class="vts-page-card"` + `ref="content"`(禁止挂 `div`) |
31
42
  | L2 | 固定 `height: calc(100vh - 2 * var(--vts-margin) - ${headerHeight}px)`(禁止只用 `minHeight`) |
32
43
  | L3 | 查询区 `bg-bg_color p-5`;展示区 `ReTableBar class="bg-bg_color"` |
33
44
  | L4 | `ReGrid` `:gap="[0,20]"` + cols;表单项 `w-[100%] !mr-0`;`@should-show-collapse` |
34
45
  | L5 | 操作列 `<ReGridItem suffix>` |
35
- | L6 | 表格绑 `tableHeight`;分页在 `ReTableBar` `#default` |
46
+ | L6 | 表格绑 `ref="multipleTableRef"` + `tableHeight`;分页在 `ReTableBar` `#default` 内、表格下方 |
36
47
  | L7 | `RePagination`:`:pagination` + `@handle-current-change` / `@handle-size-change` |
37
- | L8 | 查询条件外模块包一根元素绑 `otherRef` |
48
+ | L8 | 查询与表之间的额外模块包一根元素绑 `otherRef`(须在表格上方,高度计入 `tableTop`) |
38
49
 
39
50
  ```vue
40
51
  <script setup lang="ts">
@@ -112,6 +123,7 @@ function handleSizeChange(size: number) {
112
123
  <div>
113
124
  <el-card
114
125
  ref="content"
126
+ class="vts-page-card"
115
127
  shadow="never"
116
128
  :style="{
117
129
  height: `calc(100vh - 2 * var(--vts-margin) - ${headerHeight}px)`,
@@ -228,6 +240,8 @@ function handleSizeChange(size: number) {
228
240
 
229
241
  ## 列表 → 详情/编辑
230
242
 
243
+ 有独立详情/编辑路由时:**必须同时命中并深读 `detail-page`**。禁止只抄下面的 `toDetail` 就交卷。
244
+
231
245
  ```typescript
232
246
  import { useDetail } from "@/hooks/useDetail";
233
247
  const { toDetail } = useDetail();
@@ -244,7 +258,24 @@ function onEdit(row: Customer, mark: "edit" | "view") {
244
258
  }
245
259
  ```
246
260
 
247
- 路由 meta 与详情页 `initToDetail` → **`detail-page`**。
261
+ **同批必落兄弟路由 meta**(完整三件套见 `detail-page`;此处不可省略):
262
+
263
+ ```typescript
264
+ {
265
+ path: "/customer/detail",
266
+ name: "CustomerDetail",
267
+ component: () => import("@/views/customer/detail/index.vue"),
268
+ meta: {
269
+ title: "客户详情",
270
+ showLink: false,
271
+ activePath: "/customer/list",
272
+ keepAlive: true,
273
+ moreTags: true,
274
+ },
275
+ },
276
+ ```
277
+
278
+ 详情页 `initToDetail` + `defineOptions({ name })` 对齐 → **`detail-page`**。
248
279
 
249
280
  ## 表单页
250
281
 
@@ -305,16 +336,17 @@ async function onSubmit() {
305
336
  },
306
337
  ```
307
338
 
308
- 独立详情兄弟路由 `detail-page`。禁止业务模块再挂 Layout。
339
+ 独立详情兄弟路由(含 `activePath` / `keepAlive` / `moreTags`)→ `detail-page`。禁止业务模块再挂 Layout。
309
340
 
310
341
  ## 反例(短表)
311
342
 
312
343
  | 错误 | 正确 |
313
344
  |------|------|
314
345
  | `handleSearch` / 手写分页 | `onSearch` / `useTableSearch` |
315
- | `ref="content"` 挂 div / 只用 minHeight | `el-card` + 固定 height |
316
- | 不绑 `tableHeight` / 分页在卡片外 | 样板 L6/L7 |
346
+ | `ref="content"` 挂 div / 只用 minHeight / 缺 `vts-page-card` | `el-card.vts-page-card` + 固定 height |
347
+ | 不绑 `tableHeight` / 分页在卡片外 / 手写魔法数扣高度 | 样板 L6/L7;高度交给 `useTableSearch` |
317
348
  | 额外模块不绑 `otherRef` | L8 |
318
- | `router.push` 开详情 | `toDetail`(见 detail-page) |
349
+ | 只写 `toDetail`、详情路由缺 `activePath` | 三件套整段落盘(见 detail-page) |
350
+ | 裸 `router.push` 开详情 | `toDetail` + meta(见 detail-page) |
319
351
  | `ReTableOperate :buttons` | `:list` |
320
352
  | `@show-collapse` | `@should-show-collapse` |
@@ -2,6 +2,19 @@
2
2
 
3
3
  > 按本文件示例编写,复用项目已有回显/回填约定与选择器回写方式,勿另起一套表单赋值实现。
4
4
 
5
+ **抗略读**:编辑回显**必须** `Object.assign(formData, res.data)`,禁止 `formData = res.data` 破坏响应式。提交按钮默认带 `loading`。
6
+
7
+ ## 强制样板(编辑回显)
8
+
9
+ ```typescript
10
+ async function getDetail() {
11
+ const { id } = getParameter.value;
12
+ if (!id) return;
13
+ const res = await getDetailApi({ id });
14
+ if (res.status === 200 && res.data) Object.assign(formData, res.data);
15
+ }
16
+ ```
17
+
5
18
  ## 表单回显
6
19
 
7
20
  ### 新增/编辑页
@@ -415,7 +415,8 @@ height: \d+px、padding: \d+px、font-size: \d+px、行内 style="...颜色 hex"
415
415
  - [ ] 控件高度:input/button 计算高度 = 规范值(24/32/40 或规范预设)
416
416
  - [ ] 圆角:input、button、card、dialog 均为规范圆角
417
417
  - [ ] 表格:表头底色/文字色/字重符合规范
418
- - [ ] 卡片内边距:`.el-card__body` = 规范值(AntD 24px),不再是 5px
418
+ - [ ] 卡片内边距:`.el-card__body` = 规范 `--vts-card-padding`(桥接 `--card-padding`)
419
+ - [ ] 定高列表页:`el-card.vts-page-card` + `useTableSearch` 的 `tableHeight`;换 `--card-padding` 后列表仍无外层滚动条、分页完整、仅表格体内滚动
419
420
  - [ ] 表单标签:computed font-weight 为规范值(AntD/Semi = 400,非 700)
420
421
  - [ ] 弹窗:`--ds-dialog-bg` 为 **主题色轻渐变**(`primary-bg → component-bg`),非写死蓝渐变;`ElMessageBox` 内容区用 `--message-box-content-padding`(比 `el-dialog` body 更紧凑)
421
422
  - [ ] 侧栏:深色主题下 DevTools 检查 `.sidebar-container` computed `background` 为 **渐变**(非单色 `#001529`);激活项块色 = `--primary-6`;Logo 区 = `--sidebar-logo-bg`
@@ -608,6 +609,7 @@ height: \d+px、padding: \d+px、font-size: \d+px、行内 style="...颜色 hex"
608
609
  5. `lay-content/index.vue`:`padding-top` 用 `--vts-layout-header-offset*`,禁止写死旧顶栏组合;**禁止**与 `$storage.configure.headerHeight` 双向联动
609
610
  6. `lay-header`:布局/标签页变化须 `nextTick` 重测,且仅在高度变化时写回 `$storage.configure.headerHeight`
610
611
  7. DevTools:顶栏 computed = **当前** `--header-height`;菜单项 = **当前** `--el-menu-item-height`(两者须不同)
612
+ 8. 定高列表页:`--card-padding` / `--vts-card-padding` 变更后,抽查一页查询列表——内容区无外层滚动条、分页完整、`tableHeight` 随 padding 变化
611
613
 
612
614
  #### 7.5.4 EP 菜单变量纪律
613
615
 
@@ -9,18 +9,13 @@ tags: [vue3, detail, toDetail, useDetail, activePath, showLink, multiTags, keepA
9
9
 
10
10
  真相源:模版 `useDetail.ts`、`multiTags`、侧栏 `NavVertical`。列表打开独立路由页时按本文件落地。
11
11
 
12
- ## 概念
12
+ **反认知偏差(必读)**:`toDetail` 只负责写顶部 tag 并跳转;**侧栏高亮靠 `meta.activePath`,缓存/多开靠 `meta.keepAlive` / `moreTags`**。三者缺一不可,禁止只写 `toDetail`。
13
13
 
14
- | 能力 | 字段 / API |
15
- |------|------------|
16
- | 不进左侧菜单 | 路由 `meta.showLink: false` |
17
- | 顶部 tag(multiTags) | `useDetail().toDetail` → `handleTags('push')` |
18
- | 侧栏高亮 | 路由 `meta.activePath` = 列表 path |
19
- | 页面缓存 | `meta.keepAlive`;多开 tag 再加 `moreTags: true` |
14
+ ## 详情三件套(强制完整样板 · 整段照抄再改名)
20
15
 
21
- `showLink` 只管侧栏是否显示,不控制顶部 tag。详情不在菜单里 必须用 `toDetail` 写 tag;裸 `router.push` 只换路由。
16
+ 缺任一段 = 未完成。样板内字段**默认全部写出**(勿改成 `//` 注释再「按需打开」)。仅当业务明确不要缓存/多开时,才删掉对应行。
22
17
 
23
- ## 路由(与列表兄弟)
18
+ ### ① 兄弟路由 meta
24
19
 
25
20
  ```typescript
26
21
  {
@@ -31,36 +26,27 @@ tags: [vue3, detail, toDetail, useDetail, activePath, showLink, multiTags, keepA
31
26
  title: "客户详情",
32
27
  showLink: false,
33
28
  activePath: "/customer/list",
34
- // keepAlive: true,
35
- // moreTags: true,
29
+ keepAlive: true,
30
+ moreTags: true,
36
31
  },
37
32
  },
38
33
  ```
39
34
 
40
- | 字段 | 要求 |
41
- |------|------|
42
- | `title` | 非空 |
43
- | `showLink: false` | 必填 |
44
- | `activePath` | = 列表 path |
45
- | `name` | = `defineOptions({ name })` |
46
-
47
- ## 列表打开
35
+ ### 列表页打开
48
36
 
49
37
  ```typescript
50
38
  import { useDetail } from "@/hooks/useDetail";
51
39
  const { toDetail } = useDetail();
52
40
 
53
41
  toDetail("CustomerDetail", { webId: "0" }, "(新增)");
54
- toDetail("CustomerDetail", { id: row.id, text: row.name, mark: "edit" }, `(编辑)【${row.name}】`);
42
+ toDetail(
43
+ "CustomerDetail",
44
+ { id: row.id, text: row.name, mark: "edit" },
45
+ `(编辑)【${row.name}】`,
46
+ );
55
47
  ```
56
48
 
57
- ```typescript
58
- toDetail(name, parameter, title?, model = "query") // model: "query" | "params"
59
- ```
60
-
61
- 写入 tag 时不要把路由上的 `showLink: false` 原样塞进 `handleTags` 的 meta。
62
-
63
- ## 详情页
49
+ ### ③ 详情页初始化
64
50
 
65
51
  ```vue
66
52
  <script setup lang="ts">
@@ -72,12 +58,36 @@ const { id, mark } = getParameter as DetailParameter;
72
58
  </script>
73
59
  ```
74
60
 
75
- `initToDetail` 标题:无 id → `(新增)`;`mark===edit'` → `(编辑)【text】`;`view` → `(查看)【text】`。
61
+ ## 概念
62
+
63
+ | 能力 | 字段 / API | 谁负责 |
64
+ |------|------------|--------|
65
+ | 不进左侧菜单 | `meta.showLink: false` | 路由 |
66
+ | 顶部 tag | `toDetail` → `handleTags('push')` | Hook |
67
+ | 侧栏高亮 | `meta.activePath` = 列表 path | 路由 |
68
+ | 页面缓存 / 多开 | `meta.keepAlive` + `moreTags: true` | 路由 |
69
+
70
+ `showLink` 只管侧栏是否显示,不控制顶部 tag。裸 `router.push` 只换路由、不写 tag。
71
+
72
+ | 字段 | 要求 |
73
+ |------|------|
74
+ | `title` | 非空 |
75
+ | `showLink: false` | 必填 |
76
+ | `activePath` | = 列表 path(必填) |
77
+ | `keepAlive` / `moreTags` | 默认 `true`;不要缓存/多开再删 |
78
+ | `name` | = `defineOptions({ name })` |
79
+
80
+ ```typescript
81
+ toDetail(name, parameter, title?, model = "query") // model: "query" | "params"
82
+ ```
83
+
84
+ 写入 tag 时不要把路由上的 `showLink: false` 原样塞进 `handleTags` 的 meta。
85
+
86
+ `initToDetail` 标题:无 id → `(新增)`;`mark==='edit'` → `(编辑)【text】`;`view` → `(查看)【text】`。
76
87
 
77
88
  ## Must-do
78
89
 
79
- 1. 兄弟路由 + `showLink: false` + `activePath` + 非空 `title`
80
- 2. 列表只用 `toDetail`,禁止裸 `router.push`
81
- 3. 详情页必调 `initToDetail`;`name` 对齐
82
- 4. 参数用 `getParameter`
83
- 5. 需缓存 / 多开时配 `keepAlive` / `moreTags`
90
+ 1. 三件套整段落盘(路由 + `toDetail` + `initToDetail`)
91
+ 2. `showLink: false` + `activePath` + 非空 `title` + 默认 `keepAlive`/`moreTags`
92
+ 3. 列表只用 `toDetail`,禁止裸 `router.push`
93
+ 4. 详情页必调 `initToDetail`;`name` 对齐;参数用 `getParameter`
@@ -4,6 +4,26 @@
4
4
 
5
5
  规范文件下载、模板下载、文件流导出与附件预览。在需要下载文件、导出表格或预览附件时参照。
6
6
 
7
+ **抗略读**:导出/下载 API **必须**带 `responseType: "blob"`(默认写出,勿当可选项注释掉);前端用 `blobDown` / 项目已有 `download*`,禁止手写 `URL.createObjectURL` 另起一套(除非仓库无封装)。
8
+
9
+ ## 强制样板(导出)
10
+
11
+ ```typescript
12
+ // api
13
+ export function exportExcel(data: Recordable) {
14
+ return http.request<Result<Blob>>(
15
+ "post",
16
+ `${import.meta.env.VITE_API_BASE_URL}/xxx/export`,
17
+ { data },
18
+ { responseType: "blob" },
19
+ );
20
+ }
21
+
22
+ // 页面
23
+ const res = await exportExcel(params);
24
+ blobDown(res.data, "导出.xlsx", res);
25
+ ```
26
+
7
27
  ## 常用工具
8
28
 
9
29
  项目集成了 `@pureadmin/utils` 与自定义工具:
@@ -4,6 +4,25 @@
4
4
 
5
5
  规范系统 Loading、Message、MessageBox、NoticeBar 等反馈组件的使用。在需要提示用户或阻塞操作时参照。
6
6
 
7
+ **抗略读**:删除/危险操作**默认写出** `ElMessageBox.confirm`;列表请求接 `v-loading`;提交默认 `loading` 防重复。提示文案一律 `$t()`。
8
+
9
+ ## 强制样板(删除确认)
10
+
11
+ ```typescript
12
+ import { ElMessageBox } from "element-plus";
13
+ import { message } from "@/utils/message";
14
+
15
+ async function onDelete(row: { id: string }) {
16
+ await ElMessageBox.confirm($t("确定删除吗?"), $t("温馨提醒"), {
17
+ confirmButtonText: $t("确定"),
18
+ cancelButtonText: $t("取消"),
19
+ type: "warning",
20
+ });
21
+ // await deleteApi({ id: row.id })
22
+ message($t("删除成功"), { type: "success" });
23
+ }
24
+ ```
25
+
7
26
  ## Loading
8
27
 
9
28
  ### 局部 Loading
@@ -10,6 +10,8 @@ tags: [form, dialog, ReDialog, ReDialogResize, ReSelectQuery, ReCascader, iconPi
10
10
  > 核心选择器与 CRUD 列表筛选项高频相关。
11
11
  > **弹窗**:业务弹窗统一用 `ReDialog` / `ReDialogResize`,禁止业务页直接堆裸 `el-dialog`(除非用户明确要求)。
12
12
 
13
+ **抗略读**:弹窗走项目 `ReDialog` 黄金样板(含销毁/禁点遮罩等默认项),勿降级为裸 `el-dialog`。远程下拉用 `ReSelectQuery` 真实 props(`url`/`value`/`label`/`edit-data`),禁止臆造 `:api`。
14
+
13
15
  ## 依赖(缺则先装)
14
16
 
15
17
  按用到的能力安装(先查 `package.json`):
@@ -9,6 +9,31 @@ tags: [axios, http, api, encryption, rsa, aes, download, request]
9
9
 
10
10
  > 实现落点:`src/utils/http.ts`、`src/api/**`、全局 `Result<T>`。
11
11
 
12
+ **抗略读**:接口文件须整段按下方「强制样板」落盘;禁止组件内直接 `axios`/`fetch`。加解密靠 `VITE_ENCODE_SWITCH`,禁止臆造单请求 `crypto: true`。下载接口**默认写出** `responseType: "blob"`(勿注释掉)。
13
+
14
+ ## 强制样板(新建 API 照抄)
15
+
16
+ ```typescript
17
+ import { http } from "@/utils/http";
18
+
19
+ export function getCustomerList(data: CustomerQuery) {
20
+ return http.request<Result<CustomerPage>>(
21
+ "post",
22
+ `${import.meta.env.VITE_API_BASE_URL}/customer/list`,
23
+ { data },
24
+ );
25
+ }
26
+
27
+ export function downloadCustomer(data: Recordable) {
28
+ return http.request<Result<Blob>>(
29
+ "post",
30
+ `${import.meta.env.VITE_API_BASE_URL}/customer/export`,
31
+ { data },
32
+ { responseType: "blob" },
33
+ );
34
+ }
35
+ ```
36
+
12
37
  ## Http 封装架构
13
38
 
14
39
  项目使用自定义 `Http` 类封装 Axios,位于 `src/utils/http.ts`。核心特性:
@@ -11,6 +11,18 @@ tags: [vue3, icon, svg-icon, ReSvgIcon, ReIconPicker, iconify, ep, ri, unocss, m
11
11
  > **真相源**:`src/components/ReSvgIcon`、`src/components/ReIconPicker`、`src/icons/svg`、`main.ts`、`uno.config.ts`、`build/plugins/SvgSpriteLoader.ts` / `SvgLoader.ts`。
12
12
  > 选型表见 `project-inventory`「图标」节;勿另起 IconFont / 未约定的图标库。
13
13
 
14
+ **抗略读**:新建菜单/父级路由时 **`meta.icon` 默认写出**(如 `ep:user`),禁止交无图标菜单。页面内优先 `<svg-icon name="..." />`,勿散落未约定图标库。
15
+
16
+ ## 强制样板(菜单 icon)
17
+
18
+ ```typescript
19
+ meta: { title: "客户管理", icon: "ep:user", rank: 10 }
20
+ ```
21
+
22
+ ```vue
23
+ <svg-icon name="ep:search" />
24
+ ```
25
+
14
26
  ---
15
27
 
16
28
  ## 依赖(core,模板已装)
@@ -320,6 +320,17 @@ src/layout/
320
320
  | 重测 | 切换 `layout`、`hideTabs`、`hiddenSideBar` 后 `nextTick` 再测一次(避免布局切换后仍用 localStorage 旧值) |
321
321
  | 消费 | 业务页通过 `useApp().headerHeight` 读 `$storage.configure.headerHeight`;`lay-content` 的 `padding-top` 仍用 `--vts-layout-header-offset*`(勿与 storage 双向绑定,避免重测循环) |
322
322
 
323
+ ### 内容区高度与滚动
324
+
325
+ | 场景 | 卡片 / 高度 | 滚动落点 |
326
+ |------|-------------|----------|
327
+ | 查询列表 | `el-card.vts-page-card` + 固定 `calc(100vh - 2 * var(--vts-margin) - ${headerHeight}px)`;表格绑 `useTableSearch().tableHeight` | 仅 `el-table` 体内;分页在表格下方 |
328
+ | 树 + 表分栏 | 同上定高卡;左右并排,高度走 Hook 几何实测 | 同查询列表 |
329
+ | 长表单 / 详情 / 说明 | 同公式可用 `minHeight`;不绑 `tableHeight` | `.app-main` 内 `el-scrollbar` |
330
+ | Card 内边距 | `--vts-card-padding` → `--card-padding` | 换规范只改令牌;列表高度由 Hook 实测跟随 |
331
+
332
+ `.vts-page-card`:`overflow: hidden`,`.el-card__body` 高度 100%。样式在 `element-plus.scss`。
333
+
323
334
  ### 混合布局路由结构
324
335
 
325
336
  混合模式(mix)左侧栏只展示**一级**菜单,页面组件须挂在 **`children`** 下:
@@ -7,6 +7,20 @@ tags: [vue3, permission, auth, directive, hooks, v-auth, role, access-control]
7
7
 
8
8
  # 权限控制与自定义指令
9
9
 
10
+ **抗略读**:有权限需求时,`meta.roles` 与 `v-auth`/`ReAuth` **默认写出**(勿注释成可选项)。禁止 `v-if="roles.includes(...)"` 手写角色判断。
11
+
12
+ ## 强制样板(页面 + 按钮)
13
+
14
+ ```typescript
15
+ // 路由:roles 默认写出
16
+ meta: { title: "用户管理", roles: ["admin", "common"] }
17
+ ```
18
+
19
+ ```vue
20
+ <el-button v-auth="['admin']" @click="onDelete">{{ $t("删除") }}</el-button>
21
+ <ReAuth :value="['admin']"><!-- 区域级 --></ReAuth>
22
+ ```
23
+
10
24
  ## 权限体系概览
11
25
 
12
26
  项目权限分为三个层级:
@@ -33,7 +47,7 @@ export default {
33
47
  component: () => import("@/views/system/user/index.vue"),
34
48
  meta: {
35
49
  title: "用户管理",
36
- roles: ["admin", "editor"], // 只有这些角色可以访问
50
+ roles: ["admin", "editor"],
37
51
  },
38
52
  },
39
53
  {
@@ -42,7 +56,7 @@ export default {
42
56
  component: () => import("@/views/system/config/index.vue"),
43
57
  meta: {
44
58
  title: "系统配置",
45
- roles: ["admin"], // 仅管理员可访问
59
+ roles: ["admin"],
46
60
  },
47
61
  },
48
62
  ],
@@ -220,7 +220,7 @@
220
220
  | **useApp** | 全局应用状态(布局、字典、登出等) | `baseCodeGet(codeType)`、`filterValue()`、`headerHeight`、`svgLoading` |
221
221
  | **useDetail** | 列表→详情:写顶部 tag 并跳转 | `toDetail(name, params, title?, 'query'\|'params')`、`initToDetail(name)`、`getParameter`;路由配 `showLink:false`(不进侧栏)+`activePath`(见 `detail-page`) |
222
222
  | **useFormCheck** | 多表单校验聚合 | 追踪各字段校验状态,emit `form-validate` 事件 |
223
- | **useTableSearch** | 列表页搜索/分页/Loading/动态表高 | `content`(须绑 `el-card`)、`otherRef`(查询条件外模块的统一根元素,高度自动从 `tableHeight` 扣除)、`tableHeight`、`paginationRef`、`tableData`、`pagination`、`loading`、`onSearch()`、`reset()`、`formRef`、`gridRef`、`collapsed` / `showCollapse` / `collapsedToggle` |
223
+ | **useTableSearch** | 列表页搜索/分页/Loading/动态表高 | `content`(须绑 `el-card.vts-page-card`)、`multipleTableRef`(建议绑 `el-table`)、`otherRef`(表上方额外模块)、`tableHeight`(卡片底边 − 表格顶边 − 分页;空数据仍有占位高)、`paginationRef`、`tableData`、`pagination`、`loading`、`onSearch()`、`reset()`、`formRef`、`gridRef`、`collapsed` / `showCollapse` / `collapsedToggle` |
224
224
  | **useTags** | 多标签页管理 | 右键菜单(刷新/关闭/关闭其他/全屏) |
225
225
  | **useTheme** | 主题切换(暗色/亮色) | 主题色生成、布局预设、缓存重置 |
226
226
  | **useWebSocket** | WebSocket 通信 | `subscribe()`、`send()`、`sendRaw()`、`isConnected` |