@morya-ui/mcp 0.2.4 → 0.2.6
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 +1 -1
- package/data/catalog.json +1835 -1021
- package/data/example-coverage.json +98 -5
- package/data/golden-pages/dashboard-page.vue +1 -1
- package/data/golden-pages/empty-state.vue +66 -0
- package/data/golden-pages/form-page.vue +7 -3
- package/data/golden-pages/landing-page.vue +328 -0
- package/data/golden-pages/list-page.vue +29 -8
- package/data/golden-pages/login-page.vue +191 -0
- package/dist/__tests__/resources.test.js +1 -1
- package/dist/__tests__/tools.test.js +28 -0
- package/dist/decisions.js +59 -4
- package/dist/golden-pages.js +22 -1
- package/dist/index.js +1 -1
- package/dist/page-snippets.js +108 -16
- package/dist/patterns.d.ts +17 -5
- package/dist/patterns.js +241 -35
- package/dist/resources.d.ts +1 -1
- package/dist/tools.js +82 -17
- package/package.json +1 -1
package/dist/patterns.js
CHANGED
|
@@ -40,32 +40,35 @@ export const pagePatterns = [
|
|
|
40
40
|
{ component: 'Select', role: '枚举或状态筛选' },
|
|
41
41
|
{ component: 'Button', role: '查询、新增和行操作' },
|
|
42
42
|
{ component: 'Table', role: '数据展示', reason: 'paginator 启用内置分页' },
|
|
43
|
-
{ component: 'Tag', role: '
|
|
44
|
-
{ component: '
|
|
43
|
+
{ component: 'Tag', role: '业务状态展示(分类/可关闭)', required: false },
|
|
44
|
+
{ component: 'Status', role: '行内状态圆点+文案', required: false },
|
|
45
|
+
{ component: 'Dialog', role: '新建/编辑短表单(首选)或删除确认', required: false },
|
|
45
46
|
],
|
|
46
47
|
structure: [
|
|
47
48
|
'MConfigProvider',
|
|
48
|
-
'└── MLayout (has-sider)',
|
|
49
|
-
' ├── MLayoutSider → MMenu
|
|
49
|
+
'└── MLayout (has-sider fill-viewport)',
|
|
50
|
+
' ├── MLayoutSider → MMenu',
|
|
50
51
|
' └── MLayout',
|
|
51
52
|
' ├── MLayoutHeader → MBreadcrumb',
|
|
52
53
|
' └── MLayoutContent',
|
|
53
54
|
' └── MPageContent',
|
|
54
55
|
' ├── MPageFilters (MSpace + MInput + MSelect + MButton)',
|
|
55
56
|
' ├── MPageToolbar (title + primary action)',
|
|
56
|
-
'
|
|
57
|
+
' ├── MTable (paginator, #cell-* slots, #empty)',
|
|
58
|
+
' └── MDialog (create/edit MForm, optional)',
|
|
57
59
|
],
|
|
58
60
|
layout: {
|
|
59
61
|
page: 'MLayout fillViewport + has-sider;MLayoutContent 内用 MPageContent 堆叠区块',
|
|
60
62
|
header: 'MLayoutHeader 放面包屑;标题与主操作用 MPageToolbar',
|
|
61
63
|
filters: 'MPageFilters 包裹筛选控件;内部 MSpace wrap 或 MFlex',
|
|
62
64
|
data: 'MTable 直接放在 MPageContent 内,不要额外 MCard;paginator + #empty',
|
|
65
|
+
form: '列表新建/编辑默认 MDialog + MForm,不要默认拆独立表单路由',
|
|
63
66
|
},
|
|
64
67
|
styleRules: [
|
|
65
68
|
'新增、保存等主操作使用 primary MButton',
|
|
66
69
|
'查询等次要操作使用 secondary 或 outlined MButton',
|
|
67
|
-
'删除使用 danger MButton,并配合
|
|
68
|
-
'
|
|
70
|
+
'删除使用 danger MButton,并配合 MConfirmDialog',
|
|
71
|
+
'业务状态优先使用 MStatus(圆点+文案)或 MTag(分类标签),不使用 Button severity 表达状态',
|
|
69
72
|
'间距、颜色、圆角优先使用 --m-* 设计令牌,不直接硬编码色板',
|
|
70
73
|
],
|
|
71
74
|
interactionRules: [
|
|
@@ -73,10 +76,12 @@ export const pagePatterns = [
|
|
|
73
76
|
'加载中禁用重复提交并显示 loading 状态',
|
|
74
77
|
'空数据使用 MTable #empty 插槽或 DataView 空态,不渲染空白表格',
|
|
75
78
|
'行级危险操作必须有确认反馈',
|
|
79
|
+
'新建/编辑短表单用 MDialog,保存成功 message.success 并关闭弹窗',
|
|
76
80
|
'CRUD 成功/删除等单行回执使用 message API,不要默认 Toast',
|
|
77
81
|
],
|
|
78
82
|
avoid: [
|
|
79
83
|
'不要用 MDialog 承载完整列表页',
|
|
84
|
+
'不要为列表短 CRUD 默认生成 /entities/new 独立表单路由',
|
|
80
85
|
'不要用 MButton 代替业务状态标签',
|
|
81
86
|
'不要为每个筛选字段手写一套不一致的 label 和间距',
|
|
82
87
|
'不要用 toast.add({ summary }) 代替 message.success 表达单行操作结果',
|
|
@@ -138,10 +143,50 @@ export const pagePatterns = [
|
|
|
138
143
|
],
|
|
139
144
|
avoid: [
|
|
140
145
|
'不要绕过 MForm 直接手写不一致的字段校验状态',
|
|
141
|
-
'
|
|
146
|
+
'列表上的短新建/编辑优先 MDialog,不要默认拆成独立路由表单页',
|
|
147
|
+
'不要把完整多分组编辑流程塞进过窄的 Dialog;字段很多时用独立表单页或 MDrawer',
|
|
142
148
|
'不要使用 placeholder 代替 label',
|
|
143
149
|
],
|
|
144
150
|
},
|
|
151
|
+
{
|
|
152
|
+
id: 'form-in-dialog',
|
|
153
|
+
title: '列表内新建 / 编辑弹窗',
|
|
154
|
+
titleEn: 'Create or edit in a dialog from a list',
|
|
155
|
+
description: '从列表触发的短表单 CRUD:留在列表页,用 MDialog 承载 MForm。',
|
|
156
|
+
descriptionEn: 'Short CRUD forms launched from a list: keep the list page and host MForm in MDialog.',
|
|
157
|
+
keywords: ['弹窗表单', '新建弹窗', '编辑弹窗', '列表新建', 'dialog form', 'modal form', 'inline create', 'inline edit'],
|
|
158
|
+
components: [
|
|
159
|
+
{ component: 'Dialog', role: '承载短表单' },
|
|
160
|
+
{ component: 'Form', role: '声明式校验' },
|
|
161
|
+
{ component: 'FormItem', role: '字段标签与校验', required: false },
|
|
162
|
+
{ component: 'Input', role: '文本字段' },
|
|
163
|
+
{ component: 'Select', role: '枚举字段', required: false },
|
|
164
|
+
{ component: 'Button', role: '页脚保存/取消' },
|
|
165
|
+
],
|
|
166
|
+
structure: [
|
|
167
|
+
'列表页(list-page)',
|
|
168
|
+
'└── MDialog',
|
|
169
|
+
' ├── MForm + 字段',
|
|
170
|
+
' └── #footer → 取消 / 保存',
|
|
171
|
+
],
|
|
172
|
+
layout: {
|
|
173
|
+
host: '留在列表页,不另开路由',
|
|
174
|
+
dialog: 'MDialog 承载短表单;宽度约 28–36rem',
|
|
175
|
+
form: 'MForm + 字段;页脚用 Dialog #footer',
|
|
176
|
+
},
|
|
177
|
+
styleRules: [
|
|
178
|
+
'Dialog 宽度约 28–36rem;树/长内容区可限高滚动',
|
|
179
|
+
'页脚用 Dialog #footer,不要把主按钮只放在表单流末尾却看不见',
|
|
180
|
+
],
|
|
181
|
+
interactionRules: [
|
|
182
|
+
'保存成功后关闭 Dialog 并 message.success',
|
|
183
|
+
'取消或关闭丢弃未保存草稿(或按产品要求提示)',
|
|
184
|
+
],
|
|
185
|
+
avoid: [
|
|
186
|
+
'不要为 ≤8 字段的列表 CRUD 再开 /entities/new 路由',
|
|
187
|
+
'不要用 Toast 代替单行 message 回执',
|
|
188
|
+
],
|
|
189
|
+
},
|
|
145
190
|
{
|
|
146
191
|
id: 'dashboard',
|
|
147
192
|
title: '仪表盘 / 监控工作台',
|
|
@@ -163,7 +208,10 @@ export const pagePatterns = [
|
|
|
163
208
|
{ component: 'GridItem', role: '栅格子项' },
|
|
164
209
|
{ component: 'Card', role: '图表与列表明细分区' },
|
|
165
210
|
{ component: 'Icon', role: 'KPI 图标', required: false },
|
|
166
|
-
{ component: 'Tag', role: '
|
|
211
|
+
{ component: 'Tag', role: '分类标签和告警级别', required: false },
|
|
212
|
+
{ component: 'Status', role: '行内状态圆点+文案', required: false },
|
|
213
|
+
{ component: 'Empty', role: '明细表空态', required: false },
|
|
214
|
+
{ component: 'Result', role: '接口失败结果区', required: false },
|
|
167
215
|
{ component: 'Table', role: '明细数据', required: false },
|
|
168
216
|
{ component: 'Button', role: '刷新、查看详情和快捷操作', required: false },
|
|
169
217
|
{ component: 'Skeleton', role: '初始加载占位', required: false },
|
|
@@ -186,8 +234,8 @@ export const pagePatterns = [
|
|
|
186
234
|
content: '图表和明细按业务优先级排列,重要告警靠前',
|
|
187
235
|
responsive: '多列网格在窄屏降为单列或两列',
|
|
188
236
|
},
|
|
189
|
-
styleRules: ['指标使用 text hierarchy,不用大面积高饱和背景', '
|
|
190
|
-
interactionRules: ['首次加载使用 Skeleton', '
|
|
237
|
+
styleRules: ['指标使用 text hierarchy,不用大面积高饱和背景', '状态用 MStatus / 告警级别用 MTag', '图表容器使用 Card 和 --m-* Token 保持表面一致', '实时刷新操作使用 secondary 或 text MButton'],
|
|
238
|
+
interactionRules: ['首次加载使用 Skeleton', '无数据用 MEmpty;接口错误用 MResult 或 Message', '刷新中禁用重复请求并保留上次数据', '告警需要可追踪到详情或处理入口'],
|
|
191
239
|
avoid: ['不要把所有信息都做成 KPI 卡片', '不要使用颜色作为唯一告警表达', '不要用固定像素绝对定位实现响应式布局'],
|
|
192
240
|
},
|
|
193
241
|
{
|
|
@@ -219,17 +267,29 @@ export const pagePatterns = [
|
|
|
219
267
|
titleEn: 'Empty state / no results page',
|
|
220
268
|
description: '用于首次使用、搜索无结果、资源已清空或暂时没有内容的场景。',
|
|
221
269
|
descriptionEn: 'For first use, no search results, empty resources, or temporarily unavailable content.',
|
|
270
|
+
goldenPage: 'docs/golden-pages/empty-state.vue',
|
|
222
271
|
keywords: ['空状态', '无数据', '无结果', '首次使用', 'empty', 'no results', 'no data', 'zero state'],
|
|
223
272
|
components: [
|
|
224
|
-
{ component: '
|
|
273
|
+
{ component: 'PageContent', role: '承载空态的内容区', required: false },
|
|
274
|
+
{ component: 'PageToolbar', role: '保留列表上下文标题与主操作', required: false },
|
|
275
|
+
{ component: 'Empty', role: '空态主体:图标、标题、说明与操作', required: true },
|
|
276
|
+
{ component: 'Icon', role: '空态视觉锚点(也可由 Empty icon 提供)', required: false },
|
|
225
277
|
{ component: 'Button', role: '创建、重置筛选或返回', required: false },
|
|
278
|
+
{ component: 'Table', role: '表格 #empty 插槽承载', required: false },
|
|
279
|
+
{ component: 'DataView', role: '列表或网格空态承载', required: false },
|
|
226
280
|
{ component: 'Card', role: '在列表或详情容器中承载空态', required: false },
|
|
227
281
|
],
|
|
228
|
-
structure: [
|
|
282
|
+
structure: [
|
|
283
|
+
'MPageContent (or MTable #empty)',
|
|
284
|
+
'└── MEmpty',
|
|
285
|
+
' ├── icon / image',
|
|
286
|
+
' ├── title + description',
|
|
287
|
+
' └── #extra primary + optional secondary MButton',
|
|
288
|
+
],
|
|
229
289
|
layout: { page: '空态在当前内容区域内居中,不一定占满整个视口', content: '说明原因、下一步动作和可选的辅助信息', responsive: '保持按钮可触达,窄屏垂直排列' },
|
|
230
|
-
styleRules: ['空态文案使用中性、可行动的语气', '主恢复动作使用 primary MButton,次动作使用 text', '不要用错误色表达正常的无数据状态'],
|
|
290
|
+
styleRules: ['空态文案使用中性、可行动的语气', '主恢复动作使用 primary MButton,次动作使用 text', '不要用错误色表达正常的无数据状态', '颜色与间距使用 --m-* Token'],
|
|
231
291
|
interactionRules: ['搜索无结果提供清除筛选或修改条件入口', '首次使用提供创建或导入入口', '保留页面标题和上下文导航'],
|
|
232
|
-
avoid: ['不要只显示“暂无数据”而没有下一步', '不要把无数据和接口错误混为一谈'],
|
|
292
|
+
avoid: ['不要只显示“暂无数据”而没有下一步', '不要把无数据和接口错误混为一谈', '不要堆 emoji 或促销贴纸'],
|
|
233
293
|
},
|
|
234
294
|
{
|
|
235
295
|
id: 'auth-page',
|
|
@@ -237,22 +297,79 @@ export const pagePatterns = [
|
|
|
237
297
|
titleEn: 'Login / authentication page',
|
|
238
298
|
description: '用于登录、注册、找回密码和二次认证流程。',
|
|
239
299
|
descriptionEn: 'For login, registration, password recovery, and second-factor authentication flows.',
|
|
300
|
+
goldenPage: 'docs/golden-pages/login-page.vue',
|
|
240
301
|
keywords: ['登录', '注册', '认证', '密码', '验证码', 'login', 'register', 'authentication', 'password', 'otp'],
|
|
241
302
|
components: [
|
|
242
|
-
{ component: '
|
|
303
|
+
{ component: 'ConfigProvider', role: '应用根包裹' },
|
|
243
304
|
{ component: 'Form', role: '认证字段校验' },
|
|
244
|
-
{ component: 'FormItem', role: '字段标签和错误信息', required: false
|
|
245
|
-
{ component: 'Input', role: '
|
|
305
|
+
{ component: 'FormItem', role: '字段标签和错误信息', required: false },
|
|
306
|
+
{ component: 'Input', role: '账号字段' },
|
|
307
|
+
{ component: 'InputPassword', role: '密码字段' },
|
|
246
308
|
{ component: 'InputOtp', role: '验证码字段', required: false },
|
|
247
309
|
{ component: 'Button', role: '提交认证' },
|
|
248
|
-
{ component: '
|
|
310
|
+
{ component: 'Space', role: '主次操作排列', required: false },
|
|
249
311
|
],
|
|
250
|
-
structure: [
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
312
|
+
structure: [
|
|
313
|
+
'MConfigProvider',
|
|
314
|
+
'└── Split or centered shell',
|
|
315
|
+
' ├── Brand panel (optional Express craft)',
|
|
316
|
+
' └── Form panel',
|
|
317
|
+
' ├── Persistent form-level alert (token-styled) when needed',
|
|
318
|
+
' ├── MForm + MInput / MInputPassword',
|
|
319
|
+
' └── Submit + recovery links',
|
|
320
|
+
],
|
|
321
|
+
layout: { page: '认证表单使用窄 max-width 并保持视觉聚焦', form: '字段垂直排列', feedback: '字段错误用 errorMessage;表单级常驻错误用 token 告警条,勿默认 Toast' },
|
|
322
|
+
styleRules: ['密码字段使用 MInputPassword', '提交按钮使用 primary', '品牌视觉可定制,但颜色仍映射 --m-*', '不要用高对比装饰削弱错误信息和焦点状态'],
|
|
323
|
+
interactionRules: ['提交中显示 loading 并防止重复提交', '错误不清空用户已填写的非敏感字段', '支持键盘提交和清晰焦点顺序'],
|
|
254
324
|
avoid: ['不要用 placeholder 代替字段 label', '不要把所有认证错误只放在 Toast 中', '不要在错误时回显密码'],
|
|
255
325
|
},
|
|
326
|
+
{
|
|
327
|
+
id: 'marketing-landing',
|
|
328
|
+
title: '营销落地页',
|
|
329
|
+
titleEn: 'Marketing landing page',
|
|
330
|
+
description: '用于产品官网、发布页、定价前导等公开营销表面;首屏单一任务,控件仍用 morya-ui。',
|
|
331
|
+
descriptionEn: 'Public marketing surfaces (product home, launch, pre-pricing). One job in the first viewport; controls still use morya-ui.',
|
|
332
|
+
goldenPage: 'docs/golden-pages/landing-page.vue',
|
|
333
|
+
keywords: [
|
|
334
|
+
'落地页',
|
|
335
|
+
'营销',
|
|
336
|
+
'官网',
|
|
337
|
+
'首页',
|
|
338
|
+
'landing',
|
|
339
|
+
'marketing',
|
|
340
|
+
'pricing',
|
|
341
|
+
'hero',
|
|
342
|
+
'产品介绍',
|
|
343
|
+
],
|
|
344
|
+
components: [
|
|
345
|
+
{ component: 'ConfigProvider', role: '应用根包裹' },
|
|
346
|
+
{ component: 'Button', role: 'CTA' },
|
|
347
|
+
{ component: 'Tag', role: '能力标签', required: false },
|
|
348
|
+
{ component: 'Accordion', role: 'FAQ', required: false },
|
|
349
|
+
{ component: 'Card', role: '套餐或能力卡', required: false },
|
|
350
|
+
],
|
|
351
|
+
structure: [
|
|
352
|
+
'MConfigProvider',
|
|
353
|
+
'├── Nav (brand + links + CTA)',
|
|
354
|
+
'├── Hero (brand, one headline, one lead, CTA group, one visual)',
|
|
355
|
+
'├── Sections (one job each)',
|
|
356
|
+
'└── Footer',
|
|
357
|
+
],
|
|
358
|
+
layout: {
|
|
359
|
+
page: '首屏单一构图;勿做成后台仪表盘',
|
|
360
|
+
hero: '品牌 + 标题 + 一句支持文案 + CTA + 一个主视觉',
|
|
361
|
+
sections: '每节一个目的与一个短说明',
|
|
362
|
+
responsive: '导航链接可折叠;能力卡降为单列',
|
|
363
|
+
},
|
|
364
|
+
styleRules: [
|
|
365
|
+
'交互控件使用 MButton / MTag / MAccordion 等',
|
|
366
|
+
'颜色与间距使用 --m-*,可用 color-mix 做氛围',
|
|
367
|
+
'避开紫渐变、奶油衬线陶土、报纸风等 AI 默认脸(除非 brief 指定)',
|
|
368
|
+
'首屏不要堆统计条、促销胶囊、日程碎片',
|
|
369
|
+
],
|
|
370
|
+
interactionRules: ['CTA 指向明确下一步', 'FAQ 用 Accordion 降低长页噪声', '尊重 prefers-reduced-motion 若加动画'],
|
|
371
|
+
avoid: ['不要用第二套 UI 库', '不要把 Ops 的 MPageFilters 壳套在营销页上', '不要用 inset 小卡片代替首屏主视觉'],
|
|
372
|
+
},
|
|
256
373
|
{
|
|
257
374
|
id: 'wizard-form',
|
|
258
375
|
title: '分步表单 / 向导',
|
|
@@ -285,16 +402,18 @@ export const pagePatterns = [
|
|
|
285
402
|
components: [
|
|
286
403
|
{ component: 'Breadcrumb', role: '页面层级导航' },
|
|
287
404
|
{ component: 'Card', role: '内容分组表面' },
|
|
288
|
-
{ component: '
|
|
405
|
+
{ component: 'Status', role: '标题旁轻量状态(圆点+文案)', required: false },
|
|
406
|
+
{ component: 'Tag', role: '分类/强调态标签', required: false },
|
|
289
407
|
{ component: 'Divider', role: '内容分组分隔', required: false },
|
|
290
408
|
{ component: 'Button', role: '编辑、返回和危险操作' },
|
|
291
409
|
{ component: 'Dialog', role: '危险操作确认', required: false },
|
|
410
|
+
{ component: 'Result', role: '资源不存在或无权访问时的结果页', required: false },
|
|
292
411
|
],
|
|
293
412
|
structure: [
|
|
294
413
|
'Page',
|
|
295
414
|
'├── Breadcrumb',
|
|
296
415
|
'├── PageHeader',
|
|
297
|
-
'│ ├── Title +
|
|
416
|
+
'│ ├── Title + MStatus (or MTag)',
|
|
298
417
|
'│ └── Actions',
|
|
299
418
|
'├── Summary Card',
|
|
300
419
|
'├── Properties Card',
|
|
@@ -307,14 +426,14 @@ export const pagePatterns = [
|
|
|
307
426
|
actions: '编辑为主操作,返回为次操作,删除放在低强调区域',
|
|
308
427
|
},
|
|
309
428
|
styleRules: [
|
|
310
|
-
'
|
|
429
|
+
'轻量状态优先 MStatus;需要芯片感或可关闭时用 MTag',
|
|
311
430
|
'内容分组使用 MCard,必要时用 MDivider 分隔',
|
|
312
431
|
'编辑使用 primary 或 outlined MButton,删除使用 danger',
|
|
313
432
|
'不使用大面积自定义背景色覆盖组件库 surface Token',
|
|
314
433
|
],
|
|
315
434
|
interactionRules: [
|
|
316
435
|
'详情加载中使用 Skeleton 或 ProgressSpinner',
|
|
317
|
-
'
|
|
436
|
+
'资源不存在用 MResult status="404";列表空数据用 MEmpty,不要混用',
|
|
318
437
|
'危险操作必须确认并在成功后刷新或离开当前详情页',
|
|
319
438
|
],
|
|
320
439
|
avoid: [
|
|
@@ -323,6 +442,61 @@ export const pagePatterns = [
|
|
|
323
442
|
'不要让详情页的操作按钮分散在多个无关区域',
|
|
324
443
|
],
|
|
325
444
|
},
|
|
445
|
+
{
|
|
446
|
+
id: 'result-page',
|
|
447
|
+
title: '结果页 / 阻断页',
|
|
448
|
+
titleEn: 'Result / terminal page',
|
|
449
|
+
description: '用于提交成功、失败回执、403、404、500 等流程终点或阻断场景。',
|
|
450
|
+
descriptionEn: 'Terminal outcomes: success, failure, 403, 404, 500, and other blocking pages.',
|
|
451
|
+
keywords: [
|
|
452
|
+
'结果',
|
|
453
|
+
'成功',
|
|
454
|
+
'失败',
|
|
455
|
+
'403',
|
|
456
|
+
'404',
|
|
457
|
+
'500',
|
|
458
|
+
'无权',
|
|
459
|
+
'不存在',
|
|
460
|
+
'result',
|
|
461
|
+
'success',
|
|
462
|
+
'error',
|
|
463
|
+
'forbidden',
|
|
464
|
+
'not found',
|
|
465
|
+
],
|
|
466
|
+
components: [
|
|
467
|
+
{ component: 'Result', role: '结果主体:图标、标题、说明与操作', required: true },
|
|
468
|
+
{ component: 'Button', role: '下一步:返回、重试、查看详情', required: false },
|
|
469
|
+
{ component: 'PageContent', role: '承载结果区', required: false },
|
|
470
|
+
],
|
|
471
|
+
structure: [
|
|
472
|
+
'MPageContent (optional shell)',
|
|
473
|
+
'└── MResult',
|
|
474
|
+
' ├── status icon',
|
|
475
|
+
' ├── title + description',
|
|
476
|
+
' └── #extra primary + optional secondary MButton',
|
|
477
|
+
],
|
|
478
|
+
layout: {
|
|
479
|
+
page: '结果在内容区居中,不一定占满整屏',
|
|
480
|
+
content: '说明发生了什么,以及用户下一步能做什么',
|
|
481
|
+
responsive: '操作按钮可触达,窄屏垂直排列',
|
|
482
|
+
},
|
|
483
|
+
styleRules: [
|
|
484
|
+
'使用 MResult 的 status 语义,不要手写大图标+文案拼盘',
|
|
485
|
+
'成功/失败用对应 severity 色;403/404 保持中性',
|
|
486
|
+
'主恢复动作使用 primary MButton,次动作使用 secondary 或 text',
|
|
487
|
+
'颜色与间距使用 --m-* Token',
|
|
488
|
+
],
|
|
489
|
+
interactionRules: [
|
|
490
|
+
'成功后提供查看详情或返回列表',
|
|
491
|
+
'失败提供重试或联系支持入口',
|
|
492
|
+
'不要把无数据空态做成 Result;空态用 MEmpty',
|
|
493
|
+
],
|
|
494
|
+
avoid: [
|
|
495
|
+
'不要用 Empty 表达 403/404/提交失败',
|
|
496
|
+
'不要只用颜色表达结果而无标题文案',
|
|
497
|
+
'不要在结果页堆砌无关营销内容',
|
|
498
|
+
],
|
|
499
|
+
},
|
|
326
500
|
];
|
|
327
501
|
/** Advisory page-writing standards for MCP and human authors — not enforced blockers. */
|
|
328
502
|
export const pageStandards = [
|
|
@@ -340,8 +514,16 @@ export const pageStandards = [
|
|
|
340
514
|
'Place MPageContent inside MLayoutContent for consistent padding and section gap',
|
|
341
515
|
'Call get_golden_page before writing a full page and copy the matching structure',
|
|
342
516
|
],
|
|
343
|
-
discouraged: [
|
|
344
|
-
|
|
517
|
+
discouraged: [
|
|
518
|
+
'手写 min-height:100vh 代替 Layout',
|
|
519
|
+
'在 MLayoutContent 上写 padding/gap',
|
|
520
|
+
'嵌套多层 MPageContent',
|
|
521
|
+
],
|
|
522
|
+
discouragedEn: [
|
|
523
|
+
'Hand-written min-height:100vh instead of Layout',
|
|
524
|
+
'Padding/gap on MLayoutContent instead of MPageContent',
|
|
525
|
+
'Nesting multiple MPageContent shells',
|
|
526
|
+
],
|
|
345
527
|
mcp: { snippet: 'layout-app-shell' },
|
|
346
528
|
},
|
|
347
529
|
{
|
|
@@ -351,17 +533,24 @@ export const pageStandards = [
|
|
|
351
533
|
recommend: [
|
|
352
534
|
'筛选区用 MPageFilters,标题+操作用 MPageToolbar,表单区用 MPageSection',
|
|
353
535
|
'列表页 MTable 直接放在 MPageContent 内',
|
|
536
|
+
'同行控件用 MSpace / MFlex;区块之间依赖 MPageContent 的 gap',
|
|
354
537
|
'局部区块优先 get_page_snippet(filters / toolbar / form-actions 等)',
|
|
355
538
|
],
|
|
356
539
|
recommendEn: [
|
|
357
540
|
'Use MPageFilters, MPageToolbar, and MPageSection for filters, headers/actions, and forms',
|
|
358
541
|
'Place MTable directly in MPageContent on list pages',
|
|
542
|
+
'Use MSpace / MFlex for same-row controls; let MPageContent gap separate sections',
|
|
359
543
|
'Prefer get_page_snippet for local blocks such as filters, toolbar, or form-actions',
|
|
360
544
|
],
|
|
361
|
-
discouraged: [
|
|
545
|
+
discouraged: [
|
|
546
|
+
'手写 .page-filters / .page-toolbar 类',
|
|
547
|
+
'用 MCard 或带 padding 的容器再包 MPageFilters / MPageSection / MPageToolbar',
|
|
548
|
+
'在已有 MPage* 区块之间再写 margin 叠间距',
|
|
549
|
+
],
|
|
362
550
|
discouragedEn: [
|
|
363
551
|
'Hand-written .page-filters / .page-toolbar classes',
|
|
364
|
-
'Wrapping MPageFilters
|
|
552
|
+
'Wrapping MPageFilters / MPageSection / MPageToolbar with MCard or extra padded containers',
|
|
553
|
+
'Adding margin between MPage* siblings that PageContent gap already spaces',
|
|
365
554
|
],
|
|
366
555
|
mcp: { decision: 'surface-nesting-choice' },
|
|
367
556
|
},
|
|
@@ -493,7 +682,7 @@ export const designRules = {
|
|
|
493
682
|
'Double borders on filter + table wrapper',
|
|
494
683
|
],
|
|
495
684
|
},
|
|
496
|
-
goldenPages: ['list-page', 'form-page', 'dashboard-page'],
|
|
685
|
+
goldenPages: ['list-page', 'form-page', 'dashboard-page', 'login-page', 'landing-page', 'empty-state'],
|
|
497
686
|
},
|
|
498
687
|
actions: {
|
|
499
688
|
primary: { component: 'MButton', props: ['severity omitted or primary'] },
|
|
@@ -502,8 +691,10 @@ export const designRules = {
|
|
|
502
691
|
cancel: { component: 'MButton', props: ['severity="secondary"', 'text'] },
|
|
503
692
|
},
|
|
504
693
|
status: {
|
|
505
|
-
|
|
694
|
+
preferred: 'MStatus',
|
|
695
|
+
chip: 'MTag',
|
|
506
696
|
mapping: { active: 'success', pending: 'warn', disabled: 'secondary', error: 'danger' },
|
|
697
|
+
note: '行内轻量状态用 MStatus;芯片/可关闭标签用 MTag',
|
|
507
698
|
},
|
|
508
699
|
feedback: {
|
|
509
700
|
default: 'message',
|
|
@@ -517,15 +708,30 @@ export const designRules = {
|
|
|
517
708
|
when: ['同时需要标题与补充说明', '后台任务/批量结果含统计', '异步通知感、角落堆叠'],
|
|
518
709
|
avoid: ['仅有单行文案时不要使用 Toast'],
|
|
519
710
|
},
|
|
711
|
+
empty: {
|
|
712
|
+
component: 'MEmpty',
|
|
713
|
+
when: ['列表无数据', '筛选无结果', '首次使用'],
|
|
714
|
+
avoid: ['不要用错误色表达正常空态', '不要用 MResult 表达无数据'],
|
|
715
|
+
},
|
|
716
|
+
result: {
|
|
717
|
+
component: 'MResult',
|
|
718
|
+
when: ['提交成功/失败页', '403 / 404 / 500', '流程终点回执'],
|
|
719
|
+
avoid: ['不要用手写图标+文案替代 MResult', '不要用 MEmpty 表达阻断错误'],
|
|
720
|
+
},
|
|
520
721
|
inlineMessage: {
|
|
521
|
-
|
|
522
|
-
when: [
|
|
722
|
+
api: 'field errorMessage | token-styled role=alert',
|
|
723
|
+
when: [
|
|
724
|
+
'登录/表单区常驻错误:优先字段 errorMessage',
|
|
725
|
+
'表单级总结:使用 --m-* 样式的 role=alert 条(见 login-page 黄金样例)',
|
|
726
|
+
'MMessage 组件当前主要为 message 服务宿主,勿臆造 severity 子节点 API',
|
|
727
|
+
],
|
|
523
728
|
},
|
|
524
729
|
doc: 'docs/feedback-message-vs-toast.md',
|
|
525
730
|
},
|
|
526
731
|
global: [
|
|
527
732
|
'优先使用组件库组件和 --m-* Token',
|
|
528
733
|
'操作反馈默认 message;仅一行文案时优先于 toast',
|
|
734
|
+
'空态用 MEmpty;结果/阻断页用 MResult;行内状态优先 MStatus',
|
|
529
735
|
'图标按钮建议提供 aria-label;表单字段建议有可见 label',
|
|
530
736
|
'浮层默认 Teleport 到 body;有明确布局约束时再改 appendTo',
|
|
531
737
|
'优先使用组件 documented variant,少写深层 CSS 覆盖',
|
package/dist/resources.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
import type { Catalog } from './catalog.js';
|
|
3
3
|
export declare function registerCatalogResources(server: McpServer, catalog: Catalog): void;
|
|
4
4
|
export declare function countCatalogResources(catalog: Catalog): number;
|
package/dist/tools.js
CHANGED
|
@@ -3,7 +3,7 @@ import { componentDecisions, findDecision, scoreDecision } from './decisions.js'
|
|
|
3
3
|
import { listGoldenPages, readGoldenPageSource } from './golden-pages.js';
|
|
4
4
|
import { filterPageSnippets, findPageSnippet, pageSnippets, scorePageSnippet } from './page-snippets.js';
|
|
5
5
|
import { designRules, findPattern, pagePatterns, scorePattern } from './patterns.js';
|
|
6
|
-
import {
|
|
6
|
+
import { countCatalogResources, countCatalogResourceTemplates } from './resources.js';
|
|
7
7
|
function inspectButtonIconOnlyUsage(code, issues) {
|
|
8
8
|
const pairedTagRe = /<MButton\b([^>]*)>([\s\S]*?)<\/MButton>/gi;
|
|
9
9
|
let match = pairedTagRe.exec(code);
|
|
@@ -65,16 +65,17 @@ function generatedPageCode(patternId, intent, locale) {
|
|
|
65
65
|
const pageImports = useLayoutShell
|
|
66
66
|
? ', MPageContent, MPageFilters, MPageHeader, MPageSection, MPageToolbar'
|
|
67
67
|
: '';
|
|
68
|
-
const listImports = isList ? ', MSelect, MSpace, MTable' : '';
|
|
68
|
+
const listImports = isList ? ', MEmpty, MSelect, MSpace, MTable' : '';
|
|
69
69
|
const formImports = isForm || isAuth || isWizard ? ', MForm, MFormItem, MSelect' : '';
|
|
70
70
|
const dashboardImports = isDashboard ? ', MCard, MGrid, MGridItem, MPagePlaceholder, MPageStat, MSkeleton, MTable' : '';
|
|
71
71
|
const detailImports = isDetail ? ', MDivider' : '';
|
|
72
|
-
const emptyImports = isEmpty ? ',
|
|
72
|
+
const emptyImports = isEmpty ? ', MEmpty, MPageToolbar' : '';
|
|
73
73
|
const wizardImports = isWizard ? ', MStepper' : '';
|
|
74
74
|
const settingsImports = isSettings ? ', MTabs' : '';
|
|
75
|
+
const statusImports = isList || isDetail || isDashboard ? ', MStatus' : '';
|
|
75
76
|
const script = `<script setup lang="ts">
|
|
76
77
|
import { ref } from 'vue'
|
|
77
|
-
import { MButton, MCard, MConfigProvider, MInput, MTag, zhCN${layoutImports}${pageImports}${listImports}${formImports}${dashboardImports}${detailImports}${emptyImports}${wizardImports}${settingsImports} } from 'morya-ui'
|
|
78
|
+
import { MButton, MCard, MConfigProvider, MInput, MTag, zhCN${layoutImports}${pageImports}${listImports}${formImports}${dashboardImports}${detailImports}${emptyImports}${wizardImports}${settingsImports}${statusImports} } from 'morya-ui'
|
|
78
79
|
|
|
79
80
|
const loading = ref(false)
|
|
80
81
|
const error = ref('')
|
|
@@ -112,8 +113,18 @@ async function submit() {
|
|
|
112
113
|
</template>
|
|
113
114
|
</MPageToolbar>
|
|
114
115
|
<MTable :columns="columns" :rows="rows" :loading="loading" paginator :rows-per-page="10" striped bordered row-key="id">
|
|
116
|
+
<template #cell-status="{ value }">
|
|
117
|
+
<MStatus :label="String(value ?? '')" :severity="value === 'active' ? 'success' : 'secondary'" />
|
|
118
|
+
</template>
|
|
115
119
|
<template #empty>
|
|
116
|
-
<
|
|
120
|
+
<MEmpty
|
|
121
|
+
:title="${zh ? '暂无数据' : 'No data yet'}"
|
|
122
|
+
:description="${zh ? '创建第一条记录开始使用。' : 'Create your first record to get started.'}"
|
|
123
|
+
>
|
|
124
|
+
<template #extra>
|
|
125
|
+
<MButton severity="primary">${zh ? '新建' : 'Create'}</MButton>
|
|
126
|
+
</template>
|
|
127
|
+
</MEmpty>
|
|
117
128
|
</template>
|
|
118
129
|
</MTable>`;
|
|
119
130
|
const formContent = ` <MPageHeader :title="title" :description="${zh ? '填写表单并保存。' : 'Fill in the form and save.'}" />
|
|
@@ -210,17 +221,20 @@ ${content}
|
|
|
210
221
|
else if (isEmpty) {
|
|
211
222
|
innerTemplate = `<MConfigProvider :locale="zhCN">
|
|
212
223
|
<main class="m-generated-page">
|
|
213
|
-
<
|
|
214
|
-
<
|
|
215
|
-
<
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
+
<MPageToolbar :title="title">
|
|
225
|
+
<template #actions>
|
|
226
|
+
<MButton severity="primary" @click="submit">${zh ? '新建' : 'Create'}</MButton>
|
|
227
|
+
</template>
|
|
228
|
+
</MPageToolbar>
|
|
229
|
+
<MEmpty
|
|
230
|
+
:title="${zh ? '暂无内容' : 'Nothing here yet'}"
|
|
231
|
+
:description="${zh ? '创建第一条记录开始使用。' : 'Create your first record to get started.'}"
|
|
232
|
+
icon="database"
|
|
233
|
+
>
|
|
234
|
+
<template #extra>
|
|
235
|
+
<MButton severity="primary" @click="submit">${zh ? '创建' : 'Create'}</MButton>
|
|
236
|
+
</template>
|
|
237
|
+
</MEmpty>
|
|
224
238
|
</main>
|
|
225
239
|
</MConfigProvider>`;
|
|
226
240
|
}
|
|
@@ -857,6 +871,34 @@ export function createToolHandlers(catalog = loadCatalog()) {
|
|
|
857
871
|
: '标准建议:MPageFilters 已自带表面样式,通常不必再用 MCard 包裹。',
|
|
858
872
|
});
|
|
859
873
|
}
|
|
874
|
+
if (/<MPageSection\b[^>]*variant=["']form["']/i.test(code) &&
|
|
875
|
+
/<MCard\b[^>]*>[\s\S]*?<MPageSection\b/i.test(code)) {
|
|
876
|
+
suggestions.push({
|
|
877
|
+
standardId: 'page-sections',
|
|
878
|
+
type: 'redundant-wrapper',
|
|
879
|
+
message: locale === 'en-US'
|
|
880
|
+
? 'Standard: MPageSection variant="form" already provides a surface; wrapping it in MCard is usually redundant.'
|
|
881
|
+
: '标准建议:MPageSection variant="form" 已自带表面样式,通常不必再用 MCard 包裹。',
|
|
882
|
+
});
|
|
883
|
+
}
|
|
884
|
+
if (/<MPageContent\b[^>]*>[\s\S]*?<MPageContent\b/i.test(code)) {
|
|
885
|
+
suggestions.push({
|
|
886
|
+
standardId: 'layout-shell',
|
|
887
|
+
type: 'redundant-wrapper',
|
|
888
|
+
message: locale === 'en-US'
|
|
889
|
+
? 'Standard: prefer a single MPageContent shell for page padding and section gap.'
|
|
890
|
+
: '标准建议:页面 padding 与区块间距优先只用一层 MPageContent。',
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
if (/<(?:div|section|main)\b[^>]*style=["'][^"']*(?:padding|gap)\s*:[^"']*["'][^>]*>\s*<(?:MPage(?:Content|Filters|Toolbar|Header|Section)|MTable)\b/i.test(code)) {
|
|
894
|
+
suggestions.push({
|
|
895
|
+
standardId: 'page-sections',
|
|
896
|
+
type: 'redundant-wrapper',
|
|
897
|
+
message: locale === 'en-US'
|
|
898
|
+
? 'Standard: avoid extra padded wrappers around MPage* blocks; prefer the components’ built-in spacing.'
|
|
899
|
+
: '标准建议:少在 MPage* 外包带 padding 的容器;优先使用组件自带间距。',
|
|
900
|
+
});
|
|
901
|
+
}
|
|
860
902
|
if (/style="[^"]*overflow(?:-y|-x)?\s*:\s*(auto|scroll)/i.test(code) ||
|
|
861
903
|
/<style\b[^>]*>[\s\S]*?overflow(?:-y|-x)?\s*:\s*(auto|scroll)/i.test(code)) {
|
|
862
904
|
suggestions.push({
|
|
@@ -885,6 +927,29 @@ export function createToolHandlers(catalog = loadCatalog()) {
|
|
|
885
927
|
: '标准建议:整页高度优先 MLayout fillViewport,少写 min-height:100vh。',
|
|
886
928
|
});
|
|
887
929
|
}
|
|
930
|
+
if ((/fill-viewport|fillViewport/i.test(code) || /<MLayout\b/i.test(code))
|
|
931
|
+
&& !/html\s*,\s*body\s*,\s*#app[\s\S]{0,80}height\s*:\s*100%/i.test(code)) {
|
|
932
|
+
suggestions.push({
|
|
933
|
+
standardId: 'layout-shell',
|
|
934
|
+
type: 'height-chain',
|
|
935
|
+
message: locale === 'en-US'
|
|
936
|
+
? 'Prefer layout-app-shell / golden pages: include html, body, #app { height: 100% } (morya-app-shell.css) with MLayout.'
|
|
937
|
+
: '对齐 layout-app-shell / 黄金样例:配合 MLayout 使用 html, body, #app { height: 100% }(morya-app-shell.css)。',
|
|
938
|
+
});
|
|
939
|
+
}
|
|
940
|
+
if (/<MMenu\b/i.test(code)) {
|
|
941
|
+
const iconCount = (code.match(/\bicon\s*:/g) || []).length;
|
|
942
|
+
const looksLikeMenuModel = /MMenu[\s\S]{0,1200}\b(label|model)\s*[:=]/i.test(code) || /:model=/i.test(code);
|
|
943
|
+
if (looksLikeMenuModel && iconCount === 0) {
|
|
944
|
+
suggestions.push({
|
|
945
|
+
standardId: 'layout-shell',
|
|
946
|
+
type: 'menu-missing-icons',
|
|
947
|
+
message: locale === 'en-US'
|
|
948
|
+
? 'Prefer golden / layout-app-shell menu items that include icon (e.g. user, shield, home).'
|
|
949
|
+
: '对齐黄金样例 / layout-app-shell:侧栏菜单项带上 icon(如 user、shield、home)。',
|
|
950
|
+
});
|
|
951
|
+
}
|
|
952
|
+
}
|
|
888
953
|
return textResult({
|
|
889
954
|
ok: true,
|
|
890
955
|
advisory: true,
|
|
@@ -943,7 +1008,7 @@ export function createToolHandlers(catalog = loadCatalog()) {
|
|
|
943
1008
|
default: 'message',
|
|
944
1009
|
message: { when: ['single-line action result', 'save/delete/create confirmations'] },
|
|
945
1010
|
toast: { when: ['summary + detail', 'async or background notifications'] },
|
|
946
|
-
inlineMessage: {
|
|
1011
|
+
inlineMessage: { api: 'field errorMessage | token-styled role=alert', when: ['persistent form/auth errors'] },
|
|
947
1012
|
doc: 'docs/feedback-message-vs-toast.md',
|
|
948
1013
|
},
|
|
949
1014
|
global: [
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@morya-ui/mcp",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.6",
|
|
5
5
|
"description": "MCP server for morya-ui — component docs, examples, and usage guidance for AI coding agents.",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"homepage": "https://github.com/morya-space/morya-ui/tree/main/packages/ui-mcp#readme",
|