@morya-ui/mcp 0.2.9 → 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,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>
@@ -0,0 +1,84 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * 黄金样例:结果 / 阻断页(System)
4
+ * 默认展示 403;换成 success / 404 / 500 时只改 status + 文案 + #footer 动作。
5
+ * 无数据空态请用 empty-state / MEmpty,不要用 MResult。
6
+ * @see DESIGN.md · surfaces § System · MResult API(操作用 #footer)
7
+ */
8
+ import {
9
+ MBreadcrumb,
10
+ MButton,
11
+ MConfigProvider,
12
+ MLayout,
13
+ MLayoutContent,
14
+ MLayoutHeader,
15
+ MPageContent,
16
+ MResult,
17
+ MSpace,
18
+ zhCN,
19
+ } from 'morya-ui'
20
+ </script>
21
+
22
+ <template>
23
+ <MConfigProvider :locale="zhCN">
24
+ <MLayout fill-viewport>
25
+ <MLayoutHeader padding="var(--m-space-4) var(--m-space-6)">
26
+ <MBreadcrumb
27
+ :model="[
28
+ { label: '首页', to: '/' },
29
+ { label: '项目管理', to: '/projects' },
30
+ { label: '无权限' },
31
+ ]"
32
+ />
33
+ </MLayoutHeader>
34
+
35
+ <MLayoutContent>
36
+ <MPageContent>
37
+ <div class="result-shell">
38
+ <MResult
39
+ status="403"
40
+ title="没有权限查看该项目"
41
+ description="当前账号无权访问「青禾书房」工作区。可返回上一页,或联系管理员开通权限。"
42
+ size="large"
43
+ >
44
+ <template #footer>
45
+ <MSpace>
46
+ <MButton severity="primary">
47
+ 返回上一页
48
+ </MButton>
49
+ <MButton severity="secondary" text>
50
+ 联系管理员
51
+ </MButton>
52
+ </MSpace>
53
+ </template>
54
+ </MResult>
55
+ </div>
56
+ </MPageContent>
57
+ </MLayoutContent>
58
+ </MLayout>
59
+ </MConfigProvider>
60
+ </template>
61
+
62
+ <style scoped>
63
+ .result-shell {
64
+ display: grid;
65
+ place-items: center;
66
+ min-height: min(28rem, 70vh);
67
+ padding: var(--m-space-8) var(--m-space-4);
68
+ border-radius: var(--m-radius-lg);
69
+ background:
70
+ radial-gradient(
71
+ 70% 55% at 50% 0%,
72
+ color-mix(in srgb, var(--m-color-primary) 10%, transparent),
73
+ transparent 60%
74
+ ),
75
+ var(--m-color-surface);
76
+ border: 1px dashed color-mix(in srgb, var(--m-color-border) 80%, transparent);
77
+ }
78
+
79
+ @media (prefers-reduced-motion: reduce) {
80
+ .result-shell {
81
+ background: var(--m-color-surface);
82
+ }
83
+ }
84
+ </style>
@@ -0,0 +1,163 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * 黄金样例:设置 / 配置页
4
+ * 窄栏 + Tabs 分组 + MPageSection form;保存用 message 单行回执。
5
+ * @see DESIGN.md · page-layouts.md § Settings · visual-craft § Ops polish
6
+ */
7
+ import {
8
+ MBreadcrumb,
9
+ MButton,
10
+ MConfigProvider,
11
+ MForm,
12
+ MFormItem,
13
+ MInput,
14
+ MLayout,
15
+ MLayoutContent,
16
+ MLayoutHeader,
17
+ MPageContent,
18
+ MPageHeader,
19
+ MPageSection,
20
+ MSelect,
21
+ MSpace,
22
+ MSwitch,
23
+ MTabs,
24
+ message,
25
+ zhCN,
26
+ } from 'morya-ui'
27
+ import { reactive, ref } from 'vue'
28
+
29
+ const activeTab = ref('profile')
30
+ const submitting = ref(false)
31
+
32
+ const tabs = [
33
+ { label: '资料', value: 'profile' },
34
+ { label: '通知', value: 'notify' },
35
+ { label: '安全', value: 'security' },
36
+ ]
37
+
38
+ const profile = reactive({
39
+ displayName: '林晓',
40
+ email: 'linxiao@example.com',
41
+ language: 'zh-CN' as string | undefined,
42
+ })
43
+
44
+ const notify = reactive({
45
+ emailDigest: true,
46
+ productUpdates: false,
47
+ })
48
+
49
+ const languageOptions = [
50
+ { label: '简体中文', value: 'zh-CN' },
51
+ { label: 'English', value: 'en-US' },
52
+ ]
53
+
54
+ async function saveProfile() {
55
+ submitting.value = true
56
+ try {
57
+ // await api.saveProfile(profile)
58
+ message.success('资料已保存')
59
+ } finally {
60
+ submitting.value = false
61
+ }
62
+ }
63
+
64
+ async function saveNotify() {
65
+ submitting.value = true
66
+ try {
67
+ // await api.saveNotify(notify)
68
+ message.success('通知偏好已保存')
69
+ } finally {
70
+ submitting.value = false
71
+ }
72
+ }
73
+ </script>
74
+
75
+ <template>
76
+ <MConfigProvider :locale="zhCN">
77
+ <MLayout fill-viewport>
78
+ <MLayoutHeader padding="var(--m-space-4) var(--m-space-6)">
79
+ <MBreadcrumb :model="[{ label: '首页', to: '/' }, { label: '系统设置' }]" />
80
+ </MLayoutHeader>
81
+
82
+ <MLayoutContent>
83
+ <MPageContent width="narrow">
84
+ <MPageHeader
85
+ title="系统设置"
86
+ description="按域分组配置;切换 Tab 时保留未保存输入,保存成功用 message 一句话回执。"
87
+ />
88
+
89
+ <MTabs v-model="activeTab" :tabs="tabs">
90
+ <template #default="{ activeValue }">
91
+ <MPageSection v-if="activeValue === 'profile'" variant="form" title="个人资料">
92
+ <MForm @submit.prevent="saveProfile">
93
+ <MFormItem label="显示名称" name="displayName" required>
94
+ <MInput v-model="profile.displayName" fluid />
95
+ </MFormItem>
96
+ <MFormItem label="工作邮箱" name="email" required>
97
+ <MInput v-model="profile.email" type="email" fluid />
98
+ </MFormItem>
99
+ <MFormItem label="界面语言" name="language">
100
+ <MSelect
101
+ v-model="profile.language"
102
+ :options="languageOptions"
103
+ placeholder="选择语言"
104
+ fluid
105
+ />
106
+ </MFormItem>
107
+ <MPageSection variant="actions">
108
+ <MSpace>
109
+ <MButton native-type="submit" severity="primary" :loading="submitting">
110
+ 保存资料
111
+ </MButton>
112
+ <MButton severity="secondary" text native-type="button">
113
+ 恢复默认
114
+ </MButton>
115
+ </MSpace>
116
+ </MPageSection>
117
+ </MForm>
118
+ </MPageSection>
119
+
120
+ <MPageSection v-else-if="activeValue === 'notify'" variant="form" title="通知">
121
+ <MForm @submit.prevent="saveNotify">
122
+ <MFormItem label="邮件摘要" name="emailDigest">
123
+ <MSwitch v-model="notify.emailDigest" />
124
+ </MFormItem>
125
+ <MFormItem label="产品动态" name="productUpdates">
126
+ <MSwitch v-model="notify.productUpdates" />
127
+ </MFormItem>
128
+ <MPageSection variant="actions">
129
+ <MButton native-type="submit" severity="primary" :loading="submitting">
130
+ 保存通知
131
+ </MButton>
132
+ </MPageSection>
133
+ </MForm>
134
+ </MPageSection>
135
+
136
+ <MPageSection v-else variant="form" title="安全">
137
+ <p class="settings-hint">
138
+ 修改密码、两步验证等敏感操作放在此区;危险动作使用 severity="danger" 并配合确认。
139
+ </p>
140
+ <MSpace>
141
+ <MButton severity="secondary">
142
+ 修改密码
143
+ </MButton>
144
+ <MButton severity="danger" text>
145
+ 注销账号
146
+ </MButton>
147
+ </MSpace>
148
+ </MPageSection>
149
+ </template>
150
+ </MTabs>
151
+ </MPageContent>
152
+ </MLayoutContent>
153
+ </MLayout>
154
+ </MConfigProvider>
155
+ </template>
156
+
157
+ <style scoped>
158
+ .settings-hint {
159
+ margin: 0 0 var(--m-space-4);
160
+ color: var(--m-color-text-muted);
161
+ font-size: var(--m-font-size-sm);
162
+ }
163
+ </style>
@@ -0,0 +1,182 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * 黄金样例:分步向导(wizard-form)
4
+ * MStepper + 每步一个主任务;完成态可接 result-page。
5
+ * @see DESIGN.md · surfaces § Flow · pattern wizard-form
6
+ */
7
+ import {
8
+ MBreadcrumb,
9
+ MButton,
10
+ MConfigProvider,
11
+ MForm,
12
+ MFormItem,
13
+ MInput,
14
+ MInputTags,
15
+ MLayout,
16
+ MLayoutContent,
17
+ MLayoutHeader,
18
+ MPageContent,
19
+ MPageHeader,
20
+ MPageSection,
21
+ MSelect,
22
+ MSpace,
23
+ MStepper,
24
+ message,
25
+ zhCN,
26
+ } from 'morya-ui'
27
+ import { computed, reactive, ref } from 'vue'
28
+
29
+ const activeStep = ref(0)
30
+ const submitting = ref(false)
31
+
32
+ const steps = [
33
+ { label: '工作空间', description: '名称与地区' },
34
+ { label: '邀请成员', description: '可选邮箱' },
35
+ { label: '确认', description: '核对后创建' },
36
+ ]
37
+
38
+ const model = reactive({
39
+ name: '',
40
+ region: undefined as string | undefined,
41
+ invites: [] as string[],
42
+ })
43
+
44
+ const regionOptions = [
45
+ { label: '中国大陆', value: 'cn' },
46
+ { label: '亚太', value: 'apac' },
47
+ { label: '北美', value: 'na' },
48
+ ]
49
+
50
+ const canNext = computed(() => {
51
+ if (activeStep.value === 0) return Boolean(model.name && model.region)
52
+ return true
53
+ })
54
+
55
+ function back() {
56
+ if (activeStep.value > 0) activeStep.value -= 1
57
+ }
58
+
59
+ function next() {
60
+ if (!canNext.value) return
61
+ if (activeStep.value < steps.length - 1) activeStep.value += 1
62
+ }
63
+
64
+ async function finish() {
65
+ submitting.value = true
66
+ try {
67
+ // await api.createWorkspace(model)
68
+ message.success('工作空间已创建')
69
+ // 成功页可镜像 get_golden_page('result-page'),将 status 改为 success
70
+ } finally {
71
+ submitting.value = false
72
+ }
73
+ }
74
+ </script>
75
+
76
+ <template>
77
+ <MConfigProvider :locale="zhCN">
78
+ <MLayout fill-viewport>
79
+ <MLayoutHeader padding="var(--m-space-4) var(--m-space-6)">
80
+ <MBreadcrumb
81
+ :model="[
82
+ { label: '首页', to: '/' },
83
+ { label: '工作空间', to: '/workspaces' },
84
+ { label: '创建' },
85
+ ]"
86
+ />
87
+ </MLayoutHeader>
88
+
89
+ <MLayoutContent>
90
+ <MPageContent width="narrow">
91
+ <MPageHeader
92
+ title="创建工作空间"
93
+ description="三步完成;每步只做一件事。Stepper 使用 steps(不是 items)。"
94
+ />
95
+
96
+ <MStepper v-model="activeStep" :steps="steps" linear />
97
+
98
+ <MPageSection variant="form">
99
+ <MForm v-if="activeStep === 0" @submit.prevent="next">
100
+ <MFormItem label="名称" name="name" required>
101
+ <MInput v-model="model.name" placeholder="例如:青禾书房" fluid />
102
+ </MFormItem>
103
+ <MFormItem label="地区" name="region" required>
104
+ <MSelect
105
+ v-model="model.region"
106
+ :options="regionOptions"
107
+ placeholder="选择地区"
108
+ fluid
109
+ />
110
+ </MFormItem>
111
+ </MForm>
112
+
113
+ <MForm v-else-if="activeStep === 1" @submit.prevent="next">
114
+ <MFormItem label="邀请邮箱" name="invites">
115
+ <MInputTags
116
+ v-model="model.invites"
117
+ placeholder="输入邮箱后回车"
118
+ />
119
+ </MFormItem>
120
+ <p class="wizard-hint">
121
+ 可跳过;稍后也可在成员管理中邀请。
122
+ </p>
123
+ </MForm>
124
+
125
+ <div v-else class="wizard-summary">
126
+ <p><strong>名称</strong> {{ model.name || '—' }}</p>
127
+ <p><strong>地区</strong> {{ model.region || '—' }}</p>
128
+ <p><strong>邀请</strong> {{ model.invites.length ? model.invites.join('、') : '暂无' }}</p>
129
+ </div>
130
+
131
+ <MPageSection variant="actions">
132
+ <MSpace>
133
+ <MButton
134
+ severity="secondary"
135
+ text
136
+ :disabled="activeStep === 0 || submitting"
137
+ @click="back"
138
+ >
139
+ 上一步
140
+ </MButton>
141
+ <MButton
142
+ v-if="activeStep < steps.length - 1"
143
+ severity="primary"
144
+ :disabled="!canNext"
145
+ @click="next"
146
+ >
147
+ 下一步
148
+ </MButton>
149
+ <MButton
150
+ v-else
151
+ severity="primary"
152
+ :loading="submitting"
153
+ @click="finish"
154
+ >
155
+ 创建
156
+ </MButton>
157
+ </MSpace>
158
+ </MPageSection>
159
+ </MPageSection>
160
+ </MPageContent>
161
+ </MLayoutContent>
162
+ </MLayout>
163
+ </MConfigProvider>
164
+ </template>
165
+
166
+ <style scoped>
167
+ .wizard-hint,
168
+ .wizard-summary {
169
+ margin: 0;
170
+ color: var(--m-color-text-muted);
171
+ font-size: var(--m-font-size-sm);
172
+ }
173
+
174
+ .wizard-summary {
175
+ display: grid;
176
+ gap: var(--m-space-2);
177
+ }
178
+
179
+ .wizard-summary p {
180
+ margin: 0;
181
+ }
182
+ </style>