@morya-ui/mcp 0.3.3 → 0.3.5
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/LICENSE +21 -21
- package/README.md +151 -151
- package/bin/morya-ui-mcp.js +2 -2
- package/data/catalog.json +62 -51
- package/data/example-coverage.json +6 -4
- package/data/golden-pages/dashboard-page.vue +138 -138
- package/data/golden-pages/detail-page.vue +178 -178
- package/data/golden-pages/empty-state.vue +56 -56
- package/data/golden-pages/form-in-dialog.vue +166 -166
- package/data/golden-pages/form-page.vue +107 -107
- package/data/golden-pages/landing-page.vue +358 -358
- package/data/golden-pages/list-page-dense.vue +168 -0
- package/data/golden-pages/list-page-rail.vue +272 -0
- package/data/golden-pages/list-page.vue +222 -216
- package/data/golden-pages/login-page.vue +231 -231
- package/data/golden-pages/result-page.vue +84 -84
- package/data/golden-pages/settings-page.vue +163 -163
- package/data/golden-pages/wizard-form.vue +182 -182
- package/dist/__tests__/page-snippets.test.js +29 -0
- package/dist/__tests__/tools.test.js +94 -1
- package/dist/decisions.d.ts +2 -0
- package/dist/decisions.js +76 -7
- package/dist/golden-pages.d.ts +18 -0
- package/dist/golden-pages.js +76 -22
- package/dist/index.js +9 -3
- package/dist/page-snippets.js +960 -257
- package/dist/patterns.d.ts +5 -3
- package/dist/patterns.js +53 -13
- package/dist/style-presets.d.ts +33 -0
- package/dist/style-presets.js +131 -0
- package/dist/tools.d.ts +12 -0
- package/dist/tools.js +301 -210
- package/package.json +1 -1
package/dist/decisions.js
CHANGED
|
@@ -45,7 +45,7 @@ export const componentDecisions = [
|
|
|
45
45
|
recipe: recipe([
|
|
46
46
|
['v-model / modelValue 控制开关(不要用 visible)', 'v-model / modelValue for open state (not visible)'],
|
|
47
47
|
['header 或 title 设弹窗标题', 'header or title for the dialog title'],
|
|
48
|
-
['width 约 32rem 适配短表单(见 form-in-dialog
|
|
48
|
+
['width 约 32rem 适配短表单(见 form-in-dialog snippet)', 'width ~32rem for short forms (see form-in-dialog snippet)'],
|
|
49
49
|
['表单字段用 fluid;主按钮 severity="primary"', 'Form fields use fluid; primary button severity="primary"'],
|
|
50
50
|
], [
|
|
51
51
|
['#footer 放取消/保存', '#footer for cancel / save'],
|
|
@@ -54,6 +54,7 @@ export const componentDecisions = [
|
|
|
54
54
|
['@close 重置表单模型', '@close to reset the form model'],
|
|
55
55
|
['保存可用按钮 @click,或 MForm @submit + type="submit"', 'Save via button @click, or MForm @submit + type="submit"'],
|
|
56
56
|
]),
|
|
57
|
+
relatedSnippets: ['form-in-dialog', 'page-header-actions'],
|
|
57
58
|
...anti([
|
|
58
59
|
['用普通 Dialog 手写删除确认 → MConfirmDialog', 'Hand-rolled delete confirm in Dialog → MConfirmDialog'],
|
|
59
60
|
['长多分组配置塞进 Dialog → Form 页或 Drawer', 'Long multi-section config in Dialog → Form page or Drawer'],
|
|
@@ -74,6 +75,7 @@ export const componentDecisions = [
|
|
|
74
75
|
['#footer 取消/保存', '#footer cancel / save'],
|
|
75
76
|
['默认插槽 MForm', 'Default slot: MForm'],
|
|
76
77
|
], [['@close 清理草稿', '@close to clear draft state']]),
|
|
78
|
+
relatedSnippets: ['form-in-drawer'],
|
|
77
79
|
...anti([
|
|
78
80
|
['极短 2–3 字段也用 Drawer → Dialog', 'Tiny 2–3 field form in Drawer → Dialog'],
|
|
79
81
|
['危险删除只用 Drawer 无确认 → MConfirmDialog', 'Destructive delete in Drawer without confirm → MConfirmDialog'],
|
|
@@ -97,6 +99,7 @@ export const componentDecisions = [
|
|
|
97
99
|
['MForm @submit(或 @submit.prevent)', 'MForm @submit (or @submit.prevent)'],
|
|
98
100
|
['提交按钮 type="submit" + severity="primary"', 'Submit button type="submit" + severity="primary"'],
|
|
99
101
|
]),
|
|
102
|
+
relatedSnippets: ['page-content-form', 'form-header', 'form-body', 'form-actions'],
|
|
100
103
|
...anti([
|
|
101
104
|
['列表短 CRUD 开独立路由 → form-in-dialog', 'Short list CRUD as a route → form-in-dialog'],
|
|
102
105
|
['表单外再套装饰性 MCard → 用 PageSection form', 'Extra decorative MCard around form → PageSection form'],
|
|
@@ -151,6 +154,7 @@ export const componentDecisions = [
|
|
|
151
154
|
['modal 默认阻塞;任务型内容放默认插槽', 'modal blocks by default; task content in default slot'],
|
|
152
155
|
['危险确认不要用 Dialog 冒充 → ConfirmDialog', 'Do not fake destructive confirm with Dialog → ConfirmDialog'],
|
|
153
156
|
], [['#footer 主/次操作', '#footer primary / secondary actions']], [['@close / @update:modelValue', '@close / @update:modelValue']]),
|
|
157
|
+
relatedSnippets: ['form-in-dialog'],
|
|
154
158
|
...anti([
|
|
155
159
|
['Dialog + 手写「确定删除」→ MConfirmDialog', 'Dialog + hand-rolled Delete? → MConfirmDialog'],
|
|
156
160
|
['v-model:visible → v-model / modelValue', 'v-model:visible → v-model / modelValue'],
|
|
@@ -171,6 +175,7 @@ export const componentDecisions = [
|
|
|
171
175
|
['position="right"(常用)', 'position="right" (typical)'],
|
|
172
176
|
['header 说明侧栏任务', 'header describes the side-panel job'],
|
|
173
177
|
], [['#footer 操作区', '#footer actions']], [['@close', '@close']]),
|
|
178
|
+
relatedSnippets: ['form-in-drawer'],
|
|
174
179
|
...anti([['简单 Yes/No 确认用 Drawer → ConfirmDialog / ConfirmPopup', 'Simple Yes/No in Drawer → ConfirmDialog / ConfirmPopup']]),
|
|
175
180
|
},
|
|
176
181
|
{
|
|
@@ -226,6 +231,7 @@ export const componentDecisions = [
|
|
|
226
231
|
recipe: recipe([
|
|
227
232
|
['columns + rows(没有 data prop)', 'columns + rows (no data prop)'],
|
|
228
233
|
['row-key 默认 id;不稳定时显式指定', 'row-key defaults to id; set explicitly when needed'],
|
|
234
|
+
['全视口主列表可 MPageContent fill + MTable fill;嵌入/短页跳过', 'Full-viewport main lists may use fill; skip for embedded/short pages'],
|
|
229
235
|
['分页:paginator + rows-per-page 或 v-model:page', 'Paging: paginator + rows-per-page or v-model:page'],
|
|
230
236
|
['行选择:selectionMode + v-model:selection', 'Row select: selectionMode + v-model:selection'],
|
|
231
237
|
], [
|
|
@@ -235,6 +241,7 @@ export const componentDecisions = [
|
|
|
235
241
|
...anti([
|
|
236
242
|
[':data → :rows', ':data → :rows'],
|
|
237
243
|
['手写 <table> → MTable', 'Hand-rolled <table> → MTable'],
|
|
244
|
+
['嵌入表硬套 fill → 去掉 fill', 'Forced fill on embedded table → remove fill'],
|
|
238
245
|
]),
|
|
239
246
|
},
|
|
240
247
|
{
|
|
@@ -612,11 +619,27 @@ export const componentDecisions = [
|
|
|
612
619
|
avoidWhen: ['普通表单字段分组', 'KPI 指标展示'],
|
|
613
620
|
avoidWhenEn: ['Regular form field grouping', 'KPI metric display'],
|
|
614
621
|
recipe: recipe([
|
|
615
|
-
['内放 MInput / MSelect
|
|
616
|
-
['
|
|
617
|
-
['
|
|
618
|
-
|
|
619
|
-
|
|
622
|
+
['内放 MInput / MSelect;同行用 MSpace wrap', 'Place MInput / MSelect inside; peers in MSpace wrap'],
|
|
623
|
+
['查询/重置放 #actions(与折叠切换同列)', 'Query/reset in #actions (trailing with toggle)'],
|
|
624
|
+
['默认 variant="filled";dense craft 用 plain + size="small"', 'Default variant="filled"; dense craft: plain + size="small"'],
|
|
625
|
+
['有次要条件:collapsible + v-model:expanded + #advanced', 'Secondary fields: collapsible + v-model:expanded + #advanced'],
|
|
626
|
+
['toggle 默认「高级筛选/收起」+ chevron;已选用 FilterChips', 'Toggle defaults to Advanced/Collapse + chevron; active via FilterChips'],
|
|
627
|
+
], [
|
|
628
|
+
['#actions 查询/重置', '#actions for query / reset'],
|
|
629
|
+
['#advanced 高级筛选', '#advanced for advanced filters'],
|
|
630
|
+
['#active 可选;更常见是下方 FilterChips', '#active optional; sibling FilterChips is more common'],
|
|
631
|
+
]),
|
|
632
|
+
relatedSnippets: [
|
|
633
|
+
'list-filters-stack',
|
|
634
|
+
'list-filters',
|
|
635
|
+
'list-filters-collapsible',
|
|
636
|
+
'list-filter-chips',
|
|
637
|
+
'list-filters-dense',
|
|
638
|
+
],
|
|
639
|
+
...anti([
|
|
640
|
+
['表单字段组用 PageFilters → PageSection form / Fieldset', 'Form field groups via PageFilters → PageSection form / Fieldset'],
|
|
641
|
+
['手写已选条 → list-filter-chips / list-filters-stack', 'Hand-rolled active bar → list-filter-chips / list-filters-stack'],
|
|
642
|
+
]),
|
|
620
643
|
},
|
|
621
644
|
{
|
|
622
645
|
component: 'PageToolbar',
|
|
@@ -725,10 +748,45 @@ export const componentDecisions = [
|
|
|
725
748
|
recipe: recipe([
|
|
726
749
|
['MLayout fill-viewport(或 :fill-viewport="true")', 'MLayout fill-viewport (or :fill-viewport="true")'],
|
|
727
750
|
['内容放 MLayoutContent', 'Content in MLayoutContent'],
|
|
751
|
+
['是否再 fill 表格:见下方「MPageContent fill + MTable fill」判断', 'Whether to fill the table: see “MPageContent fill + MTable fill” below'],
|
|
728
752
|
['侧栏用 MLayoutSider + MMenu,不是随便一个 Drawer', 'Sider: MLayoutSider + MMenu, not a random Drawer'],
|
|
729
753
|
]),
|
|
730
754
|
...anti([['在 Layout 外再包一层 100vh 滚动 → 去掉', 'Extra 100vh scroll wrapper outside Layout → remove']]),
|
|
731
755
|
},
|
|
756
|
+
{
|
|
757
|
+
component: 'MPageContent fill + MTable fill',
|
|
758
|
+
when: [
|
|
759
|
+
'全视口后台列表,页面主任务就是浏览一张表',
|
|
760
|
+
'内容高度表格会留下大块空白、分页悬在中间不好看',
|
|
761
|
+
'希望只有表体滚动、分页贴在页面最下方',
|
|
762
|
+
],
|
|
763
|
+
whenEn: [
|
|
764
|
+
'Full-viewport admin list whose main job is browsing one table',
|
|
765
|
+
'A content-sized table would leave a large empty band with mid-page pagination',
|
|
766
|
+
'Only the table body should scroll; pagination should stay at the page bottom',
|
|
767
|
+
],
|
|
768
|
+
avoidWhen: [
|
|
769
|
+
'仪表盘/详情里的嵌入小表',
|
|
770
|
+
'内容本身很短、内容高度即可',
|
|
771
|
+
'整页应作为文档滚动(长筛选+说明+表格)',
|
|
772
|
+
'Dialog / Drawer 内表格',
|
|
773
|
+
],
|
|
774
|
+
avoidWhenEn: [
|
|
775
|
+
'Embedded tables on dashboards or detail pages',
|
|
776
|
+
'Short content where a content-sized table is fine',
|
|
777
|
+
'Whole page should scroll as a document',
|
|
778
|
+
'Tables inside Dialog / Drawer',
|
|
779
|
+
],
|
|
780
|
+
recipe: recipe([
|
|
781
|
+
['先判断是否适合 fill,再写 MPageContent fill + MTable fill', 'Decide fill first, then MPageContent fill + MTable fill'],
|
|
782
|
+
['paginator 或同级 MPagination', 'paginator or sibling MPagination'],
|
|
783
|
+
['适合时不要手写 min-height / calc', 'When fill fits, do not hand-write min-height / calc'],
|
|
784
|
+
]),
|
|
785
|
+
...anti([
|
|
786
|
+
['嵌入/短页硬套 fill → 去掉', 'Forced fill on embedded/short page → remove'],
|
|
787
|
+
['适合 fill 却手写 calc → 改用 fill', 'Hand-written calc when fill fits → use fill'],
|
|
788
|
+
]),
|
|
789
|
+
},
|
|
732
790
|
{
|
|
733
791
|
component: 'MScrollbar',
|
|
734
792
|
when: [
|
|
@@ -796,9 +854,13 @@ export const componentDecisions = [
|
|
|
796
854
|
recipe: recipe([
|
|
797
855
|
['MPageContent > MPageFilters + MTable', 'MPageContent > MPageFilters + MTable'],
|
|
798
856
|
['表格直接放 PageContent,不套 Card', 'Table directly under PageContent, no Card'],
|
|
857
|
+
['高度:全视口主列表再考虑 fill;嵌入/短页跳过', 'Height: consider fill only for full-viewport main lists'],
|
|
799
858
|
['空态用 Table #empty + MEmpty', 'Empty: Table #empty + MEmpty'],
|
|
800
859
|
]),
|
|
801
|
-
...anti([
|
|
860
|
+
...anti([
|
|
861
|
+
['Table 外包 MCard → 去掉 Card', 'MCard around Table → remove Card'],
|
|
862
|
+
['嵌入表硬套 fill → 去掉 fill', 'Forced fill on embedded table → remove fill'],
|
|
863
|
+
]),
|
|
802
864
|
},
|
|
803
865
|
{
|
|
804
866
|
component: 'MCard',
|
|
@@ -1055,6 +1117,7 @@ export const componentDecisions = [
|
|
|
1055
1117
|
['icon 或 illustration 按需(默认即可)', 'icon or illustration as needed (default OK)'],
|
|
1056
1118
|
['表格内放在 #empty', 'Inside tables: #empty slot'],
|
|
1057
1119
|
], [['#extra 放下一步按钮(创建…)', '#extra for next-step button (Create…)']]),
|
|
1120
|
+
relatedSnippets: ['empty-block'],
|
|
1058
1121
|
...anti([
|
|
1059
1122
|
['403/404 用 Empty → Result', '403/404 via Empty → Result'],
|
|
1060
1123
|
['#action → #extra', '#action → #extra'],
|
|
@@ -1079,6 +1142,7 @@ export const componentDecisions = [
|
|
|
1079
1142
|
['title + description', 'title + description'],
|
|
1080
1143
|
['size 按页面比重', 'size for page weight'],
|
|
1081
1144
|
], [['#footer 逃逸/下一步按钮', '#footer escape / next actions']]),
|
|
1145
|
+
relatedSnippets: ['result-block'],
|
|
1082
1146
|
...anti([
|
|
1083
1147
|
['表格无数据用 Result → Empty', 'Table no-rows via Result → Empty'],
|
|
1084
1148
|
['操作插槽写成 #extra → #footer', 'Actions slot #extra → #footer'],
|
|
@@ -1115,6 +1179,7 @@ export const componentDecisions = [
|
|
|
1115
1179
|
['触发器放默认插槽(按钮/图标)', 'Trigger in default slot (button/icon)'],
|
|
1116
1180
|
['危险项用文档 severity / 确认流', 'Danger items: docs severity / confirm flow'],
|
|
1117
1181
|
]),
|
|
1182
|
+
relatedSnippets: ['row-actions-menu', 'list-row-actions'],
|
|
1118
1183
|
...anti([['表单枚举用 Dropdown → Select', 'Form enum via Dropdown → Select']]),
|
|
1119
1184
|
},
|
|
1120
1185
|
{
|
|
@@ -1204,6 +1269,7 @@ export const componentDecisions = [
|
|
|
1204
1269
|
["message.success('已保存') / info / warn / error", "message.success('Saved') / info / warn / error"],
|
|
1205
1270
|
['大多数操作反馈的默认选择', 'Default for most operation feedback'],
|
|
1206
1271
|
]),
|
|
1272
|
+
relatedSnippets: ['confirm-delete', 'form-in-dialog'],
|
|
1207
1273
|
...anti([
|
|
1208
1274
|
["toast.add({ summary: '已保存' }) → message.success('已保存')", "toast.add({ summary: 'Saved' }) → message.success('Saved')"],
|
|
1209
1275
|
['<MMessage severity> 当内嵌 Alert → errorMessage / role="alert"', '<MMessage severity> as inline Alert → errorMessage / role="alert"'],
|
|
@@ -1236,6 +1302,7 @@ export const componentDecisions = [
|
|
|
1236
1302
|
['表单级:token 样式的 role="alert" 条(见 login-page)', 'Form-level: token-styled role="alert" bar (see login-page)'],
|
|
1237
1303
|
['<MMessage> 只是 message 宿主,不是内嵌 Alert', '<MMessage> is the message host, not an inline Alert'],
|
|
1238
1304
|
]),
|
|
1305
|
+
relatedSnippets: ['auth-split-shell'],
|
|
1239
1306
|
...anti([
|
|
1240
1307
|
['登录失败只闪 Toast → 表单区 alert / errorMessage', 'Login failure only via Toast → form alert / errorMessage'],
|
|
1241
1308
|
['编造 MMessage severity 插槽 Alert API', 'Invented MMessage severity slot Alert API'],
|
|
@@ -1280,6 +1347,7 @@ export const componentDecisions = [
|
|
|
1280
1347
|
['@accept 执行删除', '@accept to perform delete'],
|
|
1281
1348
|
['@reject 关闭', '@reject to dismiss'],
|
|
1282
1349
|
]),
|
|
1350
|
+
relatedSnippets: ['confirm-delete'],
|
|
1283
1351
|
...anti([
|
|
1284
1352
|
['普通 Dialog 手写「确定/取消」删除 → ConfirmDialog', 'Hand-rolled Dialog Yes/No delete → ConfirmDialog'],
|
|
1285
1353
|
['编辑表单误用 ConfirmDialog → Dialog + Form', 'Edit form via ConfirmDialog → Dialog + Form'],
|
|
@@ -1303,6 +1371,7 @@ export const componentDecisions = [
|
|
|
1303
1371
|
['target 锚定触发元素(或 position)', 'target anchors the trigger (or position)'],
|
|
1304
1372
|
['placement top|bottom|left|right', 'placement top|bottom|left|right'],
|
|
1305
1373
|
], undefined, [['@accept / @reject', '@accept / @reject']]),
|
|
1374
|
+
relatedSnippets: ['confirm-delete'],
|
|
1306
1375
|
...anti([['无 target 的全局危险确认 → ConfirmDialog', 'Global dangerous confirm without target → ConfirmDialog']]),
|
|
1307
1376
|
},
|
|
1308
1377
|
],
|
package/dist/golden-pages.d.ts
CHANGED
|
@@ -1,13 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Golden pages = optional whole-page assembly demos / block-order baselines.
|
|
3
|
+
* Default agent path is composition-first (page snippets + decision recipes).
|
|
4
|
+
* FROZEN: do not add new craft variants (e.g. more list-page-*). Express density/sider via style direction + Ops polish.
|
|
5
|
+
*/
|
|
1
6
|
export interface GoldenPageRecord {
|
|
2
7
|
id: string;
|
|
3
8
|
file: string;
|
|
4
9
|
title: string;
|
|
5
10
|
titleEn: string;
|
|
6
11
|
patternId: string;
|
|
12
|
+
/** Optional craft cue this sample demonstrates (structure may be shared). Not a style-preset catalog. */
|
|
13
|
+
style?: string;
|
|
14
|
+
/** Canonical structure id when this is a craft variant. */
|
|
15
|
+
variantOf?: string;
|
|
7
16
|
}
|
|
8
17
|
export declare const goldenPageCatalog: GoldenPageRecord[];
|
|
9
18
|
export declare function listGoldenPages(): GoldenPageRecord[];
|
|
10
19
|
export declare function findGoldenPage(id: string): GoldenPageRecord | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* Pick the best golden for a pattern + optional style preset.
|
|
22
|
+
* Falls back to the pattern's canonical sample when no style variant exists.
|
|
23
|
+
*/
|
|
24
|
+
export declare function resolveGoldenPageId(args: {
|
|
25
|
+
patternId?: string | null;
|
|
26
|
+
canonicalGoldenId?: string | null;
|
|
27
|
+
styleId?: string | null;
|
|
28
|
+
}): string | null;
|
|
11
29
|
export declare function readGoldenPageSource(id: string): {
|
|
12
30
|
record: GoldenPageRecord;
|
|
13
31
|
source: string;
|
package/dist/golden-pages.js
CHANGED
|
@@ -1,85 +1,111 @@
|
|
|
1
1
|
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
2
2
|
import { dirname, join } from 'node:path';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { resolveListCraftVariant } from './style-presets.js';
|
|
4
5
|
export const goldenPageCatalog = [
|
|
5
6
|
{
|
|
6
7
|
id: 'list-page',
|
|
7
8
|
file: 'list-page.vue',
|
|
8
|
-
title: '
|
|
9
|
-
titleEn: 'List page
|
|
9
|
+
title: '列表页结构样例(soft)',
|
|
10
|
+
titleEn: 'List page structure sample (soft)',
|
|
10
11
|
patternId: 'admin-list',
|
|
12
|
+
style: 'soft',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
id: 'list-page-dense',
|
|
16
|
+
file: 'list-page-dense.vue',
|
|
17
|
+
title: '列表页结构样例(dense)',
|
|
18
|
+
titleEn: 'List page structure sample (dense)',
|
|
19
|
+
patternId: 'admin-list',
|
|
20
|
+
style: 'dense',
|
|
21
|
+
variantOf: 'list-page',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
id: 'list-page-rail',
|
|
25
|
+
file: 'list-page-rail.vue',
|
|
26
|
+
title: '列表页结构样例(rail)',
|
|
27
|
+
titleEn: 'List page structure sample (rail)',
|
|
28
|
+
patternId: 'admin-list',
|
|
29
|
+
style: 'rail',
|
|
30
|
+
variantOf: 'list-page',
|
|
11
31
|
},
|
|
12
32
|
{
|
|
13
33
|
id: 'form-page',
|
|
14
34
|
file: 'form-page.vue',
|
|
15
|
-
title: '
|
|
16
|
-
titleEn: 'Form page
|
|
35
|
+
title: '表单页结构样例',
|
|
36
|
+
titleEn: 'Form page structure sample',
|
|
17
37
|
patternId: 'form-page',
|
|
18
38
|
},
|
|
19
39
|
{
|
|
20
40
|
id: 'dashboard-page',
|
|
21
41
|
file: 'dashboard-page.vue',
|
|
22
|
-
title: '
|
|
23
|
-
titleEn: 'Dashboard
|
|
42
|
+
title: '仪表盘结构样例',
|
|
43
|
+
titleEn: 'Dashboard structure sample',
|
|
24
44
|
patternId: 'dashboard',
|
|
25
45
|
},
|
|
26
46
|
{
|
|
27
47
|
id: 'login-page',
|
|
28
48
|
file: 'login-page.vue',
|
|
29
|
-
title: '
|
|
30
|
-
titleEn: 'Login page
|
|
49
|
+
title: '登录页结构样例',
|
|
50
|
+
titleEn: 'Login page structure sample',
|
|
31
51
|
patternId: 'auth-page',
|
|
32
52
|
},
|
|
33
53
|
{
|
|
34
54
|
id: 'landing-page',
|
|
35
55
|
file: 'landing-page.vue',
|
|
36
|
-
title: '
|
|
37
|
-
titleEn: 'Marketing landing
|
|
56
|
+
title: '营销落地页结构样例',
|
|
57
|
+
titleEn: 'Marketing landing structure sample',
|
|
38
58
|
patternId: 'marketing-landing',
|
|
39
59
|
},
|
|
40
60
|
{
|
|
41
61
|
id: 'empty-state',
|
|
42
62
|
file: 'empty-state.vue',
|
|
43
|
-
title: '
|
|
44
|
-
titleEn: 'Empty state
|
|
63
|
+
title: '空状态结构样例',
|
|
64
|
+
titleEn: 'Empty state structure sample',
|
|
45
65
|
patternId: 'empty-state',
|
|
46
66
|
},
|
|
47
67
|
{
|
|
48
68
|
id: 'detail-page',
|
|
49
69
|
file: 'detail-page.vue',
|
|
50
|
-
title: '
|
|
51
|
-
titleEn: 'Detail page
|
|
70
|
+
title: '详情页结构样例',
|
|
71
|
+
titleEn: 'Detail page structure sample',
|
|
52
72
|
patternId: 'detail-page',
|
|
53
73
|
},
|
|
54
74
|
{
|
|
55
75
|
id: 'form-in-dialog',
|
|
56
76
|
file: 'form-in-dialog.vue',
|
|
57
|
-
title: '
|
|
58
|
-
titleEn: 'List create/edit dialog
|
|
77
|
+
title: '列表内弹窗表单结构样例',
|
|
78
|
+
titleEn: 'List create/edit dialog structure sample',
|
|
59
79
|
patternId: 'form-in-dialog',
|
|
60
80
|
},
|
|
61
81
|
{
|
|
62
82
|
id: 'result-page',
|
|
63
83
|
file: 'result-page.vue',
|
|
64
|
-
title: '结果 /
|
|
65
|
-
titleEn: 'Result / terminal page
|
|
84
|
+
title: '结果 / 阻断页结构样例',
|
|
85
|
+
titleEn: 'Result / terminal page structure sample',
|
|
66
86
|
patternId: 'result-page',
|
|
67
87
|
},
|
|
68
88
|
{
|
|
69
89
|
id: 'settings-page',
|
|
70
90
|
file: 'settings-page.vue',
|
|
71
|
-
title: '
|
|
72
|
-
titleEn: 'Settings page
|
|
91
|
+
title: '设置页结构样例',
|
|
92
|
+
titleEn: 'Settings page structure sample',
|
|
73
93
|
patternId: 'settings-page',
|
|
74
94
|
},
|
|
75
95
|
{
|
|
76
96
|
id: 'wizard-form',
|
|
77
97
|
file: 'wizard-form.vue',
|
|
78
|
-
title: '
|
|
79
|
-
titleEn: 'Wizard form
|
|
98
|
+
title: '分步向导结构样例',
|
|
99
|
+
titleEn: 'Wizard form structure sample',
|
|
80
100
|
patternId: 'wizard-form',
|
|
81
101
|
},
|
|
82
102
|
];
|
|
103
|
+
/** Optional legacy exact keys for list craft (density / sider) — not a style catalog. */
|
|
104
|
+
const listCraftGolden = {
|
|
105
|
+
dense: 'list-page-dense',
|
|
106
|
+
compact: 'list-page-dense',
|
|
107
|
+
rail: 'list-page-rail',
|
|
108
|
+
};
|
|
83
109
|
const pkgRoot = join(dirname(fileURLToPath(import.meta.url)), '..');
|
|
84
110
|
const bundledDir = join(pkgRoot, 'data/golden-pages');
|
|
85
111
|
const repoDir = join(pkgRoot, '../../design-kit/docs/golden-pages');
|
|
@@ -97,6 +123,34 @@ export function findGoldenPage(id) {
|
|
|
97
123
|
const key = id.trim().toLowerCase().replace(/\.vue$/i, '').replace(/[-_\s]/g, '');
|
|
98
124
|
return goldenPageCatalog.find((item) => item.id.replace(/[-_\s]/g, '') === key);
|
|
99
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* Pick the best golden for a pattern + optional style preset.
|
|
128
|
+
* Falls back to the pattern's canonical sample when no style variant exists.
|
|
129
|
+
*/
|
|
130
|
+
export function resolveGoldenPageId(args) {
|
|
131
|
+
const style = args.styleId?.trim().toLowerCase() || '';
|
|
132
|
+
const canonical = args.canonicalGoldenId?.split('/').pop()?.replace(/\.vue$/i, '') ||
|
|
133
|
+
(args.patternId
|
|
134
|
+
? goldenPageCatalog.find((item) => item.patternId === args.patternId && !item.variantOf)?.id
|
|
135
|
+
: null) ||
|
|
136
|
+
null;
|
|
137
|
+
if (args.patternId === 'admin-list' || canonical === 'list-page' || canonical?.startsWith('list-page')) {
|
|
138
|
+
const key = style.replace(/[-_\s]/g, '');
|
|
139
|
+
const exact = style ? listCraftGolden[style] || listCraftGolden[key] : null;
|
|
140
|
+
const fromText = resolveListCraftVariant(args.styleId);
|
|
141
|
+
const styled = exact || fromText;
|
|
142
|
+
if (styled && findGoldenPage(styled))
|
|
143
|
+
return styled;
|
|
144
|
+
return 'list-page';
|
|
145
|
+
}
|
|
146
|
+
if (canonical && findGoldenPage(canonical))
|
|
147
|
+
return canonical;
|
|
148
|
+
if (args.patternId) {
|
|
149
|
+
const match = goldenPageCatalog.find((item) => item.patternId === args.patternId && !item.variantOf);
|
|
150
|
+
return match?.id ?? null;
|
|
151
|
+
}
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
100
154
|
export function readGoldenPageSource(id) {
|
|
101
155
|
const record = findGoldenPage(id);
|
|
102
156
|
if (!record)
|
package/dist/index.js
CHANGED
|
@@ -86,14 +86,20 @@ register('get_pattern', 'Read the structure, component composition, layout, and
|
|
|
86
86
|
pattern: z.string().min(1),
|
|
87
87
|
mode: z.string().optional(),
|
|
88
88
|
}, async (args) => handlers.getPattern(args));
|
|
89
|
-
register('recommend_page', 'Recommend a page pattern and component composition from product intent. Pass includeScaffold: true for starter Vue code.', {
|
|
89
|
+
register('recommend_page', 'Recommend a page pattern and component composition from product intent. Pass style as free-text user description when they specified a look (no preset catalog). Pass includeScaffold: true for starter Vue code.', {
|
|
90
90
|
intent: z.string().min(1),
|
|
91
91
|
pageType: z.string().optional(),
|
|
92
92
|
features: z.array(z.string()).max(20).optional(),
|
|
93
|
+
style: z.string().optional().describe('Optional free-text style description from the user (not a preset id).'),
|
|
93
94
|
mode: z.string().optional(),
|
|
94
95
|
includeScaffold: z.boolean().optional(),
|
|
95
96
|
}, async (args) => handlers.recommendPage(args));
|
|
96
|
-
register('get_design_rules', 'Return design-token, semantic-action, accessibility, and composition rules for generated pages.', { mode: z.string().optional() }, async (args) => handlers.getDesignRules(args));
|
|
97
|
+
register('get_design_rules', 'Return design-token, style-direction rules, semantic-action, accessibility, and composition rules for generated pages.', { mode: z.string().optional() }, async (args) => handlers.getDesignRules(args));
|
|
98
|
+
register('get_style_direction', 'Resolve visual style direction: follow user reference/description, infer from clear cues, or ask when uncertain. No named preset catalog.', {
|
|
99
|
+
intent: z.string().optional(),
|
|
100
|
+
style: z.string().optional().describe('Free-text style description from the user.'),
|
|
101
|
+
mode: z.string().optional(),
|
|
102
|
+
}, async (args) => handlers.getStyleDirection(args));
|
|
97
103
|
register('recommend_component', 'List, read, or recommend component selection guides. Omit query/decision to list; pass decision only to read; pass query to recommend.', {
|
|
98
104
|
query: z.string().optional(),
|
|
99
105
|
decision: z.string().optional(),
|
|
@@ -102,7 +108,7 @@ register('recommend_component', 'List, read, or recommend component selection gu
|
|
|
102
108
|
offset: z.number().int().min(0).optional(),
|
|
103
109
|
}, async (args) => handlers.recommendComponent(args));
|
|
104
110
|
register('list_golden_pages', 'List golden page samples for list, form, and dashboard layouts.', { mode: z.string().optional() }, async (args) => handlers.listGoldenPageCatalog(args));
|
|
105
|
-
register('get_golden_page', 'Read a golden page Vue source sample (list-page, form-page, dashboard-page, login-page, landing-page, empty-state, detail-page, form-in-dialog, result-page, settings-page, wizard-form).', {
|
|
111
|
+
register('get_golden_page', 'Read a golden page Vue source sample (list-page, list-page-dense, list-page-rail, form-page, dashboard-page, login-page, landing-page, empty-state, detail-page, form-in-dialog, result-page, settings-page, wizard-form). List craft variants: dense/compact cues → dense; rail/brand-sider cues → rail; otherwise list-page.', {
|
|
106
112
|
page: z.string().min(1),
|
|
107
113
|
mode: z.string().optional(),
|
|
108
114
|
}, async (args) => handlers.getGoldenPage(args));
|