@morya-ui/mcp 0.3.1 → 0.3.3
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 +5 -3
- package/data/catalog.json +298 -132
- package/data/example-coverage.json +25 -15
- package/data/golden-pages/dashboard-page.vue +10 -8
- package/data/golden-pages/empty-state.vue +20 -51
- package/data/golden-pages/form-in-dialog.vue +3 -3
- package/data/golden-pages/list-page.vue +77 -12
- package/dist/__tests__/tools.test.js +18 -1
- package/dist/decisions.d.ts +14 -0
- package/dist/decisions.js +777 -41
- package/dist/page-snippets.js +127 -35
- package/dist/tools.js +46 -28
- package/package.json +4 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"generatedAt": "2026-09-
|
|
3
|
-
"catalogGeneratedAt": "2026-09-
|
|
2
|
+
"generatedAt": "2026-09-24T08:45:50.660Z",
|
|
3
|
+
"catalogGeneratedAt": "2026-09-24T08:45:45.036Z",
|
|
4
4
|
"components": [
|
|
5
5
|
{
|
|
6
6
|
"id": "Accordion",
|
|
@@ -198,12 +198,14 @@
|
|
|
198
198
|
"exportName": "MButton",
|
|
199
199
|
"examples": 20,
|
|
200
200
|
"props": {
|
|
201
|
-
"total":
|
|
202
|
-
"covered":
|
|
201
|
+
"total": 27,
|
|
202
|
+
"covered": 22,
|
|
203
203
|
"missing": [
|
|
204
204
|
"variant",
|
|
205
|
-
"
|
|
206
|
-
"
|
|
205
|
+
"ripple",
|
|
206
|
+
"press",
|
|
207
|
+
"nativeType",
|
|
208
|
+
"autofocus"
|
|
207
209
|
]
|
|
208
210
|
},
|
|
209
211
|
"events": {
|
|
@@ -231,7 +233,7 @@
|
|
|
231
233
|
{
|
|
232
234
|
"id": "Card",
|
|
233
235
|
"exportName": "MCard",
|
|
234
|
-
"examples":
|
|
236
|
+
"examples": 10,
|
|
235
237
|
"props": {
|
|
236
238
|
"total": 8,
|
|
237
239
|
"covered": 4,
|
|
@@ -1920,13 +1922,15 @@
|
|
|
1920
1922
|
{
|
|
1921
1923
|
"id": "Page",
|
|
1922
1924
|
"exportName": "MPageContent",
|
|
1923
|
-
"examples":
|
|
1925
|
+
"examples": 10,
|
|
1924
1926
|
"props": {
|
|
1925
|
-
"total":
|
|
1926
|
-
"covered":
|
|
1927
|
+
"total": 24,
|
|
1928
|
+
"covered": 19,
|
|
1927
1929
|
"missing": [
|
|
1930
|
+
"bands",
|
|
1931
|
+
"expandLabel",
|
|
1932
|
+
"collapseLabel",
|
|
1928
1933
|
"headingLevel",
|
|
1929
|
-
"trendSeverity",
|
|
1930
1934
|
"minHeight"
|
|
1931
1935
|
]
|
|
1932
1936
|
},
|
|
@@ -1936,9 +1940,15 @@
|
|
|
1936
1940
|
"missing": []
|
|
1937
1941
|
},
|
|
1938
1942
|
"slots": {
|
|
1939
|
-
"total":
|
|
1940
|
-
"covered":
|
|
1941
|
-
"missing": [
|
|
1943
|
+
"total": 8,
|
|
1944
|
+
"covered": 3,
|
|
1945
|
+
"missing": [
|
|
1946
|
+
"active",
|
|
1947
|
+
"breadcrumb",
|
|
1948
|
+
"tags",
|
|
1949
|
+
"icon",
|
|
1950
|
+
"extra"
|
|
1951
|
+
]
|
|
1942
1952
|
},
|
|
1943
1953
|
"methods": {
|
|
1944
1954
|
"total": 0,
|
|
@@ -3377,6 +3387,6 @@
|
|
|
3377
3387
|
"summary": {
|
|
3378
3388
|
"components": 93,
|
|
3379
3389
|
"componentsWithMissingCoverage": 93,
|
|
3380
|
-
"totalExamples":
|
|
3390
|
+
"totalExamples": 817
|
|
3381
3391
|
}
|
|
3382
3392
|
}
|
|
@@ -13,8 +13,8 @@ import {
|
|
|
13
13
|
MLayoutContent,
|
|
14
14
|
MLayoutHeader,
|
|
15
15
|
MPageContent,
|
|
16
|
+
MEmpty,
|
|
16
17
|
MPageHeader,
|
|
17
|
-
MPagePlaceholder,
|
|
18
18
|
MPageStat,
|
|
19
19
|
MStatus,
|
|
20
20
|
MTable,
|
|
@@ -23,9 +23,9 @@ import {
|
|
|
23
23
|
} from 'morya-ui'
|
|
24
24
|
|
|
25
25
|
const stats = [
|
|
26
|
-
{ label: '总用户', value: '12,480', trend: '+8.2%', icon: 'users' },
|
|
27
|
-
{ label: '今日活跃', value: '1,926', trend: '+3.1%', icon: 'activity' },
|
|
28
|
-
{ label: '待处理工单', value: '47', trend: '-12%', trendSeverity: 'warn' as const, icon: 'clipboard' },
|
|
26
|
+
{ label: '总用户', value: '12,480', trend: '+8.2%', trendDirection: 'up' as const, trendLabel: '较上月', icon: 'users' },
|
|
27
|
+
{ label: '今日活跃', value: '1,926', trend: '+3.1%', trendDirection: 'up' as const, trendLabel: '较昨日', icon: 'activity' },
|
|
28
|
+
{ label: '待处理工单', value: '47', trend: '-12%', trendDirection: 'down' as const, trendLabel: '较上周', trendSeverity: 'warn' as const, icon: 'clipboard' },
|
|
29
29
|
{ label: '系统健康', value: '99.9%', trend: '稳定', trendSeverity: 'secondary' as const, icon: 'heart' },
|
|
30
30
|
]
|
|
31
31
|
|
|
@@ -88,6 +88,8 @@ function statusSeverity(s: string) {
|
|
|
88
88
|
:value="item.value"
|
|
89
89
|
:trend="item.trend"
|
|
90
90
|
:trend-severity="item.trendSeverity ?? 'primary'"
|
|
91
|
+
:trend-direction="item.trendDirection"
|
|
92
|
+
:trend-label="item.trendLabel"
|
|
91
93
|
:icon="item.icon"
|
|
92
94
|
/>
|
|
93
95
|
</MGridItem>
|
|
@@ -95,15 +97,15 @@ function statusSeverity(s: string) {
|
|
|
95
97
|
|
|
96
98
|
<MGrid :cols="2" :x-gap="16" :y-gap="16" responsive="screen">
|
|
97
99
|
<MGridItem :span="1">
|
|
98
|
-
<MCard title="趋势概览">
|
|
99
|
-
<
|
|
100
|
-
aria-label="图表占位"
|
|
100
|
+
<MCard title="趋势概览" shadow="always">
|
|
101
|
+
<MEmpty
|
|
101
102
|
description="接入图表组件后展示近 7 日活跃与转化。"
|
|
103
|
+
icon="chart-bar"
|
|
102
104
|
/>
|
|
103
105
|
</MCard>
|
|
104
106
|
</MGridItem>
|
|
105
107
|
<MGridItem :span="1">
|
|
106
|
-
<MCard title="最近工单">
|
|
108
|
+
<MCard title="最近工单" shadow="always">
|
|
107
109
|
<MTable
|
|
108
110
|
:columns="recentColumns"
|
|
109
111
|
:rows="recentRows"
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
MLayoutContent,
|
|
14
14
|
MLayoutHeader,
|
|
15
15
|
MPageContent,
|
|
16
|
-
|
|
16
|
+
MPageHeader,
|
|
17
17
|
zhCN,
|
|
18
18
|
} from 'morya-ui'
|
|
19
19
|
</script>
|
|
@@ -27,61 +27,30 @@ import {
|
|
|
27
27
|
|
|
28
28
|
<MLayoutContent>
|
|
29
29
|
<MPageContent>
|
|
30
|
-
<
|
|
30
|
+
<MPageHeader title="课程" description="管理目录与可见性。">
|
|
31
31
|
<template #actions>
|
|
32
|
-
<MButton
|
|
32
|
+
<MButton severity="primary">
|
|
33
|
+
新建课程
|
|
34
|
+
</MButton>
|
|
33
35
|
</template>
|
|
34
|
-
</
|
|
36
|
+
</MPageHeader>
|
|
35
37
|
|
|
36
|
-
<
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
>
|
|
42
|
-
<
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
38
|
+
<MEmpty
|
|
39
|
+
title="还没有课程"
|
|
40
|
+
description="创建第一门课程后,学员就能在目录里看到它。也可以稍后从模板导入。"
|
|
41
|
+
icon="book"
|
|
42
|
+
>
|
|
43
|
+
<template #extra>
|
|
44
|
+
<MButton severity="primary">
|
|
45
|
+
创建第一门课程
|
|
46
|
+
</MButton>
|
|
47
|
+
<MButton severity="secondary" text>
|
|
48
|
+
从模板导入
|
|
49
|
+
</MButton>
|
|
50
|
+
</template>
|
|
51
|
+
</MEmpty>
|
|
48
52
|
</MPageContent>
|
|
49
53
|
</MLayoutContent>
|
|
50
54
|
</MLayout>
|
|
51
55
|
</MConfigProvider>
|
|
52
56
|
</template>
|
|
53
|
-
|
|
54
|
-
<style scoped>
|
|
55
|
-
.empty-state-shell {
|
|
56
|
-
display: grid;
|
|
57
|
-
place-items: center;
|
|
58
|
-
min-height: 18rem;
|
|
59
|
-
padding: var(--m-space-8) var(--m-space-6);
|
|
60
|
-
border: 1px dashed color-mix(in srgb, var(--m-color-border) 80%, var(--m-color-primary));
|
|
61
|
-
border-radius: var(--m-radius-md);
|
|
62
|
-
background:
|
|
63
|
-
radial-gradient(
|
|
64
|
-
120% 80% at 50% 0%,
|
|
65
|
-
color-mix(in srgb, var(--m-color-primary) 10%, transparent),
|
|
66
|
-
transparent 55%
|
|
67
|
-
),
|
|
68
|
-
var(--m-color-surface);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
@media (prefers-reduced-motion: no-preference) {
|
|
72
|
-
.empty-state-shell {
|
|
73
|
-
animation: empty-in 400ms ease both;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
@keyframes empty-in {
|
|
78
|
-
from {
|
|
79
|
-
opacity: 0;
|
|
80
|
-
transform: translateY(0.35rem);
|
|
81
|
-
}
|
|
82
|
-
to {
|
|
83
|
-
opacity: 1;
|
|
84
|
-
transform: translateY(0);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
</style>
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
MLayoutContent,
|
|
17
17
|
MLayoutHeader,
|
|
18
18
|
MPageContent,
|
|
19
|
-
|
|
19
|
+
MPageHeader,
|
|
20
20
|
MSelect,
|
|
21
21
|
MSpace,
|
|
22
22
|
MStatus,
|
|
@@ -101,13 +101,13 @@ async function onSave() {
|
|
|
101
101
|
|
|
102
102
|
<MLayoutContent>
|
|
103
103
|
<MPageContent>
|
|
104
|
-
<
|
|
104
|
+
<MPageHeader title="用户管理" description="短表单留在列表内弹窗;字段多时再走独立表单页。">
|
|
105
105
|
<template #actions>
|
|
106
106
|
<MButton severity="primary" @click="openCreate">
|
|
107
107
|
新建用户
|
|
108
108
|
</MButton>
|
|
109
109
|
</template>
|
|
110
|
-
</
|
|
110
|
+
</MPageHeader>
|
|
111
111
|
|
|
112
112
|
<MTable
|
|
113
113
|
:columns="columns"
|
|
@@ -15,18 +15,22 @@ import {
|
|
|
15
15
|
MLayoutSider,
|
|
16
16
|
MMenu,
|
|
17
17
|
MPageContent,
|
|
18
|
+
MPageFilterChips,
|
|
18
19
|
MPageFilters,
|
|
19
|
-
|
|
20
|
+
MPageHeader,
|
|
20
21
|
MSelect,
|
|
21
22
|
MSpace,
|
|
22
23
|
MStatus,
|
|
23
24
|
MTable,
|
|
25
|
+
MTag,
|
|
24
26
|
zhCN,
|
|
25
27
|
} from 'morya-ui'
|
|
26
|
-
import { ref } from 'vue'
|
|
28
|
+
import { computed, ref } from 'vue'
|
|
27
29
|
|
|
28
30
|
const keyword = ref('')
|
|
29
31
|
const status = ref<string | undefined>()
|
|
32
|
+
const department = ref<string | null>(null)
|
|
33
|
+
const filtersExpanded = ref(false)
|
|
30
34
|
const siderCollapsed = ref(false)
|
|
31
35
|
|
|
32
36
|
const menuModel = [
|
|
@@ -41,6 +45,39 @@ const statusOptions = [
|
|
|
41
45
|
{ label: '停用', value: 'inactive' },
|
|
42
46
|
]
|
|
43
47
|
|
|
48
|
+
const departmentOptions = [
|
|
49
|
+
{ label: '研发', value: '研发' },
|
|
50
|
+
{ label: '运营', value: '运营' },
|
|
51
|
+
{ label: '市场', value: '市场' },
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
const activeFilters = computed(() => {
|
|
55
|
+
const items: Array<{ key: string; label: string }> = []
|
|
56
|
+
if (keyword.value.trim()) {
|
|
57
|
+
items.push({ key: 'keyword', label: `关键词:${keyword.value.trim()}` })
|
|
58
|
+
}
|
|
59
|
+
if (status.value) {
|
|
60
|
+
const label = statusOptions.find((o) => o.value === status.value)?.label ?? status.value
|
|
61
|
+
items.push({ key: 'status', label: `状态:${label}` })
|
|
62
|
+
}
|
|
63
|
+
if (department.value) {
|
|
64
|
+
items.push({ key: 'department', label: `部门:${department.value}` })
|
|
65
|
+
}
|
|
66
|
+
return items
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
function clearFilter(key: string) {
|
|
70
|
+
if (key === 'keyword') keyword.value = ''
|
|
71
|
+
if (key === 'status') status.value = undefined
|
|
72
|
+
if (key === 'department') department.value = null
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function resetFilters() {
|
|
76
|
+
keyword.value = ''
|
|
77
|
+
status.value = undefined
|
|
78
|
+
department.value = null
|
|
79
|
+
}
|
|
80
|
+
|
|
44
81
|
const columns = [
|
|
45
82
|
{ key: 'name', label: '名称' },
|
|
46
83
|
{ key: 'status', label: '状态', width: 120 },
|
|
@@ -76,7 +113,20 @@ const rows = [
|
|
|
76
113
|
|
|
77
114
|
<MLayoutContent>
|
|
78
115
|
<MPageContent>
|
|
79
|
-
<
|
|
116
|
+
<MPageHeader title="用户管理" description="维护账号、角色与权限。">
|
|
117
|
+
<template #actions>
|
|
118
|
+
<MButton severity="primary">
|
|
119
|
+
新建用户
|
|
120
|
+
</MButton>
|
|
121
|
+
</template>
|
|
122
|
+
</MPageHeader>
|
|
123
|
+
|
|
124
|
+
<MPageFilters
|
|
125
|
+
v-model:expanded="filtersExpanded"
|
|
126
|
+
aria-label="筛选"
|
|
127
|
+
variant="filled"
|
|
128
|
+
collapsible
|
|
129
|
+
>
|
|
80
130
|
<MSpace wrap>
|
|
81
131
|
<MInput v-model="keyword" placeholder="搜索名称" clearable style="width: 14rem" />
|
|
82
132
|
<MSelect
|
|
@@ -86,22 +136,37 @@ const rows = [
|
|
|
86
136
|
clearable
|
|
87
137
|
style="width: 10rem"
|
|
88
138
|
/>
|
|
89
|
-
<MButton severity="
|
|
139
|
+
<MButton severity="secondary">
|
|
90
140
|
查询
|
|
91
141
|
</MButton>
|
|
92
|
-
<MButton severity="secondary">
|
|
142
|
+
<MButton severity="secondary" text @click="resetFilters">
|
|
93
143
|
重置
|
|
94
144
|
</MButton>
|
|
95
145
|
</MSpace>
|
|
146
|
+
<template #advanced>
|
|
147
|
+
<MSpace wrap>
|
|
148
|
+
<MSelect
|
|
149
|
+
v-model="department"
|
|
150
|
+
:options="departmentOptions"
|
|
151
|
+
placeholder="部门"
|
|
152
|
+
clearable
|
|
153
|
+
style="width: 10rem"
|
|
154
|
+
/>
|
|
155
|
+
</MSpace>
|
|
156
|
+
</template>
|
|
96
157
|
</MPageFilters>
|
|
97
158
|
|
|
98
|
-
<
|
|
99
|
-
<
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
159
|
+
<MPageFilterChips v-if="activeFilters.length" label="已选" aria-label="已选筛选">
|
|
160
|
+
<MTag
|
|
161
|
+
v-for="item in activeFilters"
|
|
162
|
+
:key="item.key"
|
|
163
|
+
:value="item.label"
|
|
164
|
+
size="small"
|
|
165
|
+
bordered
|
|
166
|
+
closable
|
|
167
|
+
@close="clearFilter(item.key)"
|
|
168
|
+
/>
|
|
169
|
+
</MPageFilterChips>
|
|
105
170
|
|
|
106
171
|
<MTable
|
|
107
172
|
:columns="columns"
|
|
@@ -149,7 +149,7 @@ describe('@morya-ui/mcp handlers', () => {
|
|
|
149
149
|
});
|
|
150
150
|
it('searches page snippets scope', () => {
|
|
151
151
|
const result = read(handlers.search({ query: 'toolbar', scope: 'snippets', limit: 5 }));
|
|
152
|
-
expect(result.items.some((item) => item.type === 'snippet' && item.id === 'list-toolbar')).toBe(true);
|
|
152
|
+
expect(result.items.some((item) => item.type === 'snippet' && item.id === 'list-batch-toolbar')).toBe(true);
|
|
153
153
|
});
|
|
154
154
|
it('suggests double-border page composition as advisory standard', () => {
|
|
155
155
|
const result = read(handlers.validatePage({
|
|
@@ -282,6 +282,23 @@ describe('@morya-ui/mcp handlers', () => {
|
|
|
282
282
|
expect(result.id).toBe('overlay-choice');
|
|
283
283
|
expect(result.options.some((option) => option.component === 'Drawer')).toBe(true);
|
|
284
284
|
});
|
|
285
|
+
it('returns L2 recipes and antiPatterns on decision reads', () => {
|
|
286
|
+
const result = read(handlers.recommendComponent({ decision: 'data-display-choice' }));
|
|
287
|
+
const table = result.options.find((option) => option.component === 'Table');
|
|
288
|
+
expect(table?.recipe?.props?.length).toBeGreaterThanOrEqual(3);
|
|
289
|
+
expect(table?.recipe?.props?.some((line) => /rows/i.test(line))).toBe(true);
|
|
290
|
+
expect(table?.antiPatterns?.length).toBeGreaterThan(0);
|
|
291
|
+
});
|
|
292
|
+
it('covers feedback and confirm decision guides', () => {
|
|
293
|
+
const feedback = read(handlers.recommendComponent({ decision: 'feedback-choice' }));
|
|
294
|
+
expect(feedback.options.map((option) => option.component)).toEqual(expect.arrayContaining(['message', 'toast', 'field errorMessage / role="alert"']));
|
|
295
|
+
expect(feedback.options.every((option) => (option.recipe?.props?.length ?? 0) >= 3)).toBe(true);
|
|
296
|
+
const confirm = read(handlers.recommendComponent({ decision: 'confirm-choice' }));
|
|
297
|
+
expect(confirm.options.map((option) => option.component)).toEqual([
|
|
298
|
+
'ConfirmDialog',
|
|
299
|
+
'ConfirmPopup',
|
|
300
|
+
]);
|
|
301
|
+
});
|
|
285
302
|
it('covers current selection, status, empty, and menu guides', () => {
|
|
286
303
|
const selection = read(handlers.recommendComponent({ decision: 'selection-choice' }));
|
|
287
304
|
expect(selection.options.map((option) => option.component)).toEqual(expect.arrayContaining(['Select', 'CascadeSelect', 'Listbox', 'SelectButton', 'Radio']));
|
package/dist/decisions.d.ts
CHANGED
|
@@ -1,9 +1,21 @@
|
|
|
1
|
+
export interface ComponentDecisionRecipe {
|
|
2
|
+
/** 3–8 critical props / usage notes (zh). Not a full API manual. */
|
|
3
|
+
props: string[];
|
|
4
|
+
propsEn: string[];
|
|
5
|
+
slots?: string[];
|
|
6
|
+
slotsEn?: string[];
|
|
7
|
+
events?: string[];
|
|
8
|
+
eventsEn?: string[];
|
|
9
|
+
}
|
|
1
10
|
export interface ComponentDecisionOption {
|
|
2
11
|
component: string;
|
|
3
12
|
when: string[];
|
|
4
13
|
whenEn: string[];
|
|
5
14
|
avoidWhen: string[];
|
|
6
15
|
avoidWhenEn: string[];
|
|
16
|
+
recipe: ComponentDecisionRecipe;
|
|
17
|
+
antiPatterns: string[];
|
|
18
|
+
antiPatternsEn: string[];
|
|
7
19
|
}
|
|
8
20
|
export interface ComponentDecision {
|
|
9
21
|
id: string;
|
|
@@ -17,3 +29,5 @@ export interface ComponentDecision {
|
|
|
17
29
|
export declare const componentDecisions: ComponentDecision[];
|
|
18
30
|
export declare function findDecision(name: string): ComponentDecision | undefined;
|
|
19
31
|
export declare function scoreDecision(decision: ComponentDecision, query: string): number;
|
|
32
|
+
/** Ensures every option carries a non-empty L2 recipe (used by tests / generate script). */
|
|
33
|
+
export declare function assertDecisionRecipes(decisions?: ComponentDecision[]): void;
|