@morya-ui/setup 0.2.5 → 0.2.7
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 +26 -5
- package/package.json +1 -1
- package/src/cli.mjs +108 -15
- package/src/copy-template.mjs +18 -2
- package/src/fs-utils.mjs +2 -1
- 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 +15 -5
- package/template/design-tokens/tokens.json +1 -1
- package/template/docs/components.md +2 -1
- package/template/docs/feedback-message-vs-toast.md +5 -5
- 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
|
@@ -4,10 +4,12 @@ One-shot setup for consumer Vue projects using [`morya-ui`](https://www.npmjs.co
|
|
|
4
4
|
|
|
5
5
|
Docs (zh/en):
|
|
6
6
|
|
|
7
|
-
- [
|
|
7
|
+
- [One-shot setup](https://morya-space.github.io/morya-ui/docs/setup) — CLI commands and options
|
|
8
|
+
- [AI setup](https://morya-space.github.io/morya-ui/docs/ai-setup) — Agent workflow
|
|
8
9
|
- [Agent Skill](https://morya-space.github.io/morya-ui/docs/agent-skill) — `morya-ui-pages` behavior
|
|
10
|
+
- [Quick start](https://morya-space.github.io/morya-ui/docs/quick-start) — install and first component
|
|
9
11
|
|
|
10
|
-
Installs the UI library, copies Agent skill / Cursor rules / design docs from `
|
|
12
|
+
Installs the UI library, copies Agent skill / Cursor rules / design docs from `design-kit`, 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
13
|
|
|
12
14
|
## Usage
|
|
13
15
|
|
|
@@ -17,6 +19,16 @@ In your app project root:
|
|
|
17
19
|
npx @morya-ui/setup
|
|
18
20
|
```
|
|
19
21
|
|
|
22
|
+
Other common commands:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# AI config + MCP only (library already installed)
|
|
26
|
+
npx @morya-ui/setup ai
|
|
27
|
+
|
|
28
|
+
# Library + styles only
|
|
29
|
+
npx @morya-ui/setup app
|
|
30
|
+
```
|
|
31
|
+
|
|
20
32
|
### Options
|
|
21
33
|
|
|
22
34
|
| Flag | Meaning |
|
|
@@ -28,10 +40,16 @@ npx @morya-ui/setup
|
|
|
28
40
|
| `--skip-install` | Do not install `morya-ui` |
|
|
29
41
|
| `--skip-template` | Do not copy AI skill / rules / docs / tokens |
|
|
30
42
|
| `--skip-mcp` | Do not write `.cursor/mcp.json` |
|
|
31
|
-
| `--skip-styles` | Do not inject `styles.css`
|
|
43
|
+
| `--skip-styles` | Do not inject `styles.css` / write app-shell CSS |
|
|
32
44
|
| `--skip-scripts` | Do not add `check:colors` to `package.json` |
|
|
33
45
|
| `-h`, `--help` | Show help |
|
|
34
46
|
|
|
47
|
+
Example: MCP only:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npx @morya-ui/setup ai --skip-template --skip-scripts
|
|
51
|
+
```
|
|
52
|
+
|
|
35
53
|
### Conflict policy
|
|
36
54
|
|
|
37
55
|
- Template files and `.cursor/rules/*`: **skip** if the destination exists (unless `--force`).
|
|
@@ -41,7 +59,7 @@ npx @morya-ui/setup
|
|
|
41
59
|
|
|
42
60
|
### What gets copied
|
|
43
61
|
|
|
44
|
-
From the package `template/` (synced from repo `
|
|
62
|
+
From the package `template/` (synced from repo `design-kit/`):
|
|
45
63
|
|
|
46
64
|
- `DESIGN.md`
|
|
47
65
|
- `.agents/skills/morya-ui-pages/`
|
|
@@ -51,6 +69,8 @@ From the package `template/` (synced from repo `ai-design-config/`):
|
|
|
51
69
|
- `scripts/check-raw-colors.mjs`
|
|
52
70
|
- `src/examples/`
|
|
53
71
|
|
|
72
|
+
`morya-app-shell.css` is written by the CLI (not copied from the template tree).
|
|
73
|
+
|
|
54
74
|
### MCP (Cursor)
|
|
55
75
|
|
|
56
76
|
Writes / merges:
|
|
@@ -76,6 +96,7 @@ Looks for `src/main.ts` / `.js` / `.tsx` / `.jsx`, root `main.*`, `src/app.ts` /
|
|
|
76
96
|
|
|
77
97
|
```ts
|
|
78
98
|
import 'morya-ui/styles.css'
|
|
99
|
+
import './styles/morya-app-shell.css'
|
|
79
100
|
```
|
|
80
101
|
|
|
81
102
|
The CLI does **not** call `app.use(MoryaUI)` or edit `App.vue`.
|
|
@@ -83,7 +104,7 @@ The CLI does **not** call `app.use(MoryaUI)` or edit `App.vue`.
|
|
|
83
104
|
## Local development (this monorepo)
|
|
84
105
|
|
|
85
106
|
```bash
|
|
86
|
-
pnpm setup:sync-template # refresh packages/setup/template from
|
|
107
|
+
pnpm setup:sync-template # refresh packages/setup/template from design-kit
|
|
87
108
|
pnpm setup:build
|
|
88
109
|
node packages/setup/bin/morya-ui-setup.js --cwd /path/to/app --dry-run
|
|
89
110
|
```
|
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.7",
|
|
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
|
@@ -1,15 +1,37 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
1
2
|
import { dirname, join, resolve } from 'node:path'
|
|
2
3
|
import { fileURLToPath } from 'node:url'
|
|
3
4
|
import { copyTemplate } from './copy-template.mjs'
|
|
5
|
+
import { readJson } from './fs-utils.mjs'
|
|
4
6
|
import { installMoryaUi } from './install.mjs'
|
|
5
7
|
import { mergeMcpConfig } from './mcp.mjs'
|
|
6
8
|
import { ensureCheckColorsScript } from './package-json.mjs'
|
|
7
|
-
import { ensureStylesImport } from './styles.mjs'
|
|
9
|
+
import { ensureShellStyles, ensureStylesImport } from './styles.mjs'
|
|
8
10
|
|
|
9
11
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
10
12
|
const PKG_ROOT = resolve(__dirname, '..')
|
|
11
13
|
const TEMPLATE_ROOT = join(PKG_ROOT, 'template')
|
|
12
14
|
|
|
15
|
+
const MODES = new Set(['app', 'ai', 'full'])
|
|
16
|
+
|
|
17
|
+
/** AI pack paths under template/ (excludes runtime src/styles — owned by app mode). */
|
|
18
|
+
const AI_TEMPLATE_INCLUDE = [
|
|
19
|
+
'DESIGN.md',
|
|
20
|
+
'.agents/skills/morya-ui-pages',
|
|
21
|
+
'.cursor/rules',
|
|
22
|
+
'docs',
|
|
23
|
+
'design-tokens',
|
|
24
|
+
'scripts/check-raw-colors.mjs',
|
|
25
|
+
'src/examples',
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
/** Mode → default skips (user --skip-* can only add more skips). */
|
|
29
|
+
const MODE_DEFAULTS = {
|
|
30
|
+
full: {},
|
|
31
|
+
app: { skipTemplate: true, skipMcp: true, skipScripts: true },
|
|
32
|
+
ai: { skipInstall: true, skipStyles: true },
|
|
33
|
+
}
|
|
34
|
+
|
|
13
35
|
const SKIP_FLAGS = {
|
|
14
36
|
'--skip-install': 'skipInstall',
|
|
15
37
|
'--skip-template': 'skipTemplate',
|
|
@@ -19,13 +41,20 @@ const SKIP_FLAGS = {
|
|
|
19
41
|
}
|
|
20
42
|
|
|
21
43
|
export function printHelp() {
|
|
22
|
-
console.log(`Usage: morya-ui-setup [options]
|
|
44
|
+
console.log(`Usage: morya-ui-setup [command] [options]
|
|
23
45
|
|
|
24
|
-
|
|
46
|
+
Commands:
|
|
47
|
+
(default) / full Install morya-ui, AI template, MCP, styles, check:colors
|
|
48
|
+
app Install morya-ui and inject styles / app-shell CSS
|
|
49
|
+
ai Copy Agent skill / rules / DESIGN / docs / tokens / examples,
|
|
50
|
+
merge Cursor MCP, add check:colors
|
|
51
|
+
|
|
52
|
+
Default command:
|
|
25
53
|
- install morya-ui
|
|
26
54
|
- copy DESIGN.md, Agent skill, Cursor rules, docs, tokens, examples
|
|
27
55
|
- merge .cursor/mcp.json for @morya-ui/mcp
|
|
28
56
|
- inject import 'morya-ui/styles.css' into the app entry when found
|
|
57
|
+
- write src/styles/morya-app-shell.css and inject its import
|
|
29
58
|
- add check:colors script when missing
|
|
30
59
|
|
|
31
60
|
Options:
|
|
@@ -36,7 +65,7 @@ Options:
|
|
|
36
65
|
--skip-install Skip dependency install
|
|
37
66
|
--skip-template Skip copying AI template files
|
|
38
67
|
--skip-mcp Skip writing .cursor/mcp.json
|
|
39
|
-
--skip-styles Skip injecting styles.css
|
|
68
|
+
--skip-styles Skip injecting styles.css / app-shell CSS
|
|
40
69
|
--skip-scripts Skip adding check:colors to package.json
|
|
41
70
|
-h, --help Show this help
|
|
42
71
|
`)
|
|
@@ -47,6 +76,7 @@ Options:
|
|
|
47
76
|
*/
|
|
48
77
|
export function parseArgs(argv) {
|
|
49
78
|
const options = {
|
|
79
|
+
mode: 'full',
|
|
50
80
|
cwd: process.cwd(),
|
|
51
81
|
pm: undefined,
|
|
52
82
|
force: false,
|
|
@@ -65,6 +95,10 @@ export function parseArgs(argv) {
|
|
|
65
95
|
options.help = true
|
|
66
96
|
continue
|
|
67
97
|
}
|
|
98
|
+
if (MODES.has(arg)) {
|
|
99
|
+
options.mode = arg
|
|
100
|
+
continue
|
|
101
|
+
}
|
|
68
102
|
if (arg === '--force') {
|
|
69
103
|
options.force = true
|
|
70
104
|
continue
|
|
@@ -100,6 +134,11 @@ export function parseArgs(argv) {
|
|
|
100
134
|
throw new Error(`Unknown argument: ${arg}`)
|
|
101
135
|
}
|
|
102
136
|
|
|
137
|
+
const defaults = MODE_DEFAULTS[options.mode] || {}
|
|
138
|
+
for (const [key, value] of Object.entries(defaults)) {
|
|
139
|
+
if (value) options[key] = true
|
|
140
|
+
}
|
|
141
|
+
|
|
103
142
|
return options
|
|
104
143
|
}
|
|
105
144
|
|
|
@@ -108,11 +147,30 @@ function rel(cwd, path) {
|
|
|
108
147
|
return path.startsWith(cwd) ? path.slice(cwd.length).replace(/^[\\/]/, '') || path : path
|
|
109
148
|
}
|
|
110
149
|
|
|
150
|
+
/**
|
|
151
|
+
* @param {string} cwd
|
|
152
|
+
*/
|
|
153
|
+
function hasMoryaUiDependency(cwd) {
|
|
154
|
+
const path = join(cwd, 'package.json')
|
|
155
|
+
if (!existsSync(path)) return false
|
|
156
|
+
try {
|
|
157
|
+
const pkg = readJson(path)
|
|
158
|
+
return Boolean(
|
|
159
|
+
pkg.dependencies?.['morya-ui']
|
|
160
|
+
|| pkg.devDependencies?.['morya-ui']
|
|
161
|
+
|| pkg.peerDependencies?.['morya-ui'],
|
|
162
|
+
)
|
|
163
|
+
} catch {
|
|
164
|
+
return false
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
111
168
|
/**
|
|
112
169
|
* @param {ReturnType<typeof parseArgs>} options
|
|
113
170
|
*/
|
|
114
171
|
export async function runSetup(options) {
|
|
115
172
|
const {
|
|
173
|
+
mode,
|
|
116
174
|
cwd,
|
|
117
175
|
force,
|
|
118
176
|
dryRun,
|
|
@@ -124,14 +182,25 @@ export async function runSetup(options) {
|
|
|
124
182
|
pm,
|
|
125
183
|
} = options
|
|
126
184
|
|
|
127
|
-
console.log(`@morya-ui/setup → ${cwd}${dryRun ? ' (dry-run)' : ''}`)
|
|
185
|
+
console.log(`@morya-ui/setup [${mode}] → ${cwd}${dryRun ? ' (dry-run)' : ''}`)
|
|
128
186
|
console.log('')
|
|
129
187
|
|
|
188
|
+
if (mode === 'ai' && !hasMoryaUiDependency(cwd)) {
|
|
189
|
+
console.log(
|
|
190
|
+
'Warning: morya-ui is not listed in package.json. Run `npx @morya-ui/setup app` first (or pnpm add morya-ui).',
|
|
191
|
+
)
|
|
192
|
+
console.log('')
|
|
193
|
+
}
|
|
194
|
+
|
|
130
195
|
const install = installMoryaUi(cwd, { pm, dryRun, skipInstall })
|
|
131
196
|
|
|
132
197
|
const template = skipTemplate
|
|
133
198
|
? { copied: [], skipped: [], forced: [], skippedStep: true }
|
|
134
|
-
: copyTemplate(TEMPLATE_ROOT, cwd, {
|
|
199
|
+
: copyTemplate(TEMPLATE_ROOT, cwd, {
|
|
200
|
+
force,
|
|
201
|
+
dryRun,
|
|
202
|
+
include: mode === 'ai' || mode === 'full' ? AI_TEMPLATE_INCLUDE : undefined,
|
|
203
|
+
})
|
|
135
204
|
|
|
136
205
|
const mcp = skipMcp
|
|
137
206
|
? { path: join(cwd, '.cursor', 'mcp.json'), action: 'skipped-flag' }
|
|
@@ -141,6 +210,10 @@ export async function runSetup(options) {
|
|
|
141
210
|
? { action: 'skipped', reason: 'skip-styles' }
|
|
142
211
|
: ensureStylesImport(cwd, { dryRun })
|
|
143
212
|
|
|
213
|
+
const shell = skipStyles
|
|
214
|
+
? { fileAction: 'skipped', importAction: 'skipped', reason: 'skip-styles' }
|
|
215
|
+
: ensureShellStyles(cwd, { dryRun, force })
|
|
216
|
+
|
|
144
217
|
const scripts = skipScripts
|
|
145
218
|
? { action: 'skipped-flag' }
|
|
146
219
|
: ensureCheckColorsScript(cwd, { force, dryRun })
|
|
@@ -153,7 +226,7 @@ export async function runSetup(options) {
|
|
|
153
226
|
}
|
|
154
227
|
|
|
155
228
|
if (template.skippedStep) {
|
|
156
|
-
console.log('Template: skipped (--skip-template)')
|
|
229
|
+
console.log('Template: skipped (--skip-template or app mode)')
|
|
157
230
|
} else {
|
|
158
231
|
console.log(
|
|
159
232
|
`Template: ${template.copied.length} copied, ${template.forced.length} overwritten, ${template.skipped.length} skipped`,
|
|
@@ -167,13 +240,13 @@ export async function runSetup(options) {
|
|
|
167
240
|
}
|
|
168
241
|
|
|
169
242
|
if (mcp.action === 'skipped-flag') {
|
|
170
|
-
console.log('MCP: skipped (--skip-mcp)')
|
|
243
|
+
console.log('MCP: skipped (--skip-mcp or app mode)')
|
|
171
244
|
} else {
|
|
172
245
|
console.log(`MCP: ${mcp.action} (${rel(cwd, mcp.path) || '.cursor/mcp.json'})`)
|
|
173
246
|
}
|
|
174
247
|
|
|
175
248
|
if (styles.reason === 'skip-styles') {
|
|
176
|
-
console.log('Styles: skipped (--skip-styles)')
|
|
249
|
+
console.log('Styles: skipped (--skip-styles or ai mode)')
|
|
177
250
|
} else if (styles.action === 'injected') {
|
|
178
251
|
console.log(`Styles: injected into ${rel(cwd, styles.path)}`)
|
|
179
252
|
} else if (styles.action === 'skipped') {
|
|
@@ -183,18 +256,38 @@ export async function runSetup(options) {
|
|
|
183
256
|
console.log(" import 'morya-ui/styles.css'")
|
|
184
257
|
}
|
|
185
258
|
|
|
259
|
+
if (shell.reason === 'skip-styles') {
|
|
260
|
+
console.log('Shell CSS: skipped (--skip-styles or ai mode)')
|
|
261
|
+
} else {
|
|
262
|
+
console.log(
|
|
263
|
+
`Shell CSS: file ${shell.fileAction}`
|
|
264
|
+
+ (shell.path ? ` (${rel(cwd, shell.path)})` : '')
|
|
265
|
+
+ `, import ${shell.importAction}`
|
|
266
|
+
+ (shell.entry ? ` (${rel(cwd, shell.entry)})` : ''),
|
|
267
|
+
)
|
|
268
|
+
}
|
|
269
|
+
|
|
186
270
|
if (scripts.action === 'skipped-flag') {
|
|
187
|
-
console.log('Scripts: skipped (--skip-scripts)')
|
|
271
|
+
console.log('Scripts: skipped (--skip-scripts or app mode)')
|
|
188
272
|
} else {
|
|
189
273
|
console.log(`Scripts: check:colors ${scripts.action}`)
|
|
190
274
|
}
|
|
191
275
|
|
|
192
276
|
console.log('')
|
|
193
277
|
console.log('Next:')
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
278
|
+
if (mode === 'app') {
|
|
279
|
+
console.log(' 1. Ensure styles.css + morya-app-shell.css imports are in your app entry.')
|
|
280
|
+
console.log(' 2. Optional AI pack: npx @morya-ui/setup ai')
|
|
281
|
+
} else if (mode === 'ai') {
|
|
282
|
+
console.log(' 1. Restart Cursor (or reload MCP) so morya-ui MCP tools appear.')
|
|
283
|
+
console.log(' 2. Have the agent read DESIGN.md before generating pages.')
|
|
284
|
+
console.log(' 3. Optional: pnpm check:colors')
|
|
285
|
+
} else {
|
|
286
|
+
console.log(' 1. Ensure styles.css + morya-app-shell.css imports are in your app entry.')
|
|
287
|
+
console.log(' 2. Restart Cursor (or reload MCP) so morya-ui MCP tools appear.')
|
|
288
|
+
console.log(' 3. Have the agent read DESIGN.md before generating pages.')
|
|
289
|
+
console.log(' 4. Optional: pnpm check:colors')
|
|
290
|
+
}
|
|
198
291
|
|
|
199
|
-
return { install, template, mcp, styles, scripts }
|
|
292
|
+
return { mode, install, template, mcp, styles, shell, scripts }
|
|
200
293
|
}
|
package/src/copy-template.mjs
CHANGED
|
@@ -20,11 +20,27 @@ function listFiles(dir) {
|
|
|
20
20
|
return out
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* @param {string} rel
|
|
25
|
+
* @param {string[] | undefined} include
|
|
26
|
+
*/
|
|
27
|
+
function matchesInclude(rel, include) {
|
|
28
|
+
if (!include || include.length === 0) return true
|
|
29
|
+
const norm = rel.replace(/\\/g, '/')
|
|
30
|
+
return include.some((prefix) => {
|
|
31
|
+
const p = prefix.replace(/\\/g, '/')
|
|
32
|
+
return norm === p || norm.startsWith(`${p}/`)
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
|
|
23
36
|
/**
|
|
24
37
|
* Copy template tree into target cwd.
|
|
38
|
+
* @param {string} templateRoot
|
|
39
|
+
* @param {string} cwd
|
|
40
|
+
* @param {{ force?: boolean, dryRun?: boolean, include?: string[] }} [options]
|
|
25
41
|
* @returns {{ copied: string[], skipped: string[], forced: string[] }}
|
|
26
42
|
*/
|
|
27
|
-
export function copyTemplate(templateRoot, cwd, { force = false, dryRun = false } = {}) {
|
|
43
|
+
export function copyTemplate(templateRoot, cwd, { force = false, dryRun = false, include } = {}) {
|
|
28
44
|
const copied = []
|
|
29
45
|
const skipped = []
|
|
30
46
|
const forced = []
|
|
@@ -35,7 +51,7 @@ export function copyTemplate(templateRoot, cwd, { force = false, dryRun = false
|
|
|
35
51
|
)
|
|
36
52
|
}
|
|
37
53
|
|
|
38
|
-
const files = listFiles(templateRoot)
|
|
54
|
+
const files = listFiles(templateRoot).filter((rel) => matchesInclude(rel, include))
|
|
39
55
|
for (const rel of files) {
|
|
40
56
|
const from = join(templateRoot, rel)
|
|
41
57
|
const to = join(cwd, rel)
|
package/src/fs-utils.mjs
CHANGED
|
@@ -2,7 +2,8 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
|
2
2
|
import { dirname } from 'node:path'
|
|
3
3
|
|
|
4
4
|
export function readJson(path) {
|
|
5
|
-
|
|
5
|
+
const text = readFileSync(path, 'utf8').replace(/^\uFEFF/, '')
|
|
6
|
+
return JSON.parse(text)
|
|
6
7
|
}
|
|
7
8
|
|
|
8
9
|
export function writeJson(path, value, { dryRun = false } = {}) {
|
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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Morya UI 设计系统(AI 第一信源)
|
|
2
2
|
|
|
3
|
-
> 基于 `morya-ui` v0.
|
|
3
|
+
> 基于 `morya-ui` v0.2.6。生成或审查业务页面时,**必须先遵守本文**,再查阅 `docs/components.md` 与 `docs/golden-pages/`。
|
|
4
4
|
|
|
5
5
|
## 1. 设计原则
|
|
6
6
|
|
|
@@ -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
|
|
|
@@ -98,7 +108,7 @@ import 'morya-ui/styles.css'
|
|
|
98
108
|
|
|
99
109
|
## 8. 禁止项
|
|
100
110
|
|
|
101
|
-
- 禁止引入第二套 UI
|
|
111
|
+
- 禁止引入第二套 UI 库混用同一页面。
|
|
102
112
|
- 禁止在业务 CSS 中写死主题色;暗色模式必须能随 `[data-theme="dark"]` 生效。
|
|
103
113
|
- 禁止用 `<div onclick>` 代替 `<button>` / `MButton`。
|
|
104
114
|
- 禁止 Select 与 Dropdown 混用:选项选择用 `MSelect` / `MTreeSelect`;动作菜单用 `MDropdown`。
|
|
@@ -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`(圆点 + 文案) |
|
|
@@ -88,13 +88,13 @@ message.error('操作失败')
|
|
|
88
88
|
| 删除确认用 Message/Toast | `MConfirmDialog` |
|
|
89
89
|
| 所有反馈都用 Toast | 默认改 Message,仅 detail 场景保留 Toast |
|
|
90
90
|
|
|
91
|
-
##
|
|
91
|
+
## 概念对照
|
|
92
92
|
|
|
93
|
-
|
|
|
93
|
+
| API | 用途 |
|
|
94
94
|
| --- | --- |
|
|
95
|
-
| `message` |
|
|
96
|
-
| `toast` |
|
|
97
|
-
| `<MMessage>` |
|
|
95
|
+
| `message` | 轻量、短暂的操作反馈(默认首选) |
|
|
96
|
+
| `toast` | 带摘要 + 详情、或需停留更久的通知 |
|
|
97
|
+
| `<MMessage>` | 页面内常驻提示条(如表单顶部错误) |
|
|
98
98
|
|
|
99
99
|
## 相关文档
|
|
100
100
|
|
|
@@ -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>
|