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

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.
@@ -1,79 +1,262 @@
1
1
  ---
2
2
  skill: crud-pages
3
- description: 查询列表页、表单页、标准 CRUD 流程。含 useTableSearch(onSearch/reset)、ReGrid 查询区、表格工具栏与动态高度布局。务必按照本文件与 core-kernel 完整示例编写。
3
+ description: 查询列表页、表单页、标准 CRUD。含 useTableSearch、列表黄金样板、表单约定。
4
4
  scope: project
5
5
  tags: [vue3, crud, list, form, detail, table, useTableSearch, page, layout]
6
6
  ---
7
7
 
8
8
  # CRUD 页面开发规范
9
9
 
10
- > 以下示例即规范真相。
11
- > 须同时遵守 `core-kernel` / `http-api`。详情跳转见 `detail-page`,回显见 `data-writeback`。
12
- > **查询列表页完整模板以 `core-kernel.md`「列表黄金样板」为准**(技能内完整示例,勿到仓库里找 views 演示页对照)。
10
+ 遵守 `core-kernel` / `http-api`。独立详情路由 → 同时加载 `detail-page`。回显 → `data-writeback`。
13
11
 
14
- ## 查询列表页
15
-
16
- ### useTableSearch 真实签名
12
+ ## useTableSearch
17
13
 
18
14
  ```typescript
19
15
  useTableSearch(formQuery, searchFn, url)
20
- // searchFn: (query: CombinedQueryData, url: string) => Promise<Result<T>>
21
- // 返回含:content, otherRef, tableData, pagination, loading, formRef, onSearch, reset,
22
- // tableHeight, paginationRef, gridRef, showCollapse, collapsed,
23
- // collapsedToggle, upBold, downBold, multipleTableRef, ...
16
+ // searchFn: (query, url) => Promise<Result<T>>
17
+ // 返回:content, otherRef, tableData, pagination, loading, formRef, onSearch, reset,
18
+ // tableHeight, paginationRef, gridRef, showCollapse, collapsed, collapsedToggle, …
24
19
  ```
25
20
 
26
- - 查询:`onSearch()`
27
- - 重置:`reset()`(内部 `formRef.resetFields()`)
28
- - 列表:`tableData.value.tableList`(或经 computed 断言)
29
- - 分页:请求带 `pageNo` / `pageSize`;响应 `data.content` / `data.totalCount`
30
- - **高度**:`tableHeight` 由 ResizeObserver 计算,公式 = `content` 高 − `formRef` 高 − `paginationRef` 高 − `otherRef` 高 − 50;`content` / `formRef` / `paginationRef` 必须按黄金样板绑定;**查询条件以外的模块(统计块/页签/提示条等)必须包进一个根元素并绑 `otherRef`**,否则表格动态高度算错
21
+ - 查询 / 重置:`onSearch()` / `reset()`
22
+ - 列表:`tableData.value.tableList`
23
+ - 分页请求:`pageNo` / `pageSize`;响应:`data.content` / `data.totalCount`
24
+ - 高度:`tableHeight` 由 Hook 按「卡片内容区底边 − 表格顶边 − 分页」实测;左右分栏、`--vts-card-padding`、工具栏、`otherRef`(表上方)均自动消化;空数据仍保留表格占位高度。业务页只绑定,不手算魔法数
31
25
 
32
- ### 布局硬约束(生成列表页必须全部满足)
26
+ ## 内容区滚动约定
33
27
 
34
- | 区域 | 正确写法 | 禁止 |
35
- |------|---------|------|
36
- | 外层容器 | `<el-card ref="content" shadow="never" :style="{ height: \`calc(100vh - 2 * var(--vts-margin) - ${headerHeight}px)\` }">` | `ref` 挂原生 `div`;只用 `minHeight`;漏 `headerHeight` |
37
- | 查询区 | `el-form` `:inline="true"` + `class="bg-bg_color p-5"` | 无内边距、无背景、字段横向挤成一团 |
38
- | 栅格 | `ReGrid` `:gap="[0, 20]"` + `:cols="{ xs: 1, sm: 2, md: 2, lg: 3, xl: 4 }"`;`@should-show-collapse` | 无 `gap`;事件写成 `@show-collapse` |
39
- | 表单项 | `el-form-item` 加 `class="w-[100%] !mr-0"`;`ReGridItem` 用 `index="0"` 字符串 | 表单项无宽度、彼此贴死 |
40
- | 查询按钮 | `<ReGridItem suffix>` + `flex flex-end` | 用 `index="99"` 冒充后缀 |
41
- | 展示区 | `ReTableBar class="bg-bg_color"`,与查询区分离 | 查询与表格糊成一块、无标题栏 |
42
- | 表格高度 | `:style="{ height: \`${tableHeight}px\` }"` | 不绑 `tableHeight`(空数据时分页贴查询区) |
43
- | 额外模块 | 卡片内查询条件以外的模块包一个根元素绑 `ref="otherRef"`(多个模块合进同一根容器),高度自动从 `tableHeight` 扣除 | 额外模块不绑 `otherRef`(表格溢出/被压扁);手写固定像素或 `calc()` 猜高度 |
44
- | 分页 | 放在 `ReTableBar` `#default` 内、表格下方;`RePagination` + `:pagination` + `@handle-current-change` / `@handle-size-change` | 分页在卡片外;虚构 `v-model:current-page` |
28
+ | 场景 | 做法 |
29
+ |------|------|
30
+ | 查询列表(定高) | `el-card.vts-page-card` + 固定 `height: calc(100vh - 2 * var(--vts-margin) - ${headerHeight}px)`;表格绑 `tableHeight`;**仅表格体内滚动**;分页在表格下方;整页无外层滚动条 |
31
+ | 树 + 表分栏 | 同上;左侧树、右侧表共用定高卡,高度仍走 `useTableSearch` 几何实测 |
32
+ | 长表单 / 详情 / 说明类 | 可用 `minHeight` 同公式;内容超出时由 `.app-main` 内 `el-scrollbar` 滚动 |
33
+ | Card 内边距 | 一律走 `--vts-card-padding`(桥接 `--card-padding`);换规范只改令牌,列表高度由 Hook 跟随 |
45
34
 
46
- ### 完整列表示例
35
+ ## 列表黄金样板(唯一完整示例)
47
36
 
48
- 见 `core-kernel.md`「列表黄金样板」。本模块补充约定:
37
+ | # | 必须 |
38
+ |---|------|
39
+ | L1 | `el-card` + `class="vts-page-card"` + `ref="content"`(禁止挂 `div`) |
40
+ | L2 | 固定 `height: calc(100vh - 2 * var(--vts-margin) - ${headerHeight}px)`(禁止只用 `minHeight`) |
41
+ | L3 | 查询区 `bg-bg_color p-5`;展示区 `ReTableBar class="bg-bg_color"` |
42
+ | L4 | `ReGrid` `:gap="[0,20]"` + cols;表单项 `w-[100%] !mr-0`;`@should-show-collapse` |
43
+ | L5 | 操作列 `<ReGridItem suffix>` |
44
+ | L6 | 表格绑 `ref="multipleTableRef"` + `tableHeight`;分页在 `ReTableBar` `#default` 内、表格下方 |
45
+ | L7 | `RePagination`:`:pagination` + `@handle-current-change` / `@handle-size-change` |
46
+ | L8 | 查询与表之间的额外模块包一根元素绑 `otherRef`(须在表格上方,高度计入 `tableTop`) |
49
47
 
50
- 1. 查询区用 **`ReGrid` + `ReGridItem`**,折叠与 `collapsed` / `showCollapse` / `collapsedToggle` 配套。
51
- 2. 工具栏用 **`ReTableBar`**,行操作用 **`ReTableOperate`**(`:list` 非 `:buttons`),分页用 **`RePagination`**。
52
- 3. 查询 / 重置按钮文案 `$t('查询')` / `$t('重置')`(或模板一致的「搜索」),点击绑定 **`onSearch` / `reset`**。
53
- 4. `defineOptions({ name })` 与路由 `name` 一致(keep-alive / 标签)。
54
- 5. 需要权限的按钮加 `v-auth` / `ReAuth`。
55
- 6. 从 `useApp()` 取 **`headerHeight`**、`svgLoading`(及按需 `filterValue` / `baseCodeGet`)。
48
+ ```vue
49
+ <script setup lang="ts">
50
+ import type { OperateButton } from "@/components/ReTableOperate";
51
+ import type { Customer, CustomerQuery } from "@/api/customer";
52
+ import { getCustomerList } from "@/api/customer";
53
+ import { ReGrid } from "@/components/ReGrid";
54
+ import { ReGridItem } from "@/components/ReGridItem";
55
+ import { RePagination } from "@/components/RePagination";
56
+ import { ReTableBar } from "@/components/ReTableBar";
57
+ import { ReTableOperate } from "@/components/ReTableOperate";
58
+ import { useApp } from "@/hooks/useApp";
59
+ import { useTableSearch } from "@/hooks/useTableSearch";
60
+ import { message } from "@/utils/message";
56
61
 
57
- ### API 配套
62
+ defineOptions({ name: "CustomerList" });
58
63
 
59
- ```typescript
60
- // src/api/customer.ts — 详见 http-api
61
- export function getCustomerList(data: CustomerQuery, url: string) {
62
- return http.request<Result<CustomerPage>>("post", url, { data });
64
+ const { headerHeight, svgLoading } = useApp();
65
+ const formQuery = reactive<CustomerQuery>({ keyword: "", status: undefined });
66
+
67
+ const {
68
+ content,
69
+ otherRef,
70
+ tableHeight,
71
+ paginationRef,
72
+ loading,
73
+ formRef,
74
+ multipleTableRef,
75
+ pagination,
76
+ onSearch,
77
+ tableData,
78
+ gridRef,
79
+ showCollapse,
80
+ collapsed,
81
+ collapsedToggle,
82
+ upBold,
83
+ downBold,
84
+ reset,
85
+ } = useTableSearch(
86
+ formQuery,
87
+ getCustomerList,
88
+ `${import.meta.env.VITE_API_BASE_URL}/console/customer/list`,
89
+ );
90
+
91
+ const tableList = computed(() => tableData.value.tableList as Customer[]);
92
+
93
+ const operateButtons: OperateButton[] = [
94
+ {
95
+ label: $t("删除"),
96
+ type: "danger",
97
+ icon: "i-ep:delete",
98
+ onClick: (row) => handleDelete(row as Customer),
99
+ },
100
+ ];
101
+
102
+ onMounted(() => onSearch());
103
+
104
+ function handleDelete(row: Customer) {
105
+ message($t("删除成功"), { type: "success" });
106
+ onSearch();
107
+ }
108
+
109
+ function handleCurrentChange(cur: number) {
110
+ pagination.currentPage = cur;
111
+ onSearch();
63
112
  }
113
+
114
+ function handleSizeChange(size: number) {
115
+ pagination.pageSize = size;
116
+ onSearch();
117
+ }
118
+ </script>
119
+
120
+ <template>
121
+ <div>
122
+ <el-card
123
+ ref="content"
124
+ class="vts-page-card"
125
+ shadow="never"
126
+ :style="{
127
+ height: `calc(100vh - 2 * var(--vts-margin) - ${headerHeight}px)`,
128
+ }"
129
+ >
130
+ <div>
131
+ <el-form
132
+ ref="formRef"
133
+ :inline="true"
134
+ :model="formQuery"
135
+ class="bg-bg_color p-5"
136
+ >
137
+ <ReGrid
138
+ ref="gridRef"
139
+ :collapsed="collapsed"
140
+ :gap="[0, 20]"
141
+ :cols="{ xs: 1, sm: 2, md: 2, lg: 3, xl: 4 }"
142
+ style="max-height: 300px; overflow-y: auto;"
143
+ @should-show-collapse="(val) => (showCollapse = val)"
144
+ >
145
+ <ReGridItem index="0">
146
+ <el-form-item :label="$t('关键词')" prop="keyword" class="w-[100%] !mr-0">
147
+ <el-input v-model="formQuery.keyword" clearable :placeholder="$t('请输入')" />
148
+ </el-form-item>
149
+ </ReGridItem>
150
+ <ReGridItem index="1">
151
+ <el-form-item :label="$t('状态')" prop="status" class="w-[100%] !mr-0">
152
+ <el-select v-model="formQuery.status" clearable :placeholder="$t('请选择')">
153
+ <el-option :label="$t('启用')" :value="1" />
154
+ <el-option :label="$t('禁用')" :value="0" />
155
+ </el-select>
156
+ </el-form-item>
157
+ </ReGridItem>
158
+ <ReGridItem suffix>
159
+ <div class="flex flex-end">
160
+ <el-button
161
+ type="primary"
162
+ :loading="loading"
163
+ @click="pagination.currentPage = 1; pagination.pageSize = 10; onSearch()"
164
+ >
165
+ {{ $t("查询") }}
166
+ </el-button>
167
+ <el-button @click="reset">{{ $t("重置") }}</el-button>
168
+ <el-button
169
+ v-if="showCollapse"
170
+ type="primary"
171
+ link
172
+ class="!font-size-12"
173
+ @click="collapsedToggle"
174
+ >
175
+ {{ collapsed ? $t("展开") : $t("收起") }}
176
+ <el-icon class="el-icon--right">
177
+ <component :is="collapsed ? downBold : upBold" />
178
+ </el-icon>
179
+ </el-button>
180
+ </div>
181
+ </ReGridItem>
182
+ </ReGrid>
183
+ </el-form>
184
+ </div>
185
+
186
+ <div ref="otherRef">
187
+ <!-- 统计/页签等额外模块;无则删除整段 -->
188
+ </div>
189
+
190
+ <ReTableBar class="bg-bg_color" :columns="[]" @refresh="onSearch">
191
+ <template #title>
192
+ <span class="truncate font-size-16 font-bold">{{ $t("展示列表") }}</span>
193
+ </template>
194
+ <template #buttons>
195
+ <el-button type="primary" v-auth="['admin']">{{ $t("新增") }}</el-button>
196
+ </template>
197
+ <template #default="{ size }">
198
+ <div class="vl-parent">
199
+ <el-table
200
+ ref="multipleTableRef"
201
+ v-loading.lock="loading"
202
+ :element-loading-text="$t('加载中')"
203
+ :element-loading-spinner="svgLoading"
204
+ element-loading-svg-view-box="0, 0, 20, 20"
205
+ :style="{ height: `${tableHeight}px` }"
206
+ :size="size"
207
+ :data="tableList"
208
+ :header-cell-style="{
209
+ background: 'var(--el-fill-color-light)',
210
+ color: 'var(--el-text-color-primary)',
211
+ }"
212
+ row-key="id"
213
+ >
214
+ <el-table-column prop="code" :label="$t('编号')" min-width="120" />
215
+ <el-table-column prop="name" :label="$t('名称')" min-width="140" />
216
+ <el-table-column :label="$t('操作')" width="160" fixed="right">
217
+ <template #default="{ row, $index }">
218
+ <ReTableOperate :list="operateButtons" :row="row" :index="$index" />
219
+ </template>
220
+ </el-table-column>
221
+ </el-table>
222
+ <RePagination
223
+ ref="paginationRef"
224
+ :size="size"
225
+ :pagination="pagination"
226
+ @handle-current-change="handleCurrentChange"
227
+ @handle-size-change="handleSizeChange"
228
+ />
229
+ </div>
230
+ </template>
231
+ </ReTableBar>
232
+ </el-card>
233
+ </div>
234
+ </template>
64
235
  ```
65
236
 
237
+ 补充:`ReTableOperate` 用 `:list`;`defineOptions.name` === 路由 `name`;`headerHeight` / `svgLoading` 来自 `useApp()`。
238
+
239
+ ## 列表 → 详情/编辑
240
+
66
241
  ```typescript
67
- const { onSearch, reset, tableData, loading, pagination, content, otherRef, tableHeight, ... } =
68
- useTableSearch(
69
- formQuery,
70
- getCustomerList,
71
- `${import.meta.env.VITE_API_BASE_URL}/console/customer/list`,
242
+ import { useDetail } from "@/hooks/useDetail";
243
+ const { toDetail } = useDetail();
244
+
245
+ function onAdd() {
246
+ toDetail("CustomerDetail", { webId: "0" }, "(新增)");
247
+ }
248
+ function onEdit(row: Customer, mark: "edit" | "view") {
249
+ toDetail(
250
+ "CustomerDetail",
251
+ { id: row.id, text: row.name, mark },
252
+ mark === "edit" ? `(编辑)【${row.name}】` : `(查看)【${row.name}】`,
72
253
  );
73
- onMounted(() => onSearch());
254
+ }
74
255
  ```
75
256
 
76
- ## 表单页(新增 / 编辑)
257
+ 路由 meta 与详情页 `initToDetail` → **`detail-page`**。
258
+
259
+ ## 表单页
77
260
 
78
261
  ```vue
79
262
  <script setup lang="ts">
@@ -87,12 +270,7 @@ defineOptions({ name: "CustomerForm" });
87
270
 
88
271
  const formRef = ref<FormInstance>();
89
272
  const submitLoading = ref(false);
90
- const formData = reactive<Customer>({
91
- id: "",
92
- code: "",
93
- name: "",
94
- });
95
-
273
+ const formData = reactive<Customer>({ id: "", code: "", name: "" });
96
274
  const rules: FormRules<Customer> = {
97
275
  code: [{ required: true, message: $t("请输入编号"), trigger: "blur" }],
98
276
  name: [{ required: true, message: $t("请输入名称"), trigger: "blur" }],
@@ -107,11 +285,8 @@ async function onSubmit() {
107
285
  submitLoading.value = true;
108
286
  try {
109
287
  const res = await saveCustomer(formData);
110
- if (res.status === 200) {
111
- message($t("保存成功"), { type: "success" });
112
- } else {
113
- message(res.statusText || $t("保存失败"), { type: "error" });
114
- }
288
+ if (res.status === 200) message($t("保存成功"), { type: "success" });
289
+ else message(res.statusText || $t("保存失败"), { type: "error" });
115
290
  } finally {
116
291
  submitLoading.value = false;
117
292
  }
@@ -120,25 +295,14 @@ async function onSubmit() {
120
295
 
121
296
  <template>
122
297
  <el-form ref="formRef" :model="formData" :rules="rules" label-position="top">
123
- <el-form-item :label="$t('编号')" prop="code">
124
- <el-input v-model="formData.code" />
125
- </el-form-item>
126
- <el-form-item :label="$t('名称')" prop="name">
127
- <el-input v-model="formData.name" />
128
- </el-form-item>
129
- <el-button type="primary" :loading="submitLoading" @click="onSubmit">
130
- {{ $t("提交") }}
131
- </el-button>
298
+ <el-form-item :label="$t('编号')" prop="code"><el-input v-model="formData.code" /></el-form-item>
299
+ <el-form-item :label="$t('名称')" prop="name"><el-input v-model="formData.name" /></el-form-item>
300
+ <el-button type="primary" :loading="submitLoading" @click="onSubmit">{{ $t("提交") }}</el-button>
132
301
  </el-form>
133
302
  </template>
134
303
  ```
135
304
 
136
- ### 表单约定
137
-
138
- 1. `reactive` + `FormRules` + `formRef.validate()`
139
- 2. 提交按钮必须有 **loading** 防重复
140
- 3. 编辑回填:`Object.assign(formData, detail)`(见 `data-writeback`)
141
- 4. 远程下拉:`ReSelectQuery`(`url` + `value` + `label`,回写用 `edit-data`),见 `form-advanced`
305
+ 约定:提交必有 loading;回填见 `data-writeback`;远程下拉见 `form-advanced`。
142
306
 
143
307
  ## 路由片段
144
308
 
@@ -148,24 +312,19 @@ async function onSubmit() {
148
312
  name: "CustomerList",
149
313
  component: () => import("@/views/customer/list/index.vue"),
150
314
  meta: { title: "客户列表", roles: ["admin", "common"] },
151
- }
315
+ },
152
316
  ```
153
317
 
154
- 禁止业务模块再挂 Layout。
155
-
156
- ## 常见反例
157
-
158
- - 使用 `handleSearch` / `handleReset`(错误命名)
159
- - 不用 `useTableSearch` 手写一套分页 loading
160
- - 提交无 loading
161
- - 业务路由二次 Layout
162
- - ReSelectQuery 写成不存在的 `:api` / `label-key` API(真实 props 见 form-advanced)
163
- - **`ref="content"` 挂在 `div` 上**(`content.$el` 为空 → `tableHeight` 恒为 0)
164
- - **卡片内有查询条件以外的模块却不绑 `otherRef`**(高度公式漏减该模块 → 表格溢出卡片或底部被截);或绑到多个散落元素(`otherRef` 只有一个,多模块需包进同一根容器)
165
- - 卡片只用 **`minHeight`**,不用固定 **`height`**(页面不饱满、表格高度算错)
166
- - 查询表单无 `p-5` / 无 `gap` / 表单项无 `w-[100%] !mr-0`(字段挤在一起)
167
- - 查询区与 `ReTableBar` 无分区(条件与展示糊成一块)
168
- - 表格不绑 `tableHeight`,分页放在卡片外(无数据时分页直接贴查询条件)
169
- - `RePagination` 写成 `v-model:current-page`(组件真实 API 为 `:pagination` + handle 事件)
170
- - `ReTableOperate` 写成 `:buttons`(真实 prop 为 `:list`)
171
- - `ReGrid` 监听 `@show-collapse`(真实事件为 `@should-show-collapse`)
318
+ 独立详情兄弟路由 → `detail-page`。禁止业务模块再挂 Layout。
319
+
320
+ ## 反例(短表)
321
+
322
+ | 错误 | 正确 |
323
+ |------|------|
324
+ | `handleSearch` / 手写分页 | `onSearch` / `useTableSearch` |
325
+ | `ref="content"` 挂 div / 只用 minHeight / 缺 `vts-page-card` | `el-card.vts-page-card` + 固定 height |
326
+ | 不绑 `tableHeight` / 分页在卡片外 / 手写魔法数扣高度 | 样板 L6/L7;高度交给 `useTableSearch` |
327
+ | 额外模块不绑 `otherRef` | L8 |
328
+ | 裸 `router.push` 开详情 | `toDetail`(见 detail-page) |
329
+ | `ReTableOperate :buttons` | `:list` |
330
+ | `@show-collapse` | `@should-show-collapse` |
@@ -100,5 +100,5 @@ const { validateStatus, onValidate } = useFormCheck(['field1', 'field2'])
100
100
 
101
101
  ## 关联文件
102
102
 
103
- - [src/utils/utils.ts](file:///e:/work/vue3-web-temp/src/utils/utils.ts)
104
- - [src/hooks/useFormCheck.ts](file:///e:/work/vue3-web-temp/src/hooks/useFormCheck.ts)
103
+ - `src/utils/utils.ts`
104
+ - `src/hooks/useFormCheck.ts`
@@ -0,0 +1,92 @@
1
+ ---
2
+ skill: design-handoff
3
+ description: 设计产出物 → 业务项目样式落地。从会话/附件取交接块与令牌 CSS,按覆盖清单写入模版样式与配置。
4
+ scope: project
5
+ tags: [handoff, design-tokens, upstream, artifacts]
6
+ ---
7
+
8
+ # 设计产出 → 模板落地
9
+
10
+ ## 触发
11
+
12
+ 出现以下任一情况时执行本协议(与业务开发的先后跟用户需求:可先定风格再写业务,也可在已有业务上全量重构样式):
13
+
14
+ - 会话有「前端落地交接」块 / `【主题色系】` / `:root` 令牌 CSS
15
+ - 用户要求按设计稿 / 规范适配或重构整站风格
16
+ - 用户附带色板、规范或 HTML 原型
17
+
18
+ ## 收集产出(会话 / 附件)
19
+
20
+ | 优先 | 形态 | 用途 |
21
+ |------|------|------|
22
+ | 1 | 前端落地交接块 | `data-theme` / 侧栏 / DarkMode / 主色 |
23
+ | 2 | 令牌 CSS(`--brand-*` / `--primary-*` 等) | 写入 `design-tokens.scss` |
24
+ | 3 | 几何/间距数值 | 覆盖桥接令牌;缺则保留模版默认 |
25
+ | 4 | HTML/CSS 原型 | 微调 `element-plus.scss` |
26
+ | 5 | 项目已有 `design-tokens.scss` | 读现状 |
27
+
28
+ 定位:扫会话交接块、`## 设计令牌`、`:root {`、附件中的 `--primary-` / 主色表。
29
+ 缺项 →「待确认」;有主色则先落主色与壳桥接,不臆造色阶。
30
+
31
+ ## 覆盖清单
32
+
33
+ | # | 落点 | 写入 |
34
+ |---|------|------|
35
+ | A | `src/styles/design-tokens.scss` | 令牌 CSS;深色侧栏时追加 `--sidebar-*` |
36
+ | B | `theme.scss` 当前 `html[data-theme]` | `--vts-theme-*` → `--sidebar-*` |
37
+ | C | `public/platform-config.json` | `EpThemeColor` / `Theme` / `DarkMode`;`Version` 自增 |
38
+ | D | `index.scss` | `design-tokens` 的 `@use` 最前 |
39
+ | E | `dark.scss` / 语义色 | 跟 `--primary-*` |
40
+ | F | 冲突清单 | `design-system` 第四节 |
41
+ | G | 规范识别回执 | 动手改文件前输出 |
42
+
43
+ 侧栏派生(交接为深色渐变时):
44
+
45
+ ```scss
46
+ --sidebar-bg: linear-gradient(180deg, color-mix(in srgb, var(--primary-8) 88%, var(--primary-7)) 0%, var(--primary-9) 52%, color-mix(in srgb, var(--primary-9) 90%, #000) 100%);
47
+ --sidebar-logo-bg: linear-gradient(135deg, color-mix(in srgb, var(--primary-7) 70%, var(--primary-8)) 0%, var(--primary-8) 100%);
48
+ --sidebar-border-color: color-mix(in srgb, var(--primary-6) 22%, transparent);
49
+ --sidebar-menu-text: rgb(255 255 255 / 65%);
50
+ --sidebar-menu-text-hover: #fff;
51
+ --sidebar-menu-active-text: #fff;
52
+ --sidebar-menu-hover-bg: color-mix(in srgb, var(--primary-5) 18%, transparent);
53
+ --sidebar-menu-active-bg: var(--primary-6);
54
+ --sidebar-submenu-bg: color-mix(in srgb, var(--primary-9) 82%, #000);
55
+ --sidebar-active-indicator: var(--primary-5);
56
+ ```
57
+
58
+ `theme.scss`:`--vts-theme-menu-bg: var(--sidebar-bg);` 等 → `layout-theme`。
59
+
60
+ ## 顺序
61
+
62
+ 1. 收集产出 1~4
63
+ 2. 规范识别回执
64
+ 3. A→E
65
+ 4. F
66
+ 5. 验证侧栏/主色
67
+ 6. 若同轮还有业务需求 → 再写/改 views(仅样式任务则到此结束)
68
+
69
+ ## 交接块字段
70
+
71
+ ```markdown
72
+ ## 前端落地交接
73
+ - 设计来源:上游产出
74
+ - 主题色系:<主题中文名>
75
+ - data-theme:<模版 html[data-theme] 值>
76
+ - 侧栏:<深色渐变|浅色>
77
+ - 内容区默认:<亮色|暗黑>
78
+ - 主色:<#HEX>
79
+
80
+ ## 设计令牌(CSS)
81
+ ```css
82
+ :root {
83
+ --brand-primary: #...;
84
+ --brand-hover: #...;
85
+ --brand-active: #...;
86
+ --primary-0: #...;
87
+ /* --primary-1 … --primary-9 */
88
+ }
89
+ ```
90
+ ```
91
+
92
+ 仅有交接块无令牌 CSS:写 `--brand-primary` + `EpThemeColor`,待确认注明缺色阶。