@morya-ui/setup 0.2.5 → 0.2.6

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 CHANGED
@@ -7,7 +7,7 @@ Docs (zh/en):
7
7
  - [AI setup](https://morya-space.github.io/morya-ui/docs/ai-setup) — one-shot CLI
8
8
  - [Agent Skill](https://morya-space.github.io/morya-ui/docs/agent-skill) — `morya-ui-pages` behavior
9
9
 
10
- Installs the UI library, copies Agent skill / Cursor rules / design docs from `ai-design-config`, merges Cursor MCP for [`@morya-ui/mcp`](https://www.npmjs.com/package/@morya-ui/mcp), and tries to inject `import 'morya-ui/styles.css'` into the app entry.
10
+ Installs the UI library, copies Agent skill / Cursor rules / design docs from `ai-design-config`, merges Cursor MCP for [`@morya-ui/mcp`](https://www.npmjs.com/package/@morya-ui/mcp), injects `import 'morya-ui/styles.css'`, and writes `src/styles/morya-app-shell.css` with its entry import.
11
11
 
12
12
  ## Usage
13
13
 
@@ -28,7 +28,7 @@ npx @morya-ui/setup
28
28
  | `--skip-install` | Do not install `morya-ui` |
29
29
  | `--skip-template` | Do not copy AI skill / rules / docs / tokens |
30
30
  | `--skip-mcp` | Do not write `.cursor/mcp.json` |
31
- | `--skip-styles` | Do not inject `styles.css` import |
31
+ | `--skip-styles` | Do not inject `styles.css` / write app-shell CSS |
32
32
  | `--skip-scripts` | Do not add `check:colors` to `package.json` |
33
33
  | `-h`, `--help` | Show help |
34
34
 
@@ -50,6 +50,7 @@ From the package `template/` (synced from repo `ai-design-config/`):
50
50
  - `design-tokens/`
51
51
  - `scripts/check-raw-colors.mjs`
52
52
  - `src/examples/`
53
+ - `src/styles/morya-app-shell.css`
53
54
 
54
55
  ### MCP (Cursor)
55
56
 
@@ -76,6 +77,7 @@ Looks for `src/main.ts` / `.js` / `.tsx` / `.jsx`, root `main.*`, `src/app.ts` /
76
77
 
77
78
  ```ts
78
79
  import 'morya-ui/styles.css'
80
+ import './styles/morya-app-shell.css'
79
81
  ```
80
82
 
81
83
  The CLI does **not** call `app.use(MoryaUI)` or edit `App.vue`.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@morya-ui/setup",
3
3
  "type": "module",
4
- "version": "0.2.5",
4
+ "version": "0.2.6",
5
5
  "description": "One-shot setup for morya-ui: install the library, Cursor MCP, Agent skills, and design config.",
6
6
  "license": "MIT",
7
7
  "homepage": "https://github.com/morya-space/morya-ui/tree/main/packages/setup#readme",
package/src/cli.mjs CHANGED
@@ -4,7 +4,7 @@ import { copyTemplate } from './copy-template.mjs'
4
4
  import { installMoryaUi } from './install.mjs'
5
5
  import { mergeMcpConfig } from './mcp.mjs'
6
6
  import { ensureCheckColorsScript } from './package-json.mjs'
7
- import { ensureStylesImport } from './styles.mjs'
7
+ import { ensureShellStyles, ensureStylesImport } from './styles.mjs'
8
8
 
9
9
  const __dirname = dirname(fileURLToPath(import.meta.url))
10
10
  const PKG_ROOT = resolve(__dirname, '..')
@@ -26,6 +26,7 @@ One-shot setup for morya-ui in a consumer Vue project:
26
26
  - copy DESIGN.md, Agent skill, Cursor rules, docs, tokens, examples
27
27
  - merge .cursor/mcp.json for @morya-ui/mcp
28
28
  - inject import 'morya-ui/styles.css' into the app entry when found
29
+ - write src/styles/morya-app-shell.css (html/body/#app height chain) and inject its import
29
30
  - add check:colors script when missing
30
31
 
31
32
  Options:
@@ -36,7 +37,7 @@ Options:
36
37
  --skip-install Skip dependency install
37
38
  --skip-template Skip copying AI template files
38
39
  --skip-mcp Skip writing .cursor/mcp.json
39
- --skip-styles Skip injecting styles.css import
40
+ --skip-styles Skip injecting styles.css / app-shell CSS
40
41
  --skip-scripts Skip adding check:colors to package.json
41
42
  -h, --help Show this help
42
43
  `)
@@ -141,6 +142,10 @@ export async function runSetup(options) {
141
142
  ? { action: 'skipped', reason: 'skip-styles' }
142
143
  : ensureStylesImport(cwd, { dryRun })
143
144
 
145
+ const shell = skipStyles
146
+ ? { fileAction: 'skipped', importAction: 'skipped', reason: 'skip-styles' }
147
+ : ensureShellStyles(cwd, { dryRun, force })
148
+
144
149
  const scripts = skipScripts
145
150
  ? { action: 'skipped-flag' }
146
151
  : ensureCheckColorsScript(cwd, { force, dryRun })
@@ -183,6 +188,17 @@ export async function runSetup(options) {
183
188
  console.log(" import 'morya-ui/styles.css'")
184
189
  }
185
190
 
191
+ if (shell.reason === 'skip-styles') {
192
+ console.log('Shell CSS: skipped (--skip-styles)')
193
+ } else {
194
+ console.log(
195
+ `Shell CSS: file ${shell.fileAction}`
196
+ + (shell.path ? ` (${rel(cwd, shell.path)})` : '')
197
+ + `, import ${shell.importAction}`
198
+ + (shell.entry ? ` (${rel(cwd, shell.entry)})` : ''),
199
+ )
200
+ }
201
+
186
202
  if (scripts.action === 'skipped-flag') {
187
203
  console.log('Scripts: skipped (--skip-scripts)')
188
204
  } else {
@@ -191,10 +207,10 @@ export async function runSetup(options) {
191
207
 
192
208
  console.log('')
193
209
  console.log('Next:')
194
- console.log(' 1. Ensure the styles import is in your app entry (if not injected).')
210
+ console.log(' 1. Ensure styles.css + morya-app-shell.css imports are in your app entry.')
195
211
  console.log(' 2. Restart Cursor (or reload MCP) so morya-ui MCP tools appear.')
196
212
  console.log(' 3. Have the agent read DESIGN.md before generating pages.')
197
213
  console.log(' 4. Optional: pnpm check:colors')
198
214
 
199
- return { install, template, mcp, styles, scripts }
215
+ return { install, template, mcp, styles, shell, scripts }
200
216
  }
package/src/styles.mjs CHANGED
@@ -1,8 +1,10 @@
1
- import { existsSync, readFileSync, writeFileSync } from 'node:fs'
2
- import { join, resolve } from 'node:path'
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
2
+ import { dirname, join, relative, resolve } from 'node:path'
3
3
 
4
4
  const STYLE_IMPORT = "import 'morya-ui/styles.css'"
5
5
  const STYLE_MARKER = 'morya-ui/styles.css'
6
+ const SHELL_MARKER = 'morya-app-shell.css'
7
+ const SHELL_REL = 'src/styles/morya-app-shell.css'
6
8
 
7
9
  const CANDIDATES = [
8
10
  'src/main.ts',
@@ -15,6 +17,14 @@ const CANDIDATES = [
15
17
  'src/app.js',
16
18
  ]
17
19
 
20
+ const SHELL_CSS = `html,
21
+ body,
22
+ #app {
23
+ height: 100%;
24
+ margin: 0;
25
+ }
26
+ `
27
+
18
28
  /**
19
29
  * @param {string} cwd
20
30
  * @returns {string | null} absolute path
@@ -41,11 +51,13 @@ export function findEntryFile(cwd) {
41
51
  }
42
52
 
43
53
  /**
44
- * Insert style import after the last leading import, or at top.
54
+ * Insert an import after the last leading import, or at top.
45
55
  * @param {string} source
56
+ * @param {string} importLine
57
+ * @param {string} marker substring that means "already present"
46
58
  */
47
- export function injectStyleImport(source) {
48
- if (source.includes(STYLE_MARKER)) {
59
+ export function injectImportLine(source, importLine, marker) {
60
+ if (source.includes(marker)) {
49
61
  return { source, changed: false, reason: 'already-present' }
50
62
  }
51
63
 
@@ -69,17 +81,35 @@ export function injectStyleImport(source) {
69
81
  }
70
82
 
71
83
  if (lastImportIndex >= 0) {
72
- lines.splice(lastImportIndex + 1, 0, STYLE_IMPORT)
73
- } else {
74
- // After shebang / 'use strict' if present
84
+ lines.splice(lastImportIndex + 1, 0, importLine)
85
+ }
86
+ else {
75
87
  let insertAt = 0
76
88
  if (lines[0]?.startsWith('#!')) insertAt = 1
77
- lines.splice(insertAt, 0, STYLE_IMPORT, '')
89
+ lines.splice(insertAt, 0, importLine, '')
78
90
  }
79
91
 
80
92
  return { source: lines.join('\n'), changed: true }
81
93
  }
82
94
 
95
+ /**
96
+ * @param {string} source
97
+ * @deprecated use injectImportLine
98
+ */
99
+ export function injectStyleImport(source) {
100
+ return injectImportLine(source, STYLE_IMPORT, STYLE_MARKER)
101
+ }
102
+
103
+ /**
104
+ * @param {string} entryFile absolute
105
+ * @param {string} shellFile absolute
106
+ */
107
+ export function shellImportLine(entryFile, shellFile) {
108
+ let rel = relative(dirname(entryFile), shellFile).replace(/\\/g, '/')
109
+ if (!rel.startsWith('.')) rel = `./${rel}`
110
+ return `import '${rel}'`
111
+ }
112
+
83
113
  /**
84
114
  * @returns {{
85
115
  * action: 'injected' | 'skipped' | 'missing-entry',
@@ -95,7 +125,7 @@ export function ensureStylesImport(cwd, { dryRun = false } = {}) {
95
125
  }
96
126
 
97
127
  const original = readFileSync(entry, 'utf8')
98
- const { source, changed, reason } = injectStyleImport(original)
128
+ const { source, changed, reason } = injectImportLine(original, STYLE_IMPORT, STYLE_MARKER)
99
129
 
100
130
  if (!changed) {
101
131
  return { action: 'skipped', path: entry, reason: reason || 'already-present' }
@@ -107,3 +137,68 @@ export function ensureStylesImport(cwd, { dryRun = false } = {}) {
107
137
 
108
138
  return { action: 'injected', path: entry, dryRun }
109
139
  }
140
+
141
+ /**
142
+ * Write morya-app-shell.css (height chain) and inject its import into the entry.
143
+ * @returns {{
144
+ * fileAction: 'written' | 'skipped' | 'dry-run',
145
+ * importAction: 'injected' | 'skipped' | 'missing-entry',
146
+ * path?: string,
147
+ * entry?: string,
148
+ * reason?: string,
149
+ * dryRun?: boolean
150
+ * }}
151
+ */
152
+ export function ensureShellStyles(cwd, { dryRun = false, force = false } = {}) {
153
+ const shellFile = join(cwd, SHELL_REL)
154
+ const entry = findEntryFile(cwd)
155
+
156
+ let fileAction = 'skipped'
157
+ if (!existsSync(shellFile) || force) {
158
+ if (dryRun) {
159
+ fileAction = 'dry-run'
160
+ }
161
+ else {
162
+ mkdirSync(dirname(shellFile), { recursive: true })
163
+ writeFileSync(shellFile, SHELL_CSS, 'utf8')
164
+ fileAction = 'written'
165
+ }
166
+ }
167
+
168
+ if (!entry) {
169
+ return {
170
+ fileAction,
171
+ importAction: 'missing-entry',
172
+ path: shellFile,
173
+ reason: 'no-entry-found',
174
+ dryRun,
175
+ }
176
+ }
177
+
178
+ const importLine = shellImportLine(entry, shellFile)
179
+ const original = readFileSync(entry, 'utf8')
180
+ const { source, changed, reason } = injectImportLine(original, importLine, SHELL_MARKER)
181
+
182
+ if (!changed) {
183
+ return {
184
+ fileAction,
185
+ importAction: 'skipped',
186
+ path: shellFile,
187
+ entry,
188
+ reason: reason || 'already-present',
189
+ dryRun,
190
+ }
191
+ }
192
+
193
+ if (!dryRun) {
194
+ writeFileSync(entry, source, 'utf8')
195
+ }
196
+
197
+ return {
198
+ fileAction,
199
+ importAction: 'injected',
200
+ path: shellFile,
201
+ entry,
202
+ dryRun,
203
+ }
204
+ }
@@ -47,8 +47,8 @@ Full API: docs site `/components` or MCP (`get_component`, `search`, `validate_u
47
47
  | Intent | Prefer |
48
48
  | --- | --- |
49
49
  | Searchable list + paging | `MPageFilters` + `MTable` (+ paginator) |
50
- | Create / edit entity page | Form golden layout + `MForm` |
51
- | Create / edit in place | `MDialog` or `MDrawer` + form |
50
+ | Create / edit from a list (few–medium fields) | **`MDialog` + `MForm`** (stay on the list) |
51
+ | Create / edit long / multi-section entity | Form golden page **or** `MDrawer` |
52
52
  | Delete | `MConfirmDialog` |
53
53
  | Lightweight inline status | `MStatus` (dot + label) |
54
54
  | Status chip / closable label | `MTag` severities |
@@ -60,6 +60,7 @@ Full API: docs site `/components` or MCP (`get_component`, `search`, `validate_u
60
60
  | Empty list / zero state | `MEmpty` (+ `empty-state` golden or `MTable` `#empty`) |
61
61
  | Submit success / HTTP error page | `MResult` |
62
62
  | Local capped scroll | Explicit `MScrollbar` |
63
+ | Admin sider nav | `MMenu` |
63
64
 
64
65
  ## Common mistakes
65
66
 
@@ -21,7 +21,14 @@ import 'morya-ui/styles.css'
21
21
  <template>
22
22
  <MConfigProvider :locale="zhCN">
23
23
  <MLayout fill-viewport has-sider>
24
- <MLayoutSider bordered>...</MLayoutSider>
24
+ <MLayoutSider bordered>
25
+ <MMenu
26
+ :model="[
27
+ { key: 'users', label: 'Users', icon: 'user', to: '/users' },
28
+ { key: 'roles', label: 'Roles', icon: 'shield', to: '/roles' },
29
+ ]"
30
+ />
31
+ </MLayoutSider>
25
32
  <MLayout>
26
33
  <MLayoutHeader>...</MLayoutHeader>
27
34
  <MLayoutContent>...</MLayoutContent>
@@ -34,6 +41,7 @@ import 'morya-ui/styles.css'
34
41
  | Role | Prefer |
35
42
  | --- | --- |
36
43
  | Admin chrome | `MLayout fillViewport` + sider / header / content |
44
+ | Sider nav | `MMenu` |
37
45
  | Page stack | `MPageContent` inside `MLayoutContent` |
38
46
  | Filters / toolbar | `MPageFilters` + `MPageToolbar` |
39
47
  | Form surfaces | `MPageHeader` + `MPageSection variant="form|actions"` |
@@ -16,12 +16,14 @@ Via MCP: `recommend_page` → `get_golden_page`; local edits: `get_page_snippet`
16
16
  ## List page — block order
17
17
 
18
18
  1. `MLayout fillViewport` + optional `MLayoutSider bordered`
19
- 2. `MLayoutHeader` → `MBreadcrumb`
20
- 3. `MLayoutContent` → `MPageContent`
21
- 4. `MPageFilters` — inner `MSpace` + Input/Select + query/reset
22
- 5. `MPageToolbar` — title + `#actions` primary action
23
- 6. `MTable` directly in content (usually **no** wrapping `MCard`)
24
- 7. Pagination via `MTable` paginator or sibling `MPagination`
19
+ 2. Sider `MMenu`
20
+ 3. `MLayoutHeader` → `MBreadcrumb`
21
+ 4. `MLayoutContent` → `MPageContent`
22
+ 5. `MPageFilters` — inner `MSpace` + Input/Select + query/reset
23
+ 6. `MPageToolbar` — title + `#actions` primary action
24
+ 7. `MTable` directly in content (usually **no** wrapping `MCard`)
25
+ 8. Pagination via `MTable` paginator or sibling `MPagination`
26
+ 9. Short create/edit → `MDialog` + `MForm` on the same page (default)
25
27
 
26
28
  ## Form page — block order
27
29
 
@@ -42,7 +44,7 @@ Via MCP: `recommend_page` → `get_golden_page`; local edits: `get_page_snippet`
42
44
 
43
45
  | Topic | Prefer | Usually avoid |
44
46
  | --- | --- | --- |
45
- | Shell | `MLayout fillViewport` + `MPageContent` | Hand `min-height: 100vh`; padding on `MLayoutContent` |
47
+ | Shell | `MLayout fillViewport` + `MPageContent` | Padding on `MLayoutContent` |
46
48
  | Sections | `MPageFilters` / `MPageToolbar` / `MPageSection` | Custom `.page-*`; extra `MCard` wrappers |
47
49
  | List table | `MTable` in `MPageContent` | Border card solely to wrap the table |
48
50
  | Spacing | `MSpace` / `MFlex` for peers; page gap from `MPageContent` | Nested padded divs stacking gaps |
@@ -6,12 +6,12 @@ Pick a lane, then a surface. Prefer project golden pages for **Ops**; use the re
6
6
 
7
7
  | Surface | Job | Compose from |
8
8
  | --- | --- | --- |
9
- | List | Scan, filter, act on many rows | Golden list + [page-layouts.md](page-layouts.md) |
10
- | Form | Create / edit one entity | Golden form |
9
+ | List | Scan, filter, act on many rows | Golden list + [page-layouts.md](page-layouts.md); **create/edit with ≤~8 fields → `MDialog` on the list** |
10
+ | Form | Create / edit one entity as a **dedicated page** | Golden form — only when multi-section, many fields, or user asked for an independent form route |
11
11
  | Dashboard | Orient + jump to work | Golden dashboard |
12
- | Detail | Read-heavy record + secondary actions | `MPageHeader` + `MDescriptions`-style sections via `MCard`/`MPageSection`, actions in toolbar; edit via route or `MDrawer`/`MDialog` |
12
+ | Detail | Read-heavy record + secondary actions | `MPageHeader` + sections via `MCard`/`MPageSection`; edit via `MDialog` / `MDrawer` (prefer over a second form route) |
13
13
  | Settings | Grouped preferences | Narrow `MPageContent` + stacked `MPageSection` / `MTabs` + `MForm` |
14
- | Hybrid list | List + drawer/dialog | List golden + overlay recipe |
14
+ | Hybrid list | List + drawer/dialog | **Default Ops CRUD pattern**: list golden + `MDialog`/`MDrawer` form |
15
15
 
16
16
  ## Account
17
17
 
@@ -10,21 +10,27 @@ alwaysApply: false
10
10
  | 类型 | 模板 |
11
11
  | --- | --- |
12
12
  | 列表页 | `docs/golden-pages/list-page.vue` |
13
- | 表单页 | `docs/golden-pages/form-page.vue` |
13
+ | 表单页 | `docs/golden-pages/form-page.vue`(仅长表单 / 独立配置页) |
14
14
  | 仪表盘 | `docs/golden-pages/dashboard-page.vue` |
15
15
  | 登录页 | `docs/golden-pages/login-page.vue` |
16
16
  | 营销落地 | `docs/golden-pages/landing-page.vue` |
17
17
  | 空状态 | `docs/golden-pages/empty-state.vue` |
18
18
 
19
+ ## 列表内新建 / 编辑(默认)
20
+
21
+ 后台 CRUD **优先**:列表页 + `MDialog`(或 `MDrawer`)内 `MForm`,不要把「新建用户 / 编辑角色」做成单独路由,除非字段很多或多步骤。
22
+
19
23
  ## 列表页区块顺序
20
24
 
21
25
  1. `MLayout fillViewport` + 可选 `MLayoutSider bordered`
22
- 2. `MLayoutHeader` → `MBreadcrumb`
23
- 3. `MLayoutContent` → `MPageContent`
24
- 4. `MPageFilters`(内 `MSpace` + Input/Select + 查询/重置)
25
- 5. `MPageToolbar`(标题 + `#actions` 主操作)
26
- 6. `MTable`(直接放置,通常不必包 `MCard`)
27
- 7. 分页由 `MTable paginator` 或同级 `MPagination`
26
+ 2. 侧栏 `MMenu`
27
+ 3. `MLayoutHeader` → `MBreadcrumb`
28
+ 4. `MLayoutContent` → `MPageContent`
29
+ 5. `MPageFilters`(内 `MSpace` + Input/Select + 查询/重置)
30
+ 6. `MPageToolbar`(标题 + `#actions` 主操作)
31
+ 7. `MTable`(直接放置,通常不必包 `MCard`)
32
+ 8. 分页由 `MTable paginator` 或同级 `MPagination`
33
+ 9. 短新建/编辑:同页 `MDialog` + `MForm`
28
34
 
29
35
  ## 表单页区块顺序
30
36
 
@@ -47,7 +53,7 @@ alwaysApply: false
47
53
 
48
54
  | 主题 | 推荐 | 通常不必 |
49
55
  | --- | --- | --- |
50
- | 骨架 | `MLayout fillViewport` + `MPageContent` | 手写 `min-height:100vh`;`MLayoutContent` 再写 padding |
56
+ | 骨架 | `MLayout fillViewport` + `MPageContent` | `MLayoutContent` 再写 padding |
51
57
  | 区块 | `MPageFilters` / `MPageToolbar` / `MPageSection` | 手写 `.page-*`;`MCard` 再包一层 |
52
58
  | 列表表格 | `MTable` 直接放在 `MPageContent` | 用 `MCard` 再包 bordered 表格 |
53
59
  | 控件间距 | 同行用 `MSpace` / `MFlex`;区块靠 `MPageContent` gap | 外包 padded `div` 叠间距 |
@@ -20,8 +20,15 @@ import 'morya-ui/styles.css'
20
20
 
21
21
  <template>
22
22
  <MConfigProvider :locale="zhCN">
23
- <MLayout has-sider>
24
- <MLayoutSider>...</MLayoutSider>
23
+ <MLayout has-sider fill-viewport>
24
+ <MLayoutSider bordered>
25
+ <MMenu
26
+ :model="[
27
+ { key: 'users', label: '用户管理', icon: 'user', to: '/users' },
28
+ { key: 'roles', label: '角色管理', icon: 'shield', to: '/roles' },
29
+ ]"
30
+ />
31
+ </MLayoutSider>
25
32
  <MLayout>
26
33
  <MLayoutHeader>...</MLayoutHeader>
27
34
  <MLayoutContent>...</MLayoutContent>
@@ -32,6 +39,7 @@ import 'morya-ui/styles.css'
32
39
  ```
33
40
 
34
41
  - 管理后台:`MLayout fillViewport` + `MLayoutSider` + `MLayoutHeader` + `MLayoutContent`
42
+ - 侧栏导航:`MMenu`
35
43
  - 页面内容堆叠:`MPageContent`(内置于 `MLayoutContent`)
36
44
  - 列表筛选 / 工具栏:`MPageFilters` + `MPageToolbar`
37
45
  - 表单引导 / 表面:`MPageHeader` + `MPageSection variant="form|actions"`
@@ -56,9 +64,11 @@ import 'morya-ui/styles.css'
56
64
  ## 4. 表单约定
57
65
 
58
66
  - 使用 `MForm` + `MFormItem`,`name` 与校验规则对应。
67
+ - **从列表新建/编辑(字段约 ≤8、单段表单)默认用 `MDialog` + `MForm`,不要为每个实体再开独立路由表单页。**
68
+ - 多分组、长流程、向导式录入再用独立表单页(`docs/golden-pages/form-page.vue`)或 `MDrawer`。
59
69
  - 字段组件自带 `label` / `invalid` / `helpText` 时优先用组件 prop(如 `MInput`、`MSelect`),复杂表单再用 `MFormItem` 包一层。
60
70
  - 尺寸:默认 medium;密集后台可 `size="small"` 或 ConfigProvider `globalDensity`。
61
- - 宽度:筛选项 `fluid` 慎用;表单页主栏 `max-width: 40rem` 左右。
71
+ - 宽度:筛选项 `fluid` 慎用;独立表单页主栏 `max-width: 40rem` 左右。
62
72
 
63
73
  ## 5. 数据展示
64
74
 
@@ -114,7 +114,8 @@
114
114
  | 我要做… | 首选组件 |
115
115
  | --- | --- |
116
116
  | 用户列表 + 搜索 + 分页 | `MTable` + `MInput` + `MPagination` |
117
- | 新建/编辑实体 | `MForm` + 字段组件 + `MDialog` 或独立路由页 |
117
+ | 新建/编辑实体(短表单,列表内) | **`MDialog` + `MForm` + 字段组件**(首选) |
118
+ | 新建/编辑实体(长表单 / 多分组) | `MForm` + 独立表单页或 `MDrawer` |
118
119
  | 删除确认 | `MConfirmDialog` |
119
120
  | 筛选侧栏 | `MDrawer` + 表单控件 |
120
121
  | 状态指示(行内) | `MStatus`(圆点 + 文案) |
@@ -26,6 +26,12 @@ import { ref } from 'vue'
26
26
 
27
27
  const keyword = ref('')
28
28
  const status = ref<string | undefined>()
29
+ const siderCollapsed = ref(false)
30
+
31
+ const menuModel = [
32
+ { key: 'users', label: '用户管理', icon: 'user', to: '/users' },
33
+ { key: 'roles', label: '角色管理', icon: 'shield', to: '/roles' },
34
+ ]
29
35
 
30
36
  const statusOptions = [
31
37
  { label: '全部', value: '' },
@@ -52,8 +58,13 @@ const rows = [
52
58
  <template>
53
59
  <MConfigProvider :locale="zhCN">
54
60
  <MLayout has-sider fill-viewport>
55
- <MLayoutSider bordered>
56
- <MMenu :model="[{ label: '用户管理', key: 'users' }, { label: '角色管理', key: 'roles' }]" />
61
+ <MLayoutSider v-model:collapsed="siderCollapsed" bordered :collapsed-width="72">
62
+ <MMenu
63
+ :model="menuModel"
64
+ :collapsed="siderCollapsed"
65
+ :collapsed-width="72"
66
+ selected-key="users"
67
+ />
57
68
  </MLayoutSider>
58
69
 
59
70
  <MLayout>
@@ -26,6 +26,12 @@ import { ref } from 'vue'
26
26
 
27
27
  const keyword = ref('')
28
28
  const status = ref<string | undefined>()
29
+ const siderCollapsed = ref(false)
30
+
31
+ const menuModel = [
32
+ { key: 'users', label: '用户管理', icon: 'user', to: '/users' },
33
+ { key: 'roles', label: '角色管理', icon: 'shield', to: '/roles' },
34
+ ]
29
35
 
30
36
  const statusOptions = [
31
37
  { label: '全部', value: '' },
@@ -52,8 +58,13 @@ const rows = [
52
58
  <template>
53
59
  <MConfigProvider :locale="zhCN">
54
60
  <MLayout has-sider fill-viewport>
55
- <MLayoutSider bordered>
56
- <MMenu :model="[{ label: '用户管理', key: 'users' }, { label: '角色管理', key: 'roles' }]" />
61
+ <MLayoutSider v-model:collapsed="siderCollapsed" bordered :collapsed-width="72">
62
+ <MMenu
63
+ :model="menuModel"
64
+ :collapsed="siderCollapsed"
65
+ :collapsed-width="72"
66
+ selected-key="users"
67
+ />
57
68
  </MLayoutSider>
58
69
 
59
70
  <MLayout>
@@ -0,0 +1,6 @@
1
+ html,
2
+ body,
3
+ #app {
4
+ height: 100%;
5
+ margin: 0;
6
+ }