@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 +4 -2
- package/package.json +1 -1
- package/src/cli.mjs +20 -4
- package/src/styles.mjs +105 -10
- package/template/.agents/skills/morya-ui-pages/references/component-index.md +3 -2
- package/template/.agents/skills/morya-ui-pages/references/design-system.md +9 -1
- package/template/.agents/skills/morya-ui-pages/references/page-layouts.md +9 -7
- package/template/.agents/skills/morya-ui-pages/references/surfaces.md +4 -4
- package/template/.cursor/rules/page-layout.mdc +14 -8
- package/template/DESIGN.md +13 -3
- package/template/docs/components.md +2 -1
- package/template/docs/golden-pages/list-page.vue +13 -2
- package/template/src/examples/ListPageExample.vue +13 -2
- package/template/src/styles/morya-app-shell.css +6 -0
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),
|
|
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`
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
48
|
-
if (source.includes(
|
|
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,
|
|
73
|
-
}
|
|
74
|
-
|
|
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,
|
|
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 } =
|
|
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
|
|
51
|
-
| Create / edit
|
|
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
|
|
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.
|
|
20
|
-
3. `
|
|
21
|
-
4. `
|
|
22
|
-
5. `
|
|
23
|
-
6. `
|
|
24
|
-
7.
|
|
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` |
|
|
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` +
|
|
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 |
|
|
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.
|
|
23
|
-
3. `
|
|
24
|
-
4. `
|
|
25
|
-
5. `
|
|
26
|
-
6. `
|
|
27
|
-
7.
|
|
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` |
|
|
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` 叠间距 |
|
package/template/DESIGN.md
CHANGED
|
@@ -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
|
|
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`
|
|
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
|
-
|
|
|
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
|
|
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
|
|
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>
|