@morya-ui/mcp 0.2.8 → 0.2.9
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 +74 -74
- package/data/example-coverage.json +2 -2
- package/data/golden-pages/dashboard-page.vue +41 -8
- package/data/golden-pages/empty-state.vue +22 -1
- 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/dist/__tests__/tools.test.js +10 -0
- package/dist/decisions.js +136 -1
- package/dist/page-snippets.js +27 -17
- package/dist/patterns.d.ts +4 -2
- package/dist/patterns.js +4 -2
- package/dist/tools.js +23 -3
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
/**
|
|
3
3
|
* 黄金样例:仪表盘页
|
|
4
|
-
* @see DESIGN.md §
|
|
4
|
+
* @see DESIGN.md · morya-ui-pages references/page-layouts.md · visual-craft § Ops polish
|
|
5
5
|
*/
|
|
6
6
|
import {
|
|
7
7
|
MBreadcrumb,
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
MPageHeader,
|
|
17
17
|
MPagePlaceholder,
|
|
18
18
|
MPageStat,
|
|
19
|
+
MStatus,
|
|
19
20
|
MTable,
|
|
20
21
|
MTag,
|
|
21
22
|
zhCN,
|
|
@@ -32,7 +33,7 @@ const recentColumns = [
|
|
|
32
33
|
{ key: 'id', label: '工单号', width: 96 },
|
|
33
34
|
{ key: 'title', label: '标题' },
|
|
34
35
|
{ key: 'priority', label: '优先级', width: 96 },
|
|
35
|
-
{ key: 'status', label: '状态', width:
|
|
36
|
+
{ key: 'status', label: '状态', width: 110 },
|
|
36
37
|
]
|
|
37
38
|
|
|
38
39
|
const recentRows = [
|
|
@@ -47,11 +48,23 @@ function prioritySeverity(p: string) {
|
|
|
47
48
|
return 'secondary'
|
|
48
49
|
}
|
|
49
50
|
|
|
51
|
+
function priorityLabel(p: string) {
|
|
52
|
+
if (p === 'high') return '高'
|
|
53
|
+
if (p === 'medium') return '中'
|
|
54
|
+
return '低'
|
|
55
|
+
}
|
|
56
|
+
|
|
50
57
|
function statusLabel(s: string) {
|
|
51
58
|
if (s === 'open') return '待处理'
|
|
52
59
|
if (s === 'progress') return '进行中'
|
|
53
60
|
return '已完成'
|
|
54
61
|
}
|
|
62
|
+
|
|
63
|
+
function statusSeverity(s: string) {
|
|
64
|
+
if (s === 'open') return 'warn'
|
|
65
|
+
if (s === 'progress') return 'info'
|
|
66
|
+
return 'success'
|
|
67
|
+
}
|
|
55
68
|
</script>
|
|
56
69
|
|
|
57
70
|
<template>
|
|
@@ -63,7 +76,10 @@ function statusLabel(s: string) {
|
|
|
63
76
|
|
|
64
77
|
<MLayoutContent>
|
|
65
78
|
<MPageContent density="spacious">
|
|
66
|
-
<MPageHeader
|
|
79
|
+
<MPageHeader
|
|
80
|
+
title="运营概览"
|
|
81
|
+
description="关注今日活跃与待处理工单,异常优先下钻。"
|
|
82
|
+
/>
|
|
67
83
|
|
|
68
84
|
<MGrid :cols="4" :x-gap="16" :y-gap="16" responsive="screen">
|
|
69
85
|
<MGridItem v-for="item in stats" :key="item.label" :span="1">
|
|
@@ -77,20 +93,37 @@ function statusLabel(s: string) {
|
|
|
77
93
|
</MGridItem>
|
|
78
94
|
</MGrid>
|
|
79
95
|
|
|
80
|
-
<MGrid :cols="2" :x-gap="16" :y-gap="16">
|
|
96
|
+
<MGrid :cols="2" :x-gap="16" :y-gap="16" responsive="screen">
|
|
81
97
|
<MGridItem :span="1">
|
|
82
98
|
<MCard title="趋势概览">
|
|
83
|
-
<MPagePlaceholder
|
|
99
|
+
<MPagePlaceholder
|
|
100
|
+
aria-label="图表占位"
|
|
101
|
+
description="接入图表组件后展示近 7 日活跃与转化。"
|
|
102
|
+
/>
|
|
84
103
|
</MCard>
|
|
85
104
|
</MGridItem>
|
|
86
105
|
<MGridItem :span="1">
|
|
87
106
|
<MCard title="最近工单">
|
|
88
|
-
<MTable
|
|
107
|
+
<MTable
|
|
108
|
+
:columns="recentColumns"
|
|
109
|
+
:rows="recentRows"
|
|
110
|
+
size="small"
|
|
111
|
+
:paginator="false"
|
|
112
|
+
bordered
|
|
113
|
+
row-key="id"
|
|
114
|
+
aria-label="最近工单"
|
|
115
|
+
>
|
|
89
116
|
<template #cell-priority="{ value }">
|
|
90
|
-
<MTag
|
|
117
|
+
<MTag
|
|
118
|
+
:value="priorityLabel(String(value))"
|
|
119
|
+
:severity="prioritySeverity(String(value))"
|
|
120
|
+
/>
|
|
91
121
|
</template>
|
|
92
122
|
<template #cell-status="{ value }">
|
|
93
|
-
<
|
|
123
|
+
<MStatus
|
|
124
|
+
:label="statusLabel(String(value))"
|
|
125
|
+
:severity="statusSeverity(String(value))"
|
|
126
|
+
/>
|
|
94
127
|
</template>
|
|
95
128
|
</MTable>
|
|
96
129
|
</MCard>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
/**
|
|
3
3
|
* 黄金样例:列表空状态(Flow)
|
|
4
|
-
* @see DESIGN.md
|
|
4
|
+
* @see DESIGN.md · page-layouts.md · surfaces § Flow
|
|
5
5
|
* 可嵌在列表页 MPageContent / MTable #empty 中;此处给出完整可运行骨架。
|
|
6
6
|
*/
|
|
7
7
|
import {
|
|
@@ -53,6 +53,10 @@ import {
|
|
|
53
53
|
|
|
54
54
|
<style scoped>
|
|
55
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);
|
|
56
60
|
border: 1px dashed color-mix(in srgb, var(--m-color-border) 80%, var(--m-color-primary));
|
|
57
61
|
border-radius: var(--m-radius-md);
|
|
58
62
|
background:
|
|
@@ -63,4 +67,21 @@ import {
|
|
|
63
67
|
),
|
|
64
68
|
var(--m-color-surface);
|
|
65
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
|
+
}
|
|
66
87
|
</style>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
/**
|
|
3
3
|
* 黄金样例:营销落地页(Express)
|
|
4
|
-
* @see DESIGN.md
|
|
4
|
+
* @see DESIGN.md · page-layouts.md · surfaces § Express
|
|
5
5
|
* 首屏单一任务;控件用 M*;色彩只走 --m-*;避开 AI 默认脸。
|
|
6
6
|
*/
|
|
7
7
|
import { MAccordion, MButton, MConfigProvider, MTag, zhCN } from 'morya-ui'
|
|
@@ -219,6 +219,36 @@ const faqTabs = [
|
|
|
219
219
|
font-weight: 600;
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
223
|
+
.landing-hero__track span {
|
|
224
|
+
animation: landing-fade-up 480ms ease both;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.landing-hero__track span:nth-child(2) {
|
|
228
|
+
animation-delay: 60ms;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.landing-hero__track span:nth-child(3) {
|
|
232
|
+
animation-delay: 120ms;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.landing-hero__track span:nth-child(4) {
|
|
236
|
+
animation-delay: 180ms;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
@keyframes landing-fade-up {
|
|
241
|
+
from {
|
|
242
|
+
opacity: 0;
|
|
243
|
+
transform: translateY(0.4rem);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
to {
|
|
247
|
+
opacity: 1;
|
|
248
|
+
transform: translateY(0);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
222
252
|
.landing-section {
|
|
223
253
|
padding: clamp(2.5rem, 6vw, 4rem) clamp(1.25rem, 4vw, 3rem);
|
|
224
254
|
max-width: 72rem;
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
/**
|
|
3
3
|
* 黄金样例:列表页
|
|
4
|
-
* @see DESIGN.md §
|
|
4
|
+
* @see DESIGN.md · morya-ui-pages references/page-layouts.md · visual-craft § Ops polish
|
|
5
5
|
*/
|
|
6
6
|
import {
|
|
7
7
|
MBreadcrumb,
|
|
8
8
|
MButton,
|
|
9
9
|
MConfigProvider,
|
|
10
|
+
MEmpty,
|
|
10
11
|
MInput,
|
|
11
12
|
MLayout,
|
|
12
13
|
MLayoutContent,
|
|
@@ -18,8 +19,8 @@ import {
|
|
|
18
19
|
MPageToolbar,
|
|
19
20
|
MSelect,
|
|
20
21
|
MSpace,
|
|
22
|
+
MStatus,
|
|
21
23
|
MTable,
|
|
22
|
-
MTag,
|
|
23
24
|
zhCN,
|
|
24
25
|
} from 'morya-ui'
|
|
25
26
|
import { ref } from 'vue'
|
|
@@ -31,6 +32,7 @@ const siderCollapsed = ref(false)
|
|
|
31
32
|
const menuModel = [
|
|
32
33
|
{ key: 'users', label: '用户管理', icon: 'user', to: '/users' },
|
|
33
34
|
{ key: 'roles', label: '角色管理', icon: 'shield', to: '/roles' },
|
|
35
|
+
{ key: 'settings', label: '系统设置', icon: 'settings', to: '/settings' },
|
|
34
36
|
]
|
|
35
37
|
|
|
36
38
|
const statusOptions = [
|
|
@@ -41,9 +43,9 @@ const statusOptions = [
|
|
|
41
43
|
|
|
42
44
|
const columns = [
|
|
43
45
|
{ key: 'name', label: '名称' },
|
|
44
|
-
{ key: 'status', label: '状态' },
|
|
45
|
-
{ key: 'updatedAt', label: '更新时间' },
|
|
46
|
-
{ key: 'actions', label: '操作', width:
|
|
46
|
+
{ key: 'status', label: '状态', width: 120 },
|
|
47
|
+
{ key: 'updatedAt', label: '更新时间', width: 140 },
|
|
48
|
+
{ key: 'actions', label: '操作', width: 148 },
|
|
47
49
|
]
|
|
48
50
|
|
|
49
51
|
const rows = [
|
|
@@ -104,7 +106,7 @@ const rows = [
|
|
|
104
106
|
<MTable
|
|
105
107
|
:columns="columns"
|
|
106
108
|
:rows="rows"
|
|
107
|
-
:rows-per-page="
|
|
109
|
+
:rows-per-page="5"
|
|
108
110
|
paginator
|
|
109
111
|
striped
|
|
110
112
|
bordered
|
|
@@ -112,22 +114,33 @@ const rows = [
|
|
|
112
114
|
aria-label="用户列表"
|
|
113
115
|
>
|
|
114
116
|
<template #cell-status="{ value }">
|
|
115
|
-
<
|
|
117
|
+
<MStatus
|
|
118
|
+
:label="value === 'active' ? '启用' : '停用'"
|
|
119
|
+
:severity="value === 'active' ? 'success' : 'secondary'"
|
|
120
|
+
/>
|
|
116
121
|
</template>
|
|
117
122
|
<template #cell-actions>
|
|
118
123
|
<MSpace>
|
|
119
|
-
<MButton severity="secondary" size="small">
|
|
124
|
+
<MButton severity="secondary" size="small" text>
|
|
120
125
|
编辑
|
|
121
126
|
</MButton>
|
|
122
|
-
<MButton severity="danger" size="small">
|
|
127
|
+
<MButton severity="danger" size="small" text>
|
|
123
128
|
删除
|
|
124
129
|
</MButton>
|
|
125
130
|
</MSpace>
|
|
126
131
|
</template>
|
|
127
132
|
<template #empty>
|
|
128
|
-
<
|
|
129
|
-
|
|
130
|
-
|
|
133
|
+
<MEmpty
|
|
134
|
+
title="还没有用户"
|
|
135
|
+
description="创建第一个用户后,即可分配角色与权限。"
|
|
136
|
+
icon="user"
|
|
137
|
+
>
|
|
138
|
+
<template #extra>
|
|
139
|
+
<MButton severity="primary">
|
|
140
|
+
新建用户
|
|
141
|
+
</MButton>
|
|
142
|
+
</template>
|
|
143
|
+
</MEmpty>
|
|
131
144
|
</template>
|
|
132
145
|
</MTable>
|
|
133
146
|
</MPageContent>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
/**
|
|
3
3
|
* 黄金样例:登录页(Account + 轻量品牌)
|
|
4
|
-
* @see DESIGN.md §
|
|
4
|
+
* @see DESIGN.md · surfaces § Account · visual-craft § Atmosphere
|
|
5
5
|
*/
|
|
6
6
|
import {
|
|
7
7
|
MButton,
|
|
@@ -56,7 +56,6 @@ async function onSubmit() {
|
|
|
56
56
|
<p>使用工作邮箱进入后台。</p>
|
|
57
57
|
</header>
|
|
58
58
|
|
|
59
|
-
<!-- 表单级常驻错误:token 样式告警条(字段错误优先用 errorMessage) -->
|
|
60
59
|
<p v-if="formError" class="login-alert" role="alert">
|
|
61
60
|
{{ formError }}
|
|
62
61
|
</p>
|
|
@@ -82,7 +81,7 @@ async function onSubmit() {
|
|
|
82
81
|
</MFormItem>
|
|
83
82
|
|
|
84
83
|
<MSpace style="margin-top: var(--m-space-2)" alignment="center">
|
|
85
|
-
<MButton type="submit" label="登录" :loading="submitting" />
|
|
84
|
+
<MButton type="submit" label="登录" severity="primary" :loading="submitting" />
|
|
86
85
|
<MButton type="button" label="忘记密码" severity="secondary" text />
|
|
87
86
|
</MSpace>
|
|
88
87
|
</MForm>
|
|
@@ -102,21 +101,45 @@ async function onSubmit() {
|
|
|
102
101
|
}
|
|
103
102
|
|
|
104
103
|
.login-brand {
|
|
104
|
+
position: relative;
|
|
105
105
|
display: flex;
|
|
106
106
|
flex-direction: column;
|
|
107
107
|
justify-content: flex-end;
|
|
108
108
|
gap: var(--m-space-4);
|
|
109
109
|
padding: clamp(2rem, 6vw, 4.5rem);
|
|
110
|
+
overflow: hidden;
|
|
110
111
|
background:
|
|
112
|
+
radial-gradient(
|
|
113
|
+
80% 60% at 10% 20%,
|
|
114
|
+
color-mix(in srgb, var(--m-color-primary) 22%, transparent),
|
|
115
|
+
transparent 55%
|
|
116
|
+
),
|
|
111
117
|
linear-gradient(
|
|
112
118
|
165deg,
|
|
113
|
-
color-mix(in srgb, var(--m-color-primary)
|
|
119
|
+
color-mix(in srgb, var(--m-color-primary) 16%, var(--m-color-surface)) 0%,
|
|
114
120
|
var(--m-color-surface) 55%,
|
|
115
121
|
color-mix(in srgb, var(--m-color-border) 35%, var(--m-color-surface)) 100%
|
|
116
122
|
);
|
|
117
123
|
border-right: 1px solid var(--m-color-border);
|
|
118
124
|
}
|
|
119
125
|
|
|
126
|
+
.login-brand::after {
|
|
127
|
+
content: '';
|
|
128
|
+
position: absolute;
|
|
129
|
+
inset: auto -10% -20% 40%;
|
|
130
|
+
height: 55%;
|
|
131
|
+
border-radius: 50%;
|
|
132
|
+
background: color-mix(in srgb, var(--m-color-primary) 12%, transparent);
|
|
133
|
+
pointer-events: none;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.login-brand__mark,
|
|
137
|
+
.login-brand__title,
|
|
138
|
+
.login-brand__lead {
|
|
139
|
+
position: relative;
|
|
140
|
+
z-index: 1;
|
|
141
|
+
}
|
|
142
|
+
|
|
120
143
|
.login-brand__mark {
|
|
121
144
|
margin: 0;
|
|
122
145
|
font-size: 0.8125rem;
|
|
@@ -177,6 +200,23 @@ async function onSubmit() {
|
|
|
177
200
|
line-height: 1.45;
|
|
178
201
|
}
|
|
179
202
|
|
|
203
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
204
|
+
.login-panel {
|
|
205
|
+
animation: login-panel-in 420ms ease both;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
@keyframes login-panel-in {
|
|
210
|
+
from {
|
|
211
|
+
opacity: 0;
|
|
212
|
+
transform: translateY(0.5rem);
|
|
213
|
+
}
|
|
214
|
+
to {
|
|
215
|
+
opacity: 1;
|
|
216
|
+
transform: translateY(0);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
180
220
|
@media (max-width: 768px) {
|
|
181
221
|
.login-shell {
|
|
182
222
|
grid-template-columns: 1fr;
|
|
@@ -199,6 +199,16 @@ describe('@morya-ui/mcp handlers', () => {
|
|
|
199
199
|
expect(result.id).toBe('overlay-choice');
|
|
200
200
|
expect(result.options.some((option) => option.component === 'Drawer')).toBe(true);
|
|
201
201
|
});
|
|
202
|
+
it('covers current selection, status, empty, and menu guides', () => {
|
|
203
|
+
const selection = read(handlers.recommendComponent({ decision: 'selection-choice' }));
|
|
204
|
+
expect(selection.options.map((option) => option.component)).toEqual(expect.arrayContaining(['Select', 'CascadeSelect', 'Listbox', 'SelectButton', 'Radio']));
|
|
205
|
+
const status = read(handlers.recommendComponent({ decision: 'status-label-choice' }));
|
|
206
|
+
expect(status.id).toBe('status-label-choice');
|
|
207
|
+
const empty = read(handlers.recommendComponent({ decision: 'empty-result-choice' }));
|
|
208
|
+
expect(empty.options.map((option) => option.component)).toEqual(['Empty', 'Result']);
|
|
209
|
+
const menu = read(handlers.recommendComponent({ decision: 'action-menu-choice' }));
|
|
210
|
+
expect(menu.options.some((option) => option.component === 'ContextMenu')).toBe(true);
|
|
211
|
+
});
|
|
202
212
|
it('exposes catalog health in version metadata', () => {
|
|
203
213
|
const result = read(handlers.version());
|
|
204
214
|
expect(result.health.ok).toBe(true);
|
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/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,12 +109,12 @@ 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
|
},
|
|
@@ -116,8 +122,8 @@ const loading = ref(false)`,
|
|
|
116
122
|
id: 'list-status-tag',
|
|
117
123
|
title: '表格状态列 Tag',
|
|
118
124
|
titleEn: 'Table status tag cell',
|
|
119
|
-
description: '在 #cell-status 中用 MTag
|
|
120
|
-
descriptionEn: 'Render
|
|
125
|
+
description: '在 #cell-status 中用 MTag 展示分类标签(业务状态优先用 list-status-dot / MStatus)。',
|
|
126
|
+
descriptionEn: 'Render category labels with MTag in #cell-status (prefer MStatus for business status).',
|
|
121
127
|
pageTypes: ['list'],
|
|
122
128
|
keywords: ['状态', 'tag', 'status', 'cell-status'],
|
|
123
129
|
imports: ['MTag'],
|
|
@@ -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
|
},
|