@morya-ui/mcp 0.2.8 → 0.3.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/README.md +2 -2
- package/data/catalog.json +529 -248
- package/data/example-coverage.json +67 -39
- package/data/golden-pages/dashboard-page.vue +41 -8
- package/data/golden-pages/detail-page.vue +178 -0
- package/data/golden-pages/empty-state.vue +22 -1
- package/data/golden-pages/form-in-dialog.vue +166 -0
- package/data/golden-pages/form-page.vue +1 -1
- package/data/golden-pages/landing-page.vue +31 -1
- package/data/golden-pages/list-page.vue +25 -12
- package/data/golden-pages/login-page.vue +44 -4
- package/data/golden-pages/result-page.vue +84 -0
- package/data/golden-pages/settings-page.vue +163 -0
- package/data/golden-pages/wizard-form.vue +182 -0
- package/dist/__tests__/tools.test.js +97 -4
- package/dist/decisions.js +136 -1
- package/dist/golden-pages.js +35 -0
- package/dist/index.js +1 -1
- package/dist/page-snippets.js +46 -36
- package/dist/patterns.d.ts +7 -5
- package/dist/patterns.js +29 -8
- package/dist/tools.js +181 -58
- package/package.json +1 -1
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* 黄金样例:详情页
|
|
4
|
+
* @see DESIGN.md · morya-ui-pages references/page-layouts.md · visual-craft § Ops polish
|
|
5
|
+
*/
|
|
6
|
+
import {
|
|
7
|
+
MBreadcrumb,
|
|
8
|
+
MButton,
|
|
9
|
+
MCard,
|
|
10
|
+
MConfigProvider,
|
|
11
|
+
MDivider,
|
|
12
|
+
MLayout,
|
|
13
|
+
MLayoutContent,
|
|
14
|
+
MLayoutHeader,
|
|
15
|
+
MPageContent,
|
|
16
|
+
MPageHeader,
|
|
17
|
+
MPageSection,
|
|
18
|
+
MSpace,
|
|
19
|
+
MStatus,
|
|
20
|
+
MTable,
|
|
21
|
+
MTag,
|
|
22
|
+
zhCN,
|
|
23
|
+
} from 'morya-ui'
|
|
24
|
+
|
|
25
|
+
const profile = {
|
|
26
|
+
name: '林晓',
|
|
27
|
+
email: 'linxiao@example.com',
|
|
28
|
+
role: '成员',
|
|
29
|
+
dept: '产品设计',
|
|
30
|
+
joinedAt: '2025-03-12',
|
|
31
|
+
status: 'active' as const,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const activityColumns = [
|
|
35
|
+
{ key: 'time', label: '时间', width: 160 },
|
|
36
|
+
{ key: 'action', label: '动作' },
|
|
37
|
+
{ key: 'channel', label: '来源', width: 100 },
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
const activityRows = [
|
|
41
|
+
{ id: '1', time: '2026-09-20 14:22', action: '更新了个人资料', channel: 'Web' },
|
|
42
|
+
{ id: '2', time: '2026-09-18 09:05', action: '重置了登录密码', channel: 'Web' },
|
|
43
|
+
{ id: '3', time: '2026-09-12 18:41', action: '加入「产品设计」部门', channel: 'Admin' },
|
|
44
|
+
]
|
|
45
|
+
</script>
|
|
46
|
+
|
|
47
|
+
<template>
|
|
48
|
+
<MConfigProvider :locale="zhCN">
|
|
49
|
+
<MLayout fill-viewport>
|
|
50
|
+
<MLayoutHeader padding="var(--m-space-4) var(--m-space-6)">
|
|
51
|
+
<MBreadcrumb
|
|
52
|
+
:model="[
|
|
53
|
+
{ label: '首页', to: '/' },
|
|
54
|
+
{ label: '用户管理', to: '/users' },
|
|
55
|
+
{ label: profile.name },
|
|
56
|
+
]"
|
|
57
|
+
/>
|
|
58
|
+
</MLayoutHeader>
|
|
59
|
+
|
|
60
|
+
<MLayoutContent>
|
|
61
|
+
<MPageContent>
|
|
62
|
+
<MPageHeader
|
|
63
|
+
:title="profile.name"
|
|
64
|
+
description="查看账号摘要、属性与近期活动。编辑走同页弹窗或独立表单页,勿做成营销落地。"
|
|
65
|
+
>
|
|
66
|
+
<template #actions>
|
|
67
|
+
<MSpace>
|
|
68
|
+
<MStatus
|
|
69
|
+
:label="profile.status === 'active' ? '启用' : '停用'"
|
|
70
|
+
:severity="profile.status === 'active' ? 'success' : 'secondary'"
|
|
71
|
+
/>
|
|
72
|
+
<MButton severity="primary">
|
|
73
|
+
编辑
|
|
74
|
+
</MButton>
|
|
75
|
+
<MButton severity="secondary">
|
|
76
|
+
返回
|
|
77
|
+
</MButton>
|
|
78
|
+
<MButton severity="danger" text>
|
|
79
|
+
删除
|
|
80
|
+
</MButton>
|
|
81
|
+
</MSpace>
|
|
82
|
+
</template>
|
|
83
|
+
</MPageHeader>
|
|
84
|
+
|
|
85
|
+
<MPageSection title="摘要">
|
|
86
|
+
<MSpace wrap style="gap: var(--m-space-2)">
|
|
87
|
+
<MTag :value="profile.role" />
|
|
88
|
+
<MTag :value="profile.dept" severity="info" />
|
|
89
|
+
</MSpace>
|
|
90
|
+
<p class="detail-lead">
|
|
91
|
+
工作邮箱 {{ profile.email }} · 入职 {{ profile.joinedAt }}
|
|
92
|
+
</p>
|
|
93
|
+
</MPageSection>
|
|
94
|
+
|
|
95
|
+
<MCard title="基本信息">
|
|
96
|
+
<dl class="detail-props">
|
|
97
|
+
<div>
|
|
98
|
+
<dt>姓名</dt>
|
|
99
|
+
<dd>{{ profile.name }}</dd>
|
|
100
|
+
</div>
|
|
101
|
+
<div>
|
|
102
|
+
<dt>邮箱</dt>
|
|
103
|
+
<dd>{{ profile.email }}</dd>
|
|
104
|
+
</div>
|
|
105
|
+
<div>
|
|
106
|
+
<dt>角色</dt>
|
|
107
|
+
<dd>{{ profile.role }}</dd>
|
|
108
|
+
</div>
|
|
109
|
+
<div>
|
|
110
|
+
<dt>部门</dt>
|
|
111
|
+
<dd>{{ profile.dept }}</dd>
|
|
112
|
+
</div>
|
|
113
|
+
<div>
|
|
114
|
+
<dt>入职日期</dt>
|
|
115
|
+
<dd>{{ profile.joinedAt }}</dd>
|
|
116
|
+
</div>
|
|
117
|
+
<div>
|
|
118
|
+
<dt>状态</dt>
|
|
119
|
+
<dd>
|
|
120
|
+
<MStatus
|
|
121
|
+
:label="profile.status === 'active' ? '启用' : '停用'"
|
|
122
|
+
:severity="profile.status === 'active' ? 'success' : 'secondary'"
|
|
123
|
+
/>
|
|
124
|
+
</dd>
|
|
125
|
+
</div>
|
|
126
|
+
</dl>
|
|
127
|
+
</MCard>
|
|
128
|
+
|
|
129
|
+
<MDivider />
|
|
130
|
+
|
|
131
|
+
<MCard title="近期活动">
|
|
132
|
+
<MTable
|
|
133
|
+
:columns="activityColumns"
|
|
134
|
+
:rows="activityRows"
|
|
135
|
+
row-key="id"
|
|
136
|
+
striped
|
|
137
|
+
bordered
|
|
138
|
+
aria-label="近期活动"
|
|
139
|
+
/>
|
|
140
|
+
</MCard>
|
|
141
|
+
</MPageContent>
|
|
142
|
+
</MLayoutContent>
|
|
143
|
+
</MLayout>
|
|
144
|
+
</MConfigProvider>
|
|
145
|
+
</template>
|
|
146
|
+
|
|
147
|
+
<style scoped>
|
|
148
|
+
.detail-lead {
|
|
149
|
+
margin: var(--m-space-3) 0 0;
|
|
150
|
+
color: var(--m-color-text-muted);
|
|
151
|
+
font-size: var(--m-font-size-sm);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.detail-props {
|
|
155
|
+
display: grid;
|
|
156
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
157
|
+
gap: var(--m-space-4) var(--m-space-6);
|
|
158
|
+
margin: 0;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.detail-props dt {
|
|
162
|
+
margin: 0 0 var(--m-space-1);
|
|
163
|
+
color: var(--m-color-text-muted);
|
|
164
|
+
font-size: var(--m-font-size-xs);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.detail-props dd {
|
|
168
|
+
margin: 0;
|
|
169
|
+
color: var(--m-color-text);
|
|
170
|
+
font-size: var(--m-font-size-md);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
@media (max-width: 640px) {
|
|
174
|
+
.detail-props {
|
|
175
|
+
grid-template-columns: 1fr;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
</style>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
/**
|
|
3
3
|
* 黄金样例:列表空状态(Flow)
|
|
4
|
-
* @see DESIGN.md
|
|
4
|
+
* @see DESIGN.md · page-layouts.md · surfaces § Flow
|
|
5
5
|
* 可嵌在列表页 MPageContent / MTable #empty 中;此处给出完整可运行骨架。
|
|
6
6
|
*/
|
|
7
7
|
import {
|
|
@@ -53,6 +53,10 @@ import {
|
|
|
53
53
|
|
|
54
54
|
<style scoped>
|
|
55
55
|
.empty-state-shell {
|
|
56
|
+
display: grid;
|
|
57
|
+
place-items: center;
|
|
58
|
+
min-height: 18rem;
|
|
59
|
+
padding: var(--m-space-8) var(--m-space-6);
|
|
56
60
|
border: 1px dashed color-mix(in srgb, var(--m-color-border) 80%, var(--m-color-primary));
|
|
57
61
|
border-radius: var(--m-radius-md);
|
|
58
62
|
background:
|
|
@@ -63,4 +67,21 @@ import {
|
|
|
63
67
|
),
|
|
64
68
|
var(--m-color-surface);
|
|
65
69
|
}
|
|
70
|
+
|
|
71
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
72
|
+
.empty-state-shell {
|
|
73
|
+
animation: empty-in 400ms ease both;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
@keyframes empty-in {
|
|
78
|
+
from {
|
|
79
|
+
opacity: 0;
|
|
80
|
+
transform: translateY(0.35rem);
|
|
81
|
+
}
|
|
82
|
+
to {
|
|
83
|
+
opacity: 1;
|
|
84
|
+
transform: translateY(0);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
66
87
|
</style>
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* 黄金样例:列表内短表单弹窗(form-in-dialog)
|
|
4
|
+
* 留在列表页,用 MDialog 承载 ≤8 字段的新建/编辑;长表单仍走 form-page / Drawer。
|
|
5
|
+
* @see DESIGN.md · page-layouts.md · pattern form-in-dialog
|
|
6
|
+
*/
|
|
7
|
+
import {
|
|
8
|
+
MBreadcrumb,
|
|
9
|
+
MButton,
|
|
10
|
+
MConfigProvider,
|
|
11
|
+
MDialog,
|
|
12
|
+
MForm,
|
|
13
|
+
MFormItem,
|
|
14
|
+
MInput,
|
|
15
|
+
MLayout,
|
|
16
|
+
MLayoutContent,
|
|
17
|
+
MLayoutHeader,
|
|
18
|
+
MPageContent,
|
|
19
|
+
MPageToolbar,
|
|
20
|
+
MSelect,
|
|
21
|
+
MSpace,
|
|
22
|
+
MStatus,
|
|
23
|
+
MTable,
|
|
24
|
+
message,
|
|
25
|
+
zhCN,
|
|
26
|
+
} from 'morya-ui'
|
|
27
|
+
import { reactive, ref } from 'vue'
|
|
28
|
+
|
|
29
|
+
const dialogOpen = ref(false)
|
|
30
|
+
const submitting = ref(false)
|
|
31
|
+
const editingId = ref<string | null>(null)
|
|
32
|
+
|
|
33
|
+
const model = reactive({
|
|
34
|
+
name: '',
|
|
35
|
+
email: '',
|
|
36
|
+
role: undefined as string | undefined,
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
const roleOptions = [
|
|
40
|
+
{ label: '管理员', value: 'admin' },
|
|
41
|
+
{ label: '成员', value: 'member' },
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
const columns = [
|
|
45
|
+
{ key: 'name', label: '姓名' },
|
|
46
|
+
{ key: 'email', label: '邮箱' },
|
|
47
|
+
{ key: 'status', label: '状态', width: 100 },
|
|
48
|
+
{ key: 'actions', label: '操作', width: 120 },
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
const rows = ref([
|
|
52
|
+
{ id: '1', name: '林晓', email: 'linxiao@example.com', status: 'active', role: 'member' },
|
|
53
|
+
{ id: '2', name: '周然', email: 'zhouran@example.com', status: 'inactive', role: 'admin' },
|
|
54
|
+
])
|
|
55
|
+
|
|
56
|
+
const dialogTitle = () => (editingId.value ? '编辑用户' : '新建用户')
|
|
57
|
+
|
|
58
|
+
function resetModel() {
|
|
59
|
+
model.name = ''
|
|
60
|
+
model.email = ''
|
|
61
|
+
model.role = undefined
|
|
62
|
+
editingId.value = null
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function openCreate() {
|
|
66
|
+
resetModel()
|
|
67
|
+
dialogOpen.value = true
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function openEdit(row: (typeof rows.value)[number]) {
|
|
71
|
+
editingId.value = row.id
|
|
72
|
+
model.name = row.name
|
|
73
|
+
model.email = row.email
|
|
74
|
+
model.role = row.role
|
|
75
|
+
dialogOpen.value = true
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function closeDialog() {
|
|
79
|
+
dialogOpen.value = false
|
|
80
|
+
resetModel()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function onSave() {
|
|
84
|
+
submitting.value = true
|
|
85
|
+
try {
|
|
86
|
+
// await api.save(model)
|
|
87
|
+
message.success(editingId.value ? '已保存' : '已创建')
|
|
88
|
+
closeDialog()
|
|
89
|
+
} finally {
|
|
90
|
+
submitting.value = false
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
</script>
|
|
94
|
+
|
|
95
|
+
<template>
|
|
96
|
+
<MConfigProvider :locale="zhCN">
|
|
97
|
+
<MLayout fill-viewport>
|
|
98
|
+
<MLayoutHeader padding="var(--m-space-4) var(--m-space-6)">
|
|
99
|
+
<MBreadcrumb :model="[{ label: '首页', to: '/' }, { label: '用户管理' }]" />
|
|
100
|
+
</MLayoutHeader>
|
|
101
|
+
|
|
102
|
+
<MLayoutContent>
|
|
103
|
+
<MPageContent>
|
|
104
|
+
<MPageToolbar title="用户管理">
|
|
105
|
+
<template #actions>
|
|
106
|
+
<MButton severity="primary" @click="openCreate">
|
|
107
|
+
新建用户
|
|
108
|
+
</MButton>
|
|
109
|
+
</template>
|
|
110
|
+
</MPageToolbar>
|
|
111
|
+
|
|
112
|
+
<MTable
|
|
113
|
+
:columns="columns"
|
|
114
|
+
:rows="rows"
|
|
115
|
+
row-key="id"
|
|
116
|
+
striped
|
|
117
|
+
bordered
|
|
118
|
+
aria-label="用户列表"
|
|
119
|
+
>
|
|
120
|
+
<template #cell-status="{ value }">
|
|
121
|
+
<MStatus
|
|
122
|
+
:label="value === 'active' ? '启用' : '停用'"
|
|
123
|
+
:severity="value === 'active' ? 'success' : 'secondary'"
|
|
124
|
+
/>
|
|
125
|
+
</template>
|
|
126
|
+
<template #cell-actions="{ row }">
|
|
127
|
+
<MButton severity="secondary" size="small" text @click="openEdit(row)">
|
|
128
|
+
编辑
|
|
129
|
+
</MButton>
|
|
130
|
+
</template>
|
|
131
|
+
</MTable>
|
|
132
|
+
</MPageContent>
|
|
133
|
+
</MLayoutContent>
|
|
134
|
+
</MLayout>
|
|
135
|
+
|
|
136
|
+
<MDialog
|
|
137
|
+
v-model="dialogOpen"
|
|
138
|
+
:header="dialogTitle()"
|
|
139
|
+
width="32rem"
|
|
140
|
+
@close="resetModel"
|
|
141
|
+
>
|
|
142
|
+
<MForm @submit.prevent="onSave">
|
|
143
|
+
<MFormItem label="姓名" name="name" required>
|
|
144
|
+
<MInput v-model="model.name" placeholder="请输入姓名" fluid />
|
|
145
|
+
</MFormItem>
|
|
146
|
+
<MFormItem label="邮箱" name="email" required>
|
|
147
|
+
<MInput v-model="model.email" type="email" placeholder="name@example.com" fluid />
|
|
148
|
+
</MFormItem>
|
|
149
|
+
<MFormItem label="角色" name="role" required>
|
|
150
|
+
<MSelect v-model="model.role" :options="roleOptions" placeholder="请选择角色" fluid />
|
|
151
|
+
</MFormItem>
|
|
152
|
+
</MForm>
|
|
153
|
+
|
|
154
|
+
<template #footer>
|
|
155
|
+
<MSpace style="justify-content: flex-end; width: 100%">
|
|
156
|
+
<MButton severity="secondary" text :disabled="submitting" @click="closeDialog">
|
|
157
|
+
取消
|
|
158
|
+
</MButton>
|
|
159
|
+
<MButton severity="primary" :loading="submitting" @click="onSave">
|
|
160
|
+
保存
|
|
161
|
+
</MButton>
|
|
162
|
+
</MSpace>
|
|
163
|
+
</template>
|
|
164
|
+
</MDialog>
|
|
165
|
+
</MConfigProvider>
|
|
166
|
+
</template>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
/**
|
|
3
3
|
* 黄金样例:营销落地页(Express)
|
|
4
|
-
* @see DESIGN.md
|
|
4
|
+
* @see DESIGN.md · page-layouts.md · surfaces § Express
|
|
5
5
|
* 首屏单一任务;控件用 M*;色彩只走 --m-*;避开 AI 默认脸。
|
|
6
6
|
*/
|
|
7
7
|
import { MAccordion, MButton, MConfigProvider, MTag, zhCN } from 'morya-ui'
|
|
@@ -219,6 +219,36 @@ const faqTabs = [
|
|
|
219
219
|
font-weight: 600;
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
223
|
+
.landing-hero__track span {
|
|
224
|
+
animation: landing-fade-up 480ms ease both;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.landing-hero__track span:nth-child(2) {
|
|
228
|
+
animation-delay: 60ms;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.landing-hero__track span:nth-child(3) {
|
|
232
|
+
animation-delay: 120ms;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.landing-hero__track span:nth-child(4) {
|
|
236
|
+
animation-delay: 180ms;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
@keyframes landing-fade-up {
|
|
241
|
+
from {
|
|
242
|
+
opacity: 0;
|
|
243
|
+
transform: translateY(0.4rem);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
to {
|
|
247
|
+
opacity: 1;
|
|
248
|
+
transform: translateY(0);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
222
252
|
.landing-section {
|
|
223
253
|
padding: clamp(2.5rem, 6vw, 4rem) clamp(1.25rem, 4vw, 3rem);
|
|
224
254
|
max-width: 72rem;
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
/**
|
|
3
3
|
* 黄金样例:列表页
|
|
4
|
-
* @see DESIGN.md §
|
|
4
|
+
* @see DESIGN.md · morya-ui-pages references/page-layouts.md · visual-craft § Ops polish
|
|
5
5
|
*/
|
|
6
6
|
import {
|
|
7
7
|
MBreadcrumb,
|
|
8
8
|
MButton,
|
|
9
9
|
MConfigProvider,
|
|
10
|
+
MEmpty,
|
|
10
11
|
MInput,
|
|
11
12
|
MLayout,
|
|
12
13
|
MLayoutContent,
|
|
@@ -18,8 +19,8 @@ import {
|
|
|
18
19
|
MPageToolbar,
|
|
19
20
|
MSelect,
|
|
20
21
|
MSpace,
|
|
22
|
+
MStatus,
|
|
21
23
|
MTable,
|
|
22
|
-
MTag,
|
|
23
24
|
zhCN,
|
|
24
25
|
} from 'morya-ui'
|
|
25
26
|
import { ref } from 'vue'
|
|
@@ -31,6 +32,7 @@ const siderCollapsed = ref(false)
|
|
|
31
32
|
const menuModel = [
|
|
32
33
|
{ key: 'users', label: '用户管理', icon: 'user', to: '/users' },
|
|
33
34
|
{ key: 'roles', label: '角色管理', icon: 'shield', to: '/roles' },
|
|
35
|
+
{ key: 'settings', label: '系统设置', icon: 'settings', to: '/settings' },
|
|
34
36
|
]
|
|
35
37
|
|
|
36
38
|
const statusOptions = [
|
|
@@ -41,9 +43,9 @@ const statusOptions = [
|
|
|
41
43
|
|
|
42
44
|
const columns = [
|
|
43
45
|
{ key: 'name', label: '名称' },
|
|
44
|
-
{ key: 'status', label: '状态' },
|
|
45
|
-
{ key: 'updatedAt', label: '更新时间' },
|
|
46
|
-
{ key: 'actions', label: '操作', width:
|
|
46
|
+
{ key: 'status', label: '状态', width: 120 },
|
|
47
|
+
{ key: 'updatedAt', label: '更新时间', width: 140 },
|
|
48
|
+
{ key: 'actions', label: '操作', width: 148 },
|
|
47
49
|
]
|
|
48
50
|
|
|
49
51
|
const rows = [
|
|
@@ -104,7 +106,7 @@ const rows = [
|
|
|
104
106
|
<MTable
|
|
105
107
|
:columns="columns"
|
|
106
108
|
:rows="rows"
|
|
107
|
-
:rows-per-page="
|
|
109
|
+
:rows-per-page="5"
|
|
108
110
|
paginator
|
|
109
111
|
striped
|
|
110
112
|
bordered
|
|
@@ -112,22 +114,33 @@ const rows = [
|
|
|
112
114
|
aria-label="用户列表"
|
|
113
115
|
>
|
|
114
116
|
<template #cell-status="{ value }">
|
|
115
|
-
<
|
|
117
|
+
<MStatus
|
|
118
|
+
:label="value === 'active' ? '启用' : '停用'"
|
|
119
|
+
:severity="value === 'active' ? 'success' : 'secondary'"
|
|
120
|
+
/>
|
|
116
121
|
</template>
|
|
117
122
|
<template #cell-actions>
|
|
118
123
|
<MSpace>
|
|
119
|
-
<MButton severity="secondary" size="small">
|
|
124
|
+
<MButton severity="secondary" size="small" text>
|
|
120
125
|
编辑
|
|
121
126
|
</MButton>
|
|
122
|
-
<MButton severity="danger" size="small">
|
|
127
|
+
<MButton severity="danger" size="small" text>
|
|
123
128
|
删除
|
|
124
129
|
</MButton>
|
|
125
130
|
</MSpace>
|
|
126
131
|
</template>
|
|
127
132
|
<template #empty>
|
|
128
|
-
<
|
|
129
|
-
|
|
130
|
-
|
|
133
|
+
<MEmpty
|
|
134
|
+
title="还没有用户"
|
|
135
|
+
description="创建第一个用户后,即可分配角色与权限。"
|
|
136
|
+
icon="user"
|
|
137
|
+
>
|
|
138
|
+
<template #extra>
|
|
139
|
+
<MButton severity="primary">
|
|
140
|
+
新建用户
|
|
141
|
+
</MButton>
|
|
142
|
+
</template>
|
|
143
|
+
</MEmpty>
|
|
131
144
|
</template>
|
|
132
145
|
</MTable>
|
|
133
146
|
</MPageContent>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
/**
|
|
3
3
|
* 黄金样例:登录页(Account + 轻量品牌)
|
|
4
|
-
* @see DESIGN.md §
|
|
4
|
+
* @see DESIGN.md · surfaces § Account · visual-craft § Atmosphere
|
|
5
5
|
*/
|
|
6
6
|
import {
|
|
7
7
|
MButton,
|
|
@@ -56,7 +56,6 @@ async function onSubmit() {
|
|
|
56
56
|
<p>使用工作邮箱进入后台。</p>
|
|
57
57
|
</header>
|
|
58
58
|
|
|
59
|
-
<!-- 表单级常驻错误:token 样式告警条(字段错误优先用 errorMessage) -->
|
|
60
59
|
<p v-if="formError" class="login-alert" role="alert">
|
|
61
60
|
{{ formError }}
|
|
62
61
|
</p>
|
|
@@ -82,7 +81,7 @@ async function onSubmit() {
|
|
|
82
81
|
</MFormItem>
|
|
83
82
|
|
|
84
83
|
<MSpace style="margin-top: var(--m-space-2)" alignment="center">
|
|
85
|
-
<MButton type="submit" label="登录" :loading="submitting" />
|
|
84
|
+
<MButton type="submit" label="登录" severity="primary" :loading="submitting" />
|
|
86
85
|
<MButton type="button" label="忘记密码" severity="secondary" text />
|
|
87
86
|
</MSpace>
|
|
88
87
|
</MForm>
|
|
@@ -102,21 +101,45 @@ async function onSubmit() {
|
|
|
102
101
|
}
|
|
103
102
|
|
|
104
103
|
.login-brand {
|
|
104
|
+
position: relative;
|
|
105
105
|
display: flex;
|
|
106
106
|
flex-direction: column;
|
|
107
107
|
justify-content: flex-end;
|
|
108
108
|
gap: var(--m-space-4);
|
|
109
109
|
padding: clamp(2rem, 6vw, 4.5rem);
|
|
110
|
+
overflow: hidden;
|
|
110
111
|
background:
|
|
112
|
+
radial-gradient(
|
|
113
|
+
80% 60% at 10% 20%,
|
|
114
|
+
color-mix(in srgb, var(--m-color-primary) 22%, transparent),
|
|
115
|
+
transparent 55%
|
|
116
|
+
),
|
|
111
117
|
linear-gradient(
|
|
112
118
|
165deg,
|
|
113
|
-
color-mix(in srgb, var(--m-color-primary)
|
|
119
|
+
color-mix(in srgb, var(--m-color-primary) 16%, var(--m-color-surface)) 0%,
|
|
114
120
|
var(--m-color-surface) 55%,
|
|
115
121
|
color-mix(in srgb, var(--m-color-border) 35%, var(--m-color-surface)) 100%
|
|
116
122
|
);
|
|
117
123
|
border-right: 1px solid var(--m-color-border);
|
|
118
124
|
}
|
|
119
125
|
|
|
126
|
+
.login-brand::after {
|
|
127
|
+
content: '';
|
|
128
|
+
position: absolute;
|
|
129
|
+
inset: auto -10% -20% 40%;
|
|
130
|
+
height: 55%;
|
|
131
|
+
border-radius: 50%;
|
|
132
|
+
background: color-mix(in srgb, var(--m-color-primary) 12%, transparent);
|
|
133
|
+
pointer-events: none;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.login-brand__mark,
|
|
137
|
+
.login-brand__title,
|
|
138
|
+
.login-brand__lead {
|
|
139
|
+
position: relative;
|
|
140
|
+
z-index: 1;
|
|
141
|
+
}
|
|
142
|
+
|
|
120
143
|
.login-brand__mark {
|
|
121
144
|
margin: 0;
|
|
122
145
|
font-size: 0.8125rem;
|
|
@@ -177,6 +200,23 @@ async function onSubmit() {
|
|
|
177
200
|
line-height: 1.45;
|
|
178
201
|
}
|
|
179
202
|
|
|
203
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
204
|
+
.login-panel {
|
|
205
|
+
animation: login-panel-in 420ms ease both;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
@keyframes login-panel-in {
|
|
210
|
+
from {
|
|
211
|
+
opacity: 0;
|
|
212
|
+
transform: translateY(0.5rem);
|
|
213
|
+
}
|
|
214
|
+
to {
|
|
215
|
+
opacity: 1;
|
|
216
|
+
transform: translateY(0);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
180
220
|
@media (max-width: 768px) {
|
|
181
221
|
.login-shell {
|
|
182
222
|
grid-template-columns: 1fr;
|