@qilitt-mickey/vue3-temp-skill 1.1.13 → 1.1.15

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.
@@ -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
@@ -55,6 +74,22 @@ ElMessageBox.confirm($t("确定删除吗?"), $t("温馨提醒"), {
55
74
  });
56
75
  ```
57
76
 
77
+ ## ReNoticeBar 通知栏
78
+
79
+ 落点:`src/components/ReNoticeBar`。长文本滚动 / 可关闭提示,用于页内公告条。
80
+
81
+ ```vue
82
+ <script setup lang="ts">
83
+ import ReNoticeBar from "@/components/ReNoticeBar";
84
+ </script>
85
+
86
+ <template>
87
+ <ReNoticeBar :text="$t('系统将于今晚维护,请提前保存数据')" />
88
+ </template>
89
+ ```
90
+
91
+ Props / 行为以组件源码为准;颜色走主题令牌,禁止业务写死提示条背景 hex。
92
+
58
93
  ## 关键约定
59
94
 
60
95
  1. 所有提示文案使用 `$t()`。
@@ -231,8 +231,7 @@ defineExpose({ getInstance: () => graph })
231
231
  ```vue
232
232
  <!-- g6 页面示例 -->
233
233
  <template>
234
- <div :style="{ height: `calc(100vh - 2 * var(--vts-margin) - ${headerHeight}px)` }"
235
- class="pos-relative bg-bg_color p-5 overflow-hidden">
234
+ <div class="vts-page-fill h-full pos-relative bg-bg_color p-5 overflow-hidden">
236
235
  <div class="toolbar">
237
236
  <el-alert title="antv/g6流程图展示" type="info" :closable="false" />
238
237
  </div>
@@ -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`):
@@ -202,7 +204,8 @@ function parseAddress(text: string) {
202
204
 
203
205
  ## ReDialog 通用弹窗(必用)
204
206
 
205
- > 落点:`src/components/ReDialog`。基于 `el-dialog`:居中、可拖拽、`destroy-on-close`、默认禁点遮罩关闭、可选全屏、内容区 `el-scrollbar`。写法见下方样板。
207
+ > 落点:`src/components/ReDialog`。基于 `el-dialog`:居中、可拖拽、`destroy-on-close`、默认禁点遮罩关闭、内容区 `el-scrollbar`。
208
+ > **默认值**:`width='50%'`、`maxHeight=500`、`fullscreen=false`(不展示全屏钮)。几何消费 CSS:`--dialog-header-height`(出厂 38)、`--dialog-footer-height`(38)、`--dialog-body-padding`(15)、`--dialog-border-radius`(10);设计落地后按 handoff 回填令牌即可变观感。
206
209
 
207
210
  ### Props / 插槽
208
211
 
@@ -212,7 +215,7 @@ function parseAddress(text: string) {
212
215
  | `title` | 标题文案(也可用 `#title` 插槽) |
213
216
  | `width` | 默认 `'50%'` |
214
217
  | `maxHeight` | 内容区滚动高度,默认 `500`(number 会拼 `px`) |
215
- | `fullscreen` | 是否显示右上角全屏按钮,默认 `true` |
218
+ | `fullscreen` | 是否显示右上角全屏按钮,**默认 `false`**(需要时显式 `:fullscreen="true"`) |
216
219
  | 默认插槽 | 内容区(包在 scrollbar 内) |
217
220
  | `#footer` | 有则渲染底部操作区 |
218
221
  | `#title` | 自定义标题 |
@@ -24,7 +24,7 @@ import { useApp } from '@/hooks/useApp'
24
24
  import bgImg from './img/relationBg.png'
25
25
 
26
26
  defineOptions({ name: 'Graph' })
27
- const { headerHeight, svgLoading } = useApp()
27
+ const { svgLoading } = useApp()
28
28
  const graphRef = ref()
29
29
  const loading = ref(false)
30
30
  const rootId = ref('')
@@ -260,6 +260,6 @@ const legendConfig = ref([
260
260
 
261
261
  1. 节点/连线类型只需声明项目中实际用到的字段,不必照搬库的完整类型。
262
262
  2. 根节点通过 `selfFlag === '1'` 识别,设为 `fixed: true` 固定位置。
263
- 3. 图谱容器必须有明确高度(使用 `calc(100vh - ...)` 计算)。
263
+ 3. 图谱容器必须有明确高度(定高页用 `vts-page-fill` / `vts-page-card`;内部画布 `height: 100%` 或 `calc(100% - 偏移)`;禁止业务页 `100vh` 减顶栏定高)。
264
264
  4. `setJsonData` 的回调中执行居中、缩放、高亮等初始化操作。
265
265
  5. 节点自定义样式通过 `#node` 插槽,class 根据 `data` 字段动态绑定。
@@ -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,模板已装)
@@ -189,40 +189,48 @@ html.dark {
189
189
 
190
190
  ### 4.1 间距
191
191
 
192
- 项目使用 `--vts-margin: 8px` 作为基础间距单元。UnoCSS 配置了 `presetRemToPx({ baseFontSize: 4 })`,因此 UnoCSS 中的间距类直接对应像素值:
192
+ 项目内容区内边距走 `--layout-content-padding` → `--vts-margin`(**出厂 8px**;设计 handoff 常覆盖为 24)。UnoCSS `presetRemToPx({ baseFontSize: 4 })` 间距类数字即 px:
193
193
 
194
194
  | UnoCSS 类 | 实际值 | 用途 |
195
195
  |-----------|--------|------|
196
196
  | `p-1` / `m-1` | 1px | 微调 |
197
197
  | `p-2` / `m-2` | 2px | 紧凑间距 |
198
198
  | `p-4` / `m-4` | 4px | 小间距 |
199
- | `p-8` / `m-8` | 8px | 基础间距(= --vts-margin) |
199
+ | `p-8` / `m-8` | 8px | 常等于出厂 `--vts-margin`(handoff 改 margin 后勿再假设恒等于 8) |
200
200
  | `p-12` / `m-12` | 12px | 中等间距 |
201
201
  | `p-16` / `m-16` | 16px | 大间距 |
202
202
  | `p-20` / `m-20` | 20px | 区块间距 |
203
203
 
204
- ### 4.1.1 弹窗 / MessageBox(design-tokens 定义 · element-plus.scss 执行)
204
+ ### 4.1.1 弹窗 / MessageBox(design-tokens 定义 · ReDialog / element-plus 执行)
205
205
 
206
- | 令牌 | 默认语义 | 消费位置 |
206
+ | 令牌 | 出厂默认 | 消费位置 |
207
207
  |------|---------|---------|
208
- | `--dialog-header-padding` | 16px 24px | `.el-dialog__header` / `.el-message-box__header` |
209
- | `--dialog-body-padding` | 24px | `.el-dialog__body`(表单弹窗) |
210
- | `--message-box-content-padding` | 16px 24px | `.el-message-box__content`(确认框,**比 dialog body 更紧凑**) |
211
- | `--dialog-footer-padding` | 16px 24px | `.el-dialog__footer` / `.el-message-box__btns` |
212
- | `--primary-bg` | 主色浅底(上游设计技能常提供) | 渐变顶色 |
213
- | `--ds-dialog-bg` | `linear-gradient(primary-bg → component-bg)` | `index.scss` → `--vts-dialog-bg` → `.el-dialog` / `.el-message-box` |
208
+ | `--dialog-header-height` | 38px | `.el-dialog__header`(ReDialog) |
209
+ | `--dialog-footer-height` | 38px | `.el-dialog__footer` |
210
+ | `--dialog-body-padding` | 15px | `.el-dialog__body` |
211
+ | `--dialog-border-radius` | 10px | `.el-dialog` |
212
+ | `--message-box-*-padding` | 8px 16px | MessageBox 更紧凑 |
213
+ | `--ds-dialog-bg` | `linear-gradient(primary-bg → component-bg)` | `index.scss` → `--vts-dialog-bg` |
214
214
 
215
- > **禁止**写死 `#e5f3fe → #fefefe` 蓝渐变;换主题时只改 `--primary-bg` / `--brand-primary`,渐变自动跟随。暗黑模式在 `dark.scss` 用 `color-mix` 覆写 `--primary-bg` 后同样生效。
215
+ > **禁止**写死 `#e5f3fe → #fefefe`。
216
+ > **出厂**保留上表 ReDialog 默认几何;**设计落地**时按 `design-handoff`「全量令牌核对表 · D. 弹窗」回填(如圆角改 `--radius-lg` / 8px),禁止只改品牌色漏弹窗令牌。
216
217
 
217
218
  ### 4.2 字号
218
219
 
219
- UnoCSS `theme.fontSize` 已显式定义为 px 值(避免被 `presetRemToPx` 转换):
220
+ **两套并存,勿混用:**
221
+
222
+ | 体系 | 正文基准 | 用途 |
223
+ |------|----------|------|
224
+ | `design-tokens` `--font-size-base` | **14px** | Element Plus / 令牌化组件 |
225
+ | UnoCSS `text-base` | **16px** | 工具类排版(`theme.fontSize` 显式 px) |
226
+
227
+ UnoCSS 其余档:
220
228
 
221
229
  | Token | 大小 | 行高 | 用途 |
222
230
  |-------|------|------|------|
223
231
  | `text-xs` | 12px | 16px | 辅助文字、标签 |
224
232
  | `text-sm` | 14px | 20px | 次要文字、表格 |
225
- | `text-base` | 16px | 24px | 正文默认 |
233
+ | `text-base` | 16px | 24px | Uno 正文类 |
226
234
  | `text-lg` | 18px | 28px | 小标题 |
227
235
  | `text-xl` | 20px | 28px | 标题 |
228
236
  | `text-2xl` | 24px | 32px | 页面标题 |
@@ -230,12 +238,18 @@ UnoCSS `theme.fontSize` 已显式定义为 px 值(避免被 `presetRemToPx`
230
238
 
231
239
  ### 4.3 字体
232
240
 
233
- 全局字体在 `index.scss` 中定义:
241
+ 全局字体走令牌 `--font-family`(`design-tokens.scss` 出厂为系统栈)。
242
+
243
+ `index.scss` / `reset.scss` 写法:
234
244
 
235
245
  ```css
236
- font-family: 'Alibaba PuHuiTi 3.0', 'Microsoft YaHei', Arial, sans-serif;
246
+ font-family: var(--font-family, 'Alibaba PuHuiTi 3.0', 'Microsoft YaHei', Arial, sans-serif);
237
247
  ```
238
248
 
249
+ - **出厂**:令牌已定义 → 实际为系统栈;PuHui 仅作 fallback
250
+ - **设计落地**:handoff 覆盖 `--font-family` 即全局生效
251
+ - **禁止**再写死 `'Alibaba PuHuiTi 3.0' !important` 通配符覆盖令牌
252
+
239
253
  ### 4.4 动画时长
240
254
 
241
255
  - 全局过渡时长:`--vts-transition-duration: 0.3s`
@@ -253,56 +267,68 @@ font-family: 'Alibaba PuHuiTi 3.0', 'Microsoft YaHei', Arial, sans-serif;
253
267
 
254
268
  ### 4.6 边框圆角
255
269
 
256
- 项目未定义全局圆角 token,遵循 Element Plus 默认值:
257
- - 按钮/输入框:`4px`
258
- - 卡片:`8px`(`rounded-lg`)
259
- - 弹窗:`8px`
270
+ 模版**已定义**全局圆角令牌(`design-tokens`):
271
+
272
+ | 令牌 | 出厂默认 | 用途 |
273
+ |------|----------|------|
274
+ | `--radius-sm` / `--border-radius-sm` | 4px | 小控件、Tag |
275
+ | `--radius-base` / `--border-radius-base` | 6px | 按钮、输入 |
276
+ | `--radius-lg` / `--border-radius-lg` | 8px | 卡片等 |
277
+ | `--dialog-border-radius` | 10px(出厂 ReDialog) | 弹窗;设计落地可改为 `var(--radius-lg)` |
278
+
279
+ 设计 handoff 可覆盖上述令牌;业务禁止裸写 `border-radius: 4px`。
260
280
 
261
281
  ---
262
282
 
263
283
  ## 五、布局系统
264
284
 
285
+ > **双轨**:下列数字是 **CSS 变量的出厂默认值**,不是组件内写死的常量。设计落地后按 handoff 回填令牌,壳层自动跟随。
286
+
265
287
  ### 布局模式
266
288
 
267
289
  项目支持三种布局模式,通过布局配置切换:
268
290
 
269
- 1. **垂直布局(Vertical)** — 侧边栏 210px + 顶部导航,后台管理默认布局。
291
+ 1. **垂直布局(Vertical)** — 侧边栏 `var(--sidebar-width)`(出厂 **208px**)+ 顶部导航。
270
292
  2. **水平布局(Horizontal)** — 顶部导航栏,侧边栏宽度为 0。
271
- 3. **混合布局(Mix)** — 侧边栏 210px(一级菜单)+ 顶部导航(二级菜单)。
293
+ 3. **混合布局(Mix)** — 侧边栏同 `--sidebar-width`(一级菜单)+ 顶部导航(二级菜单)。
272
294
 
273
295
  ### 布局文件结构
274
296
 
275
297
  ```
276
298
  src/layout/
277
- ├── index.vue # 布局入口
299
+ ├── index.vue
278
300
  ├── components/
279
- │ ├── sidebar/ # 侧边栏
280
- │ ├── navbar/ # 顶部导航
281
- │ ├── tagsview/ # 标签页
282
- │ └── settings/ # 设置面板
301
+ │ ├── lay-sidebar/ # 侧边栏
302
+ │ ├── lay-navbar/ # 顶部导航
303
+ │ ├── lay-tag/ # 多标签页
304
+ │ ├── lay-content/ # 内容区
305
+ │ ├── lay-header/ # 顶栏容器
306
+ │ └── lay-setting/ # 设置面板
283
307
  └── hooks/
284
- └── useLayout.ts # 布局状态管理
285
308
  ```
286
309
 
287
310
  ### 侧边栏样式架构
288
311
 
289
- `src/styles/sidebar.scss` 使用 SCSS mixin `merge-style($sideBarWidth)` 定义侧边栏样式,通过传入不同宽度被调用 3 次(对应 3 种布局):
312
+ `src/styles/sidebar.scss` 使用 SCSS mixin `merge-style($sideBarWidth)`;宽度传入**令牌**,禁止写死 210:
290
313
 
291
314
  ```scss
292
- // sidebar.scss 核心结构
293
- @mixin merge-style($sideBarWidth) {
294
- // 侧边栏容器、菜单项、子菜单、Logo 等样式
295
- // $sideBarWidth 控制展开宽度
296
- }
315
+ $sideBarWidth: var(--sidebar-width, 208px);
297
316
 
298
- // 垂直布局
299
- .layout-vertical .sidebar-container { @include merge-style(210px); }
300
- // 水平布局
301
- .layout-horizontal .horizontal-header { @include merge-style(0); }
302
- // 混合布局
303
- .layout-mix .sidebar-container { @include merge-style(210px); }
317
+ .layout-vertical .sidebar-container { @include merge-style($sideBarWidth); }
318
+ .layout-mix .sidebar-container { @include merge-style($sideBarWidth); }
304
319
  ```
305
320
 
321
+ 壳层几何出厂默认(均在 `design-tokens.scss`,可被 handoff 覆盖):
322
+
323
+ | 令牌 | 出厂 | 设计别名 |
324
+ |------|------|----------|
325
+ | `--header-height` | 64px | `--layout-header-height` |
326
+ | `--tabs-height` | 40px | (壳层多标签栏,≠ EP `--tabs-item-height`) |
327
+ | `--sidebar-width` | 208px | `--layout-sider-width` |
328
+ | `--sidebar-collapsed-width` | 64px | `--layout-sider-collapsed-width` |
329
+ | `--layout-content-padding` → `--vts-margin` | 8px | 设计常改 24 |
330
+ | `--layout-footer-height` | 48px | |
331
+
306
332
  **修改侧边栏样式时**,必须检查 3 种布局模式下是否都需要调整。
307
333
 
308
334
  ### 布局约定
@@ -310,26 +336,40 @@ src/layout/
310
336
  1. 页面组件通过 `<router-view>` 嵌套在布局内渲染。
311
337
  2. 布局切换通过 store 中的 `layout` 字段控制。
312
338
  3. 侧边栏菜单数据来源于路由配置中的 `meta` 信息。
313
- 4. 标签页(TagsView)支持关闭、刷新、关闭其他等操作。
339
+ 4. 标签页(TagsView)三种风格:`smart`(灵动)/ `chrome`(谷歌)/ `card`(卡片·设计规范);由 `configure.showModel` / `platform-config.json` 的 `ShowModel` 控制。设计 handoff 落地时用 `card`。
340
+
341
+ ### 页签风格(ShowModel)
314
342
 
315
- ### 顶栏高度(headerHeight)
343
+ | 值 | 设置文案 | 说明 |
344
+ |----|----------|------|
345
+ | `smart` | 灵动 | 底部指示条动画 |
346
+ | `chrome` | 谷歌 | Chrome 梯形页签(模版常见出厂) |
347
+ | `card` | 卡片 | 关闭钮绝对定位不占位;显现时用不透明页签底盖字;再 hover 关闭钮:圆形底 `--layout-bg`/`--bg-layout` + 图标 `--error-6`/`--error-color`=`#F5222D`(勿用 EP `#F56C6C` 偏橘) |
348
+
349
+ 设计落地改色/几何时优先改 `--vts-tag-card-*`(见 `index.scss` `:root`),勿再复制一套选择器。
350
+
351
+ ### 顶栏 / 页签高度
316
352
 
317
353
  | 环节 | 约定 |
318
354
  |------|------|
319
- | 测量 | `lay-header` 用 `ResizeObserver` 测量 `.fixed-header`,写入 `$storage.configure.headerHeight` |
320
- | 重测 | 切换 `layout`、`hideTabs`、`hiddenSideBar` 后 `nextTick` 再测一次(避免布局切换后仍用 localStorage 旧值) |
321
- | 消费 | 业务页通过 `useApp().headerHeight` 读 `$storage.configure.headerHeight`;`lay-content` 的 `padding-top` 仍用 `--vts-layout-header-offset*`(勿与 storage 双向绑定,避免重测循环) |
355
+ | 令牌 | `--header-height` / `--tabs-height` → `--layout-header-offset`;桥接 `--vts-bar-height` / `--vts-tabs-height` / `--vts-layout-header-offset*` |
356
+ | 消费 | `lay-content` 的 `padding-top` 用 `--vts-layout-header-offset*`;定高页用 `.vts-page-card` / `.vts-page-fill` |
357
+ | 禁止 | 业务页用 `100vh` 减顶栏高度;用 JS 测顶栏写 storage;在 `useApp` / `platform-config` 增加顶栏像素字段 |
358
+
359
+ 设计落地改顶栏:只改 `--header-height`(或 handoff `geometry.headerHeight`)。
322
360
 
323
361
  ### 内容区高度与滚动
324
362
 
325
363
  | 场景 | 卡片 / 高度 | 滚动落点 |
326
364
  |------|-------------|----------|
327
- | 查询列表 | `el-card.vts-page-card` + 固定 `calc(100vh - 2 * var(--vts-margin) - ${headerHeight}px)`;表格绑 `useTableSearch().tableHeight` | 仅 `el-table` 体内;分页在表格下方 |
365
+ | 查询列表 | `el-card.vts-page-card`(无 inline `100vh`);表格绑 `useTableSearch().tableHeight` | 仅 `el-table` 体内;分页在表格下方 |
328
366
  | 树 + 表分栏 | 同上定高卡;左右并排,高度走 Hook 几何实测 | 同查询列表 |
329
- | 长表单 / 详情 / 说明 | 同公式可用 `minHeight`;不绑 `tableHeight` | `.app-main` 内 `el-scrollbar` |
330
- | Card 内边距 | `--vts-card-padding` → `--card-padding` | 换规范只改令牌;列表高度由 Hook 实测跟随 |
367
+ | 非 Card 整页填满 | 根节点 `class="vts-page-fill"` + `height: 100%` / `h-full` | 内部区域自管滚动 |
368
+ | 长表单 / 详情 / 说明 | `min-height: 100%`;不绑 `tableHeight` | `.app-main` 内 `el-scrollbar` |
369
+ | Card 内边距 | `--vts-card-padding` → `--card-padding` | 换规范 / `--vts-margin` 只改令牌;列表高度由壳层 + Hook 跟随 |
331
370
 
332
- `.vts-page-card`:`overflow: hidden`,`.el-card__body` 高度 100%。样式在 `element-plus.scss`。
371
+ `.vts-page-card`:flex 填满 `.main-content:has(.vts-page-card)`,`overflow: hidden`,`.el-card__body` 高度 100%。样式在 `element-plus.scss` + `lay-content`。
372
+ `--vts-margin` 可变(模板默认 8px,设计 handoff 可覆盖);壳层对 `.main-content` 使用 **padding**(非 margin)承载该间距,定高链为 `height:100%`,避免「高度+外边距」撑出最外层滚动条。
333
373
 
334
374
  ### 混合布局路由结构
335
375
 
@@ -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
  ],