@qilitt-mickey/vue3-temp-skill 1.2.1 → 1.2.4
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 -6
- package/SKILL.md +17 -8
- package/bin/cli.js +1 -1
- package/mapping/admin.json +11 -11
- package/package.json +2 -1
- package/references/adapters/vue-antdv.md +40 -0
- package/references/adapters/vue-element-plus.md +103 -10
- package/references/design-apply.md +80 -11
- package/scripts/check-landing.mjs +576 -0
- package/scripts/generate-css.mjs +405 -0
- package/scripts/verify-render.mjs +530 -0
package/README.md
CHANGED
|
@@ -23,16 +23,31 @@ Vue 3 企业级中后台项目开发技能包。两条链路:
|
|
|
23
23
|
|
|
24
24
|
```text
|
|
25
25
|
输入:<项目根>/.design/visual-contract-<场景>-<页型>.json
|
|
26
|
-
→
|
|
27
|
-
→
|
|
28
|
-
→
|
|
29
|
-
→ 生成 ui-
|
|
30
|
-
→ 生成 ui-
|
|
31
|
-
→
|
|
26
|
+
→ ① 落地自检(六项:契约存在/完整/CSS生成/CSS引入/角色挂载/布局挂载)
|
|
27
|
+
→ ② 读契约(唯一输入,只读一次)
|
|
28
|
+
→ ③ 选 adapter(按 package.json 判定所用组件库)
|
|
29
|
+
→ ④ 生成 ui-tokens.css(:root 变量)
|
|
30
|
+
→ ⑤ 生成 ui-roles.css(.role-* 规则)
|
|
31
|
+
→ ⑥ 生成 ui-layout.css(.ui-page 网格)
|
|
32
|
+
→ ⑦ 挂载(.ui-page / .ui-region-* / .role-*)
|
|
33
|
+
→ ⑧ 重跑自检确认六项全过 → 目视对照
|
|
32
34
|
```
|
|
33
35
|
|
|
34
36
|
**AI 在链路二的工作是翻译,不是判断。** 契约写什么就生成什么——落点层级已由 `host[]` / `inner[]` 两个数组物理二分,落地端不推断属性该落哪一层。
|
|
35
37
|
|
|
38
|
+
**落地自检是交付前置条件**(`npm run check:landing`):
|
|
39
|
+
|
|
40
|
+
| 检查项 | 断点含义 |
|
|
41
|
+
|---|---|
|
|
42
|
+
| `contract-found` | `.design/` 下没有契约 → 设计侧没落盘 |
|
|
43
|
+
| `contract-valid` | 契约字段缺失 |
|
|
44
|
+
| `css-generated` | 三份 CSS 未生成 |
|
|
45
|
+
| `css-imported` | **生成了但没被 import** → 浏览器不加载 |
|
|
46
|
+
| `roles-mounted` | **CSS 有规则但模板没挂类** → 死样式,只有个别角色生效 |
|
|
47
|
+
| `layout-mounted` | 没挂 `.ui-page` → 骨架仍是项目原样 |
|
|
48
|
+
|
|
49
|
+
**任何一项断掉都会表现为「CSS 写了但页面没变化」。先跑自检定位断点,不要目视猜。**
|
|
50
|
+
|
|
36
51
|
## 视觉契约的核心机制
|
|
37
52
|
|
|
38
53
|
| 机制 | 说明 |
|
|
@@ -84,6 +99,7 @@ npx @qilitt-mickey/vue3-temp-skill@latest update
|
|
|
84
99
|
| 命令 | 职责 |
|
|
85
100
|
|------|------|
|
|
86
101
|
| `node bin/cli.js install <target>` | 安装器统一入口:`install` / `update` / `uninstall` / `list` |
|
|
102
|
+
| `npm run check:landing` | **契约落地自检**(六项):判断「契约 → 样式 → 挂载」断在哪一环 |
|
|
87
103
|
| `python scripts/match.py "需求" --json` | 能力路由:按需求返回应加载的 reference 清单 |
|
|
88
104
|
| `npm run release`(`:minor` / `:major`) | 结构检查 → 升版本 → 提交 → 发布 npm |
|
|
89
105
|
|
package/SKILL.md
CHANGED
|
@@ -30,7 +30,14 @@ tags: [vue3, typescript, element-plus, pinia, vite, unocss, crud, demo, code-qua
|
|
|
30
30
|
|
|
31
31
|
1. **项目探查**:读取目标项目的 `package.json`、路由、页面、`Re*` 组件、Hooks、API 和样式;以源码事实为准。
|
|
32
32
|
2. **能力路由**:运行 `scripts/match.py "需求" --json`,**仅**按输出中的 `load_files` 阅读 reference;禁止自行展开全部 `references/`。无 Python 时按下方能力组表手动匹配。
|
|
33
|
-
3. **视觉契约落地**:输入是设计侧的视觉契约 JSON 时,走 `design-apply`
|
|
33
|
+
3. **视觉契约落地**:输入是设计侧的视觉契约 JSON 时,走 `design-apply` 协议——**先跑落地自检,再照抄生成三份样式文件**,见 §3 输入分流。
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
node <技能>/scripts/check-landing.mjs --project <项目根>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**八项全过(exit 0)是交付前置条件**:契约存在 → 契约完整 → 契约结构合规 → 三份 CSS 生成 → 被 import → 角色类挂载 100% → 布局类挂载 → CSS 规则覆盖契约全部声明。
|
|
40
|
+
**任何一项断掉,都会出现「CSS 写了但页面没变化」——先修断点,不要目视猜。**
|
|
34
41
|
|
|
35
42
|
## 能力组路由
|
|
36
43
|
|
|
@@ -49,7 +56,7 @@ tags: [vue3, typescript, element-plus, pinia, vite, unocss, crud, demo, code-qua
|
|
|
49
56
|
| `files-and-editors` | 附件、预览、下载、富文本、二维码 | `file-management.md`、`download-export.md`、`rich-text.md`、`qrcode-barcode.md` |
|
|
50
57
|
| `realtime-and-mobile` | WebSocket、移动端、微信、验证码、动效 | 对应专项 reference |
|
|
51
58
|
| `data-compare` | 编辑页变更对比和汇总 | `data-compare.md` |
|
|
52
|
-
| `design-apply` |
|
|
59
|
+
| `design-apply` | **视觉契约落地**:落地自检 → 读契约 → 选 adapter → **`scripts/generate-css.mjs` 确定性生成三份 CSS** → 挂载(含按契约 `structure` 重建页面内容)→ **`scripts/verify-render.mjs` computed style 逐点比对** → 差异清零。**主入口 `design-apply.md`,adapter 见 `adapters/`(做到哪一步读哪一份)** | `design-apply.md`、`adapters/`、`mapping/`、`scripts/check-landing.mjs`、`scripts/generate-css.mjs`、`scripts/verify-render.mjs` |
|
|
53
60
|
|
|
54
61
|
## 工作流
|
|
55
62
|
|
|
@@ -86,16 +93,18 @@ tags: [vue3, typescript, element-plus, pinia, vite, unocss, crud, demo, code-qua
|
|
|
86
93
|
|
|
87
94
|
使用者默认**不懂设计也不懂代码**,交互遵守四条:
|
|
88
95
|
|
|
89
|
-
1.
|
|
90
|
-
2.
|
|
91
|
-
3.
|
|
92
|
-
4.
|
|
96
|
+
1. **全自动默认**:识别为视觉契约后**直接开始执行全流程**(落地自检 → 读契约 → 选 adapter → 生成三份 CSS → 挂载 → 验证 → 视觉验收),不等用户逐步确认。
|
|
97
|
+
2. **契约缺失 = 硬阻断,不允许"边猜边改"**:`check-landing.mjs` 报 `contract-found FAIL` 时**必须停下**,用大白话报告「还没拿到设计文件」并给出下一步。**严禁在契约缺失的情况下凭理解直接改项目样式**——那是旧体系的失败模式:改了一堆 CSS 却没有任何东西对应,最后只有个别地方碰巧变了。
|
|
98
|
+
3. **契约存在但 `roles-mounted` 未 100% = 不算完成**:角色类没挂满,页面上就只会有个别角色生效(常见症状:只有菜单颜色变了)。必须按 adapter 的「角色类挂载方式」补齐后重跑自检。
|
|
99
|
+
4. **术语不对用户出现**:对用户不说"契约 / 角色 / 令牌 / adapter / 区域 / 自检"——说"设计文件 / 样式规则 / 全局变量 / 组件适配 / 页面区域 / 检查"。这些词只出现在内部文档与交付说明里。
|
|
100
|
+
5. **无文件时的引导**:用户说"全系统改成 XX 风格"但没给设计文件 → 提示一句话:"请先在设计助手(project-ui-design 技能)里说『我要 XX 风格的全系统设计』拿到设计文件,再回来发给我"。如果本机同时装有设计技能,可直接引导用户回到设计会话完成设计,再回本项目落地。
|
|
101
|
+
6. **交付话术**:完成后用大白话总结(改了哪些页面 / 哪些地方变了 / 怎么验收),不输出施工坐标等技术细节;验收引导统一为"运行 `pnpm dev` 打开页面对照看效果,哪里不满意直接说"。
|
|
93
102
|
|
|
94
103
|
### 4. 验证与交付
|
|
95
104
|
|
|
96
105
|
- 对照 `references/code-quality.md` 检查类型、组件复用、API、路由、权限、安全、异常反馈、响应式和可演进性。
|
|
97
106
|
- 在目标项目执行 `pnpm lint:fix`、`pnpm type-check`;改动构建配置时再执行 `pnpm build`。
|
|
98
|
-
-
|
|
107
|
+
- **设计契约落地时追加视觉验收**:用无头浏览器对契约每条声明做 `getComputedStyle` 逐点比对(期望源就是契约 JSON 本身);差异定位顺序「挂载遗漏 → 穿透/选择器通道错 → 提权被盖 → 契约值本身错」,前三者修落地侧,最后一种 **改契约 → 重新生成 CSS**(改契约远比改代码便宜)。全部命中后截图与 `meta.source.mockup` 并排留档;不建期望值文件、不跑采集脚本、不做长期 diff 报告。
|
|
99
108
|
- 最终说明:命中能力组、修改文件、功能验证、命令结果、未解决问题和下一步。
|
|
100
109
|
|
|
101
110
|
## 视觉契约落地(design-apply 架构要点)
|
|
@@ -161,5 +170,5 @@ tags: [vue3, typescript, element-plus, pinia, vite, unocss, crud, demo, code-qua
|
|
|
161
170
|
- 功能:...
|
|
162
171
|
- `pnpm lint:fix`:通过 | 失败(原因)
|
|
163
172
|
- `pnpm type-check`:通过 | 失败(原因)
|
|
164
|
-
- 视觉契约落地(本轮有契约输入时必填,无则 N/A
|
|
173
|
+
- 视觉契约落地(本轮有契约输入时必填,无则 N/A):八项自检全过 → 视觉验收 computed style 比对差异 N 处(已改契约重新生成 / 待确认)→ 截图已留档
|
|
165
174
|
```
|
package/bin/cli.js
CHANGED
|
@@ -35,7 +35,7 @@ const TRACK_FILE = path.join(HOME, ".vue3-temp-skill.json");
|
|
|
35
35
|
// 累积性(ledger 台账)/ 终态机读凭证(四闸串联,FINAL-GATE: PASS)是落地会话的运行时闸——
|
|
36
36
|
// 缺了它们「只挑主题令牌落地 + 覆盖历史成果 + 以自述结束会话」无法被静态拦截
|
|
37
37
|
// (失效模式:运行时副本缺审计闸时,残缺口变更集会一路绿灯)。
|
|
38
|
-
const RUNTIME_SCRIPT_FILES = ["match.py", "modules.json"];
|
|
38
|
+
const RUNTIME_SCRIPT_FILES = ["match.py", "modules.json", "check-landing.mjs"];
|
|
39
39
|
const PKG_VERSION = JSON.parse(fs.readFileSync(path.join(SKILL_DIR, "package.json"), "utf-8")).version;
|
|
40
40
|
|
|
41
41
|
// ============================================================
|
package/mapping/admin.json
CHANGED
|
@@ -7,30 +7,30 @@
|
|
|
7
7
|
"adapter": "vue-element-plus",
|
|
8
8
|
|
|
9
9
|
"layoutHost": {
|
|
10
|
-
"path": "src/layout/
|
|
11
|
-
"note": "
|
|
10
|
+
"path": "src/layout/index.vue",
|
|
11
|
+
"note": "真实布局宿主:根节点挂 .ui-page 与角色类,四区域组件各自挂 .ui-region-* 与角色类。页面骨架由 ui-layout.css 的 .ui-region-* 决定,布局组件模板不改结构。"
|
|
12
12
|
},
|
|
13
13
|
|
|
14
14
|
"regions": {
|
|
15
15
|
"sidebar": {
|
|
16
|
-
"scope": ".
|
|
17
|
-
"var": "--
|
|
18
|
-
"collapsedVar": "--
|
|
16
|
+
"scope": ".sidebar-container",
|
|
17
|
+
"var": "--sidebar-width",
|
|
18
|
+
"collapsedVar": "--sidebar-width-collapsed",
|
|
19
19
|
"fallback": "208px"
|
|
20
20
|
},
|
|
21
21
|
"header": {
|
|
22
|
-
"scope": ".
|
|
23
|
-
"var": "--
|
|
22
|
+
"scope": ".navbar",
|
|
23
|
+
"var": "--header-height",
|
|
24
24
|
"fallback": "54px"
|
|
25
25
|
},
|
|
26
26
|
"tabs": {
|
|
27
|
-
"scope": ".
|
|
28
|
-
"var": "--
|
|
27
|
+
"scope": ".tags-view",
|
|
28
|
+
"var": "--tabs-height",
|
|
29
29
|
"fallback": "40px"
|
|
30
30
|
},
|
|
31
31
|
"content": {
|
|
32
|
-
"scope": ".
|
|
33
|
-
"var": "--
|
|
32
|
+
"scope": ".app-main",
|
|
33
|
+
"var": "--layout-content-padding",
|
|
34
34
|
"fallback": "24px"
|
|
35
35
|
}
|
|
36
36
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qilitt-mickey/vue3-temp-skill",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Vue 3 企业级中后台项目开发规范技能包 — core-kernel 架构、按需功能模块、视觉契约落地",
|
|
6
6
|
"bin": {
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"release": "node release.js",
|
|
26
26
|
"release:minor": "node release.js minor",
|
|
27
27
|
"release:major": "node release.js major",
|
|
28
|
+
"check:landing": "node scripts/check-landing.mjs --project .",
|
|
28
29
|
"match": "python scripts/match.py",
|
|
29
30
|
"match:batch": "python scripts/match.py --batch",
|
|
30
31
|
"postpublish": "echo 'Published @qilitt-mickey/vue3-temp-skill to npmjs.org'"
|
|
@@ -44,6 +44,46 @@ description: Vue 3 + Ant Design Vue 适配。提供深度选择器语法、组
|
|
|
44
44
|
| DatePicker | `.ant-picker` | `.ant-picker-input input` |
|
|
45
45
|
| Form | `.ant-form` | `.ant-form-item` |
|
|
46
46
|
|
|
47
|
+
## 角色类挂载方式(视觉不生效的头号原因)
|
|
48
|
+
|
|
49
|
+
**CSS 生成了不等于视觉会变**——必须把角色类挂到组件上。
|
|
50
|
+
|
|
51
|
+
| 契约角色 | 挂载写法 |
|
|
52
|
+
|---|---|
|
|
53
|
+
| `button.*` | `<a-button :class="[...]">` |
|
|
54
|
+
| `shell.sidebar.item` | `<a-menu-item :class="[...]">` |
|
|
55
|
+
| `shell.tabs.item` | `<a-tab-pane :class="[...]">` |
|
|
56
|
+
| `input.base` / `filter.input` | `<a-input :class="[...]">`,`inner` 用 `:deep(.ant-input)` |
|
|
57
|
+
| `select.base` | `<a-select :class="[...]">`,`inner` 用 `:deep(.ant-select-selector)` |
|
|
58
|
+
| `filter.dateRange` | `<a-range-picker :class="[...]">` |
|
|
59
|
+
| `tag.status` | `<a-tag :class="[...]">` |
|
|
60
|
+
| `card.base` | `<a-card :class="[...]">`,`inner` 用 `:deep(.ant-card-body)` |
|
|
61
|
+
| `table.wrapper` | `<a-table :class="[...]">` |
|
|
62
|
+
| `table.headerCell` / `table.cell` | **用 `#headerCell` / `#bodyCell` 插槽**:`#bodyCell="{ column, record }"` 内给单元格外层元素加类 |
|
|
63
|
+
| `table.cell.amount` | 同上 + `align: 'right'` |
|
|
64
|
+
| `pagination.bar` | `<a-pagination :class="[...]">` |
|
|
65
|
+
|
|
66
|
+
**Table 的特殊性**:`a-table` 的单元格同样由组件内部生成。推荐用 `#bodyCell` 插槽批量挂载:
|
|
67
|
+
|
|
68
|
+
```vue
|
|
69
|
+
<a-table :data-source="rows" :columns="columns" class="role-table-wrapper">
|
|
70
|
+
<template #headerCell="{ column }">
|
|
71
|
+
<div :class="column.dataIndex === 'name' ? 'role-table-header-cell' : 'role-table-header-cell'">
|
|
72
|
+
{{ column.title }}
|
|
73
|
+
</div>
|
|
74
|
+
</template>
|
|
75
|
+
<template #bodyCell="{ column, record }">
|
|
76
|
+
<div :class="cellRole(column, record)">{{ record[column.dataIndex] }}</div>
|
|
77
|
+
</template>
|
|
78
|
+
</a-table>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### 批量挂载的做法
|
|
82
|
+
|
|
83
|
+
一个页面有 20+ 角色时**逐个手写 class 会漏**。做法:把契约角色名转成类名清单,交给页面统一挂载,并在第 0 步自检里确认覆盖率 100%。
|
|
84
|
+
|
|
85
|
+
**类名转换规则**:`table.headerCell` → `.role-table-header-cell`(点号与驼峰都转小写连字符)。
|
|
86
|
+
|
|
47
87
|
## 提权通道(唯一允许处)
|
|
48
88
|
|
|
49
89
|
| 情况 | 处置 |
|
|
@@ -15,19 +15,26 @@ description: Vue 3 + Element Plus 适配。提供深度选择器语法、组件
|
|
|
15
15
|
|
|
16
16
|
**本项目建议**:`ui-roles.css` 写成**全局样式文件**(不 scoped),避免每个页面重复引入。
|
|
17
17
|
|
|
18
|
-
##
|
|
18
|
+
## 内层穿透语法(双通道,选错 = 规则整条静默失效)
|
|
19
19
|
|
|
20
|
-
契约的 `inner[]`
|
|
20
|
+
契约的 `inner[]` 落到组件内部文本盒时,需要穿透到组件库内部元素。**先看样式落点文件的类型,再选语法通道**:
|
|
21
21
|
|
|
22
|
-
|
|
|
23
|
-
|
|
24
|
-
|
|
|
25
|
-
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
| 落点文件 | 语法通道 | 示例 |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| 全局 CSS(`ui-roles.css`,本项目默认) | **普通后代/子代选择器**——禁止 `:deep()` | `.role-table-header-cell .cell { … }` |
|
|
25
|
+
| SFC `<style scoped>` | `:deep()` | `.role-table-header-cell :deep(.cell) { … }` |
|
|
26
|
+
|
|
27
|
+
**角色 → 内层目标对照**(目标由组件库 DOM 结构决定,不由契约决定):
|
|
28
|
+
|
|
29
|
+
| 角色 | 全局文件写法 | scoped 写法 |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| `table.headerCell` / `table.cell` | `.role-… .cell`(EP 单元格内层文本盒) | `:deep(.cell)` |
|
|
32
|
+
| `shell.sidebar.item` | `.role-… span` | `:deep(span)` |
|
|
33
|
+
| `filter.input` / `input.base` | `.role-… .el-input__wrapper`(EP 视觉盒,边框在 box-shadow 上) | `:deep(.el-input__wrapper)` |
|
|
34
|
+
| `select.base` | `.role-… .el-select__wrapper` | `:deep(.el-select__wrapper)` |
|
|
35
|
+
| `shell.tabs.item` | 直接作用在文本节点(无需穿透) | 同左 |
|
|
29
36
|
|
|
30
|
-
**规则**:`host[]` 作用在组件根元素(加角色类即可);`inner[]`
|
|
37
|
+
**规则**:`host[]` 作用在组件根元素(加角色类即可);`inner[]` 作用在根元素内部——按上表目标写选择器。**全局文件里出现 `:deep()`,浏览器静默丢弃整条规则且无任何报错**——这是「只有主题色对、其余全不对」事故的根源(见实证坑 1)。
|
|
31
38
|
|
|
32
39
|
## 组件库前缀速查
|
|
33
40
|
|
|
@@ -47,6 +54,92 @@ description: Vue 3 + Element Plus 适配。提供深度选择器语法、组件
|
|
|
47
54
|
| Tabs | `.el-tabs` | `.el-tabs__item` |
|
|
48
55
|
| DatePicker | `.el-date-editor` | `.el-range-input` |
|
|
49
56
|
|
|
57
|
+
## 角色类挂载方式(视觉不生效的头号原因)
|
|
58
|
+
|
|
59
|
+
**CSS 生成了不等于视觉会变**——必须把角色类挂到组件上。Element Plus 各组件的挂法不同:
|
|
60
|
+
|
|
61
|
+
| 契约角色 | 挂载写法 | 说明 |
|
|
62
|
+
|---|---|---|
|
|
63
|
+
| `shell.sidebar.item` / `.active` | `<el-menu-item :class="[...]">` | 直接作用在组件根元素 |
|
|
64
|
+
| `shell.header.icon` | `<el-button :class="[...]">` | 同上 |
|
|
65
|
+
| `button.primary` / `.secondary` / `.danger` | `<el-button :class="[...]">` | 同上 |
|
|
66
|
+
| `card.base` | `<el-card :class="[...]">` | 内边距在 `.el-card__body`:全局文件写后代规则 `.role-page-card > .el-card__body`;项目样式层对其 padding 带 `!important` 时同属性提权(实证坑 3) |
|
|
67
|
+
| `table.wrapper` | `<el-table :class="[...]">` | 表格**根元素**挂 wrapper |
|
|
68
|
+
| `table.headerCell` | `:header-cell-class-name="() => 'role-table-header-cell'"` | EP 的 `th` 不能直接加类;`<template #header><th class>` **无效**(加不进 EP 生成的 `th`) |
|
|
69
|
+
| `table.cell` | 列级 `class-name="role-table-cell"` 或 `:cell-class-name` 回调 | `<el-table-column :class>` **无效**(类落在列组件上,不落到 td) |
|
|
70
|
+
| `table.cell.amount` | `:cell-class-name` 回调按列派发 `.role-table-cell-amount` | 右对齐写在该角色类的 `.cell` 层(`text-align: right`) |
|
|
71
|
+
| `tag.status` | `<el-tag :class="[...]">` | 直接作用 |
|
|
72
|
+
| `input.base` / `filter.input` | `<el-input :class="[...]">` | `inner` 用后代规则 `.role-… .el-input__wrapper`(scoped 内才用 `:deep()`) |
|
|
73
|
+
| `select.base` | `<el-select :class="[...]">` | `inner` 用后代规则 `.role-… .el-select__wrapper` |
|
|
74
|
+
| `filter.dateRange` | `<el-date-picker :class="[...]">` | — |
|
|
75
|
+
| `pagination.bar` | `<el-pagination :class="[...]">` | 直接作用 |
|
|
76
|
+
| `shell.tabs.item` | 自绘或 `<el-tab-pane>` | 激活态另由 `item.active` 角色承载 |
|
|
77
|
+
|
|
78
|
+
**Table 的特殊性**:`el-table` 的 `th` / `td` 由组件内部生成,**不能在 `<el-table>` 或 `<el-table-column>` 上直接加类来影响单元格**。正确通道是 EP 提供的三个回调/属性:
|
|
79
|
+
|
|
80
|
+
```vue
|
|
81
|
+
<el-table
|
|
82
|
+
:data="rows"
|
|
83
|
+
class="role-table-wrapper"
|
|
84
|
+
:header-cell-class-name="() => 'role-table-header-cell'"
|
|
85
|
+
:cell-class-name="cellClassName"
|
|
86
|
+
>
|
|
87
|
+
<!-- 列级:能按列名静态指定的,直接用 class-name -->
|
|
88
|
+
<el-table-column type="selection" class-name="role-table-cell-selection" />
|
|
89
|
+
<el-table-column type="index" class-name="role-table-cell-index" />
|
|
90
|
+
<el-table-column prop="no" label="运单号" class-name="role-table-cell" />
|
|
91
|
+
<el-table-column prop="fee" label="运费" class-name="role-table-cell-amount" />
|
|
92
|
+
<el-table-column prop="ops" label="操作" class-name="role-table-cell-action" />
|
|
93
|
+
|
|
94
|
+
<!-- 单元格内部内容(状态标签)走 default 插槽 -->
|
|
95
|
+
<el-table-column prop="status" label="运单状态" class-name="role-table-cell-status">
|
|
96
|
+
<template #default="{ row }">
|
|
97
|
+
<el-tag class="role-tag-status">{{ row.status }}</el-tag>
|
|
98
|
+
</template>
|
|
99
|
+
</el-table-column>
|
|
100
|
+
</el-table>
|
|
101
|
+
|
|
102
|
+
<script setup lang="ts">
|
|
103
|
+
// 需要按行/列动态判断的,用回调按列派发
|
|
104
|
+
const cellClassName = ({ row, column, columnIndex }: any) => {
|
|
105
|
+
if (column.property === 'fee') return 'role-table-cell-amount';
|
|
106
|
+
if (column.property === 'status') return 'role-table-cell-status';
|
|
107
|
+
return 'role-table-cell';
|
|
108
|
+
};
|
|
109
|
+
</script>
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**三个无效写法(实测事故)**:`<template #header><th class="role-…">`(加不进 EP 生成的 th)、`<el-table-column :class>`(类不落到 td)、在 `<el-table>` 上加单元格类(只影响根元素)。
|
|
113
|
+
|
|
114
|
+
### 批量挂载的做法
|
|
115
|
+
|
|
116
|
+
一个页面有 20+ 角色时**逐个手写 class 会漏**。做法:把契约的角色名转成类名清单,交给页面统一挂载,并在第 0 步自检里确认覆盖率 100%。
|
|
117
|
+
|
|
118
|
+
**类名转换规则**:`table.headerCell` → `.role-table-header-cell`(点号与驼峰都转小写连字符)。
|
|
119
|
+
|
|
120
|
+
## 结构等价转换(契约值 → EP DOM 结构)
|
|
121
|
+
|
|
122
|
+
契约声明的是**设计意图值**;EP 的 DOM 结构有时要求把值**换一种 CSS 机制落到对应元素**——这是翻译的一部分,不是改契约。生成前逐条对照本表:
|
|
123
|
+
|
|
124
|
+
| 契约声明 | EP DOM 事实 | 生成规则 |
|
|
125
|
+
|---|---|---|
|
|
126
|
+
| `border: 1px solid X`(输入类控件) | EP 输入框边框是 `.el-input__wrapper` 的 `box-shadow: 0 0 0 1px #DCDFE6 inset` | 生成 `box-shadow: 0 0 0 1px X inset`(整条覆盖 EP 默认 box-shadow,勿只写 border-color) |
|
|
127
|
+
| 表头 `height: 48px` + `padding`(左右间距) | 表头双层:`th.el-table__cell > .cell` | **拆解**:th 的上下 padding 归零(高度才精确等于 48px),左右间距放内层 `.cell`(`padding: 0 16px`) |
|
|
128
|
+
| 单元格 `height: 55px` + `padding` | td 同为双层 | 同上拆解;density 变体只改 height 与竖向 padding |
|
|
129
|
+
| 分页激活页 `background` | EP 激活页 `.el-pager li.is-active` 默认只有字色 | 背景色落到 `.role-pagination-bar .el-pager li.is-active`(后代规则) |
|
|
130
|
+
| 侧栏激活项「左侧竖条」变体 | `.el-menu-item.is-active` 自带底色 | 竖条用 `::before` 伪元素(`position:absolute; left:-8px; width:3px`),菜单项保持 `position: relative` |
|
|
131
|
+
| 卡片 `padding: 0` | 内边距在 `.el-card__body`,项目样式层对其带 `!important` | 写 `.role-page-card > .el-card__body { padding: 0 !important; }`(实证坑 3 的唯一提权例) |
|
|
132
|
+
| 表格行 hover / 选中底色 | 行状态类由 EP 挂在 `tr` 上(`el-table__row-hover` 等) | 用 `.el-table` 状态类 + 角色类的后代规则实现(`when` 联动),不手写 JS 监听 |
|
|
133
|
+
|
|
134
|
+
**判断顺序**:先查本表 → 表里没有的结构差异,查「角色类挂对元素没」(上一节挂载表)→ 都不是,才回查契约值本身。
|
|
135
|
+
|
|
136
|
+
## 实证坑(踩过的事故,生成时必须规避)
|
|
137
|
+
|
|
138
|
+
1. **`:deep()` 在全局 CSS 文件中是非法语法**:浏览器会**静默丢弃整条规则**(不报错),导致 inner 层样式全部失效、只剩主题色生效。全局文件穿透一律用普通后代选择器(`.role-x .el-y__wrapper`)。
|
|
139
|
+
2. **角色类不得覆盖宿主容器的布局模式**:若角色类挂在项目既有 flex/grid 容器上(如 `.vts-table-area` 是 flex column),CSS 里只写视觉属性(padding/border/颜色),**禁止写 `display`/`flex-direction`/`justify-content`**,否则内部子元素被横向挤压裁切。
|
|
140
|
+
3. **Element Plus 对 `.el-card__body` 的 padding 带 `!important`**(项目 element-plus.scss):角色类要归零卡片内边距,必须同属性加 `!important` 提权,否则不生效。
|
|
141
|
+
4. **表格行高精确控制**:`th/td` 的上下 padding 必须归零、左右间距放到内部 `.cell` 上(`padding: 0 16px`),否则 height 会被 padding 撑大(48px 变 57px 事故)。
|
|
142
|
+
|
|
50
143
|
## 提权通道(唯一允许处)
|
|
51
144
|
|
|
52
145
|
角色类是独立命名空间(`role-` 前缀)+ 独立文件,**通常不需要提权**。仅两种情况需要:
|
|
@@ -36,7 +36,30 @@ src/styles/ui-layout.css 区域网格
|
|
|
36
36
|
|
|
37
37
|
---
|
|
38
38
|
|
|
39
|
-
##
|
|
39
|
+
## 执行流程(七步)
|
|
40
|
+
|
|
41
|
+
### 第 0 步 · 落地自检(先跑,断在哪一目了然)
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
node <技能>/scripts/check-landing.mjs --project <项目根>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**八项检查,逐项给修复动作,exit 1 表示链路有断点:**
|
|
48
|
+
|
|
49
|
+
| # | 检查项 | 断点含义 |
|
|
50
|
+
|---|---|---|
|
|
51
|
+
| 1 | `contract-found` | `.design/visual-contract-*.json` 不存在 → 设计侧没落盘 |
|
|
52
|
+
| 2 | `contract-valid` | 契约字段缺失(`meta.scene` / `tokens.color` / `roles` / `layout.areas`) |
|
|
53
|
+
| 3 | `contract-structure` | 角色名不在角色库 / `regions` 与 `areas` 不一致 / 绑定的区域名不存在 → 设计侧结构违规,**回设计侧修** |
|
|
54
|
+
| 4 | `css-generated` | 三份 CSS 未生成 → 第 3/4/5 步没执行 |
|
|
55
|
+
| 5 | `css-imported` | **生成了但没被 import** → 浏览器根本不加载 |
|
|
56
|
+
| 6 | `roles-mounted` | **CSS 有规则但模板没挂类** → 死样式,视觉不变 |
|
|
57
|
+
| 7 | `layout-mounted` | 模板没出现 `.ui-page` → 骨架仍是项目原样 |
|
|
58
|
+
| 8 | `css-rules` | 模板挂了类但 CSS 里没有对应规则 → 生成不完整或被手改 |
|
|
59
|
+
|
|
60
|
+
**八项全过是交付前置条件。** 不过就修断点,**不要直接目视猜问题在哪**。
|
|
61
|
+
|
|
62
|
+
> **⚠️ 导入顺序硬坑(实证)**:三份 CSS 必须作为 **TS import 在组件库全量 CSS 之后**引入(如 `main.ts` 里 `import 'element-plus/dist/index.css'` 之后)。若 `@import` 进项目 `index.scss`,而组件库 CSS 在 `main.ts` 中晚于 `index.scss` 加载(独立 `<style>` 标签),则契约层会被组件库同特异性规则**整层盖住**——表现为「只有主题色对、其余全不对」。`adapter.output.entryImport` 指定正确入口。
|
|
40
63
|
|
|
41
64
|
### 第 1 步 · 读契约(只读一次)
|
|
42
65
|
|
|
@@ -62,6 +85,16 @@ src/styles/ui-layout.css 区域网格
|
|
|
62
85
|
|
|
63
86
|
**adapter 不定义设计意图、不定义风格、不改写契约值。**
|
|
64
87
|
|
|
88
|
+
### 第 3/4/5 步 · 生成三份 CSS(机器执行,禁止手抄)
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
node <技能>/scripts/generate-css.mjs --project <项目根>
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**`generate-css.mjs` 是第 3/4/5 步的可执行实现**:读契约 `tokens/roles/layout` + `adapters/<adapter>.json`(机器可读映射:`innerTargets`/`stateChannels`/`extraRules`/`variableBridges`/`layoutBridge`),确定性生成三份 CSS。**同一输入必得同一输出**——手抄会漏值、引错变量名、状态写不全(这正是历次「只有主题色对」事故的根源)。
|
|
95
|
+
|
|
96
|
+
下面第 3/4/5 步的「翻译规则表」是生成器的**行为规格**与人工核对依据,不再让 AI 逐条手写:
|
|
97
|
+
|
|
65
98
|
### 第 3 步 · 写令牌 → `src/styles/ui-tokens.css`
|
|
66
99
|
|
|
67
100
|
契约 `tokens` 逐条转 CSS 变量:
|
|
@@ -86,9 +119,9 @@ src/styles/ui-layout.css 区域网格
|
|
|
86
119
|
契约 `roles` 逐条转类规则。**这是纯粹的翻译,每个角色一段:**
|
|
87
120
|
|
|
88
121
|
```css
|
|
89
|
-
/* 角色:table.headerCell */
|
|
122
|
+
/* 角色:table.headerCell —— ui-roles.css 是全局文件:内层穿透用普通后代选择器,禁 :deep() */
|
|
90
123
|
.role-table-header-cell { background-color: #FAFAFA; height: 48px; padding: 16px; border-bottom: 1px solid var(--color-border-base); }
|
|
91
|
-
.role-table-header-cell
|
|
124
|
+
.role-table-header-cell .cell { font-size: var(--font-size-md); font-weight: 500; color: var(--color-text-primary); text-align: left; white-space: nowrap; }
|
|
92
125
|
```
|
|
93
126
|
|
|
94
127
|
**翻译规则表**:
|
|
@@ -96,13 +129,15 @@ src/styles/ui-layout.css 区域网格
|
|
|
96
129
|
| 契约结构 | 生成 |
|
|
97
130
|
|---|---|
|
|
98
131
|
| `roles.<名>.host[]` | `.role-<名转连字符> { … }` |
|
|
99
|
-
| `roles.<名>.inner[]` | 同类名 + adapter
|
|
132
|
+
| `roles.<名>.inner[]` | 同类名 + adapter 给出的内层目标选择器(**全局文件用普通后代选择器**;`:deep()` 只允许出现在 SFC `<style scoped>`——通道写错整条规则被浏览器**静默丢弃、无任何报错**) |
|
|
100
133
|
| `roles.<名>.states.hover.host[]` | `.role-<名>:hover { … }` |
|
|
101
134
|
| `roles.<名>.states.disabled.inner[]` | `.role-<名>:disabled { … }`(或 adapter 的 disabled 通道) |
|
|
102
135
|
| 声明值 `{令牌}` | 替换为 `var(--令牌)` |
|
|
103
136
|
|
|
104
137
|
**角色名转换**:`table.headerCell` → 类名 `.role-table-header-cell`(点号转连字符 + `role-` 前缀,避免与组件库类名冲突)。
|
|
105
138
|
|
|
139
|
+
**契约值 → 组件库 DOM 的结构等价转换**:组件库真实 DOM 与契约属性并非一一对应(如 Element Plus 输入框边框实际是 `box-shadow: … inset`、表头是 `th > .cell` 双层结构、卡片内边距由 `.el-card__body` 承载并带 `!important`)。这类转换**以 adapter 的「结构等价转换」表为准,禁止现场临时发明**;转换只改「属性落在哪个 DOM 节点、用什么属性表达」,**不改契约值本身**——值仍然来自契约,只是表达载体等价替换。
|
|
140
|
+
|
|
106
141
|
**AI 在这一步不做的事**:
|
|
107
142
|
|
|
108
143
|
| 不做 | 为什么 |
|
|
@@ -190,17 +225,21 @@ src/styles/ui-layout.css 区域网格
|
|
|
190
225
|
|
|
191
226
|
**实现方式二选一**:能挂 class 就挂 class(简单可靠);挂不上就用组件的 `row-class-name` 回调。**由 adapter 决定,不由落地端临时发明。**
|
|
192
227
|
|
|
193
|
-
### 第 6 步 · 挂载 +
|
|
228
|
+
### 第 6 步 · 挂载 + 覆盖验证
|
|
194
229
|
|
|
195
|
-
1.
|
|
230
|
+
1. 在样式入口引入三份文件(**入口位置按 `adapter.output.entryImport`——通常是 main.ts 在组件库全量 CSS 之后**)
|
|
196
231
|
2. 页面根节点挂 `.ui-page`,各区域挂 `.ui-region-<名>`
|
|
197
|
-
3.
|
|
198
|
-
4.
|
|
199
|
-
|
|
200
|
-
|
|
232
|
+
3. 按 adapter 的「角色类挂载方式」给组件挂 `.role-*`(**表格走插槽,不是在 `<el-table>` 上加类**)
|
|
233
|
+
4. **按契约 `structure` 重建页面骨架**——区块数量/顺序、列数与列宽档位、控件类型、图标 path、折叠/吸附结构,**照骨架对齐**。
|
|
234
|
+
**⚠️ 形态/内容分离(红线)**:`structure` 里标了 `sampleOnly` 的业务内容(列名文字、筛选项 label、数据行、菜单文字、状态文案)**一律不烧进项目**——项目侧保持自身业务字段(列名/prop/API/mock/权限/字典),只把「形态」对齐骨架。设计稿的演示占位不是目标项目的业务事实;覆盖业务逻辑 = 交付失败。
|
|
235
|
+
5. **重跑第 0 步的落地自检,八项全过才算挂载完成**
|
|
236
|
+
6. 进入第 7 步视觉验收(computed style 逐点比对契约 + visualSanity 通用断言,目视截图只作最后留档)
|
|
201
237
|
|
|
202
238
|
| 现象 | 根因 | 修正动作 |
|
|
203
239
|
|---|---|---|
|
|
240
|
+
| 页面完全没变化 | 三份 CSS 没被 import | 在样式入口 import 一次 |
|
|
241
|
+
| 只有个别地方变了 | 角色类挂载覆盖率低 | 按 adapter 挂法补齐,重跑自检 |
|
|
242
|
+
| 布局还是项目原样 | 没挂 `.ui-page` / `.ui-region-*` | 布局组件加类 |
|
|
204
243
|
| 有差异 | 契约值或落点层不对 | **改契约 → 重新生成 CSS** |
|
|
205
244
|
| 颜色不对 | 令牌值抄错 | 改 `tokens` → 重新生成 |
|
|
206
245
|
| 字号 / 字重不对 | 落错层(该进 `inner[]` 进了 `host[]`) | 改契约的数组归属 → 重新生成 |
|
|
@@ -209,13 +248,43 @@ src/styles/ui-layout.css 区域网格
|
|
|
209
248
|
| 布局位置不对 | `areas` 排布或 `regions` 取值不对 | 改 `layout` → 重新生成 |
|
|
210
249
|
| 某角色完全没生效 | 该角色没写进契约 | **回设计侧补抽**,不是项目侧猜 |
|
|
211
250
|
|
|
251
|
+
### 第 7 步 · 视觉验收(computed style 逐点比对契约)
|
|
252
|
+
|
|
253
|
+
目视对照只能发现「明显不像」,发现不了「差 2px、差一档灰」。**验收的期望源就是契约 JSON 本身**——不另建期望值文件、不另建长期采集脚本。
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
node <技能>/scripts/verify-render.mjs --project <项目根> --url http://localhost:<dev端口>
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
**`verify-render.mjs` 是本步的可执行实现**:Playwright 登录 → 导航目标页 → 对契约每条声明读 `getComputedStyle` 逐点比对(颜色归一、几何容差 ≤2px、`auto`/`unitless line-height`/`repeat()` 等计算值假差异自动放行)→ 输出 `[FAIL] 角色·选择器·属性 期望/实际/命中元素` 差异报告 + 全页截图。差异清零即视觉验收通过。
|
|
260
|
+
|
|
261
|
+
**第二遍:visualSanity 通用断言(契约值之外的 UX 合理性)**
|
|
262
|
+
|
|
263
|
+
契约比对只保证「值抄对了」,不保证「值组合起来合理」——表头换行、文字省略号、操作列截断、元素被隐藏都是**组合性缺陷**,逐点比对全绿也可能发生。断言规则沉淀在 `adapter.visualSanity`(通用编排,换场景/页型自动生效),至少覆盖:
|
|
264
|
+
|
|
265
|
+
| 断言 | 判据 | 典型根因 |
|
|
266
|
+
|---|---|---|
|
|
267
|
+
| no-wrap | 表头/标签/按钮 `.cell` 的 `scrollWidth ≤ clientWidth+1` 且 `white-space:nowrap` | 宽度档位不够 / padding 吃掉空间 |
|
|
268
|
+
| no-ellipsis | 可见文本元素计算值 `text-overflow` 未实际截断(scrollWidth 不超) | 列宽写死 / .cell overflow:hidden |
|
|
269
|
+
| full-visibility | 契约 structure 声明的每个元素 `display≠none && rect.width>0`(折叠钮、分隔线、操作列按钮组) | 被组件库/UnoCSS 隐藏、图标未注册 |
|
|
270
|
+
| padding-sanity | 内容区左右 padding ≥ 契约 region 值(操作列留边) | padding 被归零过头 |
|
|
271
|
+
| row-height-sanity | 数据行实际高度 = 密度档值(±2px) | padding/line-height 撑高 |
|
|
272
|
+
|
|
273
|
+
**修复定向不变**:visualSanity 失败 → 修 adapter `extraRules`(提权/结构等价转换)或契约值,不手改生成物。
|
|
274
|
+
|
|
275
|
+
1. 启动项目 dev server,跑 `verify-render.mjs`(脚本内置真实账号登录链路,不留特殊开关)
|
|
276
|
+
2. 逐条消化差异,定位顺序(由外向内):**挂载遗漏 → 穿透/选择器通道错 → 提权被组件库盖住(`!important` 对决比特异性)→ 契约值本身错**——前三者修落地侧(第 4/5/6 步或 adapter 的 `extraRules`),最后一种回设计侧改契约后重新生成
|
|
277
|
+
3. 全部命中(契约差异 0 + visualSanity 全过)后截图,与 `meta.source.mockup` 指向的设计稿并排留档
|
|
278
|
+
|
|
279
|
+
**永不直接改生成出来的 CSS 来「凑」比对结果**——比对失败的修复动作永远落回第 4/5/6 步或契约。
|
|
280
|
+
|
|
212
281
|
### 修正的唯一定向(不可破)
|
|
213
282
|
|
|
214
283
|
**只改契约或改 adapter,永远不直接改生成出来的 CSS。**
|
|
215
284
|
|
|
216
285
|
直接改 CSS 会在下次重新生成时被覆盖,且这份覆盖不会留下痕迹——同类问题会在下一轮以另一种形态复发。改契约的成本远低于改代码,因为契约是结构化的、可定位的、可重放的。
|
|
217
286
|
|
|
218
|
-
|
|
287
|
+
**不做**:不建独立的期望值文件(期望源就是契约 JSON)、不把验收脚本沉淀进项目长期维护、不跑业务回归、不写长期维护的逐点 diff 报告(第 7 步的一次性比对除外)。
|
|
219
288
|
|
|
220
289
|
---
|
|
221
290
|
|