@qilitt-mickey/vue3-temp-skill 1.1.66 → 1.1.67
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/package.json +1 -1
- package/parser/fixtures/changeset-fullpage-restore.json +1 -1
- package/parser/fixtures/changeset-pos-theme-neutral.json +264 -0
- package/parser/fixtures/changeset-pos-theme-nometa.json +260 -0
- package/parser/fixtures/verify/mockup-neutral.html +26 -0
- package/parser/fixtures/verify/mockup-nometa.html +26 -0
- package/parser/fixtures/verify/snapshot-latest.json +1 -1
- package/parser/index.js +60 -15
- package/parser/regression.js +16 -2
- package/references/design-apply.md +1 -1
- package/references/steps/maintenance.md +15 -0
- package/references/steps/regression.md +4 -2
- package/references/steps/step1-match.md +1 -1
- package/references/steps/step3-style.md +6 -0
- package/references/steps/step5-gates.md +6 -2
- package/references/steps/step6-verify.md +2 -2
- package/scripts/design-audit.mjs +256 -1
package/package.json
CHANGED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$comment": "同构异色归一正例(2026-09-23 解耦回归):基线图用【中性文件名】+ 图内 data-mockup-theme=钴蓝 声明身份,本次主题=橘橙。预期:① 不再因「文件名不含主题词」判无基线;② 主题与基线图色板不同已声明 oracle.theme_mapping → 走色值归一而非主题错配;③ exit 0。",
|
|
3
|
+
"schema": "design.changeset/1.0",
|
|
4
|
+
"changeset_version": "1.0.0",
|
|
5
|
+
"mapping_version": "1.0.0",
|
|
6
|
+
"scene": "admin",
|
|
7
|
+
"scheme": "default",
|
|
8
|
+
"mode": "restore",
|
|
9
|
+
"layout_type": "vertical",
|
|
10
|
+
"global": {
|
|
11
|
+
"brand_primary": "橘橙-5",
|
|
12
|
+
"sidebar_bg": "橘橙-10"
|
|
13
|
+
},
|
|
14
|
+
"changes": [
|
|
15
|
+
{
|
|
16
|
+
"id": "admin.layout.sidebar",
|
|
17
|
+
"style": {
|
|
18
|
+
"bg": "钴蓝-10"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"id": "admin.layout.sidebar.logo_area",
|
|
23
|
+
"style": {
|
|
24
|
+
"bg": "钴蓝-10",
|
|
25
|
+
"title_color": "#FFFFFF"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"id": "admin.layout.sidebar.menu",
|
|
30
|
+
"style": {
|
|
31
|
+
"menu_bg": "钴蓝-10",
|
|
32
|
+
"menu_text": "钴蓝-2",
|
|
33
|
+
"menu_hover_bg": "钴蓝-8",
|
|
34
|
+
"menu_title_hover": "#FFFFFF",
|
|
35
|
+
"sub_menu_bg": "钴蓝-9",
|
|
36
|
+
"sub_menu_active_text": "#FFFFFF",
|
|
37
|
+
"menu_active_bg": "钴蓝-5",
|
|
38
|
+
"menu_active_indicator": "钴蓝-5"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"id": "admin.layout.header",
|
|
43
|
+
"style": {
|
|
44
|
+
"bg": "#FFFFFF"
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"id": "admin.layout.header.lang_switch",
|
|
49
|
+
"form": "inline_switch",
|
|
50
|
+
"suppress": [
|
|
51
|
+
"icon_only",
|
|
52
|
+
"dropdown_popper"
|
|
53
|
+
],
|
|
54
|
+
"style": {
|
|
55
|
+
"icon_size": "16px",
|
|
56
|
+
"gap": "8px",
|
|
57
|
+
"text_font_size": "14px"
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"id": "admin.layout.header.user_profile",
|
|
62
|
+
"style": {
|
|
63
|
+
"avatar_width": "28px",
|
|
64
|
+
"avatar_height": "28px",
|
|
65
|
+
"avatar_radius": "全圆角",
|
|
66
|
+
"name_font_size": "14px",
|
|
67
|
+
"name_font_weight": "中等"
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"id": "admin.layout.tabs",
|
|
72
|
+
"form": "chrome",
|
|
73
|
+
"style": {
|
|
74
|
+
"bg": "#FFFFFF"
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"id": "admin.content.table.header_cell",
|
|
79
|
+
"style": {
|
|
80
|
+
"height": "48px",
|
|
81
|
+
"padding": "16px",
|
|
82
|
+
"bg": "#FAFAFA",
|
|
83
|
+
"font_weight": "中等",
|
|
84
|
+
"text_align": "左对齐",
|
|
85
|
+
"nowrap": "不换行"
|
|
86
|
+
},
|
|
87
|
+
"layer": {
|
|
88
|
+
"height": "host",
|
|
89
|
+
"padding": "host",
|
|
90
|
+
"bg": "host",
|
|
91
|
+
"font_weight": "host",
|
|
92
|
+
"text_align": "inner",
|
|
93
|
+
"nowrap": "inner"
|
|
94
|
+
},
|
|
95
|
+
"suppress_inner": true
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"id": "admin.content.table.cell",
|
|
99
|
+
"style": {
|
|
100
|
+
"padding": "16px",
|
|
101
|
+
"padding_y": "8px",
|
|
102
|
+
"vertical_align": "middle"
|
|
103
|
+
},
|
|
104
|
+
"layer": {
|
|
105
|
+
"padding": "host",
|
|
106
|
+
"padding_y": "inner",
|
|
107
|
+
"vertical_align": "inner"
|
|
108
|
+
},
|
|
109
|
+
"suppress_inner": true
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"id": "admin.content.table.cell_status",
|
|
113
|
+
"form": "filled",
|
|
114
|
+
"style": {
|
|
115
|
+
"bg": "#E8F3FF",
|
|
116
|
+
"text_color": "钴蓝-5",
|
|
117
|
+
"radius": "小圆角"
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"id": "component.tag.status",
|
|
122
|
+
"form": "filled",
|
|
123
|
+
"style": {
|
|
124
|
+
"height": "24px",
|
|
125
|
+
"radius": "小圆角",
|
|
126
|
+
"bg_color": "#F6FFED"
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
"id": "admin.content.page_header",
|
|
131
|
+
"style": {
|
|
132
|
+
"title_size": "20px",
|
|
133
|
+
"title_weight": "加粗",
|
|
134
|
+
"desc_color": "钴蓝-3",
|
|
135
|
+
"actions_gap": "8px"
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
"id": "admin.content.filter_area",
|
|
140
|
+
"style": {
|
|
141
|
+
"radius": "小圆角",
|
|
142
|
+
"padding_y": "宽松",
|
|
143
|
+
"padding_x": "宽松",
|
|
144
|
+
"bg": "#FFFFFF",
|
|
145
|
+
"border_bottom": "1px solid #F0F0F0"
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
"id": "admin.content.table.operation_bar",
|
|
150
|
+
"style": {
|
|
151
|
+
"gap": "8px",
|
|
152
|
+
"padding_y": "紧凑"
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
"id": "admin.content.table.operation_column",
|
|
157
|
+
"form": "text_link",
|
|
158
|
+
"style": {
|
|
159
|
+
"link_color": "钴蓝-5",
|
|
160
|
+
"danger_color": "错误色",
|
|
161
|
+
"align": "center",
|
|
162
|
+
"divider_color": "#F0F0F0"
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
"id": "admin.content.table.area",
|
|
167
|
+
"style": {
|
|
168
|
+
"header_bg": "#FAFAFA",
|
|
169
|
+
"row_hover_bg": "#FAFAFA",
|
|
170
|
+
"radius": "小圆角"
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
"id": "admin.content.table.cell_amount",
|
|
175
|
+
"style": {
|
|
176
|
+
"align": "right",
|
|
177
|
+
"font_variant_numeric": "等宽数字"
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
"id": "admin.content.table.cell_thumb",
|
|
182
|
+
"style": {
|
|
183
|
+
"thumb_width": "40px",
|
|
184
|
+
"thumb_height": "40px",
|
|
185
|
+
"thumb_radius": "小圆角"
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
"id": "component.button.danger",
|
|
190
|
+
"style": {
|
|
191
|
+
"bg_color": "错误色",
|
|
192
|
+
"border_color": "错误色",
|
|
193
|
+
"text_color": "#FFFFFF"
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
"id": "admin.content.pagination",
|
|
198
|
+
"style": {
|
|
199
|
+
"active_bg": "钴蓝-5",
|
|
200
|
+
"active_color": "#FFFFFF"
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
],
|
|
204
|
+
"oracle": {
|
|
205
|
+
"mockup": "verify/mockup-neutral.html",
|
|
206
|
+
"sids": [
|
|
207
|
+
"admin.layout.sidebar",
|
|
208
|
+
"admin.layout.sidebar.logo_area",
|
|
209
|
+
"admin.layout.sidebar.menu",
|
|
210
|
+
"admin.layout.header",
|
|
211
|
+
"admin.layout.header.lang_switch",
|
|
212
|
+
"admin.layout.header.user_profile",
|
|
213
|
+
"admin.layout.tabs",
|
|
214
|
+
"admin.content.page_header",
|
|
215
|
+
"admin.content.filter_area",
|
|
216
|
+
"admin.content.table.operation_bar",
|
|
217
|
+
"admin.content.table.operation_column",
|
|
218
|
+
"admin.content.table.area",
|
|
219
|
+
"admin.content.table.header_cell",
|
|
220
|
+
"admin.content.table.cell",
|
|
221
|
+
"admin.content.table.cell_status",
|
|
222
|
+
"admin.content.table.cell_amount",
|
|
223
|
+
"admin.content.table.cell_thumb",
|
|
224
|
+
"component.tag.status",
|
|
225
|
+
"component.button.danger",
|
|
226
|
+
"admin.content.pagination"
|
|
227
|
+
],
|
|
228
|
+
"theme_mapping": {
|
|
229
|
+
"from": "钴蓝",
|
|
230
|
+
"to": "橘橙"
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
"coverage": {
|
|
234
|
+
"registry": "parser/fixtures/registry-sample.json",
|
|
235
|
+
"registrable": 6,
|
|
236
|
+
"applicable": 5,
|
|
237
|
+
"covered": 5,
|
|
238
|
+
"gaps": []
|
|
239
|
+
},
|
|
240
|
+
"pending": [
|
|
241
|
+
{
|
|
242
|
+
"proposed_id": "admin.content.page_header.greeting",
|
|
243
|
+
"function": "页面标题下的说明副标题(一句话描述页面用途)",
|
|
244
|
+
"parent": "admin.content.page_header"
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
"proposed_id": "admin.content.table.cell_status_badge",
|
|
248
|
+
"function": "表格状态列的带图标状态胶囊(图标+文字+浅色底)",
|
|
249
|
+
"parent": "admin.content.table.area"
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
"proposed_id": "admin.content.table.cell_route_arrow",
|
|
253
|
+
"function": "表格线路列的起点到终点箭头展示(如 上海→北京)",
|
|
254
|
+
"parent": "admin.content.table.area"
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
"proposed_id": "admin.content.table.cell_link",
|
|
258
|
+
"function": "表格内可点击的链接文字单元(如运单号)",
|
|
259
|
+
"parent": "admin.content.table.area"
|
|
260
|
+
}
|
|
261
|
+
],
|
|
262
|
+
"notes": "整页场景还原样例(对照效果图逐模块提取)。效果图定制冲突项:侧栏底色用钴蓝-10(极深)而非模板默认 #001529,以用户效果图为准;顶栏保持白底。灰阶临时色申报:白 #FFFFFF、浅灰 #FAFAFA、边框灰 #F0F0F0 为中性色(不属任何主题梯度),按临时主题 hex 直写,全主题通用。页面存在业务结构改动(标题区按钮数量与功能、demo 演示内容去留),需项目侧双层交付:页面结构层走业务闭环开发重建,视觉语义层按本变更集落地。问候语(下午好,张伟)文案属业务层,本变更集只声明其载体形态与样式。",
|
|
263
|
+
"theme": "橘橙"
|
|
264
|
+
}
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$comment": "中性命名缺元数据回退正例:基线图文件名与图内均无主题身份,但已声明 changeset.theme。预期:仅打印「基线图未带主题身份元数据」提示,不报错、exit 0 —— 保证「改名但漏搬元数据」不会硬失败。",
|
|
3
|
+
"schema": "design.changeset/1.0",
|
|
4
|
+
"changeset_version": "1.0.0",
|
|
5
|
+
"mapping_version": "1.0.0",
|
|
6
|
+
"scene": "admin",
|
|
7
|
+
"scheme": "default",
|
|
8
|
+
"mode": "restore",
|
|
9
|
+
"layout_type": "vertical",
|
|
10
|
+
"global": {
|
|
11
|
+
"brand_primary": "钴蓝-5",
|
|
12
|
+
"sidebar_bg": "钴蓝-10"
|
|
13
|
+
},
|
|
14
|
+
"changes": [
|
|
15
|
+
{
|
|
16
|
+
"id": "admin.layout.sidebar",
|
|
17
|
+
"style": {
|
|
18
|
+
"bg": "钴蓝-10"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"id": "admin.layout.sidebar.logo_area",
|
|
23
|
+
"style": {
|
|
24
|
+
"bg": "钴蓝-10",
|
|
25
|
+
"title_color": "#FFFFFF"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"id": "admin.layout.sidebar.menu",
|
|
30
|
+
"style": {
|
|
31
|
+
"menu_bg": "钴蓝-10",
|
|
32
|
+
"menu_text": "钴蓝-2",
|
|
33
|
+
"menu_hover_bg": "钴蓝-8",
|
|
34
|
+
"menu_title_hover": "#FFFFFF",
|
|
35
|
+
"sub_menu_bg": "钴蓝-9",
|
|
36
|
+
"sub_menu_active_text": "#FFFFFF",
|
|
37
|
+
"menu_active_bg": "钴蓝-5",
|
|
38
|
+
"menu_active_indicator": "钴蓝-5"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"id": "admin.layout.header",
|
|
43
|
+
"style": {
|
|
44
|
+
"bg": "#FFFFFF"
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"id": "admin.layout.header.lang_switch",
|
|
49
|
+
"form": "inline_switch",
|
|
50
|
+
"suppress": [
|
|
51
|
+
"icon_only",
|
|
52
|
+
"dropdown_popper"
|
|
53
|
+
],
|
|
54
|
+
"style": {
|
|
55
|
+
"icon_size": "16px",
|
|
56
|
+
"gap": "8px",
|
|
57
|
+
"text_font_size": "14px"
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"id": "admin.layout.header.user_profile",
|
|
62
|
+
"style": {
|
|
63
|
+
"avatar_width": "28px",
|
|
64
|
+
"avatar_height": "28px",
|
|
65
|
+
"avatar_radius": "全圆角",
|
|
66
|
+
"name_font_size": "14px",
|
|
67
|
+
"name_font_weight": "中等"
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"id": "admin.layout.tabs",
|
|
72
|
+
"form": "chrome",
|
|
73
|
+
"style": {
|
|
74
|
+
"bg": "#FFFFFF"
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"id": "admin.content.table.header_cell",
|
|
79
|
+
"style": {
|
|
80
|
+
"height": "48px",
|
|
81
|
+
"padding": "16px",
|
|
82
|
+
"bg": "#FAFAFA",
|
|
83
|
+
"font_weight": "中等",
|
|
84
|
+
"text_align": "左对齐",
|
|
85
|
+
"nowrap": "不换行"
|
|
86
|
+
},
|
|
87
|
+
"layer": {
|
|
88
|
+
"height": "host",
|
|
89
|
+
"padding": "host",
|
|
90
|
+
"bg": "host",
|
|
91
|
+
"font_weight": "host",
|
|
92
|
+
"text_align": "inner",
|
|
93
|
+
"nowrap": "inner"
|
|
94
|
+
},
|
|
95
|
+
"suppress_inner": true
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"id": "admin.content.table.cell",
|
|
99
|
+
"style": {
|
|
100
|
+
"padding": "16px",
|
|
101
|
+
"padding_y": "8px",
|
|
102
|
+
"vertical_align": "middle"
|
|
103
|
+
},
|
|
104
|
+
"layer": {
|
|
105
|
+
"padding": "host",
|
|
106
|
+
"padding_y": "inner",
|
|
107
|
+
"vertical_align": "inner"
|
|
108
|
+
},
|
|
109
|
+
"suppress_inner": true
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"id": "admin.content.table.cell_status",
|
|
113
|
+
"form": "filled",
|
|
114
|
+
"style": {
|
|
115
|
+
"bg": "#E8F3FF",
|
|
116
|
+
"text_color": "钴蓝-5",
|
|
117
|
+
"radius": "小圆角"
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"id": "component.tag.status",
|
|
122
|
+
"form": "filled",
|
|
123
|
+
"style": {
|
|
124
|
+
"height": "24px",
|
|
125
|
+
"radius": "小圆角",
|
|
126
|
+
"bg_color": "#F6FFED"
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
"id": "admin.content.page_header",
|
|
131
|
+
"style": {
|
|
132
|
+
"title_size": "20px",
|
|
133
|
+
"title_weight": "加粗",
|
|
134
|
+
"desc_color": "钴蓝-3",
|
|
135
|
+
"actions_gap": "8px"
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
"id": "admin.content.filter_area",
|
|
140
|
+
"style": {
|
|
141
|
+
"radius": "小圆角",
|
|
142
|
+
"padding_y": "宽松",
|
|
143
|
+
"padding_x": "宽松",
|
|
144
|
+
"bg": "#FFFFFF",
|
|
145
|
+
"border_bottom": "1px solid #F0F0F0"
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
"id": "admin.content.table.operation_bar",
|
|
150
|
+
"style": {
|
|
151
|
+
"gap": "8px",
|
|
152
|
+
"padding_y": "紧凑"
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
"id": "admin.content.table.operation_column",
|
|
157
|
+
"form": "text_link",
|
|
158
|
+
"style": {
|
|
159
|
+
"link_color": "钴蓝-5",
|
|
160
|
+
"danger_color": "错误色",
|
|
161
|
+
"align": "center",
|
|
162
|
+
"divider_color": "#F0F0F0"
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
"id": "admin.content.table.area",
|
|
167
|
+
"style": {
|
|
168
|
+
"header_bg": "#FAFAFA",
|
|
169
|
+
"row_hover_bg": "#FAFAFA",
|
|
170
|
+
"radius": "小圆角"
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
"id": "admin.content.table.cell_amount",
|
|
175
|
+
"style": {
|
|
176
|
+
"align": "right",
|
|
177
|
+
"font_variant_numeric": "等宽数字"
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
"id": "admin.content.table.cell_thumb",
|
|
182
|
+
"style": {
|
|
183
|
+
"thumb_width": "40px",
|
|
184
|
+
"thumb_height": "40px",
|
|
185
|
+
"thumb_radius": "小圆角"
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
"id": "component.button.danger",
|
|
190
|
+
"style": {
|
|
191
|
+
"bg_color": "错误色",
|
|
192
|
+
"border_color": "错误色",
|
|
193
|
+
"text_color": "#FFFFFF"
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
"id": "admin.content.pagination",
|
|
198
|
+
"style": {
|
|
199
|
+
"active_bg": "钴蓝-5",
|
|
200
|
+
"active_color": "#FFFFFF"
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
],
|
|
204
|
+
"oracle": {
|
|
205
|
+
"mockup": "verify/mockup-nometa.html",
|
|
206
|
+
"sids": [
|
|
207
|
+
"admin.layout.sidebar",
|
|
208
|
+
"admin.layout.sidebar.logo_area",
|
|
209
|
+
"admin.layout.sidebar.menu",
|
|
210
|
+
"admin.layout.header",
|
|
211
|
+
"admin.layout.header.lang_switch",
|
|
212
|
+
"admin.layout.header.user_profile",
|
|
213
|
+
"admin.layout.tabs",
|
|
214
|
+
"admin.content.page_header",
|
|
215
|
+
"admin.content.filter_area",
|
|
216
|
+
"admin.content.table.operation_bar",
|
|
217
|
+
"admin.content.table.operation_column",
|
|
218
|
+
"admin.content.table.area",
|
|
219
|
+
"admin.content.table.header_cell",
|
|
220
|
+
"admin.content.table.cell",
|
|
221
|
+
"admin.content.table.cell_status",
|
|
222
|
+
"admin.content.table.cell_amount",
|
|
223
|
+
"admin.content.table.cell_thumb",
|
|
224
|
+
"component.tag.status",
|
|
225
|
+
"component.button.danger",
|
|
226
|
+
"admin.content.pagination"
|
|
227
|
+
]
|
|
228
|
+
},
|
|
229
|
+
"coverage": {
|
|
230
|
+
"registry": "parser/fixtures/registry-sample.json",
|
|
231
|
+
"registrable": 6,
|
|
232
|
+
"applicable": 5,
|
|
233
|
+
"covered": 5,
|
|
234
|
+
"gaps": []
|
|
235
|
+
},
|
|
236
|
+
"pending": [
|
|
237
|
+
{
|
|
238
|
+
"proposed_id": "admin.content.page_header.greeting",
|
|
239
|
+
"function": "页面标题下的说明副标题(一句话描述页面用途)",
|
|
240
|
+
"parent": "admin.content.page_header"
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
"proposed_id": "admin.content.table.cell_status_badge",
|
|
244
|
+
"function": "表格状态列的带图标状态胶囊(图标+文字+浅色底)",
|
|
245
|
+
"parent": "admin.content.table.area"
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
"proposed_id": "admin.content.table.cell_route_arrow",
|
|
249
|
+
"function": "表格线路列的起点到终点箭头展示(如 上海→北京)",
|
|
250
|
+
"parent": "admin.content.table.area"
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
"proposed_id": "admin.content.table.cell_link",
|
|
254
|
+
"function": "表格内可点击的链接文字单元(如运单号)",
|
|
255
|
+
"parent": "admin.content.table.area"
|
|
256
|
+
}
|
|
257
|
+
],
|
|
258
|
+
"notes": "整页场景还原样例(对照效果图逐模块提取)。效果图定制冲突项:侧栏底色用钴蓝-10(极深)而非模板默认 #001529,以用户效果图为准;顶栏保持白底。灰阶临时色申报:白 #FFFFFF、浅灰 #FAFAFA、边框灰 #F0F0F0 为中性色(不属任何主题梯度),按临时主题 hex 直写,全主题通用。页面存在业务结构改动(标题区按钮数量与功能、demo 演示内容去留),需项目侧双层交付:页面结构层走业务闭环开发重建,视觉语义层按本变更集落地。问候语(下午好,张伟)文案属业务层,本变更集只声明其载体形态与样式。",
|
|
259
|
+
"theme": "钴蓝"
|
|
260
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="zh-CN" data-mockup-theme="钴蓝" data-mockup-scene="admin" data-mockup-page-type="list">
|
|
3
|
+
<head><meta charset="UTF-8"><title>回归夹具·列表页基线图(中性命名)</title></head>
|
|
4
|
+
<body>
|
|
5
|
+
<div data-sid="admin.layout.sidebar"></div>
|
|
6
|
+
<div data-sid="admin.layout.sidebar.logo_area"></div>
|
|
7
|
+
<div data-sid="admin.layout.sidebar.menu"></div>
|
|
8
|
+
<div data-sid="admin.layout.header"></div>
|
|
9
|
+
<div data-sid="admin.layout.header.lang_switch"></div>
|
|
10
|
+
<div data-sid="admin.layout.header.user_profile"></div>
|
|
11
|
+
<div data-sid="admin.layout.tabs"></div>
|
|
12
|
+
<div data-sid="admin.content.page_header"></div>
|
|
13
|
+
<div data-sid="admin.content.filter_area"></div>
|
|
14
|
+
<div data-sid="admin.content.table.operation_bar"></div>
|
|
15
|
+
<div data-sid="admin.content.table.operation_column"></div>
|
|
16
|
+
<div data-sid="admin.content.table.area"></div>
|
|
17
|
+
<div data-sid="admin.content.table.header_cell"></div>
|
|
18
|
+
<div data-sid="admin.content.table.cell"></div>
|
|
19
|
+
<div data-sid="admin.content.table.cell_status"></div>
|
|
20
|
+
<div data-sid="admin.content.table.cell_amount"></div>
|
|
21
|
+
<div data-sid="admin.content.table.cell_thumb"></div>
|
|
22
|
+
<div data-sid="component.tag.status"></div>
|
|
23
|
+
<div data-sid="component.button.danger"></div>
|
|
24
|
+
<div data-sid="admin.content.pagination"></div>
|
|
25
|
+
</body>
|
|
26
|
+
</html>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head><meta charset="UTF-8"><title>回归夹具·列表页基线图(中性命名 · 无身份元数据)</title></head>
|
|
4
|
+
<body>
|
|
5
|
+
<div data-sid="admin.layout.sidebar"></div>
|
|
6
|
+
<div data-sid="admin.layout.sidebar.logo_area"></div>
|
|
7
|
+
<div data-sid="admin.layout.sidebar.menu"></div>
|
|
8
|
+
<div data-sid="admin.layout.header"></div>
|
|
9
|
+
<div data-sid="admin.layout.header.lang_switch"></div>
|
|
10
|
+
<div data-sid="admin.layout.header.user_profile"></div>
|
|
11
|
+
<div data-sid="admin.layout.tabs"></div>
|
|
12
|
+
<div data-sid="admin.content.page_header"></div>
|
|
13
|
+
<div data-sid="admin.content.filter_area"></div>
|
|
14
|
+
<div data-sid="admin.content.table.operation_bar"></div>
|
|
15
|
+
<div data-sid="admin.content.table.operation_column"></div>
|
|
16
|
+
<div data-sid="admin.content.table.area"></div>
|
|
17
|
+
<div data-sid="admin.content.table.header_cell"></div>
|
|
18
|
+
<div data-sid="admin.content.table.cell"></div>
|
|
19
|
+
<div data-sid="admin.content.table.cell_status"></div>
|
|
20
|
+
<div data-sid="admin.content.table.cell_amount"></div>
|
|
21
|
+
<div data-sid="admin.content.table.cell_thumb"></div>
|
|
22
|
+
<div data-sid="component.tag.status"></div>
|
|
23
|
+
<div data-sid="component.button.danger"></div>
|
|
24
|
+
<div data-sid="admin.content.pagination"></div>
|
|
25
|
+
</body>
|
|
26
|
+
</html>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"generated_at": "2026-09-
|
|
2
|
+
"generated_at": "2026-09-23T09:41:58.162Z",
|
|
3
3
|
"oracle": "E:\\work\\Project\\Template\\skills\\vue3-temp-skill\\parser\\fixtures\\verify\\oracle.json",
|
|
4
4
|
"actual": "E:\\work\\Project\\Template\\skills\\vue3-temp-skill\\parser\\fixtures\\verify\\actual-clean.json",
|
|
5
5
|
"points": {
|
package/parser/index.js
CHANGED
|
@@ -112,6 +112,9 @@ function printHelp() {
|
|
|
112
112
|
用法:
|
|
113
113
|
node parser/index.js build [--dry] 校验映射表并生成 dist/ 产物
|
|
114
114
|
node parser/index.js match <changeset.json...> 对设计变更集执行三级匹配(可多文件;顶层 mode=restore 时 G8 模式闸强制 oracle.mockup + coverage.registry 齐备)
|
|
115
|
+
主题身份:changeset.theme(优先)→ global.brand_primary(回退);
|
|
116
|
+
基线图色板:图内 data-mockup-theme(优先)→ 文件名主题词(回退)。
|
|
117
|
+
二者不一致时须声明 oracle.theme_mapping(只做色值归一,不减声明)。
|
|
115
118
|
node parser/index.js match <cs.json> --registry <reg.json>
|
|
116
119
|
匹配 + 登记表覆盖率当场重算(子项级细闸,有缺口 exit 1)
|
|
117
120
|
node parser/index.js match --batch <dir> 项目级批量匹配:目录下全部变更集 + 跨文件汇总
|
|
@@ -123,7 +126,7 @@ function printHelp() {
|
|
|
123
126
|
node parser/index.js verify --oracle <o.json> --resolve [--sids a,b]
|
|
124
127
|
实测端定位计划:sid → 词典 selector(项目页不打 data-sid 时按此抽取)
|
|
125
128
|
node parser/index.js verify … --from-theme 钴蓝 --to-theme 橘橙
|
|
126
|
-
|
|
129
|
+
同构异色归一:按语义槽位把基线图色值归一为本主题色值再比(配合 oracle.theme_mapping)
|
|
127
130
|
node parser/index.js registry <registry.json> [--verbose]
|
|
128
131
|
登记表通道核验:登记项 sid 是否在词典、dims 是否有落点()
|
|
129
132
|
node parser/index.js list [--scene admin] [--verbose] 列出全部语义 ID(--verbose 含字段与形态,供下发词汇约束)
|
|
@@ -797,10 +800,17 @@ function priorityRequirement(strategy) {
|
|
|
797
800
|
/**
|
|
798
801
|
* oracle 验收基线校验()
|
|
799
802
|
* 两类事故都出在「基线本身不可用」,而不是落地写法:
|
|
800
|
-
* ①
|
|
803
|
+
* ① 主题与基线图色板不一致:基线图是 A 色板、本次主题是 B → 未声明 oracle.theme_mapping 时
|
|
804
|
+
* 逐点比对必然全红(搬错基线)。注意:**基线图是「场景基线」、与主题无关,任何主题都读同一张**;
|
|
805
|
+
* 不一致只影响「色值归一」,正确动作是声明 theme_mapping 后继续读图,**不是换图、更不是跳过读取**。
|
|
801
806
|
* ② 覆盖度缺口:效果图里有、changes[] 却未声明的点位 → 永远不可能被还原(欠声明);
|
|
802
807
|
* 反之声明了却不在 sids 里的点位 → 落地后无法机器验收。
|
|
803
808
|
* 两者都属「交付不完整」,与颜色红线同级,必须回流设计侧补齐。
|
|
809
|
+
* 主题身份读取口径(2026-09-23 二次实证后固化,见设计侧 mockup-extraction-spec.md §1.1):
|
|
810
|
+
* 本次主题 = changeset.theme(优先)→ global.brand_primary(回退);
|
|
811
|
+
* 基线图色板 = 图内 data-mockup-theme(优先)→ 文件名主题词(回退)。
|
|
812
|
+
* 历史缺口:主题只能从 global.brand_primary 反推、基线图身份只能从文件名猜 → 「图名带钴蓝」
|
|
813
|
+
* 被读成「可还原主题白名单」,换橘橙时误判无基线 → 跳过读图、只改主题色。
|
|
804
814
|
*/
|
|
805
815
|
function checkOracle(changeset, plan, valueDict, opts = {}) {
|
|
806
816
|
const oracle = changeset.oracle;
|
|
@@ -821,7 +831,12 @@ function checkOracle(changeset, plan, valueDict, opts = {}) {
|
|
|
821
831
|
|
|
822
832
|
const issues = [];
|
|
823
833
|
|
|
824
|
-
// ①
|
|
834
|
+
// ① 主题一致性:本次主题(changeset.theme 优先,回退 global.brand_primary)
|
|
835
|
+
// vs 基线图色板(图内 data-mockup-theme 优先,回退文件名主题词)。
|
|
836
|
+
// 为什么要两端回退:主题身份以前只能从 global.brand_primary 反推、基线图身份只能从文件名猜——
|
|
837
|
+
// 于是「主题」被绑进 global 的取值收敛语义,而「图的名字」被读成了「可还原主题白名单」,
|
|
838
|
+
// 换非基线主题时被误判为无基线 → 跳过读图、只改主题色。
|
|
839
|
+
// 权威口径见设计侧 mockup-extraction-spec.md §1.1:基线图是场景基线,与主题无关。
|
|
825
840
|
const themes = new Set(
|
|
826
841
|
Object.keys(valueDict || {})
|
|
827
842
|
.map(k => /^(.+?)-\d+$/.exec(k))
|
|
@@ -832,26 +847,56 @@ function checkOracle(changeset, plan, valueDict, opts = {}) {
|
|
|
832
847
|
const m = typeof v === "string" ? /^(.+?)-\d+$/.exec(v.trim()) : null;
|
|
833
848
|
return m ? m[1] : null;
|
|
834
849
|
};
|
|
835
|
-
const
|
|
836
|
-
const
|
|
850
|
+
const themeDeclaredRaw = typeof changeset.theme === "string" ? changeset.theme.trim() : "";
|
|
851
|
+
const declaredTheme = themeDeclaredRaw || themeFromValue(changeset.global && changeset.global.brand_primary);
|
|
852
|
+
const themeSource = themeDeclaredRaw
|
|
853
|
+
? "changeset.theme"
|
|
854
|
+
: (declaredTheme ? "global.brand_primary(回退——建议改在顶层声明 changeset.theme)" : "未声明");
|
|
855
|
+
|
|
856
|
+
// 基线图色板:图内元数据为权威(基线图与主题无关,文件名主题词仅供参考)
|
|
857
|
+
const mockupResolvedEarly = resolveFirstExisting(mockup, opts.filePath);
|
|
858
|
+
let mockupTheme = null;
|
|
859
|
+
let mockupThemeSource = null;
|
|
860
|
+
if (mockupResolvedEarly) {
|
|
861
|
+
try {
|
|
862
|
+
const html = readFileSync(mockupResolvedEarly, "utf-8");
|
|
863
|
+
const m = /\bdata-mockup-theme\s*=\s*["']([^"']+)["']/i.exec(html);
|
|
864
|
+
if (m) { mockupTheme = m[1].trim(); mockupThemeSource = "图内 data-mockup-theme"; }
|
|
865
|
+
} catch { /* 图不可读,回退文件名 */ }
|
|
866
|
+
}
|
|
867
|
+
if (!mockupTheme) {
|
|
868
|
+
const byName = [...themes].find(t => mockup.includes(t)) || null;
|
|
869
|
+
if (byName) { mockupTheme = byName; mockupThemeSource = "文件名主题词(旧图回退——建议补 data-mockup-theme)"; }
|
|
870
|
+
}
|
|
837
871
|
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
//
|
|
872
|
+
console.log(` 本次主题:${declaredTheme || "(未声明)"}(来源:${themeSource})`);
|
|
873
|
+
console.log(` 基线图色板:${mockupTheme || "(未标注)"}${mockupThemeSource ? `(来源:${mockupThemeSource})` : ""}`);
|
|
874
|
+
|
|
875
|
+
// 同构异色归一:theme_mapping { "from": "<基线图色板>", "to": "<本次主题>" } 只描述
|
|
876
|
+
// 「基线图色值 → 本次主题语义槽位(-1..-10)」的归一映射,verify 用 --from-theme/--to-theme 执行;
|
|
877
|
+
// **它与「是否读取该图 / 是否逐条声明」无关**——任何主题都读同一张场景基线图并逐点抽取。
|
|
842
878
|
const tm = oracle.theme_mapping && typeof oracle.theme_mapping === "object" ? oracle.theme_mapping : null;
|
|
843
879
|
const declaredReuse = !!(tm && tm.from && tm.to
|
|
844
880
|
&& themes.has(tm.from) && themes.has(tm.to)
|
|
845
881
|
&& declaredTheme === tm.to && mockupTheme === tm.from);
|
|
846
882
|
|
|
847
883
|
if (declaredReuse) {
|
|
848
|
-
console.log(` ↺
|
|
884
|
+
console.log(` ↺ 同构异色归一已声明:基线图色板「${tm.from}」→ 本次主题「${tm.to}」(oracle.theme_mapping)。verify 需带 --from-theme ${tm.from} --to-theme ${tm.to} 做色值归一;几何 / 结构 / 字号逐点照比。`);
|
|
849
885
|
}
|
|
850
886
|
else if (declaredTheme && mockupTheme && declaredTheme !== mockupTheme) {
|
|
851
|
-
|
|
887
|
+
if (!themes.has(declaredTheme)) {
|
|
888
|
+
// 库外临时主题:语义槽位表里没有它的槽位,theme_mapping 的按槽位归一不适用
|
|
889
|
+
console.log(` ℹ 本次为库外临时主题「${declaredTheme}」:基线图色板「${mockupTheme}」无对应语义槽位,verify 不做色值归一(几何 / 结构 / 字号仍逐点照比);色值按设计侧 notes 申报的派生口径落地。`);
|
|
890
|
+
} else {
|
|
891
|
+
issues.push(`主题错配(本次主题 ≠ 基线图色板):基线图是「${mockupTheme}」、本次主题是「${declaredTheme}」(来源:${themeSource})。**基线图是场景基线、与主题无关——正确动作是继续读这张图并逐点抽取**,同时声明 oracle.theme_mapping: { "from": "${mockupTheme}", "to": "${declaredTheme}" }(它只做色值归一,不减任何声明与读取动作);若结构 / 形态确实也不同,则另立基线图后登记 baseline。**不得把"主题不匹配"读成"该主题没有基线图"而跳过读取。**`);
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
if (!mockupTheme && mockup) {
|
|
895
|
+
const hint = themeDeclaredRaw
|
|
896
|
+
? `已在 changeset.theme 声明「${themeDeclaredRaw}」——基线图身份未标注不影响读取:按注册表 baseline.<页型>.mockup 取该页型基线图即可,请在该图 <html> 上补 data-mockup-theme(含 scene / page-type)后再验。`
|
|
897
|
+
: `无法确认基线图色板 —— 请核对该图是否为本页型的场景基线图,并在 <html> 上补 data-mockup-theme / data-mockup-scene / data-mockup-page-type。`;
|
|
898
|
+
console.log(` ℹ 基线图未带主题身份元数据:${hint}`);
|
|
852
899
|
}
|
|
853
|
-
if (!mockupTheme && mockup)
|
|
854
|
-
issues.push(`效果图文件名不含任何已知主题词(${[...themes].join("/")}),无法确认主题一致性 —— 请核对是否复用了其它主题的效果图。`);
|
|
855
900
|
|
|
856
901
|
// ② 覆盖度(双向)
|
|
857
902
|
const declaredIds = plan.map(p => p.id);
|
|
@@ -899,7 +944,7 @@ function checkOracle(changeset, plan, valueDict, opts = {}) {
|
|
|
899
944
|
console.error(`\n✖ oracle 基线问题(G11 · ${issues.length} 类 → 拒绝执行):`);
|
|
900
945
|
for (const it of issues)
|
|
901
946
|
console.error(` ✘ ${it}`);
|
|
902
|
-
console.error(`
|
|
947
|
+
console.error(` 处置:按上列各条回设计侧补齐基线(补 sids 声明 / 声明 theme_mapping 做色值归一;基线图缺失时先补图)后重跑。注意「换主题」不等于「换图」——任何主题都读同一张场景基线图。`);
|
|
903
948
|
return 1;
|
|
904
949
|
}
|
|
905
950
|
console.log(`\n⚠ oracle 基线问题(${issues.length} 类):`);
|
|
@@ -2092,7 +2137,7 @@ function cmdVerify(args, dataset) {
|
|
|
2092
2137
|
console.log(` ${s}`);
|
|
2093
2138
|
}
|
|
2094
2139
|
console.log(`\n汇总:${sids.length} 点位,${missing} 个无法定位`);
|
|
2095
|
-
console.log(`抽取属性白名单两端必须一致(padding / font-size / font-weight / text-align / color / background-color / border-radius / gap 等,见 mockup-extraction-spec §3
|
|
2140
|
+
console.log(`抽取属性白名单两端必须一致(padding / font-size / font-weight / text-align / color / background-color / border-radius / gap 等,见 mockup-extraction-spec §3);主题色板与基线图不同时再加 --from-theme/--to-theme 做色值归一(几何 / 结构 / 字号照比)。`);
|
|
2096
2141
|
if (missing > 0)
|
|
2097
2142
|
process.exitCode = 1;
|
|
2098
2143
|
return;
|
package/parser/regression.js
CHANGED
|
@@ -208,12 +208,26 @@ const steps = [
|
|
|
208
208
|
expect: { code: 1, includes: "基线漂移" },
|
|
209
209
|
custom: "snapshot-drift",
|
|
210
210
|
},
|
|
211
|
+
{
|
|
212
|
+
key: "W",
|
|
213
|
+
name: "match 主题与基线图解耦正例(中性文件名 + 图内 data-mockup-theme=钴蓝,本次主题=橘橙 + theme_mapping → 走色值归一、exit 0)",
|
|
214
|
+
script: INDEX,
|
|
215
|
+
args: ["match", path.join(FIXTURES, "changeset-pos-theme-neutral.json")],
|
|
216
|
+
expect: { code: 0, includes: ["changeset.theme", "图内 data-mockup-theme", "同构异色归一已声明"] },
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
key: "X",
|
|
220
|
+
name: "match 基线图缺身份元数据不阻断(中性文件名 + 无 data-mockup-theme,已声明 changeset.theme → 仅提示、exit 0)",
|
|
221
|
+
script: INDEX,
|
|
222
|
+
args: ["match", path.join(FIXTURES, "changeset-pos-theme-nometa.json")],
|
|
223
|
+
expect: { code: 0, includes: "基线图未带主题身份元数据" },
|
|
224
|
+
},
|
|
211
225
|
];
|
|
212
226
|
|
|
213
227
|
const results = [];
|
|
214
228
|
let failed = 0;
|
|
215
229
|
|
|
216
|
-
console.log("=== A-
|
|
230
|
+
console.log("=== A-X 端到端一键回归 ===\n");
|
|
217
231
|
|
|
218
232
|
const SNAPSHOT_TMP = path.join(FIXTURES, "verify", "snapshot-latest.json");
|
|
219
233
|
|
|
@@ -276,4 +290,4 @@ if (failed > 0) {
|
|
|
276
290
|
console.error(`\n✖ ${failed} 步未通过,见上方详情`);
|
|
277
291
|
process.exit(1);
|
|
278
292
|
}
|
|
279
|
-
console.log("\n✔ A-
|
|
293
|
+
console.log("\n✔ A-X 全部通过,mapping/parser 可提交或发版");
|
|
@@ -75,7 +75,7 @@ parser match(确定性翻译)→ 施工坐标 → AI 按坐标改代码 →
|
|
|
75
75
|
"mode": "restore", // 交付模式(G8 模式闸):restore=场景还原(scheme + oracle.mockup + coverage.registry 强制必填);design=显式声明的轻量微调;缺省 design
|
|
76
76
|
"layout_type": "vertical", // 标准布局枚举:vertical / horizontal / mix(字段名固定 layout_type,非 layout)
|
|
77
77
|
"page_bindings": [ // 页面映射(G13 页面绑定闸):效果图页面 → 项目页面的身份声明;缺省 = 全部映射已有页面。效果图页面默认是设计角色坐标系,不是页面增量——未显式 target:"new" 时禁止新增页面/路由/菜单
|
|
78
|
-
{ "mockup": "
|
|
78
|
+
{ "mockup": "后台管理端.html", "target": "<项目已有列表页路由名/路径>", "note": "效果图标题是设计角色坐标系 = 已有页面换肤基线,非新增业务" }
|
|
79
79
|
],
|
|
80
80
|
"global": { "brand_primary": "钴蓝-5" }, // 可选:全局令牌通道(整套主题切换优先走此节点;值 = 语义名或临时主题 hex)
|
|
81
81
|
"changes": [
|
|
@@ -14,3 +14,18 @@
|
|
|
14
14
|
- **禁止编造的是「既有条目的映射」**(改既有 selector / css_vars / 变量指向 = 静默篡改,绝对禁止)。**词典缺 ID / 缺字段枚举不属编造**——那是新点位,按 L3 当场落地并写**新增的**临时映射条目,交付说明申报,等人工评审转正。
|
|
15
15
|
- 「宁可不做,不做错」只适用于**设计侧未给值 / 值非法**的意图缺口;用它搁置能力缺口 = 交付失败。
|
|
16
16
|
- 临时映射转正须人工评审;禁止静默修改既有映射条目的 selector/css_vars。
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 布局类问题的「实测复现」手段(无 dev server 时)
|
|
21
|
+
|
|
22
|
+
布局畸形(文字被压成两行 / 字符重叠 / 元素被裁)**不能只靠读 CSS 推理**——同一条规则在不同宽高链里结果完全不同,复现一次比推演十次可靠。当环境里起不了 dev server(如沙箱拦下构建期依赖清理、端口不可用)时,用**编译产物 + headless 浏览器**做等价复现:
|
|
23
|
+
|
|
24
|
+
1. **取编译后的 CSS**:`dist/static/css/` 下的主样式包(含 Element Plus 覆盖、设计层规则、组件 scoped)。确认目标规则真在里面(grep 选择器片段),scoped 规则的 `[data-v-xxxx]` 后缀也在这里读到,复现时必须原样带上该属性。
|
|
25
|
+
2. **写最小 DOM**:只保留目标元素的**祖先盒链**(列宽由 `<colgroup><col width>` 决定就写 col;`.cell` 是内层文本盒就写 `.cell`),其余删掉。
|
|
26
|
+
3. **跑 headless 取数**:`msedge --headless=new --disable-gpu --user-data-dir=<临时目录> --virtual-time-budget=5000 --dump-dom file:///<复现.html>`。
|
|
27
|
+
- **`--user-data-dir` 必给**,否则报 `Missing headless user data directory` 直接不输出。
|
|
28
|
+
- 页面内用 script 把 `getBoundingClientRect()` / `getComputedStyle()` 结果写进 `<title>`,再从 dump 里 grep 取回,比截图量像素精确。
|
|
29
|
+
4. **读什么**:沿祖先链逐层打印 `box 宽高 / width / min-width / flex / display / white-space`,找出**哪一层先装不下**。典型结论:「外盒 144 − 左右内边距 32 = 112 < 内容需求 120」这类确定性差额,比「看起来有点挤」可行动得多。
|
|
30
|
+
|
|
31
|
+
**判据来源优先级**:实测(headless 取数)> 静态审计闸断言 > 源码推理 > 肉眼看截图。截图放大时**先把标注线(红框等)像素抹掉再测量**——标注线会被误读成渲染元素,本轮就曾把横排文字误判成竖排。
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
## 端到端回归(改 parser/mapping 后必跑)
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
node parser/regression.js # 一键跑完 A-
|
|
10
|
+
node parser/regression.js # 一键跑完 A-X 二十四步(自动断言退出码与关键输出,任一失败 exit 1 并回显详情)
|
|
11
11
|
npm run regression # 等价写法
|
|
12
12
|
```
|
|
13
13
|
|
|
@@ -36,9 +36,11 @@ node parser/index.js match parser/fixtures/changeset-neg7-g10-dims.json # S.
|
|
|
36
36
|
node parser/index.js match parser/fixtures/changeset-neg8-g11-oracle.json # T. G11 oracle 闸负例:橘橙主色配钴蓝效果图且无 theme_mapping(主题错配)→ exit 1
|
|
37
37
|
node parser/index.js verify --oracle parser/fixtures/verify/oracle.json --actual parser/fixtures/verify/actual-clean.json # U. 残差豁免 + G12 收口正例:申报点位不计缺口、absent 已收口 → exit 0
|
|
38
38
|
# V. 快照回归(两段式,regression.js 内部自动展开):clean 夹具导快照 → 缺口夹具比对 → 基线漂移 → exit 1
|
|
39
|
+
node parser/index.js match parser/fixtures/changeset-pos-theme-neutral.json # W. 主题↔基线图解耦正例:中性文件名 + 图内 data-mockup-theme=钴蓝,本次主题=橘橙 + theme_mapping → 色值归一、exit 0
|
|
40
|
+
node parser/index.js match parser/fixtures/changeset-pos-theme-nometa.json # X. 基线图缺身份元数据不阻断:中性名 + 无 data-mockup-theme,已声明 changeset.theme → 仅提示、exit 0
|
|
39
41
|
```
|
|
40
42
|
|
|
41
|
-
通过标准:regression.js 输出 `A✔ B✔ …
|
|
43
|
+
通过标准:regression.js 输出 `A✔ B✔ … X✔` 且 exit 0;分步跑时各步符合注释预期。夹具含义见各文件 `$comment`。
|
|
42
44
|
|
|
43
45
|
## 配套工具(作者/维护侧)
|
|
44
46
|
|
|
@@ -53,7 +53,7 @@ node parser/index.js match --batch <changesets-dir>
|
|
|
53
53
|
| **G8 模式闸** | 变更集顶层 `mode: "restore"`(场景还原)但基线不全:缺 `scheme` / `oracle` / `coverage`,**或 `oracle.mockup` / `coverage.registry` 为空**(有壳无芯的伪基线)——伪还原变更集(只换主题色却以还原之名交付)拒绝执行;`mode` 非法值同样阻断 | 变更集顶层 `scheme` / `mode` / `oracle.mockup` / `coverage.registry` | `G8 模式闸:mode=restore 但缺少 … → 拒绝执行` |
|
|
54
54
|
| **G9 登记表身份闸** | `mode=restore` 但登记表**身份对不上**:`coverage.registry` 实物解析失败,或登记表 `design_scheme`/`scene` 与变更集 `scheme`/`scene` 不一致(或缺身份字段)——凡身份对不上的表视同没有表,防止覆盖率分母被偷换(面包屑/顶栏图标/表头 nowrap 等点位从分母消失,欠声明不可数) | 登记表顶层 `design_scheme` / `scene` ↔ 变更集顶层 `scheme` / `scene` | `G9 登记表身份闸:登记表 … 属于 scheme="…"/scene="…",与变更集 … 不一致 → 拒绝执行` |
|
|
55
55
|
| **G10 dims 兑现闸** | `mode=restore` 下登记项的 ID 已在 `changes[]` 声明,但登记 `dims` 维度未全部给全(`form` 看 `change.form`,其余看 `style ∪ layer` 的键;`source:none`、global 伪点位、`visible:false` 豁免)——「点位在、细节丢」(表头竖排 = nowrap 丢失)正是样式细节不还原的机制成因,dims 缺口与欠声明同级阻断 | 登记表条目 `dims` ↔ 变更集条目 `form` / `style` / `layer` | `dims 兑现缺口(G10 · … 条 → exit 1):… 缺 dims=[…]` |
|
|
56
|
-
| **G11 oracle 基线闸** | `mode=restore` 下 oracle
|
|
56
|
+
| **G11 oracle 基线闸** | `mode=restore` 下 oracle 校验发现问题(主题与基线图色板不一致且未声明归一 / `oracle.sids` 欠声明或无基线 / 实物缺口):restore 下从「⚠ 软提示继续」升格为「拒绝执行」——基线断了仍绿灯交付,verify 无从做 1:1 证明。**基线图是「场景基线」、与主题无关**:换主题的正确动作是**继续读同一张基线图**并声明 `theme_mapping: { from: <基线图色板>, to: <本次主题> }`(只做色值归一,不减读取与声明);**不是「该主题没有效果图」**——把它读成后者就会跳过读图、只改主题色 | 本次主题(`changeset.theme` → 回退 `global.brand_primary`)↔ 基线图色板(图内 `data-mockup-theme` → 回退文件名主题词);`oracle.sids` ↔ `changes[].id` 双向 | `oracle 基线问题(G11 · … 类 → 拒绝执行):主题与基线图色板不一致 …` |
|
|
57
57
|
| **G13 页面绑定闸** | 落地动作涉及**新增页面 / 路由 / 菜单项**,但变更集 `page_bindings` 未把该效果图页面对应声明为 `target:"new"`(或整字段缺省)——效果图页面默认是设计角色坐标系(演示标题),不是项目页面增量;「图上有这个页面」不构成新增依据 | 变更集顶层 `page_bindings[].target` ↔ 本次 diff 是否出现新路由/菜单/页面文件 | `G13 页面绑定闸:效果图页面 … 未声明 target:"new",禁止新增页面/路由/菜单 → 映射已有页面落地` |
|
|
58
58
|
| **G1 suppress 收口** | 条目声明某 `form`,词典 `suppress_rules[form]`(或 forms 描述「suppress 必含 x」内联标记)要求收口的出厂互斥通道未全部出现在该条目 `suppress[]` | 词典条目 `suppress_rules` | `suppress 缺口:…必须收口出厂互斥通道 [x],缺 [x]` |
|
|
59
59
|
| **G5 几何语境** | 词典 preconditions 含「几何语境随值迁移」标记 ∧ `style` 含 padding/margin 族几何字段落宿主 ∧ 未声明 `suppress_inner` | 词典条目 preconditions 标记句 | `suppress_inner 缺口:…两层之和 ≠ 设计值` |
|
|
@@ -8,6 +8,12 @@
|
|
|
8
8
|
|
|
9
9
|
对每个 `style` 字段,按 parser 转换指令执行(parser 已把语义值解析为精确值,如 `钴蓝-5` → `#245BFF`)。**冲突裁决规则(免问):凡变更集声明与项目出厂现状/模板既有口径冲突,一律按变更集执行——变更集是唯一施工依据,不反问、不逐次确认。**
|
|
10
10
|
|
|
11
|
+
0. **尺寸字段先定意图,再选属性(不照抄数值)**:尺寸值不是「拿到多少就写多少」——先判它是**硬约束**(盒子尺寸由设计定死,与内容无关 → `width` / `height`)、**下限**(由文案撑开,值只是最小值 → `min-width`,按钮 / 标签 / 页签 / 表头列宽 / 操作槽 / 状态牌一律走这条)、还是**上限**(图形盒 → `max-width`)。三条铁律:
|
|
12
|
+
- **由文案撑开的盒子不许写 exact**:写死宽度后文案一变长就是换行 / 压缩 / 重叠渲染——「设计说 40px,代码就固定 40px」必然翻车。
|
|
13
|
+
- **exact + 盒内有文案 = 必须同时收口**:形态收口(如 `icon_only`)、文案隐藏、或改下限,三选一,不许只落宽度。
|
|
14
|
+
- **同一尺寸只认一个权威来源**:列宽只在列定义、槽宽只在组件参数。发现第二处来源(设计层 CSS / 业务配置 / 词典默认值)先收敛再施工。
|
|
15
|
+
落地后**按内容复核一次**:把文案换成更长的看是撑开还是畸形;装不下时按缺口回流设计侧,不得靠裁切 / 压缩 / 换行消化。
|
|
16
|
+
|
|
11
17
|
1. **css_vars 层(优先)**:在条目 `style_scope` 作用域内覆盖该 CSS 变量。
|
|
12
18
|
```scss
|
|
13
19
|
// 例:admin.layout.sidebar.menu 的 menu_bg = 钴蓝-9(#0D2A7F)
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
- [ ] **类名冲突检查(AI 施工硬性步骤)**:新增 class 之前先在目标文件与同作用域全局样式中检索同名类——已存在时禁止直接追加重复定义,按三选一处理并在交付说明申报:① 语义相同 → **合并**进既有规则;② 语义不同 → 改用带点位前缀的新类名(如 `vts-<语义id末段>`);③ 确需覆盖 → 走作用域内更高优先级(不写 `!important`)。重复类名(尤其跨 `<style>` 块的 scoped 样式重复定义)会触发编辑器/构建报错,**本项不通过不得交付**
|
|
19
19
|
- [ ] **出厂中性检查(架构红线,不通过必须回退重做)**:diff 中不得出现「为表达本次设计而改写出厂文件」的产物——典型症状:把指示条方位/问候语 DOM/某个具体尺寸直接写进 `sidebar.scss`、`element-plus.scss`、`lay-navbar/index.vue`、`lay-content/index.vue` 等出厂载体;在出厂 `<component>` 上硬编码 `data-content-mode`/样式钩子。出厂文件若被改动,只能是「补通道」(补变量消费者 / 补 form 枚举 / 修层叠优先级,如已修的 `:has()` 与显式模式优先级反转),且必须在交付说明单列「补通道」条目写明缺失点;非此情形一律回退,改由 `design-shell.scss` + 变更集驱动。**模板源码注释保持中性**:diff 中不得出现「语义 ID / 本协议步骤号 / 面向 AI 的纪律条款」写进模板源码注释(如 `lay-content/index.vue`、`element-plus.scss`、`design-shell.scss` 的文件头与段落注释);这类内容只允许存在于技能侧文档
|
|
20
20
|
- [ ] **文本溢出回归检查**:改动侧栏菜单/顶栏/页签等含文字的模块后,必须确认「长文本显示」未被破坏(菜单项名称完整可见,必要时保留 ellipsis 溢出策略而不是隐藏/截断);宽度相关字段(width/min-width/padding)改动后逐项目视验证
|
|
21
|
+
- [ ] **尺寸意图兑现检查(每个尺寸字段都要能回答「谁决定这个数」)**:按下限语义落地——`≥N` → `min-width`、`≤N` → `max-width`、`exact` → `width`。逐项确认:① 由文案撑开的盒子(按钮 / 标签 / 页签 / 表头列宽 / 操作槽)**没有**写成固定 `exact`;② 写了 `exact` 的盒子要么内容恒定、要么已声明收口(形态收口 / 文案隐藏 / 改为下限);③ **同一尺寸只有一个权威来源**——发现「列定义写死 X、模板或公式又算成 Y」「设计层 CSS 写一套、业务配置写另一套」时**先收敛再施工**,否则改一处不生效、排查时按死值反推必然误判。**实测口径**:把文案换成更长的(改名 / 多语言 / 长数据)看盒子是撑开还是畸形——撑开 = 下限语义正确;畸形(压缩 / 换行 / 重叠)= 硬约束没兜底,返工。**接受「看起来还行」不算通过**:静态尺寸对了不等于内容装得下,必须按内容度量复核一次
|
|
21
22
|
- [ ] **形态与存在性目视验证(历史漏还原高发区,声明即必须验证,没做视同未交付)**:变更集声明了以下意图时,逐项在页面上确认真实生效——
|
|
22
23
|
- 语言切换 form=inline_switch → 顶栏出现「中|EN」并排文字(不再是纯图标)
|
|
23
24
|
- 折叠按钮 form=header_inline → 顶栏最左出现 40×40 折叠按钮且与首页页签左对齐;**同时侧栏底部出厂折叠条按设计隐藏(visible:false 声明时)**,避免双折叠按钮并存
|
|
@@ -26,7 +27,7 @@
|
|
|
26
27
|
- 页签图标 → 按 `form` 核对(item 的 `text_only`/`with_icon`,home_tab 的 `icon_only`/`text`),出处是路由 `meta.icon` 数据或本次隐藏规则,不是源码常量
|
|
27
28
|
- 内容区 form=scroll/fixed → `data-content-mode` 属性落在**页面根节点**(不是样式文件、不是壳层组件),列表页自动定高未被压掉;填满链逐层带 `data-fill`(详见下方「高度链存活检查」)
|
|
28
29
|
- 操作栏 form=with_tools/left_batch → 刷新/密度/列设置三件套齐全;批量删除红底/导出白底/未选中置灰;**效果图上有状态性表现(禁用置灰/选中态)的控件必须已申报 `oracle.states`(G14)**——交付前用浏览器脚本触发对应交互态核对,静态目视激活态不算验证通过
|
|
29
|
-
- 操作列 form=text_link → **业务列定义的 icon 字段已移除**(纯文字无图标,只改 CSS 保留图标 =
|
|
30
|
+
- 操作列 form=text_link → **业务列定义的 icon 字段已移除**(纯文字无图标,只改 CSS 保留图标 = 未还原);**按钮单行不换行且不被压缩**——列宽必须由内容度量推出,不得照抄某个固定数:`列宽 ≥ 平铺槽数 × 槽宽 + 实际单元格水平内边距`(槽宽与组件同源公式;**内边距取设计层实际生效的 td padding,而不是列宽公式内置的默认留白**——两者口径不一致就必然溢出,表现为按钮文字被压成两行或字符重叠);删除项红色;≥3 个折叠「更多」带箭头
|
|
30
31
|
- tabs form → 页签形态切换生效(chrome/card);激活态表现(有无下划线 / 是否仅文字变色 / 字重)按当次变更集 style 字段核对,**不以历史设计稿口径为准**
|
|
31
32
|
- [ ] **高度链存活检查(结构层不被设计改动压垮,也不被"保高度"绑架)**:定高页(`form=fixed` 或列表页自动定高)还原后逐项确认——
|
|
32
33
|
- `data-content-mode` 落在**页面根节点**;从页面根到表格/画布容器**每层需填满的节点都带 `data-fill`**(换了视觉容器时属性已平移;横向分栏用 `data-fill="row"`);
|
|
@@ -36,7 +37,7 @@
|
|
|
36
37
|
- [ ] **映射通道活性检查(杂必查)**:样式落地引用的每个 CSS 变量必须在项目样式中**存在消费者**(`变量名` 有 `var(--xxx)` 引用点);只定义不消费的变量 = 通道死路,翻译再准也不生效(例如 menu_active_indicator 缺 `--vts-theme-menu-active-before` 消费者时会「映射了却没效果」)。施工时 grep 核对,无消费者时改走消费者存在的作用域或补挂载点并申报。
|
|
37
38
|
- [ ] **结构施工兑现检查(structure_ops 逐条对账,与红线同级)**:变更集 `structure_ops[]` 的**每一条 op** 都必须在交付说明给出「op → 落在哪个文件 / 哪个节点 / 怎么落的」;`add_node` 必须真的新增了元素或渲染分支(模板 diff 可验),`bind_source` 必须真的改了绑定(`class-name` / `align` / `formatter`)。出现「声明了 op、代码里找不到对应改动」= 交付不完整,禁止只改 CSS 冒充结构施工
|
|
38
39
|
- [ ] **同权重失效检查( · 专查「写了不生效」)**:逐条核对设计层新增规则是否真能取胜——① 对手是组件 `<style scoped>` 时,本条特异度必须**严格高于**它(**平手即错**,因为组件样式在全局之后注入);② 对手带 `!important` 时本条必须同样 `!important`,或改走通道变量。**判定方法(必须真数,不许目测)**:对每条新规则与其对手分别写出特异度三元组 `(a,b,c)` 并**逐位比较(先 b 后 c)**,本条的 `b` 不大于对手即判为「失效」;`html` / `body` 只加 `c` 位,**对手 `b` 更高时加 `html body` 无效**,必须靠**加类**(祖先链补稳定语义类,如 `.el-menu`)把 `b` 抬到相等。同 `b` 时,`c` 更高者胜。③ **出厂用语义令牌间接设置该属性时(`var(--vts-theme-*)`),必须先走「令牌重映射」而不是抢属性**——在令牌宿主作用域重声明令牌即生效、零权重战争;只有无令牌或令牌语义不符时才抢属性。典型失效例:面包屑 `display:none`(与组件 scoped 同为 0,2,0)→ 照旧显示;页签关闭钮 `opacity`(同为 0,5,0)→ 仍要 hover 才显;页签项 `padding`(对手带 `!important`)→ 无论怎么抬特异度都改不动;**侧栏菜单 `color`(`b=2` 输给出厂 `b=3` + `!important`)→ 整栏菜单文字用浅色主题的黑色落在深色侧栏上、字全部消失**
|
|
39
|
-
- [ ] **oracle 基线可用性检查**:变更集带 `oracle` 时——① `oracle.mockup`
|
|
40
|
+
- [ ] **oracle 基线可用性检查**:变更集带 `oracle` 时——① `oracle.mockup` 必须是**本页型的场景基线图**(**与主题无关**:任何主题都读**同一张**,不得因换主题而改图、漏读或跳抽)。基线图色板与本次主题不一致时(图内 `data-mockup-theme` ↔ `changeset.theme`),声明 `oracle.theme_mapping: { "from": "<基线图色板>", "to": "<本次主题>" }`——它**只是色值归一**(verify 须带 `--from-theme/--to-theme`,几何 / 结构 / 字号逐点照比),**不减免任何读取与声明**;**不得因换主题色就判死闭环**,**也不得因换了主题色就降低本轮应有的覆盖期待**(「只有钴蓝能正经还原」是误读:基线图是场景基线、不是某主题的专属产物);结构 / 形态与基线图有差异时另立基线图并登记 `baseline`;② `oracle.sids` 与 `changes[]` **双向闭合**:效果图里有、changes[] 未声明 = **欠声明**(该点位永远不可能被还原);changes[] 已声明、效果图无 sid = **无基线**——**仅 `visible:false` 的隐藏点位豁免**(效果图上无可见元素可标 `data-sid`,parser 已自动豁免并打印清单)。其余两类出现即回流设计侧补齐,**不得直接开工**(`parser match` 已自动报出);③ **状态性期望与孤儿对账(G14/G15,抽取产物层)**:效果图上有可见状态性表现的点位(禁用置灰 / 激活 / 选中)→ `oracle.json.states` 必须有对应 `<sid>@<state>` 期望(缺失 = 状态维度放弃验收);交付前核对项目页渲染的每个区块都在 oracle 全集(sids ∪ absent ∪ residual ∪ states)内——孤儿点位 verify 当场 exit 1,提前自查可省一轮返工
|
|
40
41
|
- [ ] **设计层闭环审计闸(机器断言,ERROR 必须清零)**:施工完成后跑 `node scripts/design-audit.mjs --project <落地项目> --container-width <内容区实际宽>`,或 `VUE3_AUDIT_PROJECT=<落地项目> npm run check`。本闸把「声明了 ≠ 生效」拆成逐条静态断言,替代肉眼目视,任一 ERROR 即视为未交付。**与 `parser/index.js audit` 的分工**:parser audit 管「通道与挂载点」(selector 有没有挂载点、变量有没有消费者),本闸管「生效性与形态」(层路由、层叠胜负、列宽预算、收口断言、形态成对);E1/E4 是两者的故意重叠项,用作交叉校验:
|
|
41
42
|
- E1 变量悬空 → 使用了项目内无定义且无 `var()` 回退的自有变量,整条声明失效
|
|
42
43
|
- E2 层错位 → 内层盒属性族(`white-space` / `text-overflow` / `overflow` / `text-align` / `line-height` / `vertical-align` / `word-break`)写在表格宿主盒 `th/td.el-table__cell` 上而非 `> .cell`,会被 Element Plus 内层 `.cell` 覆盖
|
|
@@ -50,6 +51,9 @@
|
|
|
50
51
|
- E10 scoped 挂空 → 组件 scoped 规则依赖运行时并不存在的类(结构重构后残留,永不命中)
|
|
51
52
|
- E11 **交付完整性** → `mode=restore` 时 `covered < applicable`(欠声明)/ `gaps` 非空 / 缺口未在 `pending[]` 台账化(下游「继续」无工作项可执行)/ 自报 `registrable` 与登记表实物不符 / `notes` 的成功话术与 `coverage` 字段互相矛盾。**该项是「只换了主题色」的第一现场**:上游残缺口 + 下游挑拣落地,两头都能被它抓住
|
|
52
53
|
- E12 **设计层累积性** → 设计层文件行数或变更集点位相比历史峰值骤降(上一轮成果被重建覆盖)。本项会写 `.codebuddy/design/ledger.json` 台账(`--no-ledger` 可关;`--changeset <path>` / `--design-layer <path>` 可指定),**台账同时是「继续」的断点续传依据**:`applicable − 已落地点数` 即剩余工作项,不再依赖自然语言回忆
|
|
54
|
+
- E13 **固定尺寸内容度量** → 设计层出现「固定 `width` 小于内容需求、且未声明收口兜底」的规则(典型:页签位 40px 里还留着标题、图标位里塞着文案)。固定宽装不下内容时文字会换行、被压缩、甚至字符重叠渲染——**「设计说 40px 就固定 40px」必然复现**。判据:盒内还有文案却用 exact 宽度又无收口
|
|
55
|
+
- E14 **列宽预算链自洽** → 列宽(含公式推出的值)扣掉**实际生效的 td 水平内边距**后装不下该列内容(操作列 = 平铺槽数 × 槽宽);或同一列宽存在两个来源且值不一致(列定义死值 vs 模板公式)。**这是「按钮文字换行 / 重叠」的第一现场**
|
|
56
|
+
- E16 **引用完整性** → 变更集 `oracle.mockup` / `coverage.registry` 指向的实物不存在(标识改名后未同步引用,全链路静默降级为无基线还原);`mode=restore` 缺顶层 `theme` 字段
|
|
53
57
|
- [ ] **列宽由表头文案反推(E5 落地口径)**:每个业务列(含选择列、序号列)都要显式声明 `width` 或 `min-width`,取值 ≥ 该列表头文案宽度 + 左右各 16px 内边距(CJK 按 1em、ASCII 按 0.6em 估)。无宽列只能拿出厂兜底宽度参与布局,会被相邻固定宽列挤压——**表头换行与省略号截断的唯一根因就在这里**,不接受「跑起来看着还行」
|
|
54
58
|
|
|
55
59
|
- [ ] **缺口清单只含意图缺口**:清单每一项都必须是「设计侧未给值 / 值非法」且已注明「回流设计侧补齐」;能力缺口(词典缺 ID / 缺字段枚举 / 缺 form)不得出现在清单里——出现即把可落地项搁置
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
```bash
|
|
22
22
|
node parser/index.js verify --oracle oracle.json --actual actual.json
|
|
23
23
|
# 可选 --sids admin.content.table.header_cell,admin.layout.tabs.item 只验部分点位
|
|
24
|
-
#
|
|
24
|
+
# 同构异色归一(oracle.theme_mapping 已声明时必带)——基线图色值按语义槽位归一为本主题色值再比(几何/结构/字号照比):
|
|
25
25
|
node parser/index.js verify --oracle oracle.json --actual actual.json --from-theme 钴蓝 --to-theme 橘橙
|
|
26
26
|
```
|
|
27
27
|
退出码:存在缺口(含存在性缺口 / 基线漂移)→ `1`。
|
|
@@ -68,7 +68,7 @@ node parser/index.js verify --oracle oracle.json --actual actual.json --baseline
|
|
|
68
68
|
- 上轮 `hit` / `absent-hit`、本轮退步的点位 / 属性 = **基线漂移**,逐条列出且 `exit 1`——先回归上轮修法,再谈新缺口。(G14 状态键命中、G15 对账通过同样纳入上轮「命中」口径;上轮 `state-miss` / `orphan` 修复后本轮变 `hit` 属正常改善,不算漂移。)
|
|
69
69
|
- 项目还原到出厂(design-shell 回空壳)后第一轮验证**不带** `--baseline`(基线已失真),改以 clean 夹具重新起拍。
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
**夹具与设计资产的边界(职责单侧沉淀红线)**:设计侧效果图库的**基线图命名用中性名**(页面名 / 场景名,如 `后台管理端.html`)——**文件名里不要出现主题词**:主题身份只由图内 `data-mockup-theme` 与注册表 `baseline.<页型>.theme` 承担;文件名一旦带主题词,就会被读成「这是某主题专属的图」,换主题时误判成「无基线」而跳过读取。该命名是设计侧效果图库的专属资产命名——**项目侧(含 parser/fixtures 回归夹具)禁止出现与设计侧资产同名的文件**。回归需要 mockup 实物时,一律用「回归夹具-」前缀合成名(如 `回归夹具-列表页-钴蓝.html`),点集为回归机制定制、与真基线**不同且不可互替**;真实效果图基线唯一沉淀在设计侧 `page-specs/styles/<方案>/scenes/<场景>/mockups/`,项目侧 `oracle.mockup` 指向它靠 `resolveFirstExisting` 跨库解析(变更集同目录 → cwd → 技能根 → 设计侧兄弟目录),不做本地拷贝。
|
|
72
72
|
|
|
73
73
|
- 验收结果写入最终回复(命中 N / 缺口 M / 归因分布 / 修正)与 `code-quality` 结果块;**不得只报「已完成」**。
|
|
74
74
|
- **入场前置:闭环审计闸必须已清零**。`node scripts/design-audit.mjs --project <落地项目> --container-width <内容区实际宽>` 存在 ERROR 时不得进入渲染态验收——层错位 / 层叠败北 / 无宽列这类失效会让 verify 侧出现「期望值取不到」或「值对但不生效」的假缺口,先修闸门再比对,否则每轮都在给同一个根因换皮。
|
package/scripts/design-audit.mjs
CHANGED
|
@@ -29,6 +29,9 @@
|
|
|
29
29
|
* E10 scoped 挂空 组件 scoped 规则依赖运行时并不存在的类(重构残留,永不命中)
|
|
30
30
|
* E11 交付完整性 mode=restore 时 covered<applicable / gaps 非空 / 缺口未台账化 / 分母漂移 / notes 与字段矛盾
|
|
31
31
|
* E12 设计层累积性 变更集点位或设计层行数相对历史骤降(上一轮成果被覆盖)→ 写交付台账 .codebuddy/design/ledger.json
|
|
32
|
+
* E13 固定尺寸内容度量 固定 width 小于内容需求且无收口兜底 → 文字换行/压缩/重叠
|
|
33
|
+
* E14 列宽预算链自洽 列宽(含公式)扣掉实际 td 内边距后装不下内容 → 操作列按钮被压
|
|
34
|
+
* E16 引用完整性 变更集引用的实物不存在(改名未同步)/ restore 缺顶层 theme
|
|
32
35
|
*/
|
|
33
36
|
|
|
34
37
|
import fs from "fs";
|
|
@@ -609,7 +612,29 @@ function matchArrayLiteral(text, anchorRe) {
|
|
|
609
612
|
}
|
|
610
613
|
}
|
|
611
614
|
|
|
612
|
-
|
|
615
|
+
// 模板里按 prop 条件绑定的列宽(如
|
|
616
|
+
// :min-width="column.prop === 'minCategoryName' ? '120' : undefined"
|
|
617
|
+
// )会覆盖列定义数组里那次「无宽」——不识别就会把已有下限的列误报成无宽列。
|
|
618
|
+
const tplBind = new Map(); // prop → { width?, minWidth? }
|
|
619
|
+
for (const attr of ["width", "min-width"]) {
|
|
620
|
+
for (const m of text.matchAll(new RegExp(`:${attr}="([^"]+)"`, "g"))) {
|
|
621
|
+
for (const c of m[1].matchAll(/[\w.]*prop\s*===\s*['"]([^'"]+)['"]\s*\?\s*['"]?([\d.]+)(?:px)?['"]?/g)) {
|
|
622
|
+
const cur = tplBind.get(c[1]) || {};
|
|
623
|
+
if (attr === "width") cur.width = Number(c[2]);
|
|
624
|
+
else cur.minWidth = Number(c[2]);
|
|
625
|
+
tplBind.set(c[1], cur);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
for (const [prop, v] of tplBind) {
|
|
630
|
+
if (noWidth.some((c) => c.prop === prop)) {
|
|
631
|
+
const label = (noWidth.find((c) => c.prop === prop) || {}).label || prop;
|
|
632
|
+
if (v.width != null) declared.push({ label, prop, w: v.width });
|
|
633
|
+
else if (v.minWidth != null) declared.push({ label, prop, mw: v.minWidth });
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
const realNoWidth = noWidth.filter((c) => !OPS.includes(c.prop) && !tplBind.has(c.prop));
|
|
613
638
|
for (const c of realNoWidth) {
|
|
614
639
|
err(
|
|
615
640
|
"E5",
|
|
@@ -967,6 +992,231 @@ const WRITE_LEDGER = !process.argv.includes("--no-ledger");
|
|
|
967
992
|
}
|
|
968
993
|
})();
|
|
969
994
|
|
|
995
|
+
// 列名提取(供 E5/E14 共用):兼容 `label: $t('类型')` / `label="类型"` 两种写法
|
|
996
|
+
const labelOfRaw = (src) => {
|
|
997
|
+
const m =
|
|
998
|
+
src.match(/label:\s*([^,\n}]+)/) || src.match(/\blabel="([^"]*)"/) || src.match(/\blabel='([^']*)'/);
|
|
999
|
+
if (!m) return "?";
|
|
1000
|
+
const s = String(m[1]).trim();
|
|
1001
|
+
const q = s.match(/[`'"]([^`'"]+)[`'"]/);
|
|
1002
|
+
return ((q ? q[1] : s).trim() || "?");
|
|
1003
|
+
};
|
|
1004
|
+
|
|
1005
|
+
// ── E13 固定尺寸 vs 内容度量 ─────────────────────────────────
|
|
1006
|
+
// 「设计说 40px,代码就固定 40px」是本闸治理的对象:像素值不是照抄依据,
|
|
1007
|
+
// 而是「该容器要装多少内容」的结论。固定宽容器装不下内容、又没声明收口/截断
|
|
1008
|
+
// 兜底时,文字会换行、被压缩、甚至字符重叠渲染成畸形。
|
|
1009
|
+
// 判据分两档:① 选择器语义指向文本容器 → 最小需求 = 2 CJK + 左右内边距;
|
|
1010
|
+
// ② 其余固定宽 → 低于 24px 连一个字形都放不下。
|
|
1011
|
+
// 文本承载语义:只认「这个盒子本身要装字」的选择器;图形载体(图标 / 伪元素 /
|
|
1012
|
+
// 分割线 / 开关)不承载文本,不参与内容度量,否则会成片误报。
|
|
1013
|
+
const TEXTY_SEL = /(text|title|label|button|tab|span|desc)/i;
|
|
1014
|
+
const GRAPHIC_LAST = /(el-icon|icon|svg|img|checkbox|radio|switch|avatar|divider|arrow|star|loading|spinner)|::(before|after|placeholder|marker|selection)/i;
|
|
1015
|
+
const MIN_TEXT_BOX = 44;
|
|
1016
|
+
const MIN_ANY_BOX = 24;
|
|
1017
|
+
(function checkE13() {
|
|
1018
|
+
if (!designRules.length) {
|
|
1019
|
+
info("E13", "固定尺寸内容度量:无设计层规则");
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
// 项目源码里真实存在的类名(排除设计层自身)——用于剔除「挂空选择器」,
|
|
1023
|
+
// 挂空由 E4 单独报,此处只管「锚点存在但装不下内容」。
|
|
1024
|
+
const projectClasses = new Set();
|
|
1025
|
+
for (const [f, t] of srcTextByFile) {
|
|
1026
|
+
if (designSet.has(path.resolve(f))) continue;
|
|
1027
|
+
for (const m of t.matchAll(/class\s*[:=]\s*["'`]([^"'`]+)["'`]/g)) {
|
|
1028
|
+
for (const c of m[1].split(/[\s{}(),:$'"]+/)) if (/^[A-Za-z][\w-]*$/.test(c)) projectClasses.add(c);
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
// 同一选择器上的「兄弟规则」也算保护来源(收口常写成独立规则块)
|
|
1032
|
+
const hasProtect = (decls) =>
|
|
1033
|
+
String(decls.overflow || "").match(/hidden|clip/) ||
|
|
1034
|
+
String(decls["text-overflow"] || "").match(/ellipsis/) ||
|
|
1035
|
+
String(decls.display || "").match(/none/);
|
|
1036
|
+
const protectBySel = new Set();
|
|
1037
|
+
for (const r of designRules) {
|
|
1038
|
+
if (hasProtect(r.decls)) protectBySel.add(r.selector);
|
|
1039
|
+
}
|
|
1040
|
+
const hits = [];
|
|
1041
|
+
for (const r of designRules) {
|
|
1042
|
+
const raw = r.decls.width;
|
|
1043
|
+
if (raw == null) continue;
|
|
1044
|
+
if (r.decls["min-width"] != null || r.decls["max-width"] != null) continue;
|
|
1045
|
+
const m = String(raw).match(/^(\d+(?:\.\d+)?)\s*px$/i);
|
|
1046
|
+
if (!m) continue;
|
|
1047
|
+
const n = Number(m[1]);
|
|
1048
|
+
const isTexty = TEXTY_SEL.test(r.selector);
|
|
1049
|
+
const floor = isTexty ? MIN_TEXT_BOX : MIN_ANY_BOX;
|
|
1050
|
+
if (n >= floor) continue;
|
|
1051
|
+
if (hasProtect(r.decls)) continue;
|
|
1052
|
+
if (protectBySel.has(r.selector)) continue;
|
|
1053
|
+
if (GRAPHIC_LAST.test(lastCompound(r.selector)) || GRAPHIC_LAST.test(r.selector)) continue;
|
|
1054
|
+
const toks = classTokens(r.selector);
|
|
1055
|
+
if (toks.length && !toks.some((c) => projectClasses.has(c))) continue; // 挂空 → 交 E4
|
|
1056
|
+
hits.push({ r, n, floor, isTexty });
|
|
1057
|
+
}
|
|
1058
|
+
for (const h of hits.slice(0, 8)) {
|
|
1059
|
+
err(
|
|
1060
|
+
"E13",
|
|
1061
|
+
`${relp(h.r.file)}:\`${h.r.selector}\` 固定 width:${h.n}px —— ` +
|
|
1062
|
+
(h.isTexty
|
|
1063
|
+
? `该选择器指向文本容器,装下 2 个汉字 + 内边距至少需 ${h.floor}px`
|
|
1064
|
+
: `低于任何字形盒子的下限 ${h.floor}px`) +
|
|
1065
|
+
`;且未声明收口兜底(overflow:hidden / text-overflow:ellipsis / display:none)。` +
|
|
1066
|
+
`固定宽小于内容需求时文字会换行、被压缩或重叠渲染 —— ` +
|
|
1067
|
+
`请改为内容驱动(min-width / 自适应),或在同一作用域内显式写出收口策略(形态收容 + 文案隐藏成对)。`,
|
|
1068
|
+
);
|
|
1069
|
+
}
|
|
1070
|
+
if (hits.length > 8) err("E13", `固定尺寸内容度量:另有 ${hits.length - 8} 处未列出。`);
|
|
1071
|
+
if (!hits.length) info("E13", `固定尺寸内容度量:${designRules.length} 条设计层规则中未检出「固定宽 < 内容下限且无收口」`);
|
|
1072
|
+
})();
|
|
1073
|
+
|
|
1074
|
+
// ── E14 列宽预算链自洽(列定义宽 vs 实际 td 内边距)──────────
|
|
1075
|
+
// 列宽与内容需求必须用同一套「可用宽」口径。实例:列宽公式按单元格留白 24px
|
|
1076
|
+
// 推算,而设计层把 td padding 设为 16px(左右共 32px)→ 列宽比真实可用宽多算
|
|
1077
|
+
// 8px,内容必然溢出、被内层盒压缩 → 操作按钮文字换行 / 重叠。
|
|
1078
|
+
(function checkE14() {
|
|
1079
|
+
const tableFiles = [...srcTextByFile.entries()].filter(([, t]) => t.includes("el-table-column"));
|
|
1080
|
+
if (!tableFiles.length) {
|
|
1081
|
+
info("E14", "列宽预算链:未在 src 中发现 el-table-column");
|
|
1082
|
+
return;
|
|
1083
|
+
}
|
|
1084
|
+
// 设计层对数据格宿主的水平内边距(实际生效值)
|
|
1085
|
+
let tdPadX = null;
|
|
1086
|
+
for (const r of designRules) {
|
|
1087
|
+
if (!/td\.el-table__cell|\.el-table__body\s+td/.test(r.selector)) continue;
|
|
1088
|
+
const p = r.decls.padding;
|
|
1089
|
+
if (!p) continue;
|
|
1090
|
+
const parts = String(p).replace(/!important/g, "").trim().split(/\s+/);
|
|
1091
|
+
const px = (v) => {
|
|
1092
|
+
const mm = String(v).match(/(\d+(?:\.\d+)?)px/);
|
|
1093
|
+
return mm ? Number(mm[1]) : null;
|
|
1094
|
+
};
|
|
1095
|
+
const vals = parts.map(px).filter((v) => v != null);
|
|
1096
|
+
if (!vals.length) continue;
|
|
1097
|
+
// padding: A → 四边 A;A B → 上下 A 左右 B;A B C → 上 A 左右 B 下 C
|
|
1098
|
+
const x = vals.length === 1 ? vals[0] : vals.length === 2 ? vals[1] : vals.length === 3 ? vals[1] : vals[3];
|
|
1099
|
+
tdPadX = x * 2;
|
|
1100
|
+
break;
|
|
1101
|
+
}
|
|
1102
|
+
// 操作列:内容需求 = 平铺槽数 × 槽宽(与组件同源公式)
|
|
1103
|
+
const slotW = (() => {
|
|
1104
|
+
const src = readIf(path.join(PROJECT, "src", "components", "ReTableOperate", "utils.ts"));
|
|
1105
|
+
if (!src) return null;
|
|
1106
|
+
const moreChars = Number((src.match(/MORE_LABEL_CHARS\s*=\s*(\d+)/) || [])[1] || 3);
|
|
1107
|
+
const fsMatch = src.match(/default:\s*(\d+)/);
|
|
1108
|
+
const fs = fsMatch ? Number(fsMatch[1]) : 14;
|
|
1109
|
+
const btnSrc = [...srcTextByFile.entries()].find(([, t]) => /operateButtons\s*(?::[^=]+)?=\s*\[/.test(t));
|
|
1110
|
+
let maxChars = 2;
|
|
1111
|
+
if (btnSrc) {
|
|
1112
|
+
const arr = matchArrayLiteral(btnSrc[1], /operateButtons\s*(?::[^=]+)?=\s*\[/);
|
|
1113
|
+
if (arr) for (const obj of extractObjectLiterals(arr)) {
|
|
1114
|
+
const lm = obj.match(/label\s*:\s*(?:\$t\()?['"`]([^'"`]+)/);
|
|
1115
|
+
if (lm) maxChars = Math.max(maxChars, [...lm[1]].length);
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
maxChars = Math.max(maxChars, moreChars);
|
|
1119
|
+
return Math.ceil(fs + 4 + maxChars * fs);
|
|
1120
|
+
})();
|
|
1121
|
+
for (const [file, text] of tableFiles) {
|
|
1122
|
+
const rel = relp(file);
|
|
1123
|
+
const arr = matchArrayLiteral(text, /(?:const|let|var)\s+\w*[Cc]olumn\w*\s*(?::[^=]+)?=\s*\[/);
|
|
1124
|
+
if (!arr) continue;
|
|
1125
|
+
const limMatch = text.match(/operateLimit\s*=\s*(\d+)/);
|
|
1126
|
+
const limit = limMatch ? Number(limMatch[1]) : null;
|
|
1127
|
+
for (const objSrc of extractObjectLiterals(arr)) {
|
|
1128
|
+
const prop = (objSrc.match(/prop:\s*[`'"]([^`'"]+)/) || [])[1];
|
|
1129
|
+
if (!prop) continue;
|
|
1130
|
+
const label = labelOfRaw(objSrc);
|
|
1131
|
+
const ops = /^(operate|operation|actions)$/.test(prop);
|
|
1132
|
+
const wLit = (objSrc.match(/\bwidth\s*:\s*[`'"]([\d.]+)px/) || [])[1];
|
|
1133
|
+
const colW = wLit ? Number(wLit) : null;
|
|
1134
|
+
if (!ops) continue;
|
|
1135
|
+
// 模板是否用公式覆盖了列定义字面量 —— 「同一尺寸两个来源」的第一现场
|
|
1136
|
+
const usesFormula = /resolveOperateColumnWidth\s*\(/.test(text);
|
|
1137
|
+
if (colW == null && !usesFormula) continue;
|
|
1138
|
+
if (slotW == null || limit == null) {
|
|
1139
|
+
warn("E14", `${rel}:操作列「${label}」无法解析槽宽/平铺上限(ReTableOperate/utils.ts 或 operateLimit 缺失),跳过列宽预算链核对。`);
|
|
1140
|
+
continue;
|
|
1141
|
+
}
|
|
1142
|
+
const utilSrc = readIf(path.join(PROJECT, "src", "components", "ReTableOperate", "utils.ts")) || "";
|
|
1143
|
+
const padInFormula = Number((utilSrc.match(/cellPadding\s*=\s*options\.cellPadding\s*\?\?\s*(\d+)/) || [])[1] || 24);
|
|
1144
|
+
const formulaW = limit * slotW + padInFormula;
|
|
1145
|
+
const effectiveW = usesFormula ? formulaW : colW != null ? colW : formulaW;
|
|
1146
|
+
const contentNeed = limit * slotW;
|
|
1147
|
+
const available = tdPadX != null ? effectiveW - tdPadX : effectiveW - padInFormula;
|
|
1148
|
+
if (usesFormula && colW != null && colW !== formulaW) {
|
|
1149
|
+
err(
|
|
1150
|
+
"E14",
|
|
1151
|
+
`${rel}:操作列「${label}」列宽有两个来源且不一致 —— 列定义写死 width="${wLit}px",` +
|
|
1152
|
+
`但模板用 \`resolveOperateColumnWidth()\` 覆盖为 ${formulaW}px(${limit}×${slotW}+${padInFormula}),实际生效 ${effectiveW}px。` +
|
|
1153
|
+
`同一尺寸不得有两个权威来源:请删掉列定义里的死值,只保留公式(或反之),否则改一处不生效、排查时按死值反推必然误判。`,
|
|
1154
|
+
);
|
|
1155
|
+
}
|
|
1156
|
+
if (contentNeed > available) {
|
|
1157
|
+
err(
|
|
1158
|
+
"E14",
|
|
1159
|
+
`${rel}:操作列「${label}」列宽预算链断裂 —— 列宽 ${effectiveW}px(${colW != null ? `列定义 width="${wLit}px"` : `公式 ${limit}×${slotW}+${padInFormula}`})` +
|
|
1160
|
+
`扣掉实际单元格水平内边距 ${tdPadX != null ? `${tdPadX}px(设计层 td padding)` : `${padInFormula}px(公式默认)`} 后可用 ${available}px,` +
|
|
1161
|
+
`而平铺 ${limit} 个等宽槽需 ${contentNeed}px → 溢出 ${contentNeed - available}px。` +
|
|
1162
|
+
`列宽公式的内置留白必须与实际生效的 td padding 同口径;此处请把列宽抬到 ≥ ${contentNeed + (tdPadX ?? padInFormula)}px,或同步下调内边距,` +
|
|
1163
|
+
`否则按钮文字会被内层盒压缩、换行甚至字符重叠。`,
|
|
1164
|
+
);
|
|
1165
|
+
} else if (tdPadX != null && padInFormula !== tdPadX) {
|
|
1166
|
+
warn(
|
|
1167
|
+
"E14",
|
|
1168
|
+
`${rel}:操作列「${label}」列宽公式内置单元格留白 ${padInFormula}px ≠ 设计层实际 td 水平内边距 ${tdPadX}px —— 当前差额 ${Math.abs(padInFormula - tdPadX)}px 尚未导致溢出,但任一参数变动即越界;建议统一为同一来源。`,
|
|
1169
|
+
);
|
|
1170
|
+
} else {
|
|
1171
|
+
info("E14", `${rel}:操作列「${label}」列宽 ${effectiveW}px,可用 ${available}px ≥ 内容 ${contentNeed}px ✓`);
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
})();
|
|
1176
|
+
|
|
1177
|
+
// ── E16 引用完整性(交付物指向的实物必须存在)────────────────
|
|
1178
|
+
// 标识(文件名 / 路径)变更后未同步引用 = 静默断链:闸门找不到基线图、
|
|
1179
|
+
// 项目侧 parser 取不到登记表,全链路降级为「凭印象还原」。
|
|
1180
|
+
(function checkE16() {
|
|
1181
|
+
if (!changeset) {
|
|
1182
|
+
info("E16", "引用完整性:未发现变更集");
|
|
1183
|
+
return;
|
|
1184
|
+
}
|
|
1185
|
+
const probes = [
|
|
1186
|
+
["oracle.mockup", changeset.oracle?.mockup],
|
|
1187
|
+
["coverage.registry", changeset.coverage?.registry],
|
|
1188
|
+
["oracle.theme_mapping.mockup", changeset.oracle?.theme_mapping?.mockup],
|
|
1189
|
+
];
|
|
1190
|
+
let checked = 0;
|
|
1191
|
+
for (const [field, val] of probes) {
|
|
1192
|
+
if (!val || typeof val !== "string") continue;
|
|
1193
|
+
if (/^https?:\/\//.test(val)) continue;
|
|
1194
|
+
checked++;
|
|
1195
|
+
const cands = [
|
|
1196
|
+
path.resolve(PROJECT, val),
|
|
1197
|
+
path.resolve(SKILL_ROOT, val),
|
|
1198
|
+
path.resolve(SKILL_ROOT, "..", val),
|
|
1199
|
+
path.resolve(PROJECT, "..", val),
|
|
1200
|
+
];
|
|
1201
|
+
if (!cands.some((p) => fs.existsSync(p))) {
|
|
1202
|
+
err(
|
|
1203
|
+
"E16",
|
|
1204
|
+
`变更集 \`${field}\` 指向的实物不存在:${val} —— 标识变更(改名 / 换目录)后未同步引用,` +
|
|
1205
|
+
`闸门与 parser 都取不到基线(会静默降级为无基线还原)。请同步引用方(变更集字段 / 方案注册表 baseline / 登记表路径)。`,
|
|
1206
|
+
);
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
// restore 模式的顶层 theme 字段:缺失会让「本次主题」回落到 global.brand_primary 反推,
|
|
1210
|
+
// 重新引入「换主题 = 换读取路径」的分叉
|
|
1211
|
+
if ((changeset.mode || "design") === "restore" && !changeset.theme) {
|
|
1212
|
+
warn(
|
|
1213
|
+
"E16",
|
|
1214
|
+
"变更集 mode=restore 但缺顶层 `theme` 字段 —— 本次主题只能回落按 `global.brand_primary` 反推(旧口径)。补 `theme` 可让「本次主题」与「基线图色板」两个身份各自可机读、互不推断。",
|
|
1215
|
+
);
|
|
1216
|
+
}
|
|
1217
|
+
if (checked && !errors.some((e) => e.id === "E16")) info("E16", `引用完整性:${checked} 个路径引用均可解析到实物 ✓`);
|
|
1218
|
+
})();
|
|
1219
|
+
|
|
970
1220
|
const result = { project: PROJECT, errors, warnings, infos };
|
|
971
1221
|
|
|
972
1222
|
const CHECK_TITLES = {
|
|
@@ -980,6 +1230,11 @@ const CHECK_TITLES = {
|
|
|
980
1230
|
E8: "浮层残留",
|
|
981
1231
|
E9: "形态收口缺件",
|
|
982
1232
|
E10: "scoped 规则挂空",
|
|
1233
|
+
E11: "交付完整性",
|
|
1234
|
+
E12: "设计层累积性",
|
|
1235
|
+
E13: "固定尺寸内容度量",
|
|
1236
|
+
E14: "列宽预算链自洽",
|
|
1237
|
+
E16: "引用完整性",
|
|
983
1238
|
SID: "@sid 覆盖率",
|
|
984
1239
|
};
|
|
985
1240
|
|