@morya-ui/mcp 0.3.5 → 0.3.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +154 -151
- package/bin/morya-ui-mcp.js +2 -2
- package/data/catalog.json +487 -389
- package/data/example-coverage.json +56 -74
- package/data/golden-pages/dashboard-page.vue +138 -138
- package/data/golden-pages/detail-page.vue +178 -178
- package/data/golden-pages/empty-state.vue +56 -56
- package/data/golden-pages/form-in-dialog.vue +166 -166
- package/data/golden-pages/form-page.vue +107 -107
- package/data/golden-pages/landing-page.vue +358 -358
- package/data/golden-pages/list-page-dense.vue +168 -168
- package/data/golden-pages/list-page-rail.vue +272 -272
- package/data/golden-pages/list-page.vue +222 -222
- package/data/golden-pages/login-page.vue +231 -231
- package/data/golden-pages/result-page.vue +84 -84
- package/data/golden-pages/settings-page.vue +163 -163
- package/data/golden-pages/wizard-form.vue +182 -182
- package/dist/__tests__/fidelity.test.d.ts +1 -0
- package/dist/__tests__/fidelity.test.js +88 -0
- package/dist/__tests__/page-snippets.test.js +13 -5
- package/dist/__tests__/tools.test.js +10 -8
- package/dist/decisions.js +6 -16
- package/dist/index.js +46 -3
- package/dist/page-snippets.js +722 -635
- package/dist/reference-brief.d.ts +55 -0
- package/dist/reference-brief.js +468 -0
- package/dist/style-shells.d.ts +20 -0
- package/dist/style-shells.js +249 -0
- package/dist/tools.d.ts +26 -0
- package/dist/tools.js +471 -374
- package/package.json +1 -1
package/dist/page-snippets.js
CHANGED
|
@@ -8,33 +8,33 @@ export const pageSnippets = [
|
|
|
8
8
|
pageTypes: ['list'],
|
|
9
9
|
keywords: ['筛选', '搜索', '查询', 'filters', 'filter', 'search', 'keyword', 'reset', '基础筛选'],
|
|
10
10
|
imports: ['MPageFilters', 'MSpace', 'MInput', 'MSelect', 'MButton'],
|
|
11
|
-
scriptSetup: `const keyword = ref('')
|
|
12
|
-
const status = ref<string | undefined>()
|
|
13
|
-
const statusOptions = [
|
|
14
|
-
{ label: '全部', value: '' },
|
|
15
|
-
{ label: '启用', value: 'active' },
|
|
16
|
-
{ label: '停用', value: 'inactive' },
|
|
17
|
-
]
|
|
18
|
-
|
|
19
|
-
function resetFilters() {
|
|
20
|
-
keyword.value = ''
|
|
21
|
-
status.value = undefined
|
|
11
|
+
scriptSetup: `const keyword = ref('')
|
|
12
|
+
const status = ref<string | undefined>()
|
|
13
|
+
const statusOptions = [
|
|
14
|
+
{ label: '全部', value: '' },
|
|
15
|
+
{ label: '启用', value: 'active' },
|
|
16
|
+
{ label: '停用', value: 'inactive' },
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
function resetFilters() {
|
|
20
|
+
keyword.value = ''
|
|
21
|
+
status.value = undefined
|
|
22
22
|
}`,
|
|
23
|
-
template: `<MPageFilters aria-label="筛选" variant="filled">
|
|
24
|
-
<MSpace wrap>
|
|
25
|
-
<MInput v-model="keyword" placeholder="搜索名称" clearable style="width: 14rem" />
|
|
26
|
-
<MSelect
|
|
27
|
-
v-model="status"
|
|
28
|
-
:options="statusOptions"
|
|
29
|
-
placeholder="状态"
|
|
30
|
-
clearable
|
|
31
|
-
style="width: 10rem"
|
|
32
|
-
/>
|
|
33
|
-
</MSpace>
|
|
34
|
-
<template #actions>
|
|
35
|
-
<MButton severity="secondary">查询</MButton>
|
|
36
|
-
<MButton severity="secondary" text @click="resetFilters">重置</MButton>
|
|
37
|
-
</template>
|
|
23
|
+
template: `<MPageFilters aria-label="筛选" variant="filled">
|
|
24
|
+
<MSpace wrap>
|
|
25
|
+
<MInput v-model="keyword" placeholder="搜索名称" clearable style="width: 14rem" />
|
|
26
|
+
<MSelect
|
|
27
|
+
v-model="status"
|
|
28
|
+
:options="statusOptions"
|
|
29
|
+
placeholder="状态"
|
|
30
|
+
clearable
|
|
31
|
+
style="width: 10rem"
|
|
32
|
+
/>
|
|
33
|
+
</MSpace>
|
|
34
|
+
<template #actions>
|
|
35
|
+
<MButton severity="secondary">查询</MButton>
|
|
36
|
+
<MButton severity="secondary" text @click="resetFilters">重置</MButton>
|
|
37
|
+
</template>
|
|
38
38
|
</MPageFilters>`,
|
|
39
39
|
rules: [
|
|
40
40
|
'放在 MPageHeader 之后、表格(或 FilterChips)之前',
|
|
@@ -68,57 +68,57 @@ function resetFilters() {
|
|
|
68
68
|
pageTypes: ['list'],
|
|
69
69
|
keywords: ['折叠', '高级筛选', 'collapse', 'advanced', 'expand', 'filter', '次要条件'],
|
|
70
70
|
imports: ['MPageFilters', 'MSpace', 'MInput', 'MSelect', 'MButton'],
|
|
71
|
-
scriptSetup: `const filtersExpanded = ref(false)
|
|
72
|
-
const keyword = ref('')
|
|
73
|
-
const status = ref<string | undefined>()
|
|
74
|
-
const department = ref<string | null>(null)
|
|
75
|
-
const statusOptions = [
|
|
76
|
-
{ label: '全部', value: '' },
|
|
77
|
-
{ label: '启用', value: 'active' },
|
|
78
|
-
{ label: '停用', value: 'inactive' },
|
|
79
|
-
]
|
|
80
|
-
const departmentOptions = [
|
|
81
|
-
{ label: '研发', value: '研发' },
|
|
82
|
-
{ label: '运营', value: '运营' },
|
|
83
|
-
{ label: '市场', value: '市场' },
|
|
84
|
-
]
|
|
85
|
-
|
|
86
|
-
function resetFilters() {
|
|
87
|
-
keyword.value = ''
|
|
88
|
-
status.value = undefined
|
|
89
|
-
department.value = null
|
|
71
|
+
scriptSetup: `const filtersExpanded = ref(false)
|
|
72
|
+
const keyword = ref('')
|
|
73
|
+
const status = ref<string | undefined>()
|
|
74
|
+
const department = ref<string | null>(null)
|
|
75
|
+
const statusOptions = [
|
|
76
|
+
{ label: '全部', value: '' },
|
|
77
|
+
{ label: '启用', value: 'active' },
|
|
78
|
+
{ label: '停用', value: 'inactive' },
|
|
79
|
+
]
|
|
80
|
+
const departmentOptions = [
|
|
81
|
+
{ label: '研发', value: '研发' },
|
|
82
|
+
{ label: '运营', value: '运营' },
|
|
83
|
+
{ label: '市场', value: '市场' },
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
function resetFilters() {
|
|
87
|
+
keyword.value = ''
|
|
88
|
+
status.value = undefined
|
|
89
|
+
department.value = null
|
|
90
90
|
}`,
|
|
91
|
-
template: `<MPageFilters
|
|
92
|
-
v-model:expanded="filtersExpanded"
|
|
93
|
-
aria-label="筛选"
|
|
94
|
-
variant="filled"
|
|
95
|
-
collapsible
|
|
96
|
-
>
|
|
97
|
-
<MSpace wrap>
|
|
98
|
-
<MInput v-model="keyword" placeholder="搜索名称" clearable style="width: 14rem" />
|
|
99
|
-
<MSelect
|
|
100
|
-
v-model="status"
|
|
101
|
-
:options="statusOptions"
|
|
102
|
-
placeholder="状态"
|
|
103
|
-
clearable
|
|
104
|
-
style="width: 10rem"
|
|
105
|
-
/>
|
|
106
|
-
</MSpace>
|
|
107
|
-
<template #actions>
|
|
108
|
-
<MButton severity="secondary">查询</MButton>
|
|
109
|
-
<MButton severity="secondary" text @click="resetFilters">重置</MButton>
|
|
110
|
-
</template>
|
|
111
|
-
<template #advanced>
|
|
112
|
-
<MSpace wrap>
|
|
113
|
-
<MSelect
|
|
114
|
-
v-model="department"
|
|
115
|
-
:options="departmentOptions"
|
|
116
|
-
placeholder="部门"
|
|
117
|
-
clearable
|
|
118
|
-
style="width: 10rem"
|
|
119
|
-
/>
|
|
120
|
-
</MSpace>
|
|
121
|
-
</template>
|
|
91
|
+
template: `<MPageFilters
|
|
92
|
+
v-model:expanded="filtersExpanded"
|
|
93
|
+
aria-label="筛选"
|
|
94
|
+
variant="filled"
|
|
95
|
+
collapsible
|
|
96
|
+
>
|
|
97
|
+
<MSpace wrap>
|
|
98
|
+
<MInput v-model="keyword" placeholder="搜索名称" clearable style="width: 14rem" />
|
|
99
|
+
<MSelect
|
|
100
|
+
v-model="status"
|
|
101
|
+
:options="statusOptions"
|
|
102
|
+
placeholder="状态"
|
|
103
|
+
clearable
|
|
104
|
+
style="width: 10rem"
|
|
105
|
+
/>
|
|
106
|
+
</MSpace>
|
|
107
|
+
<template #actions>
|
|
108
|
+
<MButton severity="secondary">查询</MButton>
|
|
109
|
+
<MButton severity="secondary" text @click="resetFilters">重置</MButton>
|
|
110
|
+
</template>
|
|
111
|
+
<template #advanced>
|
|
112
|
+
<MSpace wrap>
|
|
113
|
+
<MSelect
|
|
114
|
+
v-model="department"
|
|
115
|
+
:options="departmentOptions"
|
|
116
|
+
placeholder="部门"
|
|
117
|
+
clearable
|
|
118
|
+
style="width: 10rem"
|
|
119
|
+
/>
|
|
120
|
+
</MSpace>
|
|
121
|
+
</template>
|
|
122
122
|
</MPageFilters>`,
|
|
123
123
|
rules: [
|
|
124
124
|
'常用条件放 default,次要条件放 #advanced',
|
|
@@ -152,45 +152,45 @@ function resetFilters() {
|
|
|
152
152
|
pageTypes: ['list'],
|
|
153
153
|
keywords: ['已选', 'chip', 'tag', 'active', 'clear', '筛选', 'FilterChips', '已选筛选'],
|
|
154
154
|
imports: ['MPageFilterChips', 'MTag'],
|
|
155
|
-
scriptSetup: `const keyword = ref('')
|
|
156
|
-
const status = ref<string | undefined>()
|
|
157
|
-
const department = ref<string | null>(null)
|
|
158
|
-
const statusOptions = [
|
|
159
|
-
{ label: '全部', value: '' },
|
|
160
|
-
{ label: '启用', value: 'active' },
|
|
161
|
-
{ label: '停用', value: 'inactive' },
|
|
162
|
-
]
|
|
163
|
-
|
|
164
|
-
const activeFilters = computed(() => {
|
|
165
|
-
const items: Array<{ key: string; label: string }> = []
|
|
166
|
-
if (keyword.value.trim()) {
|
|
167
|
-
items.push({ key: 'keyword', label: \`关键词:\${keyword.value.trim()}\` })
|
|
168
|
-
}
|
|
169
|
-
if (status.value) {
|
|
170
|
-
const label = statusOptions.find((o) => o.value === status.value)?.label ?? status.value
|
|
171
|
-
items.push({ key: 'status', label: \`状态:\${label}\` })
|
|
172
|
-
}
|
|
173
|
-
if (department.value) {
|
|
174
|
-
items.push({ key: 'department', label: \`部门:\${department.value}\` })
|
|
175
|
-
}
|
|
176
|
-
return items
|
|
177
|
-
})
|
|
178
|
-
|
|
179
|
-
function clearFilter(key: string) {
|
|
180
|
-
if (key === 'keyword') keyword.value = ''
|
|
181
|
-
if (key === 'status') status.value = undefined
|
|
182
|
-
if (key === 'department') department.value = null
|
|
155
|
+
scriptSetup: `const keyword = ref('')
|
|
156
|
+
const status = ref<string | undefined>()
|
|
157
|
+
const department = ref<string | null>(null)
|
|
158
|
+
const statusOptions = [
|
|
159
|
+
{ label: '全部', value: '' },
|
|
160
|
+
{ label: '启用', value: 'active' },
|
|
161
|
+
{ label: '停用', value: 'inactive' },
|
|
162
|
+
]
|
|
163
|
+
|
|
164
|
+
const activeFilters = computed(() => {
|
|
165
|
+
const items: Array<{ key: string; label: string }> = []
|
|
166
|
+
if (keyword.value.trim()) {
|
|
167
|
+
items.push({ key: 'keyword', label: \`关键词:\${keyword.value.trim()}\` })
|
|
168
|
+
}
|
|
169
|
+
if (status.value) {
|
|
170
|
+
const label = statusOptions.find((o) => o.value === status.value)?.label ?? status.value
|
|
171
|
+
items.push({ key: 'status', label: \`状态:\${label}\` })
|
|
172
|
+
}
|
|
173
|
+
if (department.value) {
|
|
174
|
+
items.push({ key: 'department', label: \`部门:\${department.value}\` })
|
|
175
|
+
}
|
|
176
|
+
return items
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
function clearFilter(key: string) {
|
|
180
|
+
if (key === 'keyword') keyword.value = ''
|
|
181
|
+
if (key === 'status') status.value = undefined
|
|
182
|
+
if (key === 'department') department.value = null
|
|
183
183
|
}`,
|
|
184
|
-
template: `<MPageFilterChips v-if="activeFilters.length" label="已选" aria-label="已选筛选">
|
|
185
|
-
<MTag
|
|
186
|
-
v-for="item in activeFilters"
|
|
187
|
-
:key="item.key"
|
|
188
|
-
:value="item.label"
|
|
189
|
-
size="small"
|
|
190
|
-
bordered
|
|
191
|
-
closable
|
|
192
|
-
@close="clearFilter(item.key)"
|
|
193
|
-
/>
|
|
184
|
+
template: `<MPageFilterChips v-if="activeFilters.length" label="已选" aria-label="已选筛选">
|
|
185
|
+
<MTag
|
|
186
|
+
v-for="item in activeFilters"
|
|
187
|
+
:key="item.key"
|
|
188
|
+
:value="item.label"
|
|
189
|
+
size="small"
|
|
190
|
+
bordered
|
|
191
|
+
closable
|
|
192
|
+
@close="clearFilter(item.key)"
|
|
193
|
+
/>
|
|
194
194
|
</MPageFilterChips>`,
|
|
195
195
|
rules: [
|
|
196
196
|
'放在 MPageFilters 之后、表格之前',
|
|
@@ -241,91 +241,91 @@ function clearFilter(key: string) {
|
|
|
241
241
|
'MButton',
|
|
242
242
|
'MTag',
|
|
243
243
|
],
|
|
244
|
-
scriptSetup: `const filtersExpanded = ref(false)
|
|
245
|
-
const keyword = ref('')
|
|
246
|
-
const status = ref<string | undefined>()
|
|
247
|
-
const department = ref<string | null>(null)
|
|
248
|
-
|
|
249
|
-
const statusOptions = [
|
|
250
|
-
{ label: '全部', value: '' },
|
|
251
|
-
{ label: '启用', value: 'active' },
|
|
252
|
-
{ label: '停用', value: 'inactive' },
|
|
253
|
-
]
|
|
254
|
-
const departmentOptions = [
|
|
255
|
-
{ label: '研发', value: '研发' },
|
|
256
|
-
{ label: '运营', value: '运营' },
|
|
257
|
-
{ label: '市场', value: '市场' },
|
|
258
|
-
]
|
|
259
|
-
|
|
260
|
-
const activeFilters = computed(() => {
|
|
261
|
-
const items: Array<{ key: string; label: string }> = []
|
|
262
|
-
if (keyword.value.trim()) {
|
|
263
|
-
items.push({ key: 'keyword', label: \`关键词:\${keyword.value.trim()}\` })
|
|
264
|
-
}
|
|
265
|
-
if (status.value) {
|
|
266
|
-
const label = statusOptions.find((o) => o.value === status.value)?.label ?? status.value
|
|
267
|
-
items.push({ key: 'status', label: \`状态:\${label}\` })
|
|
268
|
-
}
|
|
269
|
-
if (department.value) {
|
|
270
|
-
items.push({ key: 'department', label: \`部门:\${department.value}\` })
|
|
271
|
-
}
|
|
272
|
-
return items
|
|
273
|
-
})
|
|
274
|
-
|
|
275
|
-
function clearFilter(key: string) {
|
|
276
|
-
if (key === 'keyword') keyword.value = ''
|
|
277
|
-
if (key === 'status') status.value = undefined
|
|
278
|
-
if (key === 'department') department.value = null
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
function resetFilters() {
|
|
282
|
-
keyword.value = ''
|
|
283
|
-
status.value = undefined
|
|
284
|
-
department.value = null
|
|
244
|
+
scriptSetup: `const filtersExpanded = ref(false)
|
|
245
|
+
const keyword = ref('')
|
|
246
|
+
const status = ref<string | undefined>()
|
|
247
|
+
const department = ref<string | null>(null)
|
|
248
|
+
|
|
249
|
+
const statusOptions = [
|
|
250
|
+
{ label: '全部', value: '' },
|
|
251
|
+
{ label: '启用', value: 'active' },
|
|
252
|
+
{ label: '停用', value: 'inactive' },
|
|
253
|
+
]
|
|
254
|
+
const departmentOptions = [
|
|
255
|
+
{ label: '研发', value: '研发' },
|
|
256
|
+
{ label: '运营', value: '运营' },
|
|
257
|
+
{ label: '市场', value: '市场' },
|
|
258
|
+
]
|
|
259
|
+
|
|
260
|
+
const activeFilters = computed(() => {
|
|
261
|
+
const items: Array<{ key: string; label: string }> = []
|
|
262
|
+
if (keyword.value.trim()) {
|
|
263
|
+
items.push({ key: 'keyword', label: \`关键词:\${keyword.value.trim()}\` })
|
|
264
|
+
}
|
|
265
|
+
if (status.value) {
|
|
266
|
+
const label = statusOptions.find((o) => o.value === status.value)?.label ?? status.value
|
|
267
|
+
items.push({ key: 'status', label: \`状态:\${label}\` })
|
|
268
|
+
}
|
|
269
|
+
if (department.value) {
|
|
270
|
+
items.push({ key: 'department', label: \`部门:\${department.value}\` })
|
|
271
|
+
}
|
|
272
|
+
return items
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
function clearFilter(key: string) {
|
|
276
|
+
if (key === 'keyword') keyword.value = ''
|
|
277
|
+
if (key === 'status') status.value = undefined
|
|
278
|
+
if (key === 'department') department.value = null
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function resetFilters() {
|
|
282
|
+
keyword.value = ''
|
|
283
|
+
status.value = undefined
|
|
284
|
+
department.value = null
|
|
285
285
|
}`,
|
|
286
|
-
template: `<MPageFilters
|
|
287
|
-
v-model:expanded="filtersExpanded"
|
|
288
|
-
aria-label="筛选"
|
|
289
|
-
variant="filled"
|
|
290
|
-
collapsible
|
|
291
|
-
>
|
|
292
|
-
<MSpace wrap>
|
|
293
|
-
<MInput v-model="keyword" placeholder="搜索名称" clearable style="width: 14rem" />
|
|
294
|
-
<MSelect
|
|
295
|
-
v-model="status"
|
|
296
|
-
:options="statusOptions"
|
|
297
|
-
placeholder="状态"
|
|
298
|
-
clearable
|
|
299
|
-
style="width: 10rem"
|
|
300
|
-
/>
|
|
301
|
-
</MSpace>
|
|
302
|
-
<template #actions>
|
|
303
|
-
<MButton severity="secondary">查询</MButton>
|
|
304
|
-
<MButton severity="secondary" text @click="resetFilters">重置</MButton>
|
|
305
|
-
</template>
|
|
306
|
-
<template #advanced>
|
|
307
|
-
<MSpace wrap>
|
|
308
|
-
<MSelect
|
|
309
|
-
v-model="department"
|
|
310
|
-
:options="departmentOptions"
|
|
311
|
-
placeholder="部门"
|
|
312
|
-
clearable
|
|
313
|
-
style="width: 10rem"
|
|
314
|
-
/>
|
|
315
|
-
</MSpace>
|
|
316
|
-
</template>
|
|
317
|
-
</MPageFilters>
|
|
318
|
-
|
|
319
|
-
<MPageFilterChips v-if="activeFilters.length" label="已选" aria-label="已选筛选">
|
|
320
|
-
<MTag
|
|
321
|
-
v-for="item in activeFilters"
|
|
322
|
-
:key="item.key"
|
|
323
|
-
:value="item.label"
|
|
324
|
-
size="small"
|
|
325
|
-
bordered
|
|
326
|
-
closable
|
|
327
|
-
@close="clearFilter(item.key)"
|
|
328
|
-
/>
|
|
286
|
+
template: `<MPageFilters
|
|
287
|
+
v-model:expanded="filtersExpanded"
|
|
288
|
+
aria-label="筛选"
|
|
289
|
+
variant="filled"
|
|
290
|
+
collapsible
|
|
291
|
+
>
|
|
292
|
+
<MSpace wrap>
|
|
293
|
+
<MInput v-model="keyword" placeholder="搜索名称" clearable style="width: 14rem" />
|
|
294
|
+
<MSelect
|
|
295
|
+
v-model="status"
|
|
296
|
+
:options="statusOptions"
|
|
297
|
+
placeholder="状态"
|
|
298
|
+
clearable
|
|
299
|
+
style="width: 10rem"
|
|
300
|
+
/>
|
|
301
|
+
</MSpace>
|
|
302
|
+
<template #actions>
|
|
303
|
+
<MButton severity="secondary">查询</MButton>
|
|
304
|
+
<MButton severity="secondary" text @click="resetFilters">重置</MButton>
|
|
305
|
+
</template>
|
|
306
|
+
<template #advanced>
|
|
307
|
+
<MSpace wrap>
|
|
308
|
+
<MSelect
|
|
309
|
+
v-model="department"
|
|
310
|
+
:options="departmentOptions"
|
|
311
|
+
placeholder="部门"
|
|
312
|
+
clearable
|
|
313
|
+
style="width: 10rem"
|
|
314
|
+
/>
|
|
315
|
+
</MSpace>
|
|
316
|
+
</template>
|
|
317
|
+
</MPageFilters>
|
|
318
|
+
|
|
319
|
+
<MPageFilterChips v-if="activeFilters.length" label="已选" aria-label="已选筛选">
|
|
320
|
+
<MTag
|
|
321
|
+
v-for="item in activeFilters"
|
|
322
|
+
:key="item.key"
|
|
323
|
+
:value="item.label"
|
|
324
|
+
size="small"
|
|
325
|
+
bordered
|
|
326
|
+
closable
|
|
327
|
+
@close="clearFilter(item.key)"
|
|
328
|
+
/>
|
|
329
329
|
</MPageFilterChips>`,
|
|
330
330
|
rules: [
|
|
331
331
|
'顺序:Header → Filters → FilterChips →(可选 Toolbar)→ Table',
|
|
@@ -359,40 +359,40 @@ function resetFilters() {
|
|
|
359
359
|
pageTypes: ['list'],
|
|
360
360
|
keywords: ['dense', 'compact', '紧凑', 'plain', '监控', '告警', '小尺寸筛选'],
|
|
361
361
|
imports: ['MPageFilters', 'MSpace', 'MInput', 'MSelect', 'MButton'],
|
|
362
|
-
scriptSetup: `const keyword = ref('')
|
|
363
|
-
const status = ref<string | undefined>()
|
|
364
|
-
const statusOptions = [
|
|
365
|
-
{ label: '全部', value: '' },
|
|
366
|
-
{ label: '启用', value: 'active' },
|
|
367
|
-
{ label: '停用', value: 'inactive' },
|
|
368
|
-
]
|
|
369
|
-
|
|
370
|
-
function resetFilters() {
|
|
371
|
-
keyword.value = ''
|
|
372
|
-
status.value = undefined
|
|
362
|
+
scriptSetup: `const keyword = ref('')
|
|
363
|
+
const status = ref<string | undefined>()
|
|
364
|
+
const statusOptions = [
|
|
365
|
+
{ label: '全部', value: '' },
|
|
366
|
+
{ label: '启用', value: 'active' },
|
|
367
|
+
{ label: '停用', value: 'inactive' },
|
|
368
|
+
]
|
|
369
|
+
|
|
370
|
+
function resetFilters() {
|
|
371
|
+
keyword.value = ''
|
|
372
|
+
status.value = undefined
|
|
373
373
|
}`,
|
|
374
|
-
template: `<MPageFilters aria-label="筛选" variant="plain">
|
|
375
|
-
<MSpace wrap>
|
|
376
|
-
<MInput
|
|
377
|
-
v-model="keyword"
|
|
378
|
-
placeholder="搜索"
|
|
379
|
-
clearable
|
|
380
|
-
size="small"
|
|
381
|
-
style="width: 11rem"
|
|
382
|
-
/>
|
|
383
|
-
<MSelect
|
|
384
|
-
v-model="status"
|
|
385
|
-
:options="statusOptions"
|
|
386
|
-
placeholder="状态"
|
|
387
|
-
clearable
|
|
388
|
-
size="small"
|
|
389
|
-
style="width: 8rem"
|
|
390
|
-
/>
|
|
391
|
-
</MSpace>
|
|
392
|
-
<template #actions>
|
|
393
|
-
<MButton severity="secondary" size="small">查询</MButton>
|
|
394
|
-
<MButton severity="secondary" size="small" text @click="resetFilters">重置</MButton>
|
|
395
|
-
</template>
|
|
374
|
+
template: `<MPageFilters aria-label="筛选" variant="plain">
|
|
375
|
+
<MSpace wrap>
|
|
376
|
+
<MInput
|
|
377
|
+
v-model="keyword"
|
|
378
|
+
placeholder="搜索"
|
|
379
|
+
clearable
|
|
380
|
+
size="small"
|
|
381
|
+
style="width: 11rem"
|
|
382
|
+
/>
|
|
383
|
+
<MSelect
|
|
384
|
+
v-model="status"
|
|
385
|
+
:options="statusOptions"
|
|
386
|
+
placeholder="状态"
|
|
387
|
+
clearable
|
|
388
|
+
size="small"
|
|
389
|
+
style="width: 8rem"
|
|
390
|
+
/>
|
|
391
|
+
</MSpace>
|
|
392
|
+
<template #actions>
|
|
393
|
+
<MButton severity="secondary" size="small">查询</MButton>
|
|
394
|
+
<MButton severity="secondary" size="small" text @click="resetFilters">重置</MButton>
|
|
395
|
+
</template>
|
|
396
396
|
</MPageFilters>`,
|
|
397
397
|
rules: [
|
|
398
398
|
'仅在 dense/compact craft 使用;默认列表用 list-filters-stack',
|
|
@@ -422,10 +422,10 @@ function resetFilters() {
|
|
|
422
422
|
pageTypes: ['list'],
|
|
423
423
|
keywords: ['页头', '标题', '新建', 'header', 'title', 'create', 'actions'],
|
|
424
424
|
imports: ['MPageHeader', 'MButton'],
|
|
425
|
-
template: `<MPageHeader title="用户管理" description="维护账号、角色与权限。">
|
|
426
|
-
<template #actions>
|
|
427
|
-
<MButton severity="primary">新建用户</MButton>
|
|
428
|
-
</template>
|
|
425
|
+
template: `<MPageHeader title="用户管理" description="维护账号、角色与权限。">
|
|
426
|
+
<template #actions>
|
|
427
|
+
<MButton severity="primary">新建用户</MButton>
|
|
428
|
+
</template>
|
|
429
429
|
</MPageHeader>`,
|
|
430
430
|
rules: ['页级标题用 MPageHeader,不要用 MPageToolbar 当第二页头', '主操作放 #actions'],
|
|
431
431
|
rulesEn: ['Use MPageHeader for page identity; do not use MPageToolbar as a second page title', 'Put primary actions in #actions'],
|
|
@@ -441,12 +441,12 @@ function resetFilters() {
|
|
|
441
441
|
pageTypes: ['list'],
|
|
442
442
|
keywords: ['批量', '已选', 'toolbar', 'batch', 'export', 'delete'],
|
|
443
443
|
imports: ['MPageToolbar', 'MButton'],
|
|
444
|
-
template: `<MPageToolbar>
|
|
445
|
-
<span style="color:var(--m-color-text-muted);font-size:var(--m-font-size-sm)">已选 0 项</span>
|
|
446
|
-
<template #actions>
|
|
447
|
-
<MButton severity="secondary" text>导出</MButton>
|
|
448
|
-
<MButton severity="danger" text>删除</MButton>
|
|
449
|
-
</template>
|
|
444
|
+
template: `<MPageToolbar>
|
|
445
|
+
<span style="color:var(--m-color-text-muted);font-size:var(--m-font-size-sm)">已选 0 项</span>
|
|
446
|
+
<template #actions>
|
|
447
|
+
<MButton severity="secondary" text>导出</MButton>
|
|
448
|
+
<MButton severity="danger" text>删除</MButton>
|
|
449
|
+
</template>
|
|
450
450
|
</MPageToolbar>`,
|
|
451
451
|
rules: ['左侧用 default 插槽展示已选数量', '批量操作用 text/outlined,danger 仅 destructive', '放在 FilterChips 之后、表格之前'],
|
|
452
452
|
rulesEn: ['Use the default slot for selection count', 'Batch actions use text/outlined; danger only for destructive ops', 'Place after FilterChips and before the table'],
|
|
@@ -457,8 +457,8 @@ function resetFilters() {
|
|
|
457
457
|
id: 'list-table',
|
|
458
458
|
title: '列表页表格',
|
|
459
459
|
titleEn: 'List page table',
|
|
460
|
-
description: '标准 MTable
|
|
461
|
-
descriptionEn: '
|
|
460
|
+
description: '标准 MTable:状态列 MStatus、数字列 align end、行操作 small text、空态带下一步。全视口主列表可加 fill;嵌入/短页不要 fill。',
|
|
461
|
+
descriptionEn: 'MTable with MStatus status, end-aligned numbers, small text row actions, next-step empty. Add fill only for full-viewport main lists.',
|
|
462
462
|
pageTypes: ['list'],
|
|
463
463
|
keywords: [
|
|
464
464
|
'表格',
|
|
@@ -471,60 +471,84 @@ function resetFilters() {
|
|
|
471
471
|
'fill',
|
|
472
472
|
'撑满',
|
|
473
473
|
'分页',
|
|
474
|
+
'tabular',
|
|
475
|
+
'对齐',
|
|
474
476
|
],
|
|
475
|
-
imports: ['MTable', 'MEmpty', 'MButton'],
|
|
476
|
-
scriptSetup: `const columns = [
|
|
477
|
-
{ key: 'name', label: '名称' },
|
|
478
|
-
{ key: 'status', label: '状态' },
|
|
479
|
-
{ key: '
|
|
480
|
-
{ key: '
|
|
481
|
-
|
|
482
|
-
|
|
477
|
+
imports: ['MTable', 'MEmpty', 'MButton', 'MStatus', 'MSpace'],
|
|
478
|
+
scriptSetup: `const columns = [
|
|
479
|
+
{ key: 'name', label: '名称' },
|
|
480
|
+
{ key: 'status', label: '状态', width: 110 },
|
|
481
|
+
{ key: 'count', label: '数量', width: 96, align: 'end' as const },
|
|
482
|
+
{ key: 'updatedAt', label: '更新时间', width: 160 },
|
|
483
|
+
{ key: 'actions', label: '操作', width: 128 },
|
|
484
|
+
]
|
|
485
|
+
const rows = ref<Record<string, unknown>[]>([])
|
|
483
486
|
const loading = ref(false)`,
|
|
484
|
-
template: `<!-- 全视口主列表再加 fill;嵌入/短页去掉 fill -->
|
|
485
|
-
<MTable
|
|
486
|
-
:columns="columns"
|
|
487
|
-
:rows="rows"
|
|
488
|
-
:loading="loading"
|
|
489
|
-
fill
|
|
490
|
-
paginator
|
|
491
|
-
:rows-per-page="10"
|
|
492
|
-
striped
|
|
493
|
-
bordered
|
|
494
|
-
row-key="id"
|
|
495
|
-
aria-label="数据列表"
|
|
496
|
-
>
|
|
497
|
-
<template #
|
|
498
|
-
<
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
487
|
+
template: `<!-- 全视口主列表再加 fill;嵌入/短页去掉 fill -->
|
|
488
|
+
<MTable
|
|
489
|
+
:columns="columns"
|
|
490
|
+
:rows="rows"
|
|
491
|
+
:loading="loading"
|
|
492
|
+
fill
|
|
493
|
+
paginator
|
|
494
|
+
:rows-per-page="10"
|
|
495
|
+
striped
|
|
496
|
+
bordered
|
|
497
|
+
row-key="id"
|
|
498
|
+
aria-label="数据列表"
|
|
499
|
+
>
|
|
500
|
+
<template #cell-status="{ value }">
|
|
501
|
+
<MStatus
|
|
502
|
+
:label="value === 'active' || value === 'online' ? '启用' : '停用'"
|
|
503
|
+
:severity="value === 'active' || value === 'online' ? 'success' : 'secondary'"
|
|
504
|
+
/>
|
|
505
|
+
</template>
|
|
506
|
+
<template #cell-count="{ value }">
|
|
507
|
+
<span style="font-variant-numeric: tabular-nums">{{ value ?? '—' }}</span>
|
|
508
|
+
</template>
|
|
509
|
+
<template #cell-actions>
|
|
510
|
+
<MSpace>
|
|
511
|
+
<MButton severity="secondary" size="small" text>编辑</MButton>
|
|
512
|
+
<MButton severity="danger" size="small" text>删除</MButton>
|
|
513
|
+
</MSpace>
|
|
514
|
+
</template>
|
|
515
|
+
<template #empty>
|
|
516
|
+
<MEmpty
|
|
517
|
+
title="还没有成员"
|
|
518
|
+
description="新建第一条后,这里会列出姓名、状态与更新时间。"
|
|
519
|
+
icon="inbox"
|
|
520
|
+
>
|
|
521
|
+
<template #extra>
|
|
522
|
+
<MButton severity="primary">新建成员</MButton>
|
|
523
|
+
</template>
|
|
524
|
+
</MEmpty>
|
|
525
|
+
</template>
|
|
508
526
|
</MTable>`,
|
|
509
527
|
rules: [
|
|
510
528
|
'先判断是否适合 fill:全视口后台主列表 + 表格是主任务 → MPageContent fill + MTable fill',
|
|
511
529
|
'嵌入表、短页、整页文档滚动 → 不要 fill,内容高度即可',
|
|
512
|
-
'
|
|
530
|
+
'业务状态列用 MStatus;分类标签才用 MTag',
|
|
531
|
+
'数量/金额列 columns.align = end,并用 tabular-nums',
|
|
532
|
+
'行操作用 size="small" + text;空态文案写领域下一步,禁止「示例 / Name / No data」',
|
|
513
533
|
],
|
|
514
534
|
rulesEn: [
|
|
515
535
|
'Decide fill first: full-viewport admin main list → MPageContent fill + MTable fill',
|
|
516
536
|
'Embedded / short / document-scroll pages → skip fill',
|
|
517
|
-
'
|
|
537
|
+
'Business status → MStatus; categories → MTag',
|
|
538
|
+
'Numeric columns: align end + tabular-nums',
|
|
539
|
+
'Row actions: size="small" + text; empty copy is domain-real — no “示例 / Name / No data”',
|
|
518
540
|
],
|
|
519
541
|
avoid: [
|
|
520
542
|
'不要用 MCard 包裹 bordered MTable',
|
|
521
543
|
'不要对嵌入/短页硬套 fill',
|
|
522
544
|
'适合 fill 时不要手写 min-height / calc',
|
|
545
|
+
'不要在 #cell-status 默认用 MTag',
|
|
523
546
|
],
|
|
524
547
|
avoidEn: [
|
|
525
548
|
'Do not wrap a bordered MTable with MCard',
|
|
526
549
|
'Do not force fill on embedded or short pages',
|
|
527
550
|
'Do not hand-write min-height / calc when fill is appropriate',
|
|
551
|
+
'Do not default #cell-status to MTag',
|
|
528
552
|
],
|
|
529
553
|
},
|
|
530
554
|
{
|
|
@@ -536,11 +560,11 @@ const loading = ref(false)`,
|
|
|
536
560
|
pageTypes: ['list'],
|
|
537
561
|
keywords: ['行操作', '编辑', '删除', 'actions', 'row', 'cell'],
|
|
538
562
|
imports: ['MSpace', 'MButton'],
|
|
539
|
-
template: `<template #cell-actions>
|
|
540
|
-
<MSpace>
|
|
541
|
-
<MButton severity="secondary" size="small" text>编辑</MButton>
|
|
542
|
-
<MButton severity="danger" size="small" text>删除</MButton>
|
|
543
|
-
</MSpace>
|
|
563
|
+
template: `<template #cell-actions>
|
|
564
|
+
<MSpace>
|
|
565
|
+
<MButton severity="secondary" size="small" text>编辑</MButton>
|
|
566
|
+
<MButton severity="danger" size="small" text>删除</MButton>
|
|
567
|
+
</MSpace>
|
|
544
568
|
</template>`,
|
|
545
569
|
rules: ['行内操作用 size="small" + text/outlined,避免一排实心按钮', '危险操作使用 severity="danger" 并配合确认弹窗'],
|
|
546
570
|
rulesEn: ['Use size="small" with text/outlined row actions', 'Use severity="danger" with confirmation for destructive actions'],
|
|
@@ -556,11 +580,11 @@ const loading = ref(false)`,
|
|
|
556
580
|
pageTypes: ['list'],
|
|
557
581
|
keywords: ['分类', '标签', '可关闭', 'chip', 'category', 'tag-only'],
|
|
558
582
|
imports: ['MTag'],
|
|
559
|
-
template: `<template #cell-category="{ value }">
|
|
560
|
-
<MTag
|
|
561
|
-
:value="String(value ?? '')"
|
|
562
|
-
severity="info"
|
|
563
|
-
/>
|
|
583
|
+
template: `<template #cell-category="{ value }">
|
|
584
|
+
<MTag
|
|
585
|
+
:value="String(value ?? '')"
|
|
586
|
+
severity="info"
|
|
587
|
+
/>
|
|
564
588
|
</template>`,
|
|
565
589
|
rules: ['分类/强调态用 MTag;行内业务状态用 MStatus(list-status-dot)', '不要用 Button 颜色表达状态'],
|
|
566
590
|
rulesEn: ['Use MTag for categories; use MStatus for business status (list-status-dot)', 'Do not use Button colors for status'],
|
|
@@ -576,11 +600,11 @@ const loading = ref(false)`,
|
|
|
576
600
|
pageTypes: ['list'],
|
|
577
601
|
keywords: ['状态', 'status', 'dot', 'cell-status', '在线', '启用', '停用', '业务状态'],
|
|
578
602
|
imports: ['MStatus'],
|
|
579
|
-
template: `<template #cell-status="{ value }">
|
|
580
|
-
<MStatus
|
|
581
|
-
:label="value === 'active' || value === 'online' ? '启用' : '停用'"
|
|
582
|
-
:severity="value === 'active' || value === 'online' ? 'success' : 'secondary'"
|
|
583
|
-
/>
|
|
603
|
+
template: `<template #cell-status="{ value }">
|
|
604
|
+
<MStatus
|
|
605
|
+
:label="value === 'active' || value === 'online' ? '启用' : '停用'"
|
|
606
|
+
:severity="value === 'active' || value === 'online' ? 'success' : 'secondary'"
|
|
607
|
+
/>
|
|
584
608
|
</template>`,
|
|
585
609
|
rules: ['行内轻量业务状态优先 MStatus', '不要用 Button 颜色表达状态'],
|
|
586
610
|
rulesEn: ['Prefer MStatus for lightweight inline business status', 'Do not use Button colors for status'],
|
|
@@ -591,25 +615,33 @@ const loading = ref(false)`,
|
|
|
591
615
|
id: 'empty-block',
|
|
592
616
|
title: '空状态 MEmpty',
|
|
593
617
|
titleEn: 'Empty state with MEmpty',
|
|
594
|
-
description: '列表或内容区无数据时使用 MEmpty
|
|
595
|
-
descriptionEn: '
|
|
618
|
+
description: '列表或内容区无数据时使用 MEmpty;标题/说明写领域下一步,#extra 放唯一 primary。',
|
|
619
|
+
descriptionEn: 'MEmpty for no-data; domain next-step copy; one primary in #extra.',
|
|
596
620
|
pageTypes: ['list', 'common'],
|
|
597
621
|
keywords: ['空状态', '无数据', 'empty', 'zero state', 'no data'],
|
|
598
622
|
imports: ['MEmpty', 'MButton'],
|
|
599
|
-
template: `<MEmpty
|
|
600
|
-
title="
|
|
601
|
-
description="
|
|
602
|
-
icon="
|
|
603
|
-
>
|
|
604
|
-
<template #extra>
|
|
605
|
-
<MButton
|
|
606
|
-
<MButton
|
|
607
|
-
</template>
|
|
623
|
+
template: `<MEmpty
|
|
624
|
+
title="还没有成员"
|
|
625
|
+
description="邀请或新建第一位成员后,这里会显示状态与最近活动。"
|
|
626
|
+
icon="users"
|
|
627
|
+
>
|
|
628
|
+
<template #extra>
|
|
629
|
+
<MButton severity="primary">新建成员</MButton>
|
|
630
|
+
<MButton severity="secondary" text>导入名单</MButton>
|
|
631
|
+
</template>
|
|
608
632
|
</MEmpty>`,
|
|
609
|
-
rules: [
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
633
|
+
rules: [
|
|
634
|
+
'正常无数据用 MEmpty,不要用错误色',
|
|
635
|
+
'主 CTA 用 severity="primary",次动作用 text/secondary',
|
|
636
|
+
'文案写清「现在可以做什么」,禁止「暂无数据 / No data / 示例」',
|
|
637
|
+
],
|
|
638
|
+
rulesEn: [
|
|
639
|
+
'Use MEmpty for normal emptiness, not error colors',
|
|
640
|
+
'Primary CTA with severity="primary"; lesser actions text/secondary',
|
|
641
|
+
'Copy says what to do next — no “暂无数据 / No data / 示例”',
|
|
642
|
+
],
|
|
643
|
+
avoid: ['不要用手写 div 拼空态', '不要用 MResult 表达无数据', '不要放两个 filled primary'],
|
|
644
|
+
avoidEn: ['Do not hand-roll empty markup', 'Do not use MResult for no-data', 'Do not place two filled primaries'],
|
|
613
645
|
},
|
|
614
646
|
{
|
|
615
647
|
id: 'result-block',
|
|
@@ -620,11 +652,11 @@ const loading = ref(false)`,
|
|
|
620
652
|
pageTypes: ['common'],
|
|
621
653
|
keywords: ['结果', '成功', '失败', '404', '403', 'result', 'success', 'error'],
|
|
622
654
|
imports: ['MResult', 'MButton'],
|
|
623
|
-
template: `<MResult status="success" description="订单已创建,可在列表中查看详情。">
|
|
624
|
-
<template #extra>
|
|
625
|
-
<MButton label="查看订单" />
|
|
626
|
-
<MButton label="返回列表" severity="secondary" text />
|
|
627
|
-
</template>
|
|
655
|
+
template: `<MResult status="success" description="订单已创建,可在列表中查看详情。">
|
|
656
|
+
<template #extra>
|
|
657
|
+
<MButton label="查看订单" />
|
|
658
|
+
<MButton label="返回列表" severity="secondary" text />
|
|
659
|
+
</template>
|
|
628
660
|
</MResult>`,
|
|
629
661
|
rules: ['流程终点用 MResult', '提供明确下一步操作'],
|
|
630
662
|
rulesEn: ['Use MResult for terminal outcomes', 'Provide clear next-step actions'],
|
|
@@ -650,25 +682,52 @@ const loading = ref(false)`,
|
|
|
650
682
|
id: 'form-body',
|
|
651
683
|
title: '表单主体',
|
|
652
684
|
titleEn: 'Form body surface',
|
|
653
|
-
description: 'MPageSection variant="form"
|
|
654
|
-
descriptionEn: '
|
|
685
|
+
description: 'MPageSection variant="form" + MForm;左标签时设 label-position/label-width;常驻错误用字段 errorMessage。',
|
|
686
|
+
descriptionEn: 'MPageSection form + MForm; set label-position/label-width for left labels; persistent errors via errorMessage.',
|
|
655
687
|
pageTypes: ['form'],
|
|
656
|
-
keywords: ['表单', 'form', 'fields', 'formitem'],
|
|
657
|
-
imports: ['MPageSection', 'MForm', 'MFormItem', 'MInput'],
|
|
658
|
-
scriptSetup: `const model = reactive({ name: '' })
|
|
659
|
-
const submitting = ref(false)
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
688
|
+
keywords: ['表单', 'form', 'fields', 'formitem', 'label-width', 'errorMessage'],
|
|
689
|
+
imports: ['MPageSection', 'MForm', 'MFormItem', 'MInput', 'MSelect'],
|
|
690
|
+
scriptSetup: `const model = reactive({ name: '', role: undefined as string | undefined })
|
|
691
|
+
const submitting = ref(false)
|
|
692
|
+
const roleOptions = [
|
|
693
|
+
{ label: '管理员', value: 'admin' },
|
|
694
|
+
{ label: '成员', value: 'member' },
|
|
695
|
+
]`,
|
|
696
|
+
template: `<MPageSection variant="form">
|
|
697
|
+
<MForm
|
|
698
|
+
label-position="left"
|
|
699
|
+
label-width="6rem"
|
|
700
|
+
@submit.prevent="onSubmit"
|
|
701
|
+
>
|
|
702
|
+
<MFormItem label="姓名" name="name" required>
|
|
703
|
+
<MInput v-model="model.name" placeholder="与工牌一致的姓名" fluid />
|
|
704
|
+
</MFormItem>
|
|
705
|
+
<MFormItem label="角色" name="role" required>
|
|
706
|
+
<MSelect
|
|
707
|
+
v-model="model.role"
|
|
708
|
+
:options="roleOptions"
|
|
709
|
+
placeholder="选择角色"
|
|
710
|
+
clearable
|
|
711
|
+
fluid
|
|
712
|
+
/>
|
|
713
|
+
</MFormItem>
|
|
714
|
+
<!-- 更多字段;常驻错误:error-message="请填写必填项" -->
|
|
715
|
+
</MForm>
|
|
667
716
|
</MPageSection>`,
|
|
668
|
-
rules: [
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
717
|
+
rules: [
|
|
718
|
+
'字段用 MFormItem + fluid 控件',
|
|
719
|
+
'左标签布局设 label-position="left" + label-width(约 5–7rem)',
|
|
720
|
+
'表单表面由 MPageSection 提供,不要重复 MCard',
|
|
721
|
+
'常驻错误用字段 errorMessage 或 token 样式 role="alert",不要用 <MMessage> 当内嵌 Alert',
|
|
722
|
+
],
|
|
723
|
+
rulesEn: [
|
|
724
|
+
'Use MFormItem with fluid controls',
|
|
725
|
+
'Left labels: label-position="left" + label-width (~5–7rem)',
|
|
726
|
+
'Let MPageSection provide the surface; do not duplicate MCard',
|
|
727
|
+
'Persistent errors: field errorMessage or token role="alert" — not <MMessage> as inline alert',
|
|
728
|
+
],
|
|
729
|
+
avoid: ['不要绕过 MForm 手写校验状态', '不要把错误只画成红色边框而无文案'],
|
|
730
|
+
avoidEn: ['Do not bypass MForm for validation state', 'Do not show errors as red borders without copy'],
|
|
672
731
|
},
|
|
673
732
|
{
|
|
674
733
|
id: 'form-actions',
|
|
@@ -679,11 +738,11 @@ const submitting = ref(false)`,
|
|
|
679
738
|
pageTypes: ['form'],
|
|
680
739
|
keywords: ['保存', '取消', 'submit', 'actions', 'footer'],
|
|
681
740
|
imports: ['MPageSection', 'MSpace', 'MButton'],
|
|
682
|
-
template: `<MPageSection variant="actions">
|
|
683
|
-
<MSpace>
|
|
684
|
-
<MButton native-type="submit" severity="primary" :loading="submitting">保存</MButton>
|
|
685
|
-
<MButton severity="secondary">取消</MButton>
|
|
686
|
-
</MSpace>
|
|
741
|
+
template: `<MPageSection variant="actions">
|
|
742
|
+
<MSpace>
|
|
743
|
+
<MButton native-type="submit" severity="primary" :loading="submitting">保存</MButton>
|
|
744
|
+
<MButton severity="secondary">取消</MButton>
|
|
745
|
+
</MSpace>
|
|
687
746
|
</MPageSection>`,
|
|
688
747
|
rules: ['提交按钮在前,取消 secondary', '保存中传 loading 防重复提交'],
|
|
689
748
|
rulesEn: ['Submit first, cancel secondary', 'Pass loading while saving to prevent duplicate submits'],
|
|
@@ -694,31 +753,59 @@ const submitting = ref(false)`,
|
|
|
694
753
|
id: 'dashboard-kpi-grid',
|
|
695
754
|
title: '仪表盘 KPI 栅格',
|
|
696
755
|
titleEn: 'Dashboard KPI grid',
|
|
697
|
-
description: 'MGrid + MPageStat
|
|
698
|
-
descriptionEn: '
|
|
756
|
+
description: 'MGrid + MPageStat:带 trend / trendDirection / trendSeverity;高密条可用 density="compact"。',
|
|
757
|
+
descriptionEn: 'MGrid + MPageStat with trend / trendDirection / trendSeverity; use density="compact" for dense strips.',
|
|
699
758
|
pageTypes: ['dashboard'],
|
|
700
|
-
keywords: ['kpi', '指标', 'stat', 'grid', 'metrics'],
|
|
759
|
+
keywords: ['kpi', '指标', 'stat', 'grid', 'metrics', 'trend', 'compact'],
|
|
701
760
|
imports: ['MGrid', 'MGridItem', 'MPageStat'],
|
|
702
|
-
scriptSetup: `const metrics = ref([
|
|
703
|
-
{
|
|
704
|
-
|
|
761
|
+
scriptSetup: `const metrics = ref([
|
|
762
|
+
{
|
|
763
|
+
label: '在册成员',
|
|
764
|
+
value: '12,480',
|
|
765
|
+
trend: '+8.2%',
|
|
766
|
+
trendDirection: 'up' as const,
|
|
767
|
+
trendSeverity: 'success' as const,
|
|
768
|
+
trendLabel: '较上月',
|
|
769
|
+
icon: 'users',
|
|
770
|
+
},
|
|
771
|
+
{
|
|
772
|
+
label: '今日活跃',
|
|
773
|
+
value: '1,926',
|
|
774
|
+
trend: '-3.1%',
|
|
775
|
+
trendDirection: 'down' as const,
|
|
776
|
+
trendSeverity: 'warn' as const,
|
|
777
|
+
trendLabel: '较昨日',
|
|
778
|
+
icon: 'activity',
|
|
779
|
+
},
|
|
705
780
|
])`,
|
|
706
|
-
template: `<MGrid :cols="4" :x-gap="16" :y-gap="16" responsive="screen">
|
|
707
|
-
<MGridItem v-for="metric in metrics" :key="metric.label" :span="1">
|
|
708
|
-
<MPageStat
|
|
709
|
-
:label="metric.label"
|
|
710
|
-
:value="metric.value"
|
|
711
|
-
:trend="metric.trend"
|
|
712
|
-
:trend-direction="metric.trendDirection"
|
|
713
|
-
:trend-
|
|
714
|
-
:
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
</
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
781
|
+
template: `<MGrid :cols="4" :x-gap="16" :y-gap="16" responsive="screen">
|
|
782
|
+
<MGridItem v-for="metric in metrics" :key="metric.label" :span="1">
|
|
783
|
+
<MPageStat
|
|
784
|
+
:label="metric.label"
|
|
785
|
+
:value="metric.value"
|
|
786
|
+
:trend="metric.trend"
|
|
787
|
+
:trend-direction="metric.trendDirection"
|
|
788
|
+
:trend-severity="metric.trendSeverity"
|
|
789
|
+
:trend-label="metric.trendLabel"
|
|
790
|
+
:icon="metric.icon"
|
|
791
|
+
/>
|
|
792
|
+
</MGridItem>
|
|
793
|
+
</MGrid>
|
|
794
|
+
<!-- 高密条带示例:density="compact" layout="plain" orientation="inline" -->`,
|
|
795
|
+
rules: [
|
|
796
|
+
'KPI 用 MPageStat,不要每页自定义 stat CSS',
|
|
797
|
+
'趋势务必带 trendDirection + trendSeverity(升 success / 降 warn|danger)',
|
|
798
|
+
'responsive="screen" 便于窄屏降列',
|
|
799
|
+
'密集工作台可用 density="compact";加载中用 loading',
|
|
800
|
+
],
|
|
801
|
+
rulesEn: [
|
|
802
|
+
'Use MPageStat for KPIs — no per-page stat CSS',
|
|
803
|
+
'Always set trendDirection + trendSeverity (up→success, down→warn|danger)',
|
|
804
|
+
'Use responsive="screen" for narrow layouts',
|
|
805
|
+
'Dense dashboards may use density="compact"; use loading while fetching',
|
|
806
|
+
],
|
|
807
|
+
avoid: ['不要每个指标手写 font-size/颜色', '不要只有数字没有趋势语义(用户要求趋势时)'],
|
|
808
|
+
avoidEn: ['Do not hand-write font sizes/colors per metric', 'Do not omit trend semantics when the brief asks for trends'],
|
|
722
809
|
},
|
|
723
810
|
{
|
|
724
811
|
id: 'dashboard-chart-card',
|
|
@@ -729,14 +816,14 @@ const submitting = ref(false)`,
|
|
|
729
816
|
pageTypes: ['dashboard'],
|
|
730
817
|
keywords: ['chart', '图表', 'placeholder', 'card', 'trend'],
|
|
731
818
|
imports: ['MCard', 'MEmpty', 'MSkeleton'],
|
|
732
|
-
template: `<MCard title="趋势概览" shadow="always">
|
|
733
|
-
<MSkeleton v-if="loading" height="8rem" />
|
|
734
|
-
<MEmpty
|
|
735
|
-
v-else
|
|
736
|
-
aria-label="图表占位"
|
|
737
|
-
title="暂无图表数据"
|
|
738
|
-
description="图表区域(接入 ECharts / 业务组件)"
|
|
739
|
-
/>
|
|
819
|
+
template: `<MCard title="趋势概览" shadow="always">
|
|
820
|
+
<MSkeleton v-if="loading" height="8rem" />
|
|
821
|
+
<MEmpty
|
|
822
|
+
v-else
|
|
823
|
+
aria-label="图表占位"
|
|
824
|
+
title="暂无图表数据"
|
|
825
|
+
description="图表区域(接入 ECharts / 业务组件)"
|
|
826
|
+
/>
|
|
740
827
|
</MCard>`,
|
|
741
828
|
rules: ['图表区适合 MCard,因为需要 card 标题', '加载中用 MSkeleton'],
|
|
742
829
|
rulesEn: ['Charts belong in MCard when a card title is needed', 'Use MSkeleton while loading'],
|
|
@@ -752,18 +839,18 @@ const submitting = ref(false)`,
|
|
|
752
839
|
pageTypes: ['dashboard'],
|
|
753
840
|
keywords: ['recent', '最近', 'table', 'card', 'list'],
|
|
754
841
|
imports: ['MCard', 'MTable', 'MStatus'],
|
|
755
|
-
scriptSetup: `const recentColumns = [
|
|
756
|
-
{ key: 'id', label: '工单号', width: 96 },
|
|
757
|
-
{ key: 'title', label: '标题' },
|
|
758
|
-
{ key: 'status', label: '状态', width: 110 },
|
|
759
|
-
]
|
|
842
|
+
scriptSetup: `const recentColumns = [
|
|
843
|
+
{ key: 'id', label: '工单号', width: 96 },
|
|
844
|
+
{ key: 'title', label: '标题' },
|
|
845
|
+
{ key: 'status', label: '状态', width: 110 },
|
|
846
|
+
]
|
|
760
847
|
const recentRows = ref<Record<string, unknown>[]>([])`,
|
|
761
|
-
template: `<MCard title="最近工单">
|
|
762
|
-
<MTable :columns="recentColumns" :rows="recentRows" size="small" :paginator="false" bordered row-key="id">
|
|
763
|
-
<template #cell-status="{ value }">
|
|
764
|
-
<MStatus :label="String(value ?? '')" severity="secondary" />
|
|
765
|
-
</template>
|
|
766
|
-
</MTable>
|
|
848
|
+
template: `<MCard title="最近工单">
|
|
849
|
+
<MTable :columns="recentColumns" :rows="recentRows" size="small" :paginator="false" bordered row-key="id">
|
|
850
|
+
<template #cell-status="{ value }">
|
|
851
|
+
<MStatus :label="String(value ?? '')" severity="secondary" />
|
|
852
|
+
</template>
|
|
853
|
+
</MTable>
|
|
767
854
|
</MCard>`,
|
|
768
855
|
rules: ['明细列表放 MCard 内合理', 'size="small" 适合卡片内表格', '行内业务状态用 MStatus'],
|
|
769
856
|
rulesEn: ['Detail lists inside MCard are appropriate', 'Use size="small" for in-card tables', 'Use MStatus for row business status'],
|
|
@@ -779,10 +866,10 @@ const recentRows = ref<Record<string, unknown>[]>([])`,
|
|
|
779
866
|
pageTypes: ['list', 'common'],
|
|
780
867
|
keywords: ['page content', 'stack', 'layout content', '容器'],
|
|
781
868
|
imports: ['MPageContent'],
|
|
782
|
-
template: `<MLayoutContent>
|
|
783
|
-
<MPageContent>
|
|
784
|
-
<!-- MPageFilters → MPageToolbar → MTable -->
|
|
785
|
-
</MPageContent>
|
|
869
|
+
template: `<MLayoutContent>
|
|
870
|
+
<MPageContent>
|
|
871
|
+
<!-- MPageFilters → MPageToolbar → MTable -->
|
|
872
|
+
</MPageContent>
|
|
786
873
|
</MLayoutContent>`,
|
|
787
874
|
rules: ['所有列表区块放在 MPageContent 内', '区块顺序:筛选 → 工具栏 → 表格'],
|
|
788
875
|
rulesEn: ['Keep all list sections inside MPageContent', 'Order: filters → toolbar → table'],
|
|
@@ -798,10 +885,10 @@ const recentRows = ref<Record<string, unknown>[]>([])`,
|
|
|
798
885
|
pageTypes: ['form', 'common'],
|
|
799
886
|
keywords: ['narrow', 'form content', 'max-width'],
|
|
800
887
|
imports: ['MPageContent'],
|
|
801
|
-
template: `<MLayoutContent>
|
|
802
|
-
<MPageContent width="narrow">
|
|
803
|
-
<!-- MPageHeader → MPageSection form → actions -->
|
|
804
|
-
</MPageContent>
|
|
888
|
+
template: `<MLayoutContent>
|
|
889
|
+
<MPageContent width="narrow">
|
|
890
|
+
<!-- MPageHeader → MPageSection form → actions -->
|
|
891
|
+
</MPageContent>
|
|
805
892
|
</MLayoutContent>`,
|
|
806
893
|
rules: ['表单页使用 width="narrow"', '不要手写 max-width CSS'],
|
|
807
894
|
rulesEn: ['Use width="narrow" on form pages', 'Do not hand-write max-width CSS'],
|
|
@@ -817,14 +904,14 @@ const recentRows = ref<Record<string, unknown>[]>([])`,
|
|
|
817
904
|
pageTypes: ['detail'],
|
|
818
905
|
keywords: ['detail', '详情', 'edit', 'header'],
|
|
819
906
|
imports: ['MPageHeader', 'MButton', 'MStatus', 'MSpace'],
|
|
820
|
-
template: `<MPageHeader title="示例资源" description="查看摘要与属性。">
|
|
821
|
-
<template #actions>
|
|
822
|
-
<MSpace>
|
|
823
|
-
<MStatus label="正常" severity="success" />
|
|
824
|
-
<MButton severity="primary">编辑</MButton>
|
|
825
|
-
<MButton severity="danger" text>删除</MButton>
|
|
826
|
-
</MSpace>
|
|
827
|
-
</template>
|
|
907
|
+
template: `<MPageHeader title="示例资源" description="查看摘要与属性。">
|
|
908
|
+
<template #actions>
|
|
909
|
+
<MSpace>
|
|
910
|
+
<MStatus label="正常" severity="success" />
|
|
911
|
+
<MButton severity="primary">编辑</MButton>
|
|
912
|
+
<MButton severity="danger" text>删除</MButton>
|
|
913
|
+
</MSpace>
|
|
914
|
+
</template>
|
|
828
915
|
</MPageHeader>`,
|
|
829
916
|
rules: ['状态用 MStatus 放在 actions 区', '编辑为 primary,删除用 danger text'],
|
|
830
917
|
rulesEn: ['Use MStatus in the actions area', 'Edit is primary; delete uses danger text'],
|
|
@@ -840,13 +927,13 @@ const recentRows = ref<Record<string, unknown>[]>([])`,
|
|
|
840
927
|
pageTypes: ['list', 'form', 'dashboard', 'detail', 'common'],
|
|
841
928
|
keywords: ['页头', '主按钮', '新建', 'header', 'primary', 'actions', 'page-header'],
|
|
842
929
|
imports: ['MPageHeader', 'MButton', 'MSpace'],
|
|
843
|
-
template: `<MPageHeader title="用户管理" description="管理成员与角色。">
|
|
844
|
-
<template #actions>
|
|
845
|
-
<MSpace>
|
|
846
|
-
<MButton severity="secondary" text>导入</MButton>
|
|
847
|
-
<MButton severity="primary">新建用户</MButton>
|
|
848
|
-
</MSpace>
|
|
849
|
-
</template>
|
|
930
|
+
template: `<MPageHeader title="用户管理" description="管理成员与角色。">
|
|
931
|
+
<template #actions>
|
|
932
|
+
<MSpace>
|
|
933
|
+
<MButton severity="secondary" text>导入</MButton>
|
|
934
|
+
<MButton severity="primary">新建用户</MButton>
|
|
935
|
+
</MSpace>
|
|
936
|
+
</template>
|
|
850
937
|
</MPageHeader>`,
|
|
851
938
|
rules: ['视口内仅一个 filled primary', '次要动作用 secondary 或 text', '批量操作用 MPageToolbar,不要塞进页头'],
|
|
852
939
|
rulesEn: ['Only one filled primary in the viewport', 'Lesser actions: secondary or text', 'Batch actions belong in MPageToolbar, not the header'],
|
|
@@ -870,62 +957,62 @@ const recentRows = ref<Record<string, unknown>[]>([])`,
|
|
|
870
957
|
'inline create',
|
|
871
958
|
],
|
|
872
959
|
imports: ['MDialog', 'MForm', 'MFormItem', 'MInput', 'MSelect', 'MButton', 'MSpace', 'message'],
|
|
873
|
-
scriptSetup: `const dialogOpen = ref(false)
|
|
874
|
-
const submitting = ref(false)
|
|
875
|
-
const model = reactive({
|
|
876
|
-
name: '',
|
|
877
|
-
email: '',
|
|
878
|
-
role: undefined as string | undefined,
|
|
879
|
-
})
|
|
880
|
-
const roleOptions = [
|
|
881
|
-
{ label: '管理员', value: 'admin' },
|
|
882
|
-
{ label: '成员', value: 'member' },
|
|
883
|
-
]
|
|
884
|
-
|
|
885
|
-
function resetModel() {
|
|
886
|
-
model.name = ''
|
|
887
|
-
model.email = ''
|
|
888
|
-
model.role = undefined
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
async function onSave() {
|
|
892
|
-
submitting.value = true
|
|
893
|
-
try {
|
|
894
|
-
// await api.save(model)
|
|
895
|
-
message.success('已保存')
|
|
896
|
-
dialogOpen.value = false
|
|
897
|
-
resetModel()
|
|
898
|
-
} finally {
|
|
899
|
-
submitting.value = false
|
|
900
|
-
}
|
|
960
|
+
scriptSetup: `const dialogOpen = ref(false)
|
|
961
|
+
const submitting = ref(false)
|
|
962
|
+
const model = reactive({
|
|
963
|
+
name: '',
|
|
964
|
+
email: '',
|
|
965
|
+
role: undefined as string | undefined,
|
|
966
|
+
})
|
|
967
|
+
const roleOptions = [
|
|
968
|
+
{ label: '管理员', value: 'admin' },
|
|
969
|
+
{ label: '成员', value: 'member' },
|
|
970
|
+
]
|
|
971
|
+
|
|
972
|
+
function resetModel() {
|
|
973
|
+
model.name = ''
|
|
974
|
+
model.email = ''
|
|
975
|
+
model.role = undefined
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
async function onSave() {
|
|
979
|
+
submitting.value = true
|
|
980
|
+
try {
|
|
981
|
+
// await api.save(model)
|
|
982
|
+
message.success('已保存')
|
|
983
|
+
dialogOpen.value = false
|
|
984
|
+
resetModel()
|
|
985
|
+
} finally {
|
|
986
|
+
submitting.value = false
|
|
987
|
+
}
|
|
901
988
|
}`,
|
|
902
|
-
template: `<MDialog
|
|
903
|
-
v-model="dialogOpen"
|
|
904
|
-
header="新建用户"
|
|
905
|
-
width="32rem"
|
|
906
|
-
@close="resetModel"
|
|
907
|
-
>
|
|
908
|
-
<MForm @submit.prevent="onSave">
|
|
909
|
-
<MFormItem label="姓名" name="name" required>
|
|
910
|
-
<MInput v-model="model.name" placeholder="请输入姓名" fluid />
|
|
911
|
-
</MFormItem>
|
|
912
|
-
<MFormItem label="邮箱" name="email" required>
|
|
913
|
-
<MInput v-model="model.email" type="email" placeholder="name@example.com" fluid />
|
|
914
|
-
</MFormItem>
|
|
915
|
-
<MFormItem label="角色" name="role" required>
|
|
916
|
-
<MSelect v-model="model.role" :options="roleOptions" placeholder="请选择角色" fluid />
|
|
917
|
-
</MFormItem>
|
|
918
|
-
</MForm>
|
|
919
|
-
<template #footer>
|
|
920
|
-
<MSpace style="justify-content: flex-end; width: 100%">
|
|
921
|
-
<MButton severity="secondary" text :disabled="submitting" @click="dialogOpen = false">
|
|
922
|
-
取消
|
|
923
|
-
</MButton>
|
|
924
|
-
<MButton severity="primary" :loading="submitting" @click="onSave">
|
|
925
|
-
保存
|
|
926
|
-
</MButton>
|
|
927
|
-
</MSpace>
|
|
928
|
-
</template>
|
|
989
|
+
template: `<MDialog
|
|
990
|
+
v-model="dialogOpen"
|
|
991
|
+
header="新建用户"
|
|
992
|
+
width="32rem"
|
|
993
|
+
@close="resetModel"
|
|
994
|
+
>
|
|
995
|
+
<MForm @submit.prevent="onSave">
|
|
996
|
+
<MFormItem label="姓名" name="name" required>
|
|
997
|
+
<MInput v-model="model.name" placeholder="请输入姓名" fluid />
|
|
998
|
+
</MFormItem>
|
|
999
|
+
<MFormItem label="邮箱" name="email" required>
|
|
1000
|
+
<MInput v-model="model.email" type="email" placeholder="name@example.com" fluid />
|
|
1001
|
+
</MFormItem>
|
|
1002
|
+
<MFormItem label="角色" name="role" required>
|
|
1003
|
+
<MSelect v-model="model.role" :options="roleOptions" placeholder="请选择角色" fluid />
|
|
1004
|
+
</MFormItem>
|
|
1005
|
+
</MForm>
|
|
1006
|
+
<template #footer>
|
|
1007
|
+
<MSpace style="justify-content: flex-end; width: 100%">
|
|
1008
|
+
<MButton severity="secondary" text :disabled="submitting" @click="dialogOpen = false">
|
|
1009
|
+
取消
|
|
1010
|
+
</MButton>
|
|
1011
|
+
<MButton severity="primary" :loading="submitting" @click="onSave">
|
|
1012
|
+
保存
|
|
1013
|
+
</MButton>
|
|
1014
|
+
</MSpace>
|
|
1015
|
+
</template>
|
|
929
1016
|
</MDialog>`,
|
|
930
1017
|
rules: [
|
|
931
1018
|
'留在列表页,不另开路由',
|
|
@@ -957,44 +1044,44 @@ async function onSave() {
|
|
|
957
1044
|
pageTypes: ['list', 'detail', 'common'],
|
|
958
1045
|
keywords: ['抽屉表单', '侧滑编辑', 'drawer form', 'form-in-drawer', 'side panel edit'],
|
|
959
1046
|
imports: ['MDrawer', 'MForm', 'MFormItem', 'MInput', 'MButton', 'MSpace', 'message'],
|
|
960
|
-
scriptSetup: `const drawerOpen = ref(false)
|
|
961
|
-
const submitting = ref(false)
|
|
962
|
-
const model = reactive({ name: '', note: '' })
|
|
963
|
-
|
|
964
|
-
async function onSave() {
|
|
965
|
-
submitting.value = true
|
|
966
|
-
try {
|
|
967
|
-
message.success('已保存')
|
|
968
|
-
drawerOpen.value = false
|
|
969
|
-
} finally {
|
|
970
|
-
submitting.value = false
|
|
971
|
-
}
|
|
1047
|
+
scriptSetup: `const drawerOpen = ref(false)
|
|
1048
|
+
const submitting = ref(false)
|
|
1049
|
+
const model = reactive({ name: '', note: '' })
|
|
1050
|
+
|
|
1051
|
+
async function onSave() {
|
|
1052
|
+
submitting.value = true
|
|
1053
|
+
try {
|
|
1054
|
+
message.success('已保存')
|
|
1055
|
+
drawerOpen.value = false
|
|
1056
|
+
} finally {
|
|
1057
|
+
submitting.value = false
|
|
1058
|
+
}
|
|
972
1059
|
}`,
|
|
973
|
-
template: `<MDrawer
|
|
974
|
-
v-model="drawerOpen"
|
|
975
|
-
header="编辑详情"
|
|
976
|
-
position="right"
|
|
977
|
-
size="28rem"
|
|
978
|
-
@close="/* reset draft */"
|
|
979
|
-
>
|
|
980
|
-
<MForm @submit.prevent="onSave">
|
|
981
|
-
<MFormItem label="名称" name="name" required>
|
|
982
|
-
<MInput v-model="model.name" fluid />
|
|
983
|
-
</MFormItem>
|
|
984
|
-
<MFormItem label="备注" name="note">
|
|
985
|
-
<MInput v-model="model.note" fluid />
|
|
986
|
-
</MFormItem>
|
|
987
|
-
</MForm>
|
|
988
|
-
<template #footer>
|
|
989
|
-
<MSpace style="justify-content: flex-end; width: 100%">
|
|
990
|
-
<MButton severity="secondary" text :disabled="submitting" @click="drawerOpen = false">
|
|
991
|
-
取消
|
|
992
|
-
</MButton>
|
|
993
|
-
<MButton severity="primary" :loading="submitting" @click="onSave">
|
|
994
|
-
保存
|
|
995
|
-
</MButton>
|
|
996
|
-
</MSpace>
|
|
997
|
-
</template>
|
|
1060
|
+
template: `<MDrawer
|
|
1061
|
+
v-model="drawerOpen"
|
|
1062
|
+
header="编辑详情"
|
|
1063
|
+
position="right"
|
|
1064
|
+
size="28rem"
|
|
1065
|
+
@close="/* reset draft */"
|
|
1066
|
+
>
|
|
1067
|
+
<MForm @submit.prevent="onSave">
|
|
1068
|
+
<MFormItem label="名称" name="name" required>
|
|
1069
|
+
<MInput v-model="model.name" fluid />
|
|
1070
|
+
</MFormItem>
|
|
1071
|
+
<MFormItem label="备注" name="note">
|
|
1072
|
+
<MInput v-model="model.note" fluid />
|
|
1073
|
+
</MFormItem>
|
|
1074
|
+
</MForm>
|
|
1075
|
+
<template #footer>
|
|
1076
|
+
<MSpace style="justify-content: flex-end; width: 100%">
|
|
1077
|
+
<MButton severity="secondary" text :disabled="submitting" @click="drawerOpen = false">
|
|
1078
|
+
取消
|
|
1079
|
+
</MButton>
|
|
1080
|
+
<MButton severity="primary" :loading="submitting" @click="onSave">
|
|
1081
|
+
保存
|
|
1082
|
+
</MButton>
|
|
1083
|
+
</MSpace>
|
|
1084
|
+
</template>
|
|
998
1085
|
</MDrawer>`,
|
|
999
1086
|
rules: ['position 常用 right;较长表单可加大 size', '字段 fluid;底栏放 #footer', '成功用 message.success'],
|
|
1000
1087
|
rulesEn: ['position usually right; raise size for longer forms', 'Fields fluid; actions in #footer', 'Success via message.success'],
|
|
@@ -1010,28 +1097,28 @@ async function onSave() {
|
|
|
1010
1097
|
pageTypes: ['list', 'detail', 'common'],
|
|
1011
1098
|
keywords: ['删除', '确认', '二次确认', 'confirm', 'delete', 'ConfirmDialog', '危险'],
|
|
1012
1099
|
imports: ['MConfirmDialog', 'message'],
|
|
1013
|
-
scriptSetup: `const confirmOpen = ref(false)
|
|
1014
|
-
const pendingId = ref<string | null>(null)
|
|
1015
|
-
|
|
1016
|
-
function askDelete(id: string) {
|
|
1017
|
-
pendingId.value = id
|
|
1018
|
-
confirmOpen.value = true
|
|
1019
|
-
}
|
|
1020
|
-
|
|
1021
|
-
function onAcceptDelete() {
|
|
1022
|
-
// await api.remove(pendingId.value)
|
|
1023
|
-
message.success('已删除')
|
|
1024
|
-
pendingId.value = null
|
|
1025
|
-
confirmOpen.value = false
|
|
1100
|
+
scriptSetup: `const confirmOpen = ref(false)
|
|
1101
|
+
const pendingId = ref<string | null>(null)
|
|
1102
|
+
|
|
1103
|
+
function askDelete(id: string) {
|
|
1104
|
+
pendingId.value = id
|
|
1105
|
+
confirmOpen.value = true
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
function onAcceptDelete() {
|
|
1109
|
+
// await api.remove(pendingId.value)
|
|
1110
|
+
message.success('已删除')
|
|
1111
|
+
pendingId.value = null
|
|
1112
|
+
confirmOpen.value = false
|
|
1026
1113
|
}`,
|
|
1027
|
-
template: `<MConfirmDialog
|
|
1028
|
-
v-model="confirmOpen"
|
|
1029
|
-
header="确认删除"
|
|
1030
|
-
message="删除后不可恢复,确定继续?"
|
|
1031
|
-
accept-label="删除"
|
|
1032
|
-
reject-label="取消"
|
|
1033
|
-
accept-severity="danger"
|
|
1034
|
-
@accept="onAcceptDelete"
|
|
1114
|
+
template: `<MConfirmDialog
|
|
1115
|
+
v-model="confirmOpen"
|
|
1116
|
+
header="确认删除"
|
|
1117
|
+
message="删除后不可恢复,确定继续?"
|
|
1118
|
+
accept-label="删除"
|
|
1119
|
+
reject-label="取消"
|
|
1120
|
+
accept-severity="danger"
|
|
1121
|
+
@accept="onAcceptDelete"
|
|
1035
1122
|
/>`,
|
|
1036
1123
|
rules: ['用 MConfirmDialog,不要手写 Dialog 确定/取消', 'acceptSeverity="danger"', '成功用 message.success,不要 toast'],
|
|
1037
1124
|
rulesEn: ['Use MConfirmDialog; do not hand-roll Dialog Yes/No', 'acceptSeverity="danger"', 'Success via message.success, not toast'],
|
|
@@ -1047,15 +1134,15 @@ function onAcceptDelete() {
|
|
|
1047
1134
|
pageTypes: ['list', 'common'],
|
|
1048
1135
|
keywords: ['更多', '行菜单', 'dropdown', 'row menu', 'overflow', 'actions menu'],
|
|
1049
1136
|
imports: ['MDropdown', 'MButton'],
|
|
1050
|
-
scriptSetup: `const rowMenu = [
|
|
1051
|
-
{ label: '编辑', command: () => {/* open edit */} },
|
|
1052
|
-
{ label: '复制', command: () => {/* duplicate */} },
|
|
1053
|
-
{ label: '删除', severity: 'danger', command: () => {/* askDelete */} },
|
|
1137
|
+
scriptSetup: `const rowMenu = [
|
|
1138
|
+
{ label: '编辑', command: () => {/* open edit */} },
|
|
1139
|
+
{ label: '复制', command: () => {/* duplicate */} },
|
|
1140
|
+
{ label: '删除', severity: 'danger', command: () => {/* askDelete */} },
|
|
1054
1141
|
]`,
|
|
1055
|
-
template: `<template #cell-actions>
|
|
1056
|
-
<MDropdown :model="rowMenu">
|
|
1057
|
-
<MButton severity="secondary" size="small" text icon="more-horizontal" aria-label="更多操作" />
|
|
1058
|
-
</MDropdown>
|
|
1142
|
+
template: `<template #cell-actions>
|
|
1143
|
+
<MDropdown :model="rowMenu">
|
|
1144
|
+
<MButton severity="secondary" size="small" text icon="more-horizontal" aria-label="更多操作" />
|
|
1145
|
+
</MDropdown>
|
|
1059
1146
|
</template>`,
|
|
1060
1147
|
rules: ['操作很多时再用 Dropdown;2–3 个明确操作用 text 按钮', '危险项走 confirm-delete', '图标按钮必须 aria-label'],
|
|
1061
1148
|
rulesEn: ['Use Dropdown when many actions; 2–3 explicit actions stay as text buttons', 'Danger → confirm-delete', 'Icon-only buttons need aria-label'],
|
|
@@ -1071,45 +1158,45 @@ function onAcceptDelete() {
|
|
|
1071
1158
|
pageTypes: ['form', 'common'],
|
|
1072
1159
|
keywords: ['向导', '分步', '步骤', 'wizard', 'stepper', 'multi-step'],
|
|
1073
1160
|
imports: ['MStepper', 'MForm', 'MFormItem', 'MInput', 'MButton', 'MSpace', 'MPageSection', 'message'],
|
|
1074
|
-
scriptSetup: `const activeStep = ref(0)
|
|
1075
|
-
const submitting = ref(false)
|
|
1076
|
-
const steps = [
|
|
1077
|
-
{ label: '基本信息', description: '名称与说明' },
|
|
1078
|
-
{ label: '确认', description: '核对后创建' },
|
|
1079
|
-
]
|
|
1080
|
-
const model = reactive({ name: '' })
|
|
1081
|
-
|
|
1082
|
-
function back() {
|
|
1083
|
-
if (activeStep.value > 0) activeStep.value -= 1
|
|
1084
|
-
}
|
|
1085
|
-
function next() {
|
|
1086
|
-
if (activeStep.value < steps.length - 1) activeStep.value += 1
|
|
1087
|
-
}
|
|
1088
|
-
async function finish() {
|
|
1089
|
-
submitting.value = true
|
|
1090
|
-
try {
|
|
1091
|
-
message.success('已创建')
|
|
1092
|
-
} finally {
|
|
1093
|
-
submitting.value = false
|
|
1094
|
-
}
|
|
1161
|
+
scriptSetup: `const activeStep = ref(0)
|
|
1162
|
+
const submitting = ref(false)
|
|
1163
|
+
const steps = [
|
|
1164
|
+
{ label: '基本信息', description: '名称与说明' },
|
|
1165
|
+
{ label: '确认', description: '核对后创建' },
|
|
1166
|
+
]
|
|
1167
|
+
const model = reactive({ name: '' })
|
|
1168
|
+
|
|
1169
|
+
function back() {
|
|
1170
|
+
if (activeStep.value > 0) activeStep.value -= 1
|
|
1171
|
+
}
|
|
1172
|
+
function next() {
|
|
1173
|
+
if (activeStep.value < steps.length - 1) activeStep.value += 1
|
|
1174
|
+
}
|
|
1175
|
+
async function finish() {
|
|
1176
|
+
submitting.value = true
|
|
1177
|
+
try {
|
|
1178
|
+
message.success('已创建')
|
|
1179
|
+
} finally {
|
|
1180
|
+
submitting.value = false
|
|
1181
|
+
}
|
|
1095
1182
|
}`,
|
|
1096
|
-
template: `<MStepper v-model="activeStep" :steps="steps" linear />
|
|
1097
|
-
<MPageSection variant="form">
|
|
1098
|
-
<MForm v-if="activeStep === 0" @submit.prevent="next">
|
|
1099
|
-
<MFormItem label="名称" name="name" required>
|
|
1100
|
-
<MInput v-model="model.name" fluid />
|
|
1101
|
-
</MFormItem>
|
|
1102
|
-
</MForm>
|
|
1103
|
-
<div v-else>
|
|
1104
|
-
<p>确认创建「{{ model.name }}」?</p>
|
|
1105
|
-
</div>
|
|
1106
|
-
</MPageSection>
|
|
1107
|
-
<MPageSection variant="actions">
|
|
1108
|
-
<MSpace>
|
|
1109
|
-
<MButton severity="secondary" text :disabled="activeStep === 0" @click="back">上一步</MButton>
|
|
1110
|
-
<MButton v-if="activeStep < steps.length - 1" severity="primary" @click="next">下一步</MButton>
|
|
1111
|
-
<MButton v-else severity="primary" :loading="submitting" @click="finish">创建</MButton>
|
|
1112
|
-
</MSpace>
|
|
1183
|
+
template: `<MStepper v-model="activeStep" :steps="steps" linear />
|
|
1184
|
+
<MPageSection variant="form">
|
|
1185
|
+
<MForm v-if="activeStep === 0" @submit.prevent="next">
|
|
1186
|
+
<MFormItem label="名称" name="name" required>
|
|
1187
|
+
<MInput v-model="model.name" fluid />
|
|
1188
|
+
</MFormItem>
|
|
1189
|
+
</MForm>
|
|
1190
|
+
<div v-else>
|
|
1191
|
+
<p>确认创建「{{ model.name }}」?</p>
|
|
1192
|
+
</div>
|
|
1193
|
+
</MPageSection>
|
|
1194
|
+
<MPageSection variant="actions">
|
|
1195
|
+
<MSpace>
|
|
1196
|
+
<MButton severity="secondary" text :disabled="activeStep === 0" @click="back">上一步</MButton>
|
|
1197
|
+
<MButton v-if="activeStep < steps.length - 1" severity="primary" @click="next">下一步</MButton>
|
|
1198
|
+
<MButton v-else severity="primary" :loading="submitting" @click="finish">创建</MButton>
|
|
1199
|
+
</MSpace>
|
|
1113
1200
|
</MPageSection>`,
|
|
1114
1201
|
rules: ['用 :steps(不是 :items)', '每步一个主任务;进入下一步前校验当前步', '完成文案与「下一步」区分'],
|
|
1115
1202
|
rulesEn: ['Use :steps (not :items)', 'One job per step; validate before advancing', 'Finish label differs from Next'],
|
|
@@ -1125,50 +1212,50 @@ async function finish() {
|
|
|
1125
1212
|
pageTypes: ['common'],
|
|
1126
1213
|
keywords: ['登录', '认证', '分栏', 'login', 'auth', 'split', 'brand'],
|
|
1127
1214
|
imports: ['MForm', 'MFormItem', 'MInput', 'MInputPassword', 'MButton', 'MSpace', 'MConfigProvider', 'zhCN'],
|
|
1128
|
-
scriptSetup: `const submitting = ref(false)
|
|
1129
|
-
const formError = ref('')
|
|
1130
|
-
const model = reactive({ email: '', password: '' })
|
|
1131
|
-
|
|
1132
|
-
async function onSubmit() {
|
|
1133
|
-
formError.value = ''
|
|
1134
|
-
submitting.value = true
|
|
1135
|
-
try {
|
|
1136
|
-
if (!model.email || !model.password) {
|
|
1137
|
-
formError.value = '请输入邮箱和密码后再试。'
|
|
1138
|
-
}
|
|
1139
|
-
} finally {
|
|
1140
|
-
submitting.value = false
|
|
1141
|
-
}
|
|
1215
|
+
scriptSetup: `const submitting = ref(false)
|
|
1216
|
+
const formError = ref('')
|
|
1217
|
+
const model = reactive({ email: '', password: '' })
|
|
1218
|
+
|
|
1219
|
+
async function onSubmit() {
|
|
1220
|
+
formError.value = ''
|
|
1221
|
+
submitting.value = true
|
|
1222
|
+
try {
|
|
1223
|
+
if (!model.email || !model.password) {
|
|
1224
|
+
formError.value = '请输入邮箱和密码后再试。'
|
|
1225
|
+
}
|
|
1226
|
+
} finally {
|
|
1227
|
+
submitting.value = false
|
|
1228
|
+
}
|
|
1142
1229
|
}`,
|
|
1143
|
-
template: `<MConfigProvider :locale="zhCN">
|
|
1144
|
-
<div class="login-shell">
|
|
1145
|
-
<aside class="login-brand" aria-label="品牌">
|
|
1146
|
-
<p class="login-brand__mark">产品名</p>
|
|
1147
|
-
<h1 class="login-brand__title">一句品牌主张</h1>
|
|
1148
|
-
<p class="login-brand__lead">一句支持说明。</p>
|
|
1149
|
-
</aside>
|
|
1150
|
-
<main class="login-main">
|
|
1151
|
-
<div class="login-panel">
|
|
1152
|
-
<header class="login-panel__header">
|
|
1153
|
-
<h2>登录</h2>
|
|
1154
|
-
<p>使用工作邮箱进入。</p>
|
|
1155
|
-
</header>
|
|
1156
|
-
<p v-if="formError" class="login-alert" role="alert">{{ formError }}</p>
|
|
1157
|
-
<MForm @submit="onSubmit">
|
|
1158
|
-
<MFormItem label="邮箱" name="email" required>
|
|
1159
|
-
<MInput v-model="model.email" type="email" autocomplete="username" fluid />
|
|
1160
|
-
</MFormItem>
|
|
1161
|
-
<MFormItem label="密码" name="password" required>
|
|
1162
|
-
<MInputPassword v-model="model.password" autocomplete="current-password" fluid />
|
|
1163
|
-
</MFormItem>
|
|
1164
|
-
<MSpace style="margin-top: var(--m-space-2)" alignment="center">
|
|
1165
|
-
<MButton type="submit" label="登录" severity="primary" :loading="submitting" />
|
|
1166
|
-
<MButton type="button" label="忘记密码" severity="secondary" text />
|
|
1167
|
-
</MSpace>
|
|
1168
|
-
</MForm>
|
|
1169
|
-
</div>
|
|
1170
|
-
</main>
|
|
1171
|
-
</div>
|
|
1230
|
+
template: `<MConfigProvider :locale="zhCN">
|
|
1231
|
+
<div class="login-shell">
|
|
1232
|
+
<aside class="login-brand" aria-label="品牌">
|
|
1233
|
+
<p class="login-brand__mark">产品名</p>
|
|
1234
|
+
<h1 class="login-brand__title">一句品牌主张</h1>
|
|
1235
|
+
<p class="login-brand__lead">一句支持说明。</p>
|
|
1236
|
+
</aside>
|
|
1237
|
+
<main class="login-main">
|
|
1238
|
+
<div class="login-panel">
|
|
1239
|
+
<header class="login-panel__header">
|
|
1240
|
+
<h2>登录</h2>
|
|
1241
|
+
<p>使用工作邮箱进入。</p>
|
|
1242
|
+
</header>
|
|
1243
|
+
<p v-if="formError" class="login-alert" role="alert">{{ formError }}</p>
|
|
1244
|
+
<MForm @submit="onSubmit">
|
|
1245
|
+
<MFormItem label="邮箱" name="email" required>
|
|
1246
|
+
<MInput v-model="model.email" type="email" autocomplete="username" fluid />
|
|
1247
|
+
</MFormItem>
|
|
1248
|
+
<MFormItem label="密码" name="password" required>
|
|
1249
|
+
<MInputPassword v-model="model.password" autocomplete="current-password" fluid />
|
|
1250
|
+
</MFormItem>
|
|
1251
|
+
<MSpace style="margin-top: var(--m-space-2)" alignment="center">
|
|
1252
|
+
<MButton type="submit" label="登录" severity="primary" :loading="submitting" />
|
|
1253
|
+
<MButton type="button" label="忘记密码" severity="secondary" text />
|
|
1254
|
+
</MSpace>
|
|
1255
|
+
</MForm>
|
|
1256
|
+
</div>
|
|
1257
|
+
</main>
|
|
1258
|
+
</div>
|
|
1172
1259
|
</MConfigProvider>`,
|
|
1173
1260
|
rules: [
|
|
1174
1261
|
'品牌区用 --m-* / color-mix,默认扁平勿叠 aurora',
|
|
@@ -1210,29 +1297,29 @@ async function onSubmit() {
|
|
|
1210
1297
|
'MConfigProvider',
|
|
1211
1298
|
'zhCN',
|
|
1212
1299
|
],
|
|
1213
|
-
template: `<MConfigProvider :locale="zhCN">
|
|
1214
|
-
<MLayout fill-viewport has-sider>
|
|
1215
|
-
<MLayoutSider v-model:collapsed="siderCollapsed" bordered :collapsed-width="72">
|
|
1216
|
-
<MMenu
|
|
1217
|
-
:model="[
|
|
1218
|
-
{ key: 'users', label: '用户管理', icon: 'user', to: '/users' },
|
|
1219
|
-
{ key: 'roles', label: '角色管理', icon: 'shield', to: '/roles' },
|
|
1220
|
-
]"
|
|
1221
|
-
:collapsed="siderCollapsed"
|
|
1222
|
-
:collapsed-width="72"
|
|
1223
|
-
/>
|
|
1224
|
-
</MLayoutSider>
|
|
1225
|
-
<MLayout>
|
|
1226
|
-
<MLayoutHeader :padding="'var(--m-space-4) var(--m-space-6)'">
|
|
1227
|
-
<MBreadcrumb :model="[{ label: '首页', to: '/' }, { label: '当前页' }]" />
|
|
1228
|
-
</MLayoutHeader>
|
|
1229
|
-
<MLayoutContent>
|
|
1230
|
-
<MPageContent>
|
|
1231
|
-
<!-- page sections -->
|
|
1232
|
-
</MPageContent>
|
|
1233
|
-
</MLayoutContent>
|
|
1234
|
-
</MLayout>
|
|
1235
|
-
</MLayout>
|
|
1300
|
+
template: `<MConfigProvider :locale="zhCN">
|
|
1301
|
+
<MLayout fill-viewport has-sider>
|
|
1302
|
+
<MLayoutSider v-model:collapsed="siderCollapsed" bordered :collapsed-width="72">
|
|
1303
|
+
<MMenu
|
|
1304
|
+
:model="[
|
|
1305
|
+
{ key: 'users', label: '用户管理', icon: 'user', to: '/users' },
|
|
1306
|
+
{ key: 'roles', label: '角色管理', icon: 'shield', to: '/roles' },
|
|
1307
|
+
]"
|
|
1308
|
+
:collapsed="siderCollapsed"
|
|
1309
|
+
:collapsed-width="72"
|
|
1310
|
+
/>
|
|
1311
|
+
</MLayoutSider>
|
|
1312
|
+
<MLayout>
|
|
1313
|
+
<MLayoutHeader :padding="'var(--m-space-4) var(--m-space-6)'">
|
|
1314
|
+
<MBreadcrumb :model="[{ label: '首页', to: '/' }, { label: '当前页' }]" />
|
|
1315
|
+
</MLayoutHeader>
|
|
1316
|
+
<MLayoutContent>
|
|
1317
|
+
<MPageContent>
|
|
1318
|
+
<!-- page sections -->
|
|
1319
|
+
</MPageContent>
|
|
1320
|
+
</MLayoutContent>
|
|
1321
|
+
</MLayout>
|
|
1322
|
+
</MLayout>
|
|
1236
1323
|
</MConfigProvider>`,
|
|
1237
1324
|
rules: [
|
|
1238
1325
|
'根布局用 fillViewport',
|
|
@@ -1265,19 +1352,19 @@ async function onSubmit() {
|
|
|
1265
1352
|
'log',
|
|
1266
1353
|
],
|
|
1267
1354
|
imports: ['MScrollbar', 'MCard'],
|
|
1268
|
-
template: `<MCard title="活动日志">
|
|
1269
|
-
<MScrollbar max-height="16rem">
|
|
1270
|
-
<ul style="margin:0;padding:0;list-style:none">
|
|
1271
|
-
<li v-for="item in logLines" :key="item.id" style="padding:var(--m-space-2) 0;border-bottom:1px solid var(--m-color-border)">
|
|
1272
|
-
{{ item.text }}
|
|
1273
|
-
</li>
|
|
1274
|
-
</ul>
|
|
1275
|
-
</MScrollbar>
|
|
1355
|
+
template: `<MCard title="活动日志">
|
|
1356
|
+
<MScrollbar max-height="16rem">
|
|
1357
|
+
<ul style="margin:0;padding:0;list-style:none">
|
|
1358
|
+
<li v-for="item in logLines" :key="item.id" style="padding:var(--m-space-2) 0;border-bottom:1px solid var(--m-color-border)">
|
|
1359
|
+
{{ item.text }}
|
|
1360
|
+
</li>
|
|
1361
|
+
</ul>
|
|
1362
|
+
</MScrollbar>
|
|
1276
1363
|
</MCard>`,
|
|
1277
|
-
scriptSetup: `const logLines = [
|
|
1278
|
-
{ id: '1', text: '用户 admin 登录成功' },
|
|
1279
|
-
{ id: '2', text: '导出任务已完成' },
|
|
1280
|
-
{ id: '3', text: '配置已保存' },
|
|
1364
|
+
scriptSetup: `const logLines = [
|
|
1365
|
+
{ id: '1', text: '用户 admin 登录成功' },
|
|
1366
|
+
{ id: '2', text: '导出任务已完成' },
|
|
1367
|
+
{ id: '3', text: '配置已保存' },
|
|
1281
1368
|
]`,
|
|
1282
1369
|
rules: [
|
|
1283
1370
|
'整页主滚动推荐 MLayout fillViewport',
|