@morya-ui/mcp 0.2.8 → 0.3.0
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 +2 -2
- package/data/catalog.json +529 -248
- package/data/example-coverage.json +67 -39
- package/data/golden-pages/dashboard-page.vue +41 -8
- package/data/golden-pages/detail-page.vue +178 -0
- package/data/golden-pages/empty-state.vue +22 -1
- package/data/golden-pages/form-in-dialog.vue +166 -0
- package/data/golden-pages/form-page.vue +1 -1
- package/data/golden-pages/landing-page.vue +31 -1
- package/data/golden-pages/list-page.vue +25 -12
- package/data/golden-pages/login-page.vue +44 -4
- package/data/golden-pages/result-page.vue +84 -0
- package/data/golden-pages/settings-page.vue +163 -0
- package/data/golden-pages/wizard-form.vue +182 -0
- package/dist/__tests__/tools.test.js +97 -4
- package/dist/decisions.js +136 -1
- package/dist/golden-pages.js +35 -0
- package/dist/index.js +1 -1
- package/dist/page-snippets.js +46 -36
- package/dist/patterns.d.ts +7 -5
- package/dist/patterns.js +29 -8
- package/dist/tools.js +181 -58
- package/package.json +1 -1
package/dist/decisions.js
CHANGED
|
@@ -136,7 +136,7 @@ export const componentDecisions = [
|
|
|
136
136
|
titleEn: 'Choosing a selection control',
|
|
137
137
|
question: '选项是平面少量、层级结构、多选标签,还是需要输入搜索?',
|
|
138
138
|
questionEn: 'Are options a small flat set, a hierarchy, multi-select tags, or searchable input?',
|
|
139
|
-
keywords: ['选择', '下拉', '多选', '树选择', '搜索选择', 'select', 'treeselect', 'autocomplete', 'dropdown'],
|
|
139
|
+
keywords: ['选择', '下拉', '多选', '树选择', '搜索选择', '级联', '列表选择', '按钮组', 'select', 'treeselect', 'autocomplete', 'cascadeselect', 'listbox', 'selectbutton', 'radio', 'dropdown'],
|
|
140
140
|
options: [
|
|
141
141
|
{
|
|
142
142
|
component: 'Select',
|
|
@@ -159,6 +159,34 @@ export const componentDecisions = [
|
|
|
159
159
|
avoidWhen: ['用户只能从固定枚举中选择', '不应该允许自由输入'],
|
|
160
160
|
avoidWhenEn: ['Users must choose from a fixed enum', 'Free input must not be allowed'],
|
|
161
161
|
},
|
|
162
|
+
{
|
|
163
|
+
component: 'SelectButton',
|
|
164
|
+
when: ['选项很少且需要全部露出', '单选或多选都适合按钮组'],
|
|
165
|
+
whenEn: ['Only a few options, and all of them should stay visible', 'Single or multiple selection works as a button group'],
|
|
166
|
+
avoidWhen: ['选项超过大约 5 个', '选项很长或来自远程搜索'],
|
|
167
|
+
avoidWhenEn: ['More than about five options', 'Labels are long or options come from remote search'],
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
component: 'Radio',
|
|
171
|
+
when: ['少量互斥选项需要和表单文案一起阅读', '一次只能选一个'],
|
|
172
|
+
whenEn: ['A few mutually exclusive options should be read with the form copy', 'Only one value can be selected'],
|
|
173
|
+
avoidWhen: ['多选', '选项很多需要收进弹出层'],
|
|
174
|
+
avoidWhenEn: ['Multiple selection', 'Too many options for an always-visible list'],
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
component: 'Listbox',
|
|
178
|
+
when: ['选项需要始终以列表展示', '可筛选,但不希望收成下拉框'],
|
|
179
|
+
whenEn: ['Options should stay visible as a list', 'Filtering is useful, but a collapsed dropdown is not'],
|
|
180
|
+
avoidWhen: ['页面空间紧、只需要一个闭合的选择框'],
|
|
181
|
+
avoidWhenEn: ['Space is tight and a closed select is enough'],
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
component: 'CascadeSelect',
|
|
185
|
+
when: ['值要按多级分栏逐级点选', '层级是路径而不是可勾选的树'],
|
|
186
|
+
whenEn: ['The value is chosen column by column through levels', 'The hierarchy is a path, not a checkable tree'],
|
|
187
|
+
avoidWhen: ['需要勾选树节点或搜索整棵树'],
|
|
188
|
+
avoidWhenEn: ['Users need to check tree nodes or search the whole tree'],
|
|
189
|
+
},
|
|
162
190
|
],
|
|
163
191
|
},
|
|
164
192
|
{
|
|
@@ -452,6 +480,113 @@ export const componentDecisions = [
|
|
|
452
480
|
},
|
|
453
481
|
],
|
|
454
482
|
},
|
|
483
|
+
{
|
|
484
|
+
id: 'status-label-choice',
|
|
485
|
+
title: '如何选择状态与标签',
|
|
486
|
+
titleEn: 'Choosing status, tag, chip, or badge',
|
|
487
|
+
question: '这是行内业务状态、分类标签、可移除实体,还是挂在控件上的数量?',
|
|
488
|
+
questionEn: 'Is this an inline business status, a category label, a removable entity, or a count on another control?',
|
|
489
|
+
keywords: ['行内状态', '状态点', '角标', '徽章', '可关闭标签', '芯片', 'status', 'badge', 'chip', 'tag', 'closable'],
|
|
490
|
+
options: [
|
|
491
|
+
{
|
|
492
|
+
component: 'Status',
|
|
493
|
+
when: ['表格单元格或标题旁的业务状态', '需要圆点或语义图标加短文案'],
|
|
494
|
+
whenEn: ['Business status in a table cell or beside a title', 'A dot or semantic icon plus a short label'],
|
|
495
|
+
avoidWhen: ['可关闭的分类标签', '挂在按钮上的数字'],
|
|
496
|
+
avoidWhenEn: ['A closable category label', 'A count attached to a button'],
|
|
497
|
+
},
|
|
498
|
+
{
|
|
499
|
+
component: 'Tag',
|
|
500
|
+
when: ['分类、筛选结果或可关闭标签', '需要比 Status 更像芯片的表面'],
|
|
501
|
+
whenEn: ['A category, filter result, or closable label', 'A chip-like surface rather than an inline status'],
|
|
502
|
+
avoidWhen: ['只是一行里的轻量状态', '带图片且代表一个可移除实体'],
|
|
503
|
+
avoidWhenEn: ['A lightweight inline status', 'An entity with an image that can be removed'],
|
|
504
|
+
},
|
|
505
|
+
{
|
|
506
|
+
component: 'Chip',
|
|
507
|
+
when: ['短实体信息,可带图标、图片和移除'],
|
|
508
|
+
whenEn: ['A short entity that may include an icon, image, and remove action'],
|
|
509
|
+
avoidWhen: ['纯状态色点', '只是分类色块'],
|
|
510
|
+
avoidWhenEn: ['A status dot only', 'A category color block only'],
|
|
511
|
+
},
|
|
512
|
+
{
|
|
513
|
+
component: 'Badge',
|
|
514
|
+
when: ['角标数量或圆点,附着在按钮、头像等控件上'],
|
|
515
|
+
whenEn: ['A count or dot attached to a button, avatar, or other control'],
|
|
516
|
+
avoidWhen: ['独立成行的状态文案'],
|
|
517
|
+
avoidWhenEn: ['A standalone status sentence'],
|
|
518
|
+
},
|
|
519
|
+
],
|
|
520
|
+
},
|
|
521
|
+
{
|
|
522
|
+
id: 'empty-result-choice',
|
|
523
|
+
title: '空态还是结果页',
|
|
524
|
+
titleEn: 'Empty state vs result page',
|
|
525
|
+
question: '这是没有数据,还是流程已经结束的成功、失败或 HTTP 状态?',
|
|
526
|
+
questionEn: 'Is there simply no data, or has a flow ended in success, failure, or an HTTP status?',
|
|
527
|
+
keywords: ['空状态', '无数据', '首次使用', '筛选无结果', '结果页', '404', '403', '500', 'empty', 'result'],
|
|
528
|
+
options: [
|
|
529
|
+
{
|
|
530
|
+
component: 'Empty',
|
|
531
|
+
when: ['列表没有行', '筛选没有命中', '首次使用,需要引导创建'],
|
|
532
|
+
whenEn: ['A list has no rows', 'Filters matched nothing', 'First use, and the next step is to create something'],
|
|
533
|
+
avoidWhen: ['接口失败', '无权限', '页面不存在', '提交成功回执'],
|
|
534
|
+
avoidWhenEn: ['The request failed', 'Permission was denied', 'The page does not exist', 'A submit-success receipt'],
|
|
535
|
+
},
|
|
536
|
+
{
|
|
537
|
+
component: 'Result',
|
|
538
|
+
when: ['提交成功或失败的终点页', '403 / 404 / 500 等阻断状态', '需要明确的下一步(返回、重试、回首页)'],
|
|
539
|
+
whenEn: ['A terminal success or failure page', 'A blocking 403 / 404 / 500 state', 'The next step must be explicit (back, retry, home)'],
|
|
540
|
+
avoidWhen: ['正常的无数据', '表格内部的空行'],
|
|
541
|
+
avoidWhenEn: ['A normal empty collection', 'The empty slot inside a table'],
|
|
542
|
+
},
|
|
543
|
+
],
|
|
544
|
+
},
|
|
545
|
+
{
|
|
546
|
+
id: 'action-menu-choice',
|
|
547
|
+
title: '如何选择菜单',
|
|
548
|
+
titleEn: 'Choosing a menu',
|
|
549
|
+
question: '这是按钮上的操作项、侧栏导航、右键菜单,还是全局命令搜索?',
|
|
550
|
+
questionEn: 'Is this an action menu on a trigger, sider navigation, a context menu, or a global command search?',
|
|
551
|
+
keywords: ['动作菜单', '右键', '右键菜单', '命令面板', '导航菜单', 'context menu', 'command menu', 'menubar', '上下文菜单'],
|
|
552
|
+
options: [
|
|
553
|
+
{
|
|
554
|
+
component: 'Dropdown',
|
|
555
|
+
when: ['从按钮或图标打开一组操作', '编辑、删除、更多'],
|
|
556
|
+
whenEn: ['Actions open from a button or icon', 'Edit, delete, or more'],
|
|
557
|
+
avoidWhen: ['表单里选一个枚举值', '常驻侧栏导航'],
|
|
558
|
+
avoidWhenEn: ['Choosing an enum in a form', 'Persistent sider navigation'],
|
|
559
|
+
},
|
|
560
|
+
{
|
|
561
|
+
component: 'Menu',
|
|
562
|
+
when: ['后台侧栏或页面内的常驻导航', '需要选中项与路由同步'],
|
|
563
|
+
whenEn: ['Persistent navigation in an admin sider or page', 'Selection should stay in sync with the route'],
|
|
564
|
+
avoidWhen: ['一次性操作菜单', '右键弹出'],
|
|
565
|
+
avoidWhenEn: ['A one-shot action menu', 'A right-click popup'],
|
|
566
|
+
},
|
|
567
|
+
{
|
|
568
|
+
component: 'ContextMenu',
|
|
569
|
+
when: ['在指针位置弹出,通常由右键触发'],
|
|
570
|
+
whenEn: ['The menu opens at the pointer, usually from a right-click'],
|
|
571
|
+
avoidWhen: ['可见的导航', '表单选择'],
|
|
572
|
+
avoidWhenEn: ['Visible navigation', 'Form selection'],
|
|
573
|
+
},
|
|
574
|
+
{
|
|
575
|
+
component: 'CommandMenu',
|
|
576
|
+
when: ['全局搜索命令或跳转', '键盘优先,例如 Cmd/Ctrl+K'],
|
|
577
|
+
whenEn: ['Global command search or jump', 'Keyboard-first, such as Cmd/Ctrl+K'],
|
|
578
|
+
avoidWhen: ['少量固定操作放在按钮旁即可'],
|
|
579
|
+
avoidWhenEn: ['A few fixed actions fit next to a button'],
|
|
580
|
+
},
|
|
581
|
+
{
|
|
582
|
+
component: 'Menubar',
|
|
583
|
+
when: ['顶部应用菜单,项很多且分组'],
|
|
584
|
+
whenEn: ['A top application menu with many grouped items'],
|
|
585
|
+
avoidWhen: ['侧栏只有一层链接'],
|
|
586
|
+
avoidWhenEn: ['The sider is a single level of links'],
|
|
587
|
+
},
|
|
588
|
+
],
|
|
589
|
+
},
|
|
455
590
|
];
|
|
456
591
|
export function findDecision(name) {
|
|
457
592
|
const key = name.trim().toLowerCase().replace(/[-_\s]/g, '');
|
package/dist/golden-pages.js
CHANGED
|
@@ -44,6 +44,41 @@ export const goldenPageCatalog = [
|
|
|
44
44
|
titleEn: 'Empty state golden sample',
|
|
45
45
|
patternId: 'empty-state',
|
|
46
46
|
},
|
|
47
|
+
{
|
|
48
|
+
id: 'detail-page',
|
|
49
|
+
file: 'detail-page.vue',
|
|
50
|
+
title: '详情页黄金样例',
|
|
51
|
+
titleEn: 'Detail page golden sample',
|
|
52
|
+
patternId: 'detail-page',
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: 'form-in-dialog',
|
|
56
|
+
file: 'form-in-dialog.vue',
|
|
57
|
+
title: '列表内弹窗表单黄金样例',
|
|
58
|
+
titleEn: 'List create/edit dialog golden sample',
|
|
59
|
+
patternId: 'form-in-dialog',
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
id: 'result-page',
|
|
63
|
+
file: 'result-page.vue',
|
|
64
|
+
title: '结果 / 阻断页黄金样例',
|
|
65
|
+
titleEn: 'Result / terminal page golden sample',
|
|
66
|
+
patternId: 'result-page',
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
id: 'settings-page',
|
|
70
|
+
file: 'settings-page.vue',
|
|
71
|
+
title: '设置页黄金样例',
|
|
72
|
+
titleEn: 'Settings page golden sample',
|
|
73
|
+
patternId: 'settings-page',
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
id: 'wizard-form',
|
|
77
|
+
file: 'wizard-form.vue',
|
|
78
|
+
title: '分步向导黄金样例',
|
|
79
|
+
titleEn: 'Wizard form golden sample',
|
|
80
|
+
patternId: 'wizard-form',
|
|
81
|
+
},
|
|
47
82
|
];
|
|
48
83
|
const pkgRoot = join(dirname(fileURLToPath(import.meta.url)), '..');
|
|
49
84
|
const bundledDir = join(pkgRoot, 'data/golden-pages');
|
package/dist/index.js
CHANGED
|
@@ -102,7 +102,7 @@ register('recommend_component', 'List, read, or recommend component selection gu
|
|
|
102
102
|
offset: z.number().int().min(0).optional(),
|
|
103
103
|
}, async (args) => handlers.recommendComponent(args));
|
|
104
104
|
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).', {
|
|
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).', {
|
|
106
106
|
page: z.string().min(1),
|
|
107
107
|
mode: z.string().optional(),
|
|
108
108
|
}, async (args) => handlers.getGoldenPage(args));
|
package/dist/page-snippets.js
CHANGED
|
@@ -61,7 +61,7 @@ const statusOptions = [
|
|
|
61
61
|
descriptionEn: 'Standard MTable placed directly in MPageContent without an extra Card wrapper.',
|
|
62
62
|
pageTypes: ['list'],
|
|
63
63
|
keywords: ['表格', 'table', 'pagination', 'paginator', 'empty', 'columns', 'rows'],
|
|
64
|
-
imports: ['MTable'],
|
|
64
|
+
imports: ['MTable', 'MEmpty', 'MButton'],
|
|
65
65
|
scriptSetup: `const columns = [
|
|
66
66
|
{ key: 'name', label: '名称' },
|
|
67
67
|
{ key: 'status', label: '状态' },
|
|
@@ -82,13 +82,19 @@ const loading = ref(false)`,
|
|
|
82
82
|
aria-label="数据列表"
|
|
83
83
|
>
|
|
84
84
|
<template #empty>
|
|
85
|
-
<
|
|
86
|
-
|
|
87
|
-
|
|
85
|
+
<MEmpty
|
|
86
|
+
title="还没有数据"
|
|
87
|
+
description="创建第一条记录后,这里会列出结果。"
|
|
88
|
+
icon="inbox"
|
|
89
|
+
>
|
|
90
|
+
<template #extra>
|
|
91
|
+
<MButton severity="primary">新建</MButton>
|
|
92
|
+
</template>
|
|
93
|
+
</MEmpty>
|
|
88
94
|
</template>
|
|
89
95
|
</MTable>`,
|
|
90
|
-
rules: ['表格直接放在 MPageContent 内', '空态用
|
|
91
|
-
rulesEn: ['Place the table directly in MPageContent', 'Use
|
|
96
|
+
rules: ['表格直接放在 MPageContent 内', '空态用 MEmpty,不要留空白或单行灰字'],
|
|
97
|
+
rulesEn: ['Place the table directly in MPageContent', 'Use MEmpty for zero-data states, not a blank or muted sentence'],
|
|
92
98
|
avoid: ['不要用 MCard 包裹 bordered MTable'],
|
|
93
99
|
avoidEn: ['Do not wrap a bordered MTable with MCard'],
|
|
94
100
|
},
|
|
@@ -103,54 +109,54 @@ const loading = ref(false)`,
|
|
|
103
109
|
imports: ['MSpace', 'MButton'],
|
|
104
110
|
template: `<template #cell-actions>
|
|
105
111
|
<MSpace>
|
|
106
|
-
<MButton severity="secondary" size="small">编辑</MButton>
|
|
107
|
-
<MButton severity="danger" size="small">删除</MButton>
|
|
112
|
+
<MButton severity="secondary" size="small" text>编辑</MButton>
|
|
113
|
+
<MButton severity="danger" size="small" text>删除</MButton>
|
|
108
114
|
</MSpace>
|
|
109
115
|
</template>`,
|
|
110
|
-
rules: ['行内操作用 size="small"', '危险操作使用 severity="danger" 并配合确认弹窗'],
|
|
111
|
-
rulesEn: ['Use size="small"
|
|
116
|
+
rules: ['行内操作用 size="small" + text/outlined,避免一排实心按钮', '危险操作使用 severity="danger" 并配合确认弹窗'],
|
|
117
|
+
rulesEn: ['Use size="small" with text/outlined row actions', 'Use severity="danger" with confirmation for destructive actions'],
|
|
112
118
|
avoid: ['不要用 Dropdown 代替明确的行内按钮组,除非操作很多'],
|
|
113
119
|
avoidEn: ['Do not replace explicit row buttons with Dropdown unless there are many actions'],
|
|
114
120
|
},
|
|
115
121
|
{
|
|
116
122
|
id: 'list-status-tag',
|
|
117
|
-
title: '
|
|
118
|
-
titleEn: 'Table
|
|
119
|
-
description: '
|
|
120
|
-
descriptionEn: '
|
|
123
|
+
title: '表格分类标签列 Tag',
|
|
124
|
+
titleEn: 'Table category tag cell',
|
|
125
|
+
description: '仅用于分类 / 可关闭标签。业务状态(启用/停用/在线)请用 list-status-dot / MStatus。',
|
|
126
|
+
descriptionEn: 'Category or closable chips only. Prefer list-status-dot / MStatus for business status.',
|
|
121
127
|
pageTypes: ['list'],
|
|
122
|
-
keywords: ['
|
|
128
|
+
keywords: ['分类', '标签', '可关闭', 'chip', 'category', 'tag-only'],
|
|
123
129
|
imports: ['MTag'],
|
|
124
|
-
template: `<template #cell-
|
|
130
|
+
template: `<template #cell-category="{ value }">
|
|
125
131
|
<MTag
|
|
126
|
-
:value="value
|
|
127
|
-
|
|
132
|
+
:value="String(value ?? '')"
|
|
133
|
+
severity="info"
|
|
128
134
|
/>
|
|
129
135
|
</template>`,
|
|
130
|
-
rules: ['分类/强调态用 MTag
|
|
131
|
-
rulesEn: ['Use MTag for
|
|
132
|
-
avoid: ['
|
|
133
|
-
avoidEn: ['Do not
|
|
136
|
+
rules: ['分类/强调态用 MTag;行内业务状态用 MStatus(list-status-dot)', '不要用 Button 颜色表达状态'],
|
|
137
|
+
rulesEn: ['Use MTag for categories; use MStatus for business status (list-status-dot)', 'Do not use Button colors for status'],
|
|
138
|
+
avoid: ['不要在 #cell-status 里默认用 MTag 表示启用/停用'],
|
|
139
|
+
avoidEn: ['Do not default #cell-status to MTag for active/inactive'],
|
|
134
140
|
},
|
|
135
141
|
{
|
|
136
142
|
id: 'list-status-dot',
|
|
137
143
|
title: '表格状态列 Status',
|
|
138
144
|
titleEn: 'Table status dot cell',
|
|
139
|
-
description: '在 #cell-status 中用 MStatus
|
|
140
|
-
descriptionEn: 'Render lightweight business status with MStatus in #cell-status.',
|
|
145
|
+
description: '在 #cell-status 中用 MStatus 展示轻量业务状态(默认首选)。',
|
|
146
|
+
descriptionEn: 'Render lightweight business status with MStatus in #cell-status (default preference).',
|
|
141
147
|
pageTypes: ['list'],
|
|
142
|
-
keywords: ['状态', 'status', 'dot', 'cell-status', '在线'],
|
|
148
|
+
keywords: ['状态', 'status', 'dot', 'cell-status', '在线', '启用', '停用', '业务状态'],
|
|
143
149
|
imports: ['MStatus'],
|
|
144
150
|
template: `<template #cell-status="{ value }">
|
|
145
151
|
<MStatus
|
|
146
|
-
:label="value === 'online' ? '
|
|
147
|
-
:severity="value === 'online' ? 'success' : 'secondary'"
|
|
152
|
+
:label="value === 'active' || value === 'online' ? '启用' : '停用'"
|
|
153
|
+
:severity="value === 'active' || value === 'online' ? 'success' : 'secondary'"
|
|
148
154
|
/>
|
|
149
155
|
</template>`,
|
|
150
|
-
rules: ['
|
|
151
|
-
rulesEn: ['Prefer MStatus for lightweight inline status', 'Do not use Button colors for status'],
|
|
152
|
-
avoid: ['不要用裸文本颜色区分状态'],
|
|
153
|
-
avoidEn: ['Do not rely on raw text color for status'],
|
|
156
|
+
rules: ['行内轻量业务状态优先 MStatus', '不要用 Button 颜色表达状态'],
|
|
157
|
+
rulesEn: ['Prefer MStatus for lightweight inline business status', 'Do not use Button colors for status'],
|
|
158
|
+
avoid: ['不要用裸文本颜色区分状态', '不要默认改用 MTag 表达启用/停用'],
|
|
159
|
+
avoidEn: ['Do not rely on raw text color for status', 'Do not default to MTag for active/inactive'],
|
|
154
160
|
},
|
|
155
161
|
{
|
|
156
162
|
id: 'empty-block',
|
|
@@ -313,18 +319,22 @@ const submitting = ref(false)`,
|
|
|
313
319
|
descriptionEn: 'Recent records in a small MTable inside MCard.',
|
|
314
320
|
pageTypes: ['dashboard'],
|
|
315
321
|
keywords: ['recent', '最近', 'table', 'card', 'list'],
|
|
316
|
-
imports: ['MCard', 'MTable', '
|
|
322
|
+
imports: ['MCard', 'MTable', 'MStatus'],
|
|
317
323
|
scriptSetup: `const recentColumns = [
|
|
318
324
|
{ key: 'id', label: '工单号', width: 96 },
|
|
319
325
|
{ key: 'title', label: '标题' },
|
|
320
|
-
{ key: 'status', label: '状态', width:
|
|
326
|
+
{ key: 'status', label: '状态', width: 110 },
|
|
321
327
|
]
|
|
322
328
|
const recentRows = ref<Record<string, unknown>[]>([])`,
|
|
323
329
|
template: `<MCard title="最近工单">
|
|
324
|
-
<MTable :columns="recentColumns" :rows="recentRows" size="small" :paginator="false" bordered
|
|
330
|
+
<MTable :columns="recentColumns" :rows="recentRows" size="small" :paginator="false" bordered row-key="id">
|
|
331
|
+
<template #cell-status="{ value }">
|
|
332
|
+
<MStatus :label="String(value ?? '')" severity="secondary" />
|
|
333
|
+
</template>
|
|
334
|
+
</MTable>
|
|
325
335
|
</MCard>`,
|
|
326
|
-
rules: ['明细列表放 MCard 内合理', 'size="small" 适合卡片内表格'],
|
|
327
|
-
rulesEn: ['Detail lists inside MCard are appropriate', 'Use size="small" for in-card tables'],
|
|
336
|
+
rules: ['明细列表放 MCard 内合理', 'size="small" 适合卡片内表格', '行内业务状态用 MStatus'],
|
|
337
|
+
rulesEn: ['Detail lists inside MCard are appropriate', 'Use size="small" for in-card tables', 'Use MStatus for row business status'],
|
|
328
338
|
avoid: ['卡片内表格不要再外包一层 MPageContent'],
|
|
329
339
|
avoidEn: ['Do not wrap in-card tables with another MPageContent'],
|
|
330
340
|
},
|
package/dist/patterns.d.ts
CHANGED
|
@@ -48,7 +48,7 @@ export declare const designRules: {
|
|
|
48
48
|
readonly spacing: "--m-space-*";
|
|
49
49
|
readonly radius: "--m-radius-sm/md/lg";
|
|
50
50
|
readonly typography: "--m-font-size-xs/sm/md/lg";
|
|
51
|
-
readonly motion: "--m-motion-fast/normal";
|
|
51
|
+
readonly motion: "--m-motion-fast/normal/enter/exit/distance/ease; intensity via useMotion (data-m-motion); enter/exit via motion.transitions / transition prop (fade, scale-fade, zoom, slide-*, dialog, drawer, …)";
|
|
52
52
|
};
|
|
53
53
|
readonly composition: {
|
|
54
54
|
readonly workflow: readonly ["For full pages: recommend_page → get_golden_page → get_design_rules.", "For local edits: get_page_snippet(section) for filters/toolbar/form-actions/KPI/scrollable-panel blocks.", "Use MLayout fillViewport as the app shell; put MPageContent inside MLayoutContent.", "Prefer MPage* components over scoped CSS for filters, toolbars, headers, form surfaces, and KPI cards.", "Use MSpace or MFlex for control groups inside MPageFilters; use MPageToolbar for title + primary action."];
|
|
@@ -68,7 +68,7 @@ export declare const designRules: {
|
|
|
68
68
|
readonly do: readonly ["MLayoutHeader bordered (default)", "MPageFilters for filter regions", "MPageSection variant=\"form\" for forms", "MTable bordered when table needs grid lines"];
|
|
69
69
|
readonly avoid: readonly ["MCard wrapping MTable that is already bordered", "Nested MCard with bordered=true on both levels", "Hand-written section borders when MPageFilters or MPageSection applies", "Double borders on filter + table wrapper"];
|
|
70
70
|
};
|
|
71
|
-
readonly goldenPages: readonly ["list-page", "form-page", "dashboard-page", "login-page", "landing-page", "empty-state"];
|
|
71
|
+
readonly goldenPages: readonly ["list-page", "form-page", "dashboard-page", "login-page", "landing-page", "empty-state", "detail-page", "form-in-dialog", "result-page", "settings-page", "wizard-form"];
|
|
72
72
|
};
|
|
73
73
|
readonly actions: {
|
|
74
74
|
readonly primary: {
|
|
@@ -92,13 +92,15 @@ export declare const designRules: {
|
|
|
92
92
|
readonly status: {
|
|
93
93
|
readonly preferred: "MStatus";
|
|
94
94
|
readonly chip: "MTag";
|
|
95
|
+
readonly badge: "MBadge";
|
|
96
|
+
readonly entity: "MChip";
|
|
95
97
|
readonly mapping: {
|
|
96
98
|
readonly active: "success";
|
|
97
99
|
readonly pending: "warn";
|
|
98
100
|
readonly disabled: "secondary";
|
|
99
101
|
readonly error: "danger";
|
|
100
102
|
};
|
|
101
|
-
readonly note: "行内轻量状态用 MStatus
|
|
103
|
+
readonly note: "行内轻量状态用 MStatus;分类或可关闭标签用 MTag;数量角标用 MBadge;可移除实体用 MChip";
|
|
102
104
|
};
|
|
103
105
|
readonly feedback: {
|
|
104
106
|
readonly default: "message";
|
|
@@ -126,9 +128,9 @@ export declare const designRules: {
|
|
|
126
128
|
readonly api: "field errorMessage | token-styled role=alert";
|
|
127
129
|
readonly when: readonly ["登录/表单区常驻错误:优先字段 errorMessage", "表单级总结:使用 --m-* 样式的 role=alert 条(见 login-page 黄金样例)", "MMessage 组件当前主要为 message 服务宿主,勿臆造 severity 子节点 API"];
|
|
128
130
|
};
|
|
129
|
-
readonly doc: "
|
|
131
|
+
readonly doc: "design-kit/.agents/skills/morya-ui-pages/references/feedback.md";
|
|
130
132
|
};
|
|
131
|
-
readonly global: readonly ["优先使用组件库组件和 --m-* Token", "操作反馈默认 message;仅一行文案时优先于 toast", "空态用 MEmpty;结果/阻断页用 MResult;行内状态优先 MStatus", "图标按钮建议提供 aria-label;表单字段建议有可见 label", "浮层默认 Teleport 到 body;有明确布局约束时再改 appendTo", "优先使用组件 documented variant,少写深层 CSS 覆盖"];
|
|
133
|
+
readonly global: readonly ["优先使用组件库组件和 --m-* Token", "操作反馈默认 message;仅一行文案时优先于 toast", "空态用 MEmpty;结果/阻断页用 MResult;行内状态优先 MStatus", "图标按钮建议提供 aria-label;表单字段建议有可见 label", "浮层默认 Teleport 到 body;有明确布局约束时再改 appendTo", "优先使用组件 documented variant,少写深层 CSS 覆盖", "动效强度用 useMotion;进出场预设用 motion.transitions / 组件 transition,勿臆造动画库 API"];
|
|
132
134
|
};
|
|
133
135
|
export declare function findPattern(input: string): PagePattern | undefined;
|
|
134
136
|
export declare function scorePattern(pattern: PagePattern, query: string): number;
|
package/dist/patterns.js
CHANGED
|
@@ -186,6 +186,7 @@ export const pagePatterns = [
|
|
|
186
186
|
'不要为 ≤8 字段的列表 CRUD 再开 /entities/new 路由',
|
|
187
187
|
'不要用 Toast 代替单行 message 回执',
|
|
188
188
|
],
|
|
189
|
+
goldenPage: 'form-in-dialog',
|
|
189
190
|
},
|
|
190
191
|
{
|
|
191
192
|
id: 'dashboard',
|
|
@@ -260,6 +261,7 @@ export const pagePatterns = [
|
|
|
260
261
|
styleRules: ['开关用于二元启用状态,不用 Select 模拟开关', '保存使用 primary,恢复默认值使用 secondary 或 text', '危险配置需要明确说明影响范围并使用 danger 语义', '使用 MConfigProvider 统一 size、density 和 locale'],
|
|
261
262
|
interactionRules: ['显示未保存修改状态', '保存成功使用 message.success 单行反馈;仅有 summary+detail 时用 toast', '保存失败保留输入并显示字段或页面级错误', '切换 Tab 不应意外丢失未保存输入'],
|
|
262
263
|
avoid: ['不要把所有设置塞进一个超长表单', '不要用 placeholder 代替配置项 label', '不要隐藏影响范围较大的配置说明'],
|
|
264
|
+
goldenPage: 'settings-page',
|
|
263
265
|
},
|
|
264
266
|
{
|
|
265
267
|
id: 'empty-state',
|
|
@@ -391,6 +393,7 @@ export const pagePatterns = [
|
|
|
391
393
|
styleRules: ['每一步只承载一个清晰目标', '步骤状态使用 Stepper,不用 Button 颜色模拟', '长流程使用 Card 分组但避免多层嵌套', '危险退出使用 ConfirmDialog 或 Dialog'],
|
|
392
394
|
interactionRules: ['进入下一步前只校验当前步骤', '返回上一步保留输入', '完成前展示摘要或确认', '刷新和离开时处理未完成状态'],
|
|
393
395
|
avoid: ['不要把所有字段一次性隐藏在一个超长页面', '不要允许跳过有前置依赖的步骤', '不要让完成按钮在每一步都使用相同文案'],
|
|
396
|
+
goldenPage: 'wizard-form',
|
|
394
397
|
},
|
|
395
398
|
{
|
|
396
399
|
id: 'detail-page',
|
|
@@ -441,6 +444,7 @@ export const pagePatterns = [
|
|
|
441
444
|
'不要使用颜色作为唯一的状态表达方式',
|
|
442
445
|
'不要让详情页的操作按钮分散在多个无关区域',
|
|
443
446
|
],
|
|
447
|
+
goldenPage: 'detail-page',
|
|
444
448
|
},
|
|
445
449
|
{
|
|
446
450
|
id: 'result-page',
|
|
@@ -471,9 +475,9 @@ export const pagePatterns = [
|
|
|
471
475
|
structure: [
|
|
472
476
|
'MPageContent (optional shell)',
|
|
473
477
|
'└── MResult',
|
|
474
|
-
' ├── status icon',
|
|
478
|
+
' ├── status icon / illustration',
|
|
475
479
|
' ├── title + description',
|
|
476
|
-
' └── #
|
|
480
|
+
' └── #footer primary + optional secondary MButton',
|
|
477
481
|
],
|
|
478
482
|
layout: {
|
|
479
483
|
page: '结果在内容区居中,不一定占满整屏',
|
|
@@ -482,8 +486,8 @@ export const pagePatterns = [
|
|
|
482
486
|
},
|
|
483
487
|
styleRules: [
|
|
484
488
|
'使用 MResult 的 status 语义,不要手写大图标+文案拼盘',
|
|
485
|
-
'成功/失败用对应
|
|
486
|
-
'主恢复动作使用 primary MButton,次动作使用 secondary 或 text',
|
|
489
|
+
'成功/失败用对应 status;403/404/500 用 HTTP 类 status',
|
|
490
|
+
'主恢复动作使用 primary MButton,次动作使用 secondary 或 text(放在 #footer,不是 #extra)',
|
|
487
491
|
'颜色与间距使用 --m-* Token',
|
|
488
492
|
],
|
|
489
493
|
interactionRules: [
|
|
@@ -495,7 +499,9 @@ export const pagePatterns = [
|
|
|
495
499
|
'不要用 Empty 表达 403/404/提交失败',
|
|
496
500
|
'不要只用颜色表达结果而无标题文案',
|
|
497
501
|
'不要在结果页堆砌无关营销内容',
|
|
502
|
+
'不要使用已移除的 #extra 插槽;操作用 #footer',
|
|
498
503
|
],
|
|
504
|
+
goldenPage: 'result-page',
|
|
499
505
|
},
|
|
500
506
|
];
|
|
501
507
|
/** Advisory page-writing standards for MCP and human authors — not enforced blockers. */
|
|
@@ -639,7 +645,7 @@ export const designRules = {
|
|
|
639
645
|
spacing: '--m-space-*',
|
|
640
646
|
radius: '--m-radius-sm/md/lg',
|
|
641
647
|
typography: '--m-font-size-xs/sm/md/lg',
|
|
642
|
-
motion: '--m-motion-fast/normal',
|
|
648
|
+
motion: '--m-motion-fast/normal/enter/exit/distance/ease; intensity via useMotion (data-m-motion); enter/exit via motion.transitions / transition prop (fade, scale-fade, zoom, slide-*, dialog, drawer, …)',
|
|
643
649
|
},
|
|
644
650
|
composition: {
|
|
645
651
|
workflow: [
|
|
@@ -682,7 +688,19 @@ export const designRules = {
|
|
|
682
688
|
'Double borders on filter + table wrapper',
|
|
683
689
|
],
|
|
684
690
|
},
|
|
685
|
-
goldenPages: [
|
|
691
|
+
goldenPages: [
|
|
692
|
+
'list-page',
|
|
693
|
+
'form-page',
|
|
694
|
+
'dashboard-page',
|
|
695
|
+
'login-page',
|
|
696
|
+
'landing-page',
|
|
697
|
+
'empty-state',
|
|
698
|
+
'detail-page',
|
|
699
|
+
'form-in-dialog',
|
|
700
|
+
'result-page',
|
|
701
|
+
'settings-page',
|
|
702
|
+
'wizard-form',
|
|
703
|
+
],
|
|
686
704
|
},
|
|
687
705
|
actions: {
|
|
688
706
|
primary: { component: 'MButton', props: ['severity omitted or primary'] },
|
|
@@ -693,8 +711,10 @@ export const designRules = {
|
|
|
693
711
|
status: {
|
|
694
712
|
preferred: 'MStatus',
|
|
695
713
|
chip: 'MTag',
|
|
714
|
+
badge: 'MBadge',
|
|
715
|
+
entity: 'MChip',
|
|
696
716
|
mapping: { active: 'success', pending: 'warn', disabled: 'secondary', error: 'danger' },
|
|
697
|
-
note: '行内轻量状态用 MStatus
|
|
717
|
+
note: '行内轻量状态用 MStatus;分类或可关闭标签用 MTag;数量角标用 MBadge;可移除实体用 MChip',
|
|
698
718
|
},
|
|
699
719
|
feedback: {
|
|
700
720
|
default: 'message',
|
|
@@ -726,7 +746,7 @@ export const designRules = {
|
|
|
726
746
|
'MMessage 组件当前主要为 message 服务宿主,勿臆造 severity 子节点 API',
|
|
727
747
|
],
|
|
728
748
|
},
|
|
729
|
-
doc: '
|
|
749
|
+
doc: 'design-kit/.agents/skills/morya-ui-pages/references/feedback.md',
|
|
730
750
|
},
|
|
731
751
|
global: [
|
|
732
752
|
'优先使用组件库组件和 --m-* Token',
|
|
@@ -735,6 +755,7 @@ export const designRules = {
|
|
|
735
755
|
'图标按钮建议提供 aria-label;表单字段建议有可见 label',
|
|
736
756
|
'浮层默认 Teleport 到 body;有明确布局约束时再改 appendTo',
|
|
737
757
|
'优先使用组件 documented variant,少写深层 CSS 覆盖',
|
|
758
|
+
'动效强度用 useMotion;进出场预设用 motion.transitions / 组件 transition,勿臆造动画库 API',
|
|
738
759
|
],
|
|
739
760
|
};
|
|
740
761
|
export function findPattern(input) {
|