@morya-ui/mcp 0.4.4 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +154 -154
- package/bin/morya-ui-mcp.js +2 -2
- package/data/catalog.json +1368 -413
- package/data/example-coverage.json +78 -73
- package/data/golden-pages/dashboard-page.vue +161 -161
- package/data/golden-pages/detail-page.vue +147 -147
- 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 +382 -382
- 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 +244 -244
- 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 +167 -167
- package/dist/decisions.js +1299 -6
- package/dist/page-snippets.js +669 -669
- package/dist/style-shells.js +153 -153
- package/dist/tools.js +192 -192
- 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>查询</MButton>
|
|
36
|
-
<MButton type="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>查询</MButton>
|
|
36
|
+
<MButton type="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>查询</MButton>
|
|
109
|
-
<MButton type="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>查询</MButton>
|
|
109
|
+
<MButton type="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>查询</MButton>
|
|
304
|
-
<MButton type="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>查询</MButton>
|
|
304
|
+
<MButton type="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 size="small">查询</MButton>
|
|
394
|
-
<MButton size="small" type="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 size="small">查询</MButton>
|
|
394
|
+
<MButton size="small" type="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 type="primary">新建用户</MButton>
|
|
428
|
-
</template>
|
|
425
|
+
template: `<MPageHeader title="用户管理" description="维护账号、角色与权限。">
|
|
426
|
+
<template #actions>
|
|
427
|
+
<MButton type="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 type="text">导出</MButton>
|
|
448
|
-
<MButton danger type="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 type="text">导出</MButton>
|
|
448
|
+
<MButton danger type="text">删除</MButton>
|
|
449
|
+
</template>
|
|
450
450
|
</MPageToolbar>`,
|
|
451
451
|
rules: ['左侧用 default 插槽展示已选数量', '批量操作用 type="text" / variant="outlined",danger 仅 destructive', '放在 FilterChips 之后、表格之前'],
|
|
452
452
|
rulesEn: ['Use the default slot for selection count', 'Batch actions use type="text" / variant="outlined"; danger only for destructive ops', 'Place after FilterChips and before the table'],
|
|
@@ -475,54 +475,54 @@ function resetFilters() {
|
|
|
475
475
|
'对齐',
|
|
476
476
|
],
|
|
477
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>[]>([])
|
|
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>[]>([])
|
|
486
486
|
const loading = ref(false)`,
|
|
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
|
-
:type="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 size="small" type="text">编辑</MButton>
|
|
512
|
-
<MButton danger size="small" type="text">删除</MButton>
|
|
513
|
-
</MSpace>
|
|
514
|
-
</template>
|
|
515
|
-
<template #empty>
|
|
516
|
-
<MEmpty
|
|
517
|
-
title="还没有成员"
|
|
518
|
-
description="新建第一条后,这里会列出姓名、状态与更新时间。"
|
|
519
|
-
icon="inbox"
|
|
520
|
-
>
|
|
521
|
-
<template #extra>
|
|
522
|
-
<MButton type="primary">新建成员</MButton>
|
|
523
|
-
</template>
|
|
524
|
-
</MEmpty>
|
|
525
|
-
</template>
|
|
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
|
+
:type="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 size="small" type="text">编辑</MButton>
|
|
512
|
+
<MButton danger size="small" type="text">删除</MButton>
|
|
513
|
+
</MSpace>
|
|
514
|
+
</template>
|
|
515
|
+
<template #empty>
|
|
516
|
+
<MEmpty
|
|
517
|
+
title="还没有成员"
|
|
518
|
+
description="新建第一条后,这里会列出姓名、状态与更新时间。"
|
|
519
|
+
icon="inbox"
|
|
520
|
+
>
|
|
521
|
+
<template #extra>
|
|
522
|
+
<MButton type="primary">新建成员</MButton>
|
|
523
|
+
</template>
|
|
524
|
+
</MEmpty>
|
|
525
|
+
</template>
|
|
526
526
|
</MTable>`,
|
|
527
527
|
rules: [
|
|
528
528
|
'先判断是否适合 fill:全视口后台主列表 + 表格是主任务 → MPageContent fill + MTable fill',
|
|
@@ -560,11 +560,11 @@ const loading = ref(false)`,
|
|
|
560
560
|
pageTypes: ['list'],
|
|
561
561
|
keywords: ['行操作', '编辑', '删除', 'actions', 'row', 'cell'],
|
|
562
562
|
imports: ['MSpace', 'MButton'],
|
|
563
|
-
template: `<template #cell-actions>
|
|
564
|
-
<MSpace>
|
|
565
|
-
<MButton size="small" type="text">编辑</MButton>
|
|
566
|
-
<MButton danger size="small" type="text">删除</MButton>
|
|
567
|
-
</MSpace>
|
|
563
|
+
template: `<template #cell-actions>
|
|
564
|
+
<MSpace>
|
|
565
|
+
<MButton size="small" type="text">编辑</MButton>
|
|
566
|
+
<MButton danger size="small" type="text">删除</MButton>
|
|
567
|
+
</MSpace>
|
|
568
568
|
</template>`,
|
|
569
569
|
rules: ['行内操作用 size="small" + type="text" / variant="outlined",避免一排实心按钮', '危险操作使用 danger 并配合确认弹窗'],
|
|
570
570
|
rulesEn: ['Use size="small" with type="text" / variant="outlined" row actions', 'Use danger with confirmation for destructive actions'],
|
|
@@ -580,11 +580,11 @@ const loading = ref(false)`,
|
|
|
580
580
|
pageTypes: ['list'],
|
|
581
581
|
keywords: ['分类', '标签', '可关闭', 'chip', 'category', 'tag-only'],
|
|
582
582
|
imports: ['MTag'],
|
|
583
|
-
template: `<template #cell-category="{ value }">
|
|
584
|
-
<MTag
|
|
585
|
-
:value="String(value ?? '')"
|
|
586
|
-
type="info"
|
|
587
|
-
/>
|
|
583
|
+
template: `<template #cell-category="{ value }">
|
|
584
|
+
<MTag
|
|
585
|
+
:value="String(value ?? '')"
|
|
586
|
+
type="info"
|
|
587
|
+
/>
|
|
588
588
|
</template>`,
|
|
589
589
|
rules: ['分类/强调态用 MTag;行内业务状态用 MStatus(list-status-dot)', '不要用 Button 颜色表达状态'],
|
|
590
590
|
rulesEn: ['Use MTag for categories; use MStatus for business status (list-status-dot)', 'Do not use Button colors for status'],
|
|
@@ -600,11 +600,11 @@ const loading = ref(false)`,
|
|
|
600
600
|
pageTypes: ['list'],
|
|
601
601
|
keywords: ['状态', 'status', 'dot', 'cell-status', '在线', '启用', '停用', '业务状态'],
|
|
602
602
|
imports: ['MStatus'],
|
|
603
|
-
template: `<template #cell-status="{ value }">
|
|
604
|
-
<MStatus
|
|
605
|
-
:label="value === 'active' || value === 'online' ? '启用' : '停用'"
|
|
606
|
-
:type="value === 'active' || value === 'online' ? 'success' : 'secondary'"
|
|
607
|
-
/>
|
|
603
|
+
template: `<template #cell-status="{ value }">
|
|
604
|
+
<MStatus
|
|
605
|
+
:label="value === 'active' || value === 'online' ? '启用' : '停用'"
|
|
606
|
+
:type="value === 'active' || value === 'online' ? 'success' : 'secondary'"
|
|
607
|
+
/>
|
|
608
608
|
</template>`,
|
|
609
609
|
rules: ['行内轻量业务状态优先 MStatus', '不要用 Button 颜色表达状态'],
|
|
610
610
|
rulesEn: ['Prefer MStatus for lightweight inline business status', 'Do not use Button colors for status'],
|
|
@@ -620,15 +620,15 @@ const loading = ref(false)`,
|
|
|
620
620
|
pageTypes: ['list', 'common'],
|
|
621
621
|
keywords: ['空状态', '无数据', 'empty', 'zero state', 'no data', 'illustration'],
|
|
622
622
|
imports: ['MEmpty', 'MButton'],
|
|
623
|
-
template: `<MEmpty
|
|
624
|
-
title="还没有成员"
|
|
625
|
-
description="邀请或新建第一位成员后,这里会显示状态与最近活动。"
|
|
626
|
-
illustration="no-content"
|
|
627
|
-
>
|
|
628
|
-
<template #extra>
|
|
629
|
-
<MButton type="primary">新建成员</MButton>
|
|
630
|
-
<MButton type="text">导入名单</MButton>
|
|
631
|
-
</template>
|
|
623
|
+
template: `<MEmpty
|
|
624
|
+
title="还没有成员"
|
|
625
|
+
description="邀请或新建第一位成员后,这里会显示状态与最近活动。"
|
|
626
|
+
illustration="no-content"
|
|
627
|
+
>
|
|
628
|
+
<template #extra>
|
|
629
|
+
<MButton type="primary">新建成员</MButton>
|
|
630
|
+
<MButton type="text">导入名单</MButton>
|
|
631
|
+
</template>
|
|
632
632
|
</MEmpty>`,
|
|
633
633
|
rules: [
|
|
634
634
|
'正常无数据用 MEmpty,不要用错误色',
|
|
@@ -654,11 +654,11 @@ const loading = ref(false)`,
|
|
|
654
654
|
pageTypes: ['common'],
|
|
655
655
|
keywords: ['结果', '成功', '失败', '404', '403', 'result', 'success', 'error'],
|
|
656
656
|
imports: ['MResult', 'MButton'],
|
|
657
|
-
template: `<MResult status="success" description="订单已创建,可在列表中查看详情。">
|
|
658
|
-
<template #extra>
|
|
659
|
-
<MButton label="查看订单" />
|
|
660
|
-
<MButton label="返回列表" type="text" />
|
|
661
|
-
</template>
|
|
657
|
+
template: `<MResult status="success" description="订单已创建,可在列表中查看详情。">
|
|
658
|
+
<template #extra>
|
|
659
|
+
<MButton label="查看订单" />
|
|
660
|
+
<MButton label="返回列表" type="text" />
|
|
661
|
+
</template>
|
|
662
662
|
</MResult>`,
|
|
663
663
|
rules: ['流程终点用 MResult', '提供明确下一步操作'],
|
|
664
664
|
rulesEn: ['Use MResult for terminal outcomes', 'Provide clear next-step actions'],
|
|
@@ -689,32 +689,32 @@ const loading = ref(false)`,
|
|
|
689
689
|
pageTypes: ['form'],
|
|
690
690
|
keywords: ['表单', 'form', 'fields', 'formitem', 'label-width', 'errorMessage'],
|
|
691
691
|
imports: ['MPageSection', 'MForm', 'MFormItem', 'MInput', 'MSelect'],
|
|
692
|
-
scriptSetup: `const model = reactive({ name: '', role: undefined as string | undefined })
|
|
693
|
-
const submitting = ref(false)
|
|
694
|
-
const roleOptions = [
|
|
695
|
-
{ label: '管理员', value: 'admin' },
|
|
696
|
-
{ label: '成员', value: 'member' },
|
|
692
|
+
scriptSetup: `const model = reactive({ name: '', role: undefined as string | undefined })
|
|
693
|
+
const submitting = ref(false)
|
|
694
|
+
const roleOptions = [
|
|
695
|
+
{ label: '管理员', value: 'admin' },
|
|
696
|
+
{ label: '成员', value: 'member' },
|
|
697
697
|
]`,
|
|
698
|
-
template: `<MPageSection variant="form">
|
|
699
|
-
<MForm
|
|
700
|
-
label-position="left"
|
|
701
|
-
label-width="6rem"
|
|
702
|
-
@submit.prevent="onSubmit"
|
|
703
|
-
>
|
|
704
|
-
<MFormItem label="姓名" name="name" required>
|
|
705
|
-
<MInput v-model="model.name" placeholder="与工牌一致的姓名" fluid />
|
|
706
|
-
</MFormItem>
|
|
707
|
-
<MFormItem label="角色" name="role" required>
|
|
708
|
-
<MSelect
|
|
709
|
-
v-model="model.role"
|
|
710
|
-
:options="roleOptions"
|
|
711
|
-
placeholder="选择角色"
|
|
712
|
-
clearable
|
|
713
|
-
fluid
|
|
714
|
-
/>
|
|
715
|
-
</MFormItem>
|
|
716
|
-
<!-- 更多字段;常驻错误:error-message="请填写必填项" -->
|
|
717
|
-
</MForm>
|
|
698
|
+
template: `<MPageSection variant="form">
|
|
699
|
+
<MForm
|
|
700
|
+
label-position="left"
|
|
701
|
+
label-width="6rem"
|
|
702
|
+
@submit.prevent="onSubmit"
|
|
703
|
+
>
|
|
704
|
+
<MFormItem label="姓名" name="name" required>
|
|
705
|
+
<MInput v-model="model.name" placeholder="与工牌一致的姓名" fluid />
|
|
706
|
+
</MFormItem>
|
|
707
|
+
<MFormItem label="角色" name="role" required>
|
|
708
|
+
<MSelect
|
|
709
|
+
v-model="model.role"
|
|
710
|
+
:options="roleOptions"
|
|
711
|
+
placeholder="选择角色"
|
|
712
|
+
clearable
|
|
713
|
+
fluid
|
|
714
|
+
/>
|
|
715
|
+
</MFormItem>
|
|
716
|
+
<!-- 更多字段;常驻错误:error-message="请填写必填项" -->
|
|
717
|
+
</MForm>
|
|
718
718
|
</MPageSection>`,
|
|
719
719
|
rules: [
|
|
720
720
|
'字段用 MFormItem + fluid 控件',
|
|
@@ -740,11 +740,11 @@ const roleOptions = [
|
|
|
740
740
|
pageTypes: ['form'],
|
|
741
741
|
keywords: ['保存', '取消', 'submit', 'actions', 'footer'],
|
|
742
742
|
imports: ['MPageSection', 'MSpace', 'MButton'],
|
|
743
|
-
template: `<MPageSection variant="actions">
|
|
744
|
-
<MSpace>
|
|
745
|
-
<MButton html-type="submit" type="primary" :loading="submitting">保存</MButton>
|
|
746
|
-
<MButton>取消</MButton>
|
|
747
|
-
</MSpace>
|
|
743
|
+
template: `<MPageSection variant="actions">
|
|
744
|
+
<MSpace>
|
|
745
|
+
<MButton html-type="submit" type="primary" :loading="submitting">保存</MButton>
|
|
746
|
+
<MButton>取消</MButton>
|
|
747
|
+
</MSpace>
|
|
748
748
|
</MPageSection>`,
|
|
749
749
|
rules: ['提交按钮在前,取消 secondary', '保存中传 loading 防重复提交'],
|
|
750
750
|
rulesEn: ['Submit first, cancel secondary', 'Pass loading while saving to prevent duplicate submits'],
|
|
@@ -760,39 +760,39 @@ const roleOptions = [
|
|
|
760
760
|
pageTypes: ['dashboard'],
|
|
761
761
|
keywords: ['kpi', '指标', 'stat', 'grid', 'metrics', 'trend', 'compact'],
|
|
762
762
|
imports: ['MGrid', 'MGridItem', 'MPageStat'],
|
|
763
|
-
scriptSetup: `const metrics = ref([
|
|
764
|
-
{
|
|
765
|
-
label: '在册成员',
|
|
766
|
-
value: '12,480',
|
|
767
|
-
trend: '+8.2%',
|
|
768
|
-
trendDirection: 'up' as const,
|
|
769
|
-
trendType: 'success' as const,
|
|
770
|
-
trendLabel: '较上月',
|
|
771
|
-
icon: 'users',
|
|
772
|
-
},
|
|
773
|
-
{
|
|
774
|
-
label: '今日活跃',
|
|
775
|
-
value: '1,926',
|
|
776
|
-
trend: '-3.1%',
|
|
777
|
-
trendDirection: 'down' as const,
|
|
778
|
-
trendType: 'warning' as const,
|
|
779
|
-
trendLabel: '较昨日',
|
|
780
|
-
icon: 'activity',
|
|
781
|
-
},
|
|
763
|
+
scriptSetup: `const metrics = ref([
|
|
764
|
+
{
|
|
765
|
+
label: '在册成员',
|
|
766
|
+
value: '12,480',
|
|
767
|
+
trend: '+8.2%',
|
|
768
|
+
trendDirection: 'up' as const,
|
|
769
|
+
trendType: 'success' as const,
|
|
770
|
+
trendLabel: '较上月',
|
|
771
|
+
icon: 'users',
|
|
772
|
+
},
|
|
773
|
+
{
|
|
774
|
+
label: '今日活跃',
|
|
775
|
+
value: '1,926',
|
|
776
|
+
trend: '-3.1%',
|
|
777
|
+
trendDirection: 'down' as const,
|
|
778
|
+
trendType: 'warning' as const,
|
|
779
|
+
trendLabel: '较昨日',
|
|
780
|
+
icon: 'activity',
|
|
781
|
+
},
|
|
782
782
|
])`,
|
|
783
|
-
template: `<MGrid :cols="4" :x-gap="16" :y-gap="16" responsive="screen">
|
|
784
|
-
<MGridItem v-for="metric in metrics" :key="metric.label" :span="1">
|
|
785
|
-
<MPageStat
|
|
786
|
-
:label="metric.label"
|
|
787
|
-
:value="metric.value"
|
|
788
|
-
:trend="metric.trend"
|
|
789
|
-
:trend-direction="metric.trendDirection"
|
|
790
|
-
:trend-type="metric.trendType"
|
|
791
|
-
:trend-label="metric.trendLabel"
|
|
792
|
-
:icon="metric.icon"
|
|
793
|
-
/>
|
|
794
|
-
</MGridItem>
|
|
795
|
-
</MGrid>
|
|
783
|
+
template: `<MGrid :cols="4" :x-gap="16" :y-gap="16" responsive="screen">
|
|
784
|
+
<MGridItem v-for="metric in metrics" :key="metric.label" :span="1">
|
|
785
|
+
<MPageStat
|
|
786
|
+
:label="metric.label"
|
|
787
|
+
:value="metric.value"
|
|
788
|
+
:trend="metric.trend"
|
|
789
|
+
:trend-direction="metric.trendDirection"
|
|
790
|
+
:trend-type="metric.trendType"
|
|
791
|
+
:trend-label="metric.trendLabel"
|
|
792
|
+
:icon="metric.icon"
|
|
793
|
+
/>
|
|
794
|
+
</MGridItem>
|
|
795
|
+
</MGrid>
|
|
796
796
|
<!-- 高密条带示例:density="compact" layout="plain" orientation="inline" -->`,
|
|
797
797
|
rules: [
|
|
798
798
|
'KPI 用 MPageStat,不要每页自定义 stat CSS',
|
|
@@ -818,15 +818,15 @@ const roleOptions = [
|
|
|
818
818
|
pageTypes: ['dashboard'],
|
|
819
819
|
keywords: ['chart', '图表', 'placeholder', 'card', 'trend'],
|
|
820
820
|
imports: ['MCard', 'MEmpty', 'MSkeleton'],
|
|
821
|
-
template: `<MCard title="趋势概览" shadow="always">
|
|
822
|
-
<MSkeleton v-if="loading" height="8rem" />
|
|
823
|
-
<MEmpty
|
|
824
|
-
v-else
|
|
825
|
-
aria-label="图表占位"
|
|
826
|
-
title="还没有趋势数据"
|
|
827
|
-
description="接入图表后将展示近 7 日活跃与转化。"
|
|
828
|
-
illustration="activity-low"
|
|
829
|
-
/>
|
|
821
|
+
template: `<MCard title="趋势概览" shadow="always">
|
|
822
|
+
<MSkeleton v-if="loading" height="8rem" />
|
|
823
|
+
<MEmpty
|
|
824
|
+
v-else
|
|
825
|
+
aria-label="图表占位"
|
|
826
|
+
title="还没有趋势数据"
|
|
827
|
+
description="接入图表后将展示近 7 日活跃与转化。"
|
|
828
|
+
illustration="activity-low"
|
|
829
|
+
/>
|
|
830
830
|
</MCard>`,
|
|
831
831
|
rules: ['图表区适合 MCard,因为需要 card 标题', '加载中用 MSkeleton', '占位空态用 illustration,避免裸 icon'],
|
|
832
832
|
rulesEn: [
|
|
@@ -846,18 +846,18 @@ const roleOptions = [
|
|
|
846
846
|
pageTypes: ['dashboard'],
|
|
847
847
|
keywords: ['recent', '最近', 'table', 'card', 'list'],
|
|
848
848
|
imports: ['MCard', 'MTable', 'MStatus'],
|
|
849
|
-
scriptSetup: `const recentColumns = [
|
|
850
|
-
{ key: 'id', label: '工单号', width: 96 },
|
|
851
|
-
{ key: 'title', label: '标题' },
|
|
852
|
-
{ key: 'status', label: '状态', width: 110 },
|
|
853
|
-
]
|
|
849
|
+
scriptSetup: `const recentColumns = [
|
|
850
|
+
{ key: 'id', label: '工单号', width: 96 },
|
|
851
|
+
{ key: 'title', label: '标题' },
|
|
852
|
+
{ key: 'status', label: '状态', width: 110 },
|
|
853
|
+
]
|
|
854
854
|
const recentRows = ref<Record<string, unknown>[]>([])`,
|
|
855
|
-
template: `<MCard title="最近工单">
|
|
856
|
-
<MTable :columns="recentColumns" :rows="recentRows" size="small" :paginator="false" bordered row-key="id">
|
|
857
|
-
<template #cell-status="{ value }">
|
|
858
|
-
<MStatus :label="String(value ?? '')" default / type="text" />
|
|
859
|
-
</template>
|
|
860
|
-
</MTable>
|
|
855
|
+
template: `<MCard title="最近工单">
|
|
856
|
+
<MTable :columns="recentColumns" :rows="recentRows" size="small" :paginator="false" bordered row-key="id">
|
|
857
|
+
<template #cell-status="{ value }">
|
|
858
|
+
<MStatus :label="String(value ?? '')" default / type="text" />
|
|
859
|
+
</template>
|
|
860
|
+
</MTable>
|
|
861
861
|
</MCard>`,
|
|
862
862
|
rules: ['明细列表放 MCard 内合理', 'size="small" 适合卡片内表格', '行内业务状态用 MStatus'],
|
|
863
863
|
rulesEn: ['Detail lists inside MCard are appropriate', 'Use size="small" for in-card tables', 'Use MStatus for row business status'],
|
|
@@ -873,10 +873,10 @@ const recentRows = ref<Record<string, unknown>[]>([])`,
|
|
|
873
873
|
pageTypes: ['list', 'common'],
|
|
874
874
|
keywords: ['page content', 'stack', 'layout content', '容器'],
|
|
875
875
|
imports: ['MPageContent'],
|
|
876
|
-
template: `<MLayoutContent>
|
|
877
|
-
<MPageContent>
|
|
878
|
-
<!-- MPageFilters → MPageToolbar → MTable -->
|
|
879
|
-
</MPageContent>
|
|
876
|
+
template: `<MLayoutContent>
|
|
877
|
+
<MPageContent>
|
|
878
|
+
<!-- MPageFilters → MPageToolbar → MTable -->
|
|
879
|
+
</MPageContent>
|
|
880
880
|
</MLayoutContent>`,
|
|
881
881
|
rules: ['所有列表区块放在 MPageContent 内', '区块顺序:筛选 → 工具栏 → 表格'],
|
|
882
882
|
rulesEn: ['Keep all list sections inside MPageContent', 'Order: filters → toolbar → table'],
|
|
@@ -892,10 +892,10 @@ const recentRows = ref<Record<string, unknown>[]>([])`,
|
|
|
892
892
|
pageTypes: ['form', 'common'],
|
|
893
893
|
keywords: ['narrow', 'form content', 'max-width'],
|
|
894
894
|
imports: ['MPageContent'],
|
|
895
|
-
template: `<MLayoutContent>
|
|
896
|
-
<MPageContent width="narrow">
|
|
897
|
-
<!-- MPageHeader → MPageSection form → actions -->
|
|
898
|
-
</MPageContent>
|
|
895
|
+
template: `<MLayoutContent>
|
|
896
|
+
<MPageContent width="narrow">
|
|
897
|
+
<!-- MPageHeader → MPageSection form → actions -->
|
|
898
|
+
</MPageContent>
|
|
899
899
|
</MLayoutContent>`,
|
|
900
900
|
rules: ['表单页使用 width="narrow"', '不要手写 max-width CSS'],
|
|
901
901
|
rulesEn: ['Use width="narrow" on form pages', 'Do not hand-write max-width CSS'],
|
|
@@ -911,14 +911,14 @@ const recentRows = ref<Record<string, unknown>[]>([])`,
|
|
|
911
911
|
pageTypes: ['detail'],
|
|
912
912
|
keywords: ['detail', '详情', 'edit', 'header'],
|
|
913
913
|
imports: ['MPageHeader', 'MButton', 'MStatus', 'MSpace'],
|
|
914
|
-
template: `<MPageHeader title="林晓" description="查看账号摘要、属性与近期活动。">
|
|
915
|
-
<template #actions>
|
|
916
|
-
<MSpace>
|
|
917
|
-
<MStatus label="启用" type="success" />
|
|
918
|
-
<MButton type="primary">编辑</MButton>
|
|
919
|
-
<MButton danger type="text">删除</MButton>
|
|
920
|
-
</MSpace>
|
|
921
|
-
</template>
|
|
914
|
+
template: `<MPageHeader title="林晓" description="查看账号摘要、属性与近期活动。">
|
|
915
|
+
<template #actions>
|
|
916
|
+
<MSpace>
|
|
917
|
+
<MStatus label="启用" type="success" />
|
|
918
|
+
<MButton type="primary">编辑</MButton>
|
|
919
|
+
<MButton danger type="text">删除</MButton>
|
|
920
|
+
</MSpace>
|
|
921
|
+
</template>
|
|
922
922
|
</MPageHeader>`,
|
|
923
923
|
rules: ['状态用 MStatus 放在 actions 区', '编辑为 primary,删除用 danger text'],
|
|
924
924
|
rulesEn: ['Use MStatus in the actions area', 'Edit is primary; delete uses danger text'],
|
|
@@ -934,15 +934,15 @@ const recentRows = ref<Record<string, unknown>[]>([])`,
|
|
|
934
934
|
pageTypes: ['detail'],
|
|
935
935
|
keywords: ['descriptions', '属性', '详情字段', 'detail props', 'dl'],
|
|
936
936
|
imports: ['MDescriptions', 'MDescriptionsItem', 'MStatus'],
|
|
937
|
-
template: `<MDescriptions title="基本信息" bordered :column="2">
|
|
938
|
-
<MDescriptionsItem label="姓名">林晓</MDescriptionsItem>
|
|
939
|
-
<MDescriptionsItem label="邮箱">linxiao@example.com</MDescriptionsItem>
|
|
940
|
-
<MDescriptionsItem label="角色">成员</MDescriptionsItem>
|
|
941
|
-
<MDescriptionsItem label="部门">产品设计</MDescriptionsItem>
|
|
942
|
-
<MDescriptionsItem label="入职日期">2025-03-12</MDescriptionsItem>
|
|
943
|
-
<MDescriptionsItem label="状态">
|
|
944
|
-
<MStatus label="启用" type="success" />
|
|
945
|
-
</MDescriptionsItem>
|
|
937
|
+
template: `<MDescriptions title="基本信息" bordered :column="2">
|
|
938
|
+
<MDescriptionsItem label="姓名">林晓</MDescriptionsItem>
|
|
939
|
+
<MDescriptionsItem label="邮箱">linxiao@example.com</MDescriptionsItem>
|
|
940
|
+
<MDescriptionsItem label="角色">成员</MDescriptionsItem>
|
|
941
|
+
<MDescriptionsItem label="部门">产品设计</MDescriptionsItem>
|
|
942
|
+
<MDescriptionsItem label="入职日期">2025-03-12</MDescriptionsItem>
|
|
943
|
+
<MDescriptionsItem label="状态">
|
|
944
|
+
<MStatus label="启用" type="success" />
|
|
945
|
+
</MDescriptionsItem>
|
|
946
946
|
</MDescriptions>`,
|
|
947
947
|
rules: [
|
|
948
948
|
'属性网格用 MDescriptions,不要手写 <dl> / 嵌套表格',
|
|
@@ -966,13 +966,13 @@ const recentRows = ref<Record<string, unknown>[]>([])`,
|
|
|
966
966
|
pageTypes: ['list', 'form', 'dashboard', 'detail', 'common'],
|
|
967
967
|
keywords: ['页头', '主按钮', '新建', 'header', 'primary', 'actions', 'page-header'],
|
|
968
968
|
imports: ['MPageHeader', 'MButton', 'MSpace'],
|
|
969
|
-
template: `<MPageHeader title="用户管理" description="管理成员与角色。">
|
|
970
|
-
<template #actions>
|
|
971
|
-
<MSpace>
|
|
972
|
-
<MButton type="text">导入</MButton>
|
|
973
|
-
<MButton type="primary">新建用户</MButton>
|
|
974
|
-
</MSpace>
|
|
975
|
-
</template>
|
|
969
|
+
template: `<MPageHeader title="用户管理" description="管理成员与角色。">
|
|
970
|
+
<template #actions>
|
|
971
|
+
<MSpace>
|
|
972
|
+
<MButton type="text">导入</MButton>
|
|
973
|
+
<MButton type="primary">新建用户</MButton>
|
|
974
|
+
</MSpace>
|
|
975
|
+
</template>
|
|
976
976
|
</MPageHeader>`,
|
|
977
977
|
rules: ['视口内仅一个 filled primary', '次要动作用 secondary 或 text', '批量操作用 MPageToolbar,不要塞进页头'],
|
|
978
978
|
rulesEn: ['Only one filled primary in the viewport', 'Lesser actions: secondary or text', 'Batch actions belong in MPageToolbar, not the header'],
|
|
@@ -996,62 +996,62 @@ const recentRows = ref<Record<string, unknown>[]>([])`,
|
|
|
996
996
|
'inline create',
|
|
997
997
|
],
|
|
998
998
|
imports: ['MDialog', 'MForm', 'MFormItem', 'MInput', 'MSelect', 'MButton', 'MSpace', 'message'],
|
|
999
|
-
scriptSetup: `const dialogOpen = ref(false)
|
|
1000
|
-
const submitting = ref(false)
|
|
1001
|
-
const model = reactive({
|
|
1002
|
-
name: '',
|
|
1003
|
-
email: '',
|
|
1004
|
-
role: undefined as string | undefined,
|
|
1005
|
-
})
|
|
1006
|
-
const roleOptions = [
|
|
1007
|
-
{ label: '管理员', value: 'admin' },
|
|
1008
|
-
{ label: '成员', value: 'member' },
|
|
1009
|
-
]
|
|
1010
|
-
|
|
1011
|
-
function resetModel() {
|
|
1012
|
-
model.name = ''
|
|
1013
|
-
model.email = ''
|
|
1014
|
-
model.role = undefined
|
|
1015
|
-
}
|
|
1016
|
-
|
|
1017
|
-
async function onSave() {
|
|
1018
|
-
submitting.value = true
|
|
1019
|
-
try {
|
|
1020
|
-
// await api.save(model)
|
|
1021
|
-
message.success('已保存')
|
|
1022
|
-
dialogOpen.value = false
|
|
1023
|
-
resetModel()
|
|
1024
|
-
} finally {
|
|
1025
|
-
submitting.value = false
|
|
1026
|
-
}
|
|
999
|
+
scriptSetup: `const dialogOpen = ref(false)
|
|
1000
|
+
const submitting = ref(false)
|
|
1001
|
+
const model = reactive({
|
|
1002
|
+
name: '',
|
|
1003
|
+
email: '',
|
|
1004
|
+
role: undefined as string | undefined,
|
|
1005
|
+
})
|
|
1006
|
+
const roleOptions = [
|
|
1007
|
+
{ label: '管理员', value: 'admin' },
|
|
1008
|
+
{ label: '成员', value: 'member' },
|
|
1009
|
+
]
|
|
1010
|
+
|
|
1011
|
+
function resetModel() {
|
|
1012
|
+
model.name = ''
|
|
1013
|
+
model.email = ''
|
|
1014
|
+
model.role = undefined
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
async function onSave() {
|
|
1018
|
+
submitting.value = true
|
|
1019
|
+
try {
|
|
1020
|
+
// await api.save(model)
|
|
1021
|
+
message.success('已保存')
|
|
1022
|
+
dialogOpen.value = false
|
|
1023
|
+
resetModel()
|
|
1024
|
+
} finally {
|
|
1025
|
+
submitting.value = false
|
|
1026
|
+
}
|
|
1027
1027
|
}`,
|
|
1028
|
-
template: `<MDialog
|
|
1029
|
-
v-model="dialogOpen"
|
|
1030
|
-
header="新建用户"
|
|
1031
|
-
width="32rem"
|
|
1032
|
-
@close="resetModel"
|
|
1033
|
-
>
|
|
1034
|
-
<MForm @submit.prevent="onSave">
|
|
1035
|
-
<MFormItem label="姓名" name="name" required>
|
|
1036
|
-
<MInput v-model="model.name" placeholder="请输入姓名" fluid />
|
|
1037
|
-
</MFormItem>
|
|
1038
|
-
<MFormItem label="邮箱" name="email" required>
|
|
1039
|
-
<MInput v-model="model.email" type="email" placeholder="name@example.com" fluid />
|
|
1040
|
-
</MFormItem>
|
|
1041
|
-
<MFormItem label="角色" name="role" required>
|
|
1042
|
-
<MSelect v-model="model.role" :options="roleOptions" placeholder="请选择角色" fluid />
|
|
1043
|
-
</MFormItem>
|
|
1044
|
-
</MForm>
|
|
1045
|
-
<template #footer>
|
|
1046
|
-
<MSpace style="justify-content: flex-end; width: 100%">
|
|
1047
|
-
<MButton type="text" :disabled="submitting" @click="dialogOpen = false">
|
|
1048
|
-
取消
|
|
1049
|
-
</MButton>
|
|
1050
|
-
<MButton type="primary" :loading="submitting" @click="onSave">
|
|
1051
|
-
保存
|
|
1052
|
-
</MButton>
|
|
1053
|
-
</MSpace>
|
|
1054
|
-
</template>
|
|
1028
|
+
template: `<MDialog
|
|
1029
|
+
v-model="dialogOpen"
|
|
1030
|
+
header="新建用户"
|
|
1031
|
+
width="32rem"
|
|
1032
|
+
@close="resetModel"
|
|
1033
|
+
>
|
|
1034
|
+
<MForm @submit.prevent="onSave">
|
|
1035
|
+
<MFormItem label="姓名" name="name" required>
|
|
1036
|
+
<MInput v-model="model.name" placeholder="请输入姓名" fluid />
|
|
1037
|
+
</MFormItem>
|
|
1038
|
+
<MFormItem label="邮箱" name="email" required>
|
|
1039
|
+
<MInput v-model="model.email" type="email" placeholder="name@example.com" fluid />
|
|
1040
|
+
</MFormItem>
|
|
1041
|
+
<MFormItem label="角色" name="role" required>
|
|
1042
|
+
<MSelect v-model="model.role" :options="roleOptions" placeholder="请选择角色" fluid />
|
|
1043
|
+
</MFormItem>
|
|
1044
|
+
</MForm>
|
|
1045
|
+
<template #footer>
|
|
1046
|
+
<MSpace style="justify-content: flex-end; width: 100%">
|
|
1047
|
+
<MButton type="text" :disabled="submitting" @click="dialogOpen = false">
|
|
1048
|
+
取消
|
|
1049
|
+
</MButton>
|
|
1050
|
+
<MButton type="primary" :loading="submitting" @click="onSave">
|
|
1051
|
+
保存
|
|
1052
|
+
</MButton>
|
|
1053
|
+
</MSpace>
|
|
1054
|
+
</template>
|
|
1055
1055
|
</MDialog>`,
|
|
1056
1056
|
rules: [
|
|
1057
1057
|
'留在列表页,不另开路由',
|
|
@@ -1083,44 +1083,44 @@ async function onSave() {
|
|
|
1083
1083
|
pageTypes: ['list', 'detail', 'common'],
|
|
1084
1084
|
keywords: ['抽屉表单', '侧滑编辑', 'drawer form', 'form-in-drawer', 'side panel edit'],
|
|
1085
1085
|
imports: ['MDrawer', 'MForm', 'MFormItem', 'MInput', 'MButton', 'MSpace', 'message'],
|
|
1086
|
-
scriptSetup: `const drawerOpen = ref(false)
|
|
1087
|
-
const submitting = ref(false)
|
|
1088
|
-
const model = reactive({ name: '', note: '' })
|
|
1089
|
-
|
|
1090
|
-
async function onSave() {
|
|
1091
|
-
submitting.value = true
|
|
1092
|
-
try {
|
|
1093
|
-
message.success('已保存')
|
|
1094
|
-
drawerOpen.value = false
|
|
1095
|
-
} finally {
|
|
1096
|
-
submitting.value = false
|
|
1097
|
-
}
|
|
1086
|
+
scriptSetup: `const drawerOpen = ref(false)
|
|
1087
|
+
const submitting = ref(false)
|
|
1088
|
+
const model = reactive({ name: '', note: '' })
|
|
1089
|
+
|
|
1090
|
+
async function onSave() {
|
|
1091
|
+
submitting.value = true
|
|
1092
|
+
try {
|
|
1093
|
+
message.success('已保存')
|
|
1094
|
+
drawerOpen.value = false
|
|
1095
|
+
} finally {
|
|
1096
|
+
submitting.value = false
|
|
1097
|
+
}
|
|
1098
1098
|
}`,
|
|
1099
|
-
template: `<MDrawer
|
|
1100
|
-
v-model="drawerOpen"
|
|
1101
|
-
header="编辑详情"
|
|
1102
|
-
position="right"
|
|
1103
|
-
size="28rem"
|
|
1104
|
-
@close="/* reset draft */"
|
|
1105
|
-
>
|
|
1106
|
-
<MForm @submit.prevent="onSave">
|
|
1107
|
-
<MFormItem label="名称" name="name" required>
|
|
1108
|
-
<MInput v-model="model.name" fluid />
|
|
1109
|
-
</MFormItem>
|
|
1110
|
-
<MFormItem label="备注" name="note">
|
|
1111
|
-
<MInput v-model="model.note" fluid />
|
|
1112
|
-
</MFormItem>
|
|
1113
|
-
</MForm>
|
|
1114
|
-
<template #footer>
|
|
1115
|
-
<MSpace style="justify-content: flex-end; width: 100%">
|
|
1116
|
-
<MButton type="text" :disabled="submitting" @click="drawerOpen = false">
|
|
1117
|
-
取消
|
|
1118
|
-
</MButton>
|
|
1119
|
-
<MButton type="primary" :loading="submitting" @click="onSave">
|
|
1120
|
-
保存
|
|
1121
|
-
</MButton>
|
|
1122
|
-
</MSpace>
|
|
1123
|
-
</template>
|
|
1099
|
+
template: `<MDrawer
|
|
1100
|
+
v-model="drawerOpen"
|
|
1101
|
+
header="编辑详情"
|
|
1102
|
+
position="right"
|
|
1103
|
+
size="28rem"
|
|
1104
|
+
@close="/* reset draft */"
|
|
1105
|
+
>
|
|
1106
|
+
<MForm @submit.prevent="onSave">
|
|
1107
|
+
<MFormItem label="名称" name="name" required>
|
|
1108
|
+
<MInput v-model="model.name" fluid />
|
|
1109
|
+
</MFormItem>
|
|
1110
|
+
<MFormItem label="备注" name="note">
|
|
1111
|
+
<MInput v-model="model.note" fluid />
|
|
1112
|
+
</MFormItem>
|
|
1113
|
+
</MForm>
|
|
1114
|
+
<template #footer>
|
|
1115
|
+
<MSpace style="justify-content: flex-end; width: 100%">
|
|
1116
|
+
<MButton type="text" :disabled="submitting" @click="drawerOpen = false">
|
|
1117
|
+
取消
|
|
1118
|
+
</MButton>
|
|
1119
|
+
<MButton type="primary" :loading="submitting" @click="onSave">
|
|
1120
|
+
保存
|
|
1121
|
+
</MButton>
|
|
1122
|
+
</MSpace>
|
|
1123
|
+
</template>
|
|
1124
1124
|
</MDrawer>`,
|
|
1125
1125
|
rules: ['position 常用 right;较长表单可加大 size', '字段 fluid;底栏放 #footer', '成功用 message.success'],
|
|
1126
1126
|
rulesEn: ['position usually right; raise size for longer forms', 'Fields fluid; actions in #footer', 'Success via message.success'],
|
|
@@ -1136,28 +1136,28 @@ async function onSave() {
|
|
|
1136
1136
|
pageTypes: ['list', 'detail', 'common'],
|
|
1137
1137
|
keywords: ['删除', '确认', '二次确认', 'confirm', 'delete', 'ConfirmDialog', '危险'],
|
|
1138
1138
|
imports: ['MConfirmDialog', 'message'],
|
|
1139
|
-
scriptSetup: `const confirmOpen = ref(false)
|
|
1140
|
-
const pendingId = ref<string | null>(null)
|
|
1141
|
-
|
|
1142
|
-
function askDelete(id: string) {
|
|
1143
|
-
pendingId.value = id
|
|
1144
|
-
confirmOpen.value = true
|
|
1145
|
-
}
|
|
1146
|
-
|
|
1147
|
-
function onAcceptDelete() {
|
|
1148
|
-
// await api.remove(pendingId.value)
|
|
1149
|
-
message.success('已删除')
|
|
1150
|
-
pendingId.value = null
|
|
1151
|
-
confirmOpen.value = false
|
|
1139
|
+
scriptSetup: `const confirmOpen = ref(false)
|
|
1140
|
+
const pendingId = ref<string | null>(null)
|
|
1141
|
+
|
|
1142
|
+
function askDelete(id: string) {
|
|
1143
|
+
pendingId.value = id
|
|
1144
|
+
confirmOpen.value = true
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
function onAcceptDelete() {
|
|
1148
|
+
// await api.remove(pendingId.value)
|
|
1149
|
+
message.success('已删除')
|
|
1150
|
+
pendingId.value = null
|
|
1151
|
+
confirmOpen.value = false
|
|
1152
1152
|
}`,
|
|
1153
|
-
template: `<MConfirmDialog
|
|
1154
|
-
v-model="confirmOpen"
|
|
1155
|
-
header="确认删除"
|
|
1156
|
-
message="删除后不可恢复,确定继续?"
|
|
1157
|
-
accept-label="删除"
|
|
1158
|
-
reject-label="取消"
|
|
1159
|
-
accept-danger
|
|
1160
|
-
@accept="onAcceptDelete"
|
|
1153
|
+
template: `<MConfirmDialog
|
|
1154
|
+
v-model="confirmOpen"
|
|
1155
|
+
header="确认删除"
|
|
1156
|
+
message="删除后不可恢复,确定继续?"
|
|
1157
|
+
accept-label="删除"
|
|
1158
|
+
reject-label="取消"
|
|
1159
|
+
accept-danger
|
|
1160
|
+
@accept="onAcceptDelete"
|
|
1161
1161
|
/>`,
|
|
1162
1162
|
rules: ['用 MConfirmDialog,不要手写 Dialog 确定/取消', 'acceptColor="danger"', '成功用 message.success,不要 toast'],
|
|
1163
1163
|
rulesEn: ['Use MConfirmDialog; do not hand-roll Dialog Yes/No', 'acceptColor="danger"', 'Success via message.success, not toast'],
|
|
@@ -1173,15 +1173,15 @@ function onAcceptDelete() {
|
|
|
1173
1173
|
pageTypes: ['list', 'common'],
|
|
1174
1174
|
keywords: ['更多', '行菜单', 'dropdown', 'row menu', 'overflow', 'actions menu'],
|
|
1175
1175
|
imports: ['MDropdown', 'MButton'],
|
|
1176
|
-
scriptSetup: `const rowMenu = [
|
|
1177
|
-
{ label: '编辑', command: () => {/* open edit */} },
|
|
1178
|
-
{ label: '复制', command: () => {/* duplicate */} },
|
|
1179
|
-
{ label: '删除', command: () => {/* askDelete */} },
|
|
1176
|
+
scriptSetup: `const rowMenu = [
|
|
1177
|
+
{ label: '编辑', command: () => {/* open edit */} },
|
|
1178
|
+
{ label: '复制', command: () => {/* duplicate */} },
|
|
1179
|
+
{ label: '删除', command: () => {/* askDelete */} },
|
|
1180
1180
|
]`,
|
|
1181
|
-
template: `<template #cell-actions>
|
|
1182
|
-
<MDropdown :model="rowMenu">
|
|
1183
|
-
<MButton size="small" type="text" icon="more-horizontal" aria-label="更多操作" />
|
|
1184
|
-
</MDropdown>
|
|
1181
|
+
template: `<template #cell-actions>
|
|
1182
|
+
<MDropdown :model="rowMenu">
|
|
1183
|
+
<MButton size="small" type="text" icon="more-horizontal" aria-label="更多操作" />
|
|
1184
|
+
</MDropdown>
|
|
1185
1185
|
</template>`,
|
|
1186
1186
|
rules: ['操作很多时再用 Dropdown;2–3 个明确操作用 text 按钮', '危险项走 confirm-delete', '图标按钮必须 aria-label'],
|
|
1187
1187
|
rulesEn: ['Use Dropdown when many actions; 2–3 explicit actions stay as text buttons', 'Danger → confirm-delete', 'Icon-only buttons need aria-label'],
|
|
@@ -1197,45 +1197,45 @@ function onAcceptDelete() {
|
|
|
1197
1197
|
pageTypes: ['form', 'common'],
|
|
1198
1198
|
keywords: ['向导', '分步', '步骤', 'wizard', 'stepper', 'multi-step'],
|
|
1199
1199
|
imports: ['MStepper', 'MForm', 'MFormItem', 'MInput', 'MButton', 'MSpace', 'MPageSection', 'message'],
|
|
1200
|
-
scriptSetup: `const activeStep = ref(0)
|
|
1201
|
-
const submitting = ref(false)
|
|
1202
|
-
const steps = [
|
|
1203
|
-
{ label: '基本信息', description: '名称与说明' },
|
|
1204
|
-
{ label: '确认', description: '核对后创建' },
|
|
1205
|
-
]
|
|
1206
|
-
const model = reactive({ name: '' })
|
|
1207
|
-
|
|
1208
|
-
function back() {
|
|
1209
|
-
if (activeStep.value > 0) activeStep.value -= 1
|
|
1210
|
-
}
|
|
1211
|
-
function next() {
|
|
1212
|
-
if (activeStep.value < steps.length - 1) activeStep.value += 1
|
|
1213
|
-
}
|
|
1214
|
-
async function finish() {
|
|
1215
|
-
submitting.value = true
|
|
1216
|
-
try {
|
|
1217
|
-
message.success('已创建')
|
|
1218
|
-
} finally {
|
|
1219
|
-
submitting.value = false
|
|
1220
|
-
}
|
|
1200
|
+
scriptSetup: `const activeStep = ref(0)
|
|
1201
|
+
const submitting = ref(false)
|
|
1202
|
+
const steps = [
|
|
1203
|
+
{ label: '基本信息', description: '名称与说明' },
|
|
1204
|
+
{ label: '确认', description: '核对后创建' },
|
|
1205
|
+
]
|
|
1206
|
+
const model = reactive({ name: '' })
|
|
1207
|
+
|
|
1208
|
+
function back() {
|
|
1209
|
+
if (activeStep.value > 0) activeStep.value -= 1
|
|
1210
|
+
}
|
|
1211
|
+
function next() {
|
|
1212
|
+
if (activeStep.value < steps.length - 1) activeStep.value += 1
|
|
1213
|
+
}
|
|
1214
|
+
async function finish() {
|
|
1215
|
+
submitting.value = true
|
|
1216
|
+
try {
|
|
1217
|
+
message.success('已创建')
|
|
1218
|
+
} finally {
|
|
1219
|
+
submitting.value = false
|
|
1220
|
+
}
|
|
1221
1221
|
}`,
|
|
1222
|
-
template: `<MStepper v-model="activeStep" :steps="steps" linear />
|
|
1223
|
-
<MPageSection variant="form">
|
|
1224
|
-
<MForm v-if="activeStep === 0" @submit.prevent="next">
|
|
1225
|
-
<MFormItem label="名称" name="name" required>
|
|
1226
|
-
<MInput v-model="model.name" fluid />
|
|
1227
|
-
</MFormItem>
|
|
1228
|
-
</MForm>
|
|
1229
|
-
<div v-else>
|
|
1230
|
-
<p>确认创建「{{ model.name }}」?</p>
|
|
1231
|
-
</div>
|
|
1232
|
-
</MPageSection>
|
|
1233
|
-
<MPageSection variant="actions">
|
|
1234
|
-
<MSpace>
|
|
1235
|
-
<MButton type="text" :disabled="activeStep === 0" @click="back">上一步</MButton>
|
|
1236
|
-
<MButton v-if="activeStep < steps.length - 1" type="primary" @click="next">下一步</MButton>
|
|
1237
|
-
<MButton v-else type="primary" :loading="submitting" @click="finish">创建</MButton>
|
|
1238
|
-
</MSpace>
|
|
1222
|
+
template: `<MStepper v-model="activeStep" :steps="steps" linear />
|
|
1223
|
+
<MPageSection variant="form">
|
|
1224
|
+
<MForm v-if="activeStep === 0" @submit.prevent="next">
|
|
1225
|
+
<MFormItem label="名称" name="name" required>
|
|
1226
|
+
<MInput v-model="model.name" fluid />
|
|
1227
|
+
</MFormItem>
|
|
1228
|
+
</MForm>
|
|
1229
|
+
<div v-else>
|
|
1230
|
+
<p>确认创建「{{ model.name }}」?</p>
|
|
1231
|
+
</div>
|
|
1232
|
+
</MPageSection>
|
|
1233
|
+
<MPageSection variant="actions">
|
|
1234
|
+
<MSpace>
|
|
1235
|
+
<MButton type="text" :disabled="activeStep === 0" @click="back">上一步</MButton>
|
|
1236
|
+
<MButton v-if="activeStep < steps.length - 1" type="primary" @click="next">下一步</MButton>
|
|
1237
|
+
<MButton v-else type="primary" :loading="submitting" @click="finish">创建</MButton>
|
|
1238
|
+
</MSpace>
|
|
1239
1239
|
</MPageSection>`,
|
|
1240
1240
|
rules: ['用 :steps(不是 :items)', '每步一个主任务;进入下一步前校验当前步', '完成文案与「下一步」区分'],
|
|
1241
1241
|
rulesEn: ['Use :steps (not :items)', 'One job per step; validate before advancing', 'Finish label differs from Next'],
|
|
@@ -1251,51 +1251,51 @@ async function finish() {
|
|
|
1251
1251
|
pageTypes: ['common'],
|
|
1252
1252
|
keywords: ['登录', '认证', '分栏', 'login', 'auth', 'split', 'brand', 'filled', 'themeConfig'],
|
|
1253
1253
|
imports: ['MForm', 'MFormItem', 'MInput', 'MInputPassword', 'MButton', 'MSpace', 'MConfigProvider', 'zhCN'],
|
|
1254
|
-
scriptSetup: `const submitting = ref(false)
|
|
1255
|
-
const formError = ref('')
|
|
1256
|
-
const model = reactive({ email: '', password: '' })
|
|
1257
|
-
const themeConfig = { seed: { colorPrimary: '#0b6e4f' } }
|
|
1258
|
-
|
|
1259
|
-
async function onSubmit() {
|
|
1260
|
-
formError.value = ''
|
|
1261
|
-
submitting.value = true
|
|
1262
|
-
try {
|
|
1263
|
-
if (!model.email || !model.password) {
|
|
1264
|
-
formError.value = '请输入邮箱和密码后再试。'
|
|
1265
|
-
}
|
|
1266
|
-
} finally {
|
|
1267
|
-
submitting.value = false
|
|
1268
|
-
}
|
|
1254
|
+
scriptSetup: `const submitting = ref(false)
|
|
1255
|
+
const formError = ref('')
|
|
1256
|
+
const model = reactive({ email: '', password: '' })
|
|
1257
|
+
const themeConfig = { seed: { colorPrimary: '#0b6e4f' } }
|
|
1258
|
+
|
|
1259
|
+
async function onSubmit() {
|
|
1260
|
+
formError.value = ''
|
|
1261
|
+
submitting.value = true
|
|
1262
|
+
try {
|
|
1263
|
+
if (!model.email || !model.password) {
|
|
1264
|
+
formError.value = '请输入邮箱和密码后再试。'
|
|
1265
|
+
}
|
|
1266
|
+
} finally {
|
|
1267
|
+
submitting.value = false
|
|
1268
|
+
}
|
|
1269
1269
|
}`,
|
|
1270
|
-
template: `<MConfigProvider :locale="zhCN" input-variant="filled" :theme-config="themeConfig">
|
|
1271
|
-
<div class="login-shell">
|
|
1272
|
-
<aside class="login-brand" aria-label="品牌">
|
|
1273
|
-
<p class="login-brand__mark">产品名</p>
|
|
1274
|
-
<h1 class="login-brand__title">一句品牌主张</h1>
|
|
1275
|
-
<p class="login-brand__lead">一句支持说明。</p>
|
|
1276
|
-
</aside>
|
|
1277
|
-
<main class="login-main">
|
|
1278
|
-
<div class="login-panel">
|
|
1279
|
-
<header class="login-panel__header">
|
|
1280
|
-
<h2>登录</h2>
|
|
1281
|
-
<p>使用工作邮箱进入。</p>
|
|
1282
|
-
</header>
|
|
1283
|
-
<p v-if="formError" class="login-alert" role="alert">{{ formError }}</p>
|
|
1284
|
-
<MForm @submit="onSubmit">
|
|
1285
|
-
<MFormItem label="邮箱" name="email" required>
|
|
1286
|
-
<MInput v-model="model.email" type="email" autocomplete="username" fluid />
|
|
1287
|
-
</MFormItem>
|
|
1288
|
-
<MFormItem label="密码" name="password" required>
|
|
1289
|
-
<MInputPassword v-model="model.password" autocomplete="current-password" fluid />
|
|
1290
|
-
</MFormItem>
|
|
1291
|
-
<MSpace style="margin-top: var(--m-space-2)" alignment="center">
|
|
1292
|
-
<MButton html-type="submit" label="登录" type="primary" :loading="submitting" />
|
|
1293
|
-
<MButton html-type="button" label="忘记密码" type="text" />
|
|
1294
|
-
</MSpace>
|
|
1295
|
-
</MForm>
|
|
1296
|
-
</div>
|
|
1297
|
-
</main>
|
|
1298
|
-
</div>
|
|
1270
|
+
template: `<MConfigProvider :locale="zhCN" input-variant="filled" :theme-config="themeConfig">
|
|
1271
|
+
<div class="login-shell">
|
|
1272
|
+
<aside class="login-brand" aria-label="品牌">
|
|
1273
|
+
<p class="login-brand__mark">产品名</p>
|
|
1274
|
+
<h1 class="login-brand__title">一句品牌主张</h1>
|
|
1275
|
+
<p class="login-brand__lead">一句支持说明。</p>
|
|
1276
|
+
</aside>
|
|
1277
|
+
<main class="login-main">
|
|
1278
|
+
<div class="login-panel">
|
|
1279
|
+
<header class="login-panel__header">
|
|
1280
|
+
<h2>登录</h2>
|
|
1281
|
+
<p>使用工作邮箱进入。</p>
|
|
1282
|
+
</header>
|
|
1283
|
+
<p v-if="formError" class="login-alert" role="alert">{{ formError }}</p>
|
|
1284
|
+
<MForm @submit="onSubmit">
|
|
1285
|
+
<MFormItem label="邮箱" name="email" required>
|
|
1286
|
+
<MInput v-model="model.email" type="email" autocomplete="username" fluid />
|
|
1287
|
+
</MFormItem>
|
|
1288
|
+
<MFormItem label="密码" name="password" required>
|
|
1289
|
+
<MInputPassword v-model="model.password" autocomplete="current-password" fluid />
|
|
1290
|
+
</MFormItem>
|
|
1291
|
+
<MSpace style="margin-top: var(--m-space-2)" alignment="center">
|
|
1292
|
+
<MButton html-type="submit" label="登录" type="primary" :loading="submitting" />
|
|
1293
|
+
<MButton html-type="button" label="忘记密码" type="text" />
|
|
1294
|
+
</MSpace>
|
|
1295
|
+
</MForm>
|
|
1296
|
+
</div>
|
|
1297
|
+
</main>
|
|
1298
|
+
</div>
|
|
1299
1299
|
</MConfigProvider>`,
|
|
1300
1300
|
rules: [
|
|
1301
1301
|
'认证表单默认 input-variant="filled";品牌色走 themeConfig.seed,不要页级 hex',
|
|
@@ -1339,29 +1339,29 @@ async function onSubmit() {
|
|
|
1339
1339
|
'MConfigProvider',
|
|
1340
1340
|
'zhCN',
|
|
1341
1341
|
],
|
|
1342
|
-
template: `<MConfigProvider :locale="zhCN">
|
|
1343
|
-
<MLayout fill-viewport has-sider>
|
|
1344
|
-
<MLayoutSider v-model:collapsed="siderCollapsed" bordered :collapsed-width="72">
|
|
1345
|
-
<MMenu
|
|
1346
|
-
:model="[
|
|
1347
|
-
{ key: 'users', label: '用户管理', icon: 'user', to: '/users' },
|
|
1348
|
-
{ key: 'roles', label: '角色管理', icon: 'shield', to: '/roles' },
|
|
1349
|
-
]"
|
|
1350
|
-
:collapsed="siderCollapsed"
|
|
1351
|
-
:collapsed-width="72"
|
|
1352
|
-
/>
|
|
1353
|
-
</MLayoutSider>
|
|
1354
|
-
<MLayout>
|
|
1355
|
-
<MLayoutHeader :padding="'var(--m-space-4) var(--m-space-6)'">
|
|
1356
|
-
<MBreadcrumb :model="[{ label: '首页', to: '/' }, { label: '当前页' }]" />
|
|
1357
|
-
</MLayoutHeader>
|
|
1358
|
-
<MLayoutContent>
|
|
1359
|
-
<MPageContent>
|
|
1360
|
-
<!-- page sections -->
|
|
1361
|
-
</MPageContent>
|
|
1362
|
-
</MLayoutContent>
|
|
1363
|
-
</MLayout>
|
|
1364
|
-
</MLayout>
|
|
1342
|
+
template: `<MConfigProvider :locale="zhCN">
|
|
1343
|
+
<MLayout fill-viewport has-sider>
|
|
1344
|
+
<MLayoutSider v-model:collapsed="siderCollapsed" bordered :collapsed-width="72">
|
|
1345
|
+
<MMenu
|
|
1346
|
+
:model="[
|
|
1347
|
+
{ key: 'users', label: '用户管理', icon: 'user', to: '/users' },
|
|
1348
|
+
{ key: 'roles', label: '角色管理', icon: 'shield', to: '/roles' },
|
|
1349
|
+
]"
|
|
1350
|
+
:collapsed="siderCollapsed"
|
|
1351
|
+
:collapsed-width="72"
|
|
1352
|
+
/>
|
|
1353
|
+
</MLayoutSider>
|
|
1354
|
+
<MLayout>
|
|
1355
|
+
<MLayoutHeader :padding="'var(--m-space-4) var(--m-space-6)'">
|
|
1356
|
+
<MBreadcrumb :model="[{ label: '首页', to: '/' }, { label: '当前页' }]" />
|
|
1357
|
+
</MLayoutHeader>
|
|
1358
|
+
<MLayoutContent>
|
|
1359
|
+
<MPageContent>
|
|
1360
|
+
<!-- page sections -->
|
|
1361
|
+
</MPageContent>
|
|
1362
|
+
</MLayoutContent>
|
|
1363
|
+
</MLayout>
|
|
1364
|
+
</MLayout>
|
|
1365
1365
|
</MConfigProvider>`,
|
|
1366
1366
|
rules: [
|
|
1367
1367
|
'根布局用 fillViewport',
|
|
@@ -1394,19 +1394,19 @@ async function onSubmit() {
|
|
|
1394
1394
|
'log',
|
|
1395
1395
|
],
|
|
1396
1396
|
imports: ['MScrollbar', 'MCard'],
|
|
1397
|
-
template: `<MCard title="活动日志">
|
|
1398
|
-
<MScrollbar max-height="16rem">
|
|
1399
|
-
<ul style="margin:0;padding:0;list-style:none">
|
|
1400
|
-
<li v-for="item in logLines" :key="item.id" style="padding:var(--m-space-2) 0;border-bottom:1px solid var(--m-color-border)">
|
|
1401
|
-
{{ item.text }}
|
|
1402
|
-
</li>
|
|
1403
|
-
</ul>
|
|
1404
|
-
</MScrollbar>
|
|
1397
|
+
template: `<MCard title="活动日志">
|
|
1398
|
+
<MScrollbar max-height="16rem">
|
|
1399
|
+
<ul style="margin:0;padding:0;list-style:none">
|
|
1400
|
+
<li v-for="item in logLines" :key="item.id" style="padding:var(--m-space-2) 0;border-bottom:1px solid var(--m-color-border)">
|
|
1401
|
+
{{ item.text }}
|
|
1402
|
+
</li>
|
|
1403
|
+
</ul>
|
|
1404
|
+
</MScrollbar>
|
|
1405
1405
|
</MCard>`,
|
|
1406
|
-
scriptSetup: `const logLines = [
|
|
1407
|
-
{ id: '1', text: '用户 admin 登录成功' },
|
|
1408
|
-
{ id: '2', text: '导出任务已完成' },
|
|
1409
|
-
{ id: '3', text: '配置已保存' },
|
|
1406
|
+
scriptSetup: `const logLines = [
|
|
1407
|
+
{ id: '1', text: '用户 admin 登录成功' },
|
|
1408
|
+
{ id: '2', text: '导出任务已完成' },
|
|
1409
|
+
{ id: '3', text: '配置已保存' },
|
|
1410
1410
|
]`,
|
|
1411
1411
|
rules: [
|
|
1412
1412
|
'整页主滚动推荐 MLayout fillViewport',
|