@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.
@@ -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 §3 · morya-ui-pages surfaces § Flow
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
  * 黄金样例:表单页
4
- * @see DESIGN.md §3
4
+ * @see DESIGN.md · morya-ui-pages references/page-layouts.md
5
5
  */
6
6
  import {
7
7
  MBreadcrumb,
@@ -1,7 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  /**
3
3
  * 黄金样例:营销落地页(Express)
4
- * @see DESIGN.md §3 · morya-ui-pages surfaces § Express
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 §3
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: 128 },
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="3"
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
- <MTag :value="value === 'active' ? '启用' : '停用'" :severity="value === 'active' ? 'success' : 'secondary'" />
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
- <p style="margin: 0; padding: var(--m-space-8); text-align: center; color: var(--m-color-text-muted)">
129
- 暂无用户数据
130
- </p>
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 §3 · morya-ui-pages surfaces § Account
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) 18%, var(--m-color-surface)) 0%,
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;