@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.
- package/README.md +22 -63
- package/SKILL.md +114 -273
- package/package.json +1 -1
- package/references/api-check.md +33 -313
- package/references/code-quality.md +63 -277
- package/references/core-kernel.md +76 -396
- package/references/crud-pages.md +252 -93
- package/references/data-writeback.md +2 -2
- package/references/design-handoff.md +92 -0
- package/references/design-system.md +70 -60
- package/references/detail-page.md +55 -72
- package/references/form-advanced.md +1 -2
- package/references/layout-theme.md +103 -180
- package/references/mobile-h5.md +11 -11
- package/references/project-inventory.md +9 -10
- package/references/table-vxe.md +7 -13
- package/references/tree-table.md +2 -2
- package/references/websocket-realtime.md +3 -3
package/references/crud-pages.md
CHANGED
|
@@ -1,79 +1,262 @@
|
|
|
1
1
|
---
|
|
2
2
|
skill: crud-pages
|
|
3
|
-
description: 查询列表页、表单页、标准 CRUD
|
|
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
|
|
21
|
-
//
|
|
22
|
-
//
|
|
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
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
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
|
-
|
|
|
37
|
-
|
|
|
38
|
-
|
|
|
39
|
-
|
|
|
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
|
-
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
62
|
+
defineOptions({ name: "CustomerList" });
|
|
58
63
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
125
|
-
</el-
|
|
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
|
-
|
|
155
|
-
|
|
156
|
-
##
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
-
-
|
|
104
|
-
-
|
|
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`,待确认注明缺色阶。
|