mvframe 1.0.4 → 1.0.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.
@@ -0,0 +1,467 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * 在目标工程生成 MVFrame 推荐目录与雏形文件(views / component / api / pinia / router / config / assets)。
4
+ *
5
+ * 用法:
6
+ * node path/to/mvframe/scripts/scaffold-app.js [项目根] [--force]
7
+ * yarn exec mvframe-init-app
8
+ *
9
+ * 环境变量:MVFRAME_SCAFFOLD_TARGET=/path/to/project
10
+ *
11
+ * 选项:--no-package-json 不读不写 package.json(仅生成源码与 Vite 配置)
12
+ */
13
+
14
+ const fs = require("fs");
15
+ const path = require("path");
16
+
17
+ const FORCE = process.argv.includes("--force");
18
+ const NO_PKG = process.argv.includes("--no-package-json");
19
+ const argPath = process.argv.find(
20
+ (a, i) => i >= 2 && a !== "--force" && !a.startsWith("--"),
21
+ );
22
+ const target = path.resolve(
23
+ process.env.MVFRAME_SCAFFOLD_TARGET || argPath || process.cwd(),
24
+ );
25
+
26
+ function write(rel, content) {
27
+ const fp = path.join(target, rel);
28
+ fs.mkdirSync(path.dirname(fp), { recursive: true });
29
+ if (fs.existsSync(fp) && !FORCE) {
30
+ console.warn("[mvframe-init] 已存在,跳过(使用 --force 覆盖):", rel);
31
+ return;
32
+ }
33
+ fs.writeFileSync(fp, content, "utf8");
34
+ console.log("[mvframe-init] 写入", rel);
35
+ }
36
+
37
+ /** 与 main.js / vite.config 模板一致;版本号与 mvframe 本仓库对齐思路,可随发布调整 */
38
+ const SCAFFOLD_DEPENDENCIES = {
39
+ vue: "^3.5.0",
40
+ "vue-router": "^4.6.0",
41
+ pinia: "^3.0.0",
42
+ mvframe: "^1.0.0",
43
+ "element-plus": "^2.13.0",
44
+ };
45
+
46
+ const SCAFFOLD_DEV_DEPENDENCIES = {
47
+ vite: "^6.0.0",
48
+ "@vitejs/plugin-vue": "^5.2.0",
49
+ "sass-embedded": "^1.97.0",
50
+ "unplugin-auto-import": "^0.18.2",
51
+ };
52
+
53
+ /**
54
+ * 合并 package.json:保留原有字段与其它依赖;仅补足脚手架所需包。
55
+ * 同名依赖以目标项目已有版本为准({ ...建议, ...已有 })。
56
+ */
57
+ function mergePackageJson(projectRoot) {
58
+ const pkgPath = path.join(projectRoot, "package.json");
59
+ let pkg;
60
+
61
+ if (fs.existsSync(pkgPath)) {
62
+ try {
63
+ pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
64
+ } catch (e) {
65
+ console.warn("[mvframe-init] package.json 解析失败,跳过合并:", e.message);
66
+ return;
67
+ }
68
+ } else {
69
+ pkg = {
70
+ name: path.basename(projectRoot).replace(/[^a-z0-9-]/gi, "-").toLowerCase() || "mvframe-app",
71
+ version: "0.1.0",
72
+ private: true,
73
+ type: "module",
74
+ scripts: {
75
+ dev: "vite",
76
+ build: "vite build",
77
+ preview: "vite preview",
78
+ },
79
+ };
80
+ console.log("[mvframe-init] 新建 package.json");
81
+ }
82
+
83
+ pkg.dependencies = {
84
+ ...SCAFFOLD_DEPENDENCIES,
85
+ ...(pkg.dependencies && typeof pkg.dependencies === "object" ? pkg.dependencies : {}),
86
+ };
87
+ pkg.devDependencies = {
88
+ ...SCAFFOLD_DEV_DEPENDENCIES,
89
+ ...(pkg.devDependencies && typeof pkg.devDependencies === "object"
90
+ ? pkg.devDependencies
91
+ : {}),
92
+ };
93
+
94
+ fs.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`, "utf8");
95
+ console.log("[mvframe-init] 已合并 package.json 的 dependencies / devDependencies(已有版本优先)");
96
+ }
97
+
98
+ function main() {
99
+ if (!fs.existsSync(target)) {
100
+ console.error("[mvframe-init] 目录不存在:", target);
101
+ process.exit(1);
102
+ }
103
+
104
+ const dirs = [
105
+ "src/views/Home",
106
+ "src/views/Admin",
107
+ "src/component",
108
+ "src/assets/img",
109
+ "src/assets/style",
110
+ "src/api",
111
+ "src/router",
112
+ "src/pinia/chip",
113
+ "src/config",
114
+ "src/composition",
115
+ ];
116
+ for (const d of dirs) {
117
+ fs.mkdirSync(path.join(target, d), { recursive: true });
118
+ }
119
+
120
+ write(
121
+ "src/main.js",
122
+ `import { createApp } from "vue";
123
+ import ElementPlus from "element-plus";
124
+ import "element-plus/dist/index.css";
125
+ import App from "./App.vue";
126
+ import mvframe from "mvframe";
127
+ import routes from "./router/index.js";
128
+ import appConfig from "./config/index.js";
129
+ import "./assets/style/index.scss";
130
+
131
+ const app = createApp(App);
132
+ app.use(ElementPlus);
133
+ app.use(mvframe, {
134
+ vueRouter: {
135
+ routes,
136
+ },
137
+ pinia: {
138
+ useTab: true,
139
+ storeChips: import.meta.glob("./pinia/chip/*.js", { eager: true }),
140
+ },
141
+ config: appConfig,
142
+ });
143
+ app.mount("#app");
144
+ `,
145
+ );
146
+
147
+ write(
148
+ "src/App.vue",
149
+ `<template>
150
+ <Frame class="App" :menu="menu" :page="{}">
151
+ <template #logo>
152
+ <span class="logo-placeholder">Logo</span>
153
+ </template>
154
+ <template #logomini>
155
+ <span class="logo-mini">L</span>
156
+ </template>
157
+ </Frame>
158
+ </template>
159
+ <script setup>
160
+ import routes from "./router/index.js";
161
+
162
+ defineOptions({
163
+ name: "App",
164
+ inheritAttrs: false,
165
+ });
166
+
167
+ const menu = {
168
+ iconClass: "imicon",
169
+ routes,
170
+ };
171
+ </script>
172
+ <style lang="scss" scoped>
173
+ .logo-placeholder {
174
+ display: inline-block;
175
+ min-width: 7.5rem;
176
+ padding: 0.25rem 0.5rem;
177
+ border-radius: 0.25rem;
178
+ background: var(--color-primary, #16b1ff);
179
+ color: #fff;
180
+ font-size: 0.875rem;
181
+ }
182
+ .logo-mini {
183
+ display: inline-flex;
184
+ width: 1.875rem;
185
+ height: 1.875rem;
186
+ align-items: center;
187
+ justify-content: center;
188
+ border-radius: 0.25rem;
189
+ background: var(--color-green, #20c997);
190
+ color: #fff;
191
+ font-size: 0.75rem;
192
+ }
193
+ </style>
194
+ `,
195
+ );
196
+
197
+ write(
198
+ "src/router/baseRouter.js",
199
+ `/**
200
+ * 基础路由:登录页、公开页、与后台权限无关的入口。
201
+ * 权限接口返回后若要合并,可在本文件导出函数内拼接,或与 adminRouter 在 index 中组合。
202
+ */
203
+ export default [
204
+ {
205
+ path: "/",
206
+ name: "Home_Home",
207
+ component: () => import("@/views/Home/Home.vue"),
208
+ meta: {
209
+ title: "Home",
210
+ },
211
+ },
212
+ ];
213
+ `,
214
+ );
215
+
216
+ write(
217
+ "src/router/adminRouter.js",
218
+ `/**
219
+ * 后台 / 业务路由:可按接口权限过滤后追加,或整段替换。
220
+ * meta.admin 等字段可与 mvframe vueRouter.useAdmin / adminPermission 配合。
221
+ */
222
+ export default [
223
+ {
224
+ path: "/admin",
225
+ name: "Admin_Dashboard",
226
+ component: () => import("@/views/Admin/Dashboard.vue"),
227
+ meta: {
228
+ title: "Dashboard",
229
+ icon: "im-setting",
230
+ // admin: true,
231
+ },
232
+ },
233
+ ];
234
+ `,
235
+ );
236
+
237
+ write(
238
+ "src/router/index.js",
239
+ `import baseRouter from "./baseRouter.js";
240
+ import adminRouter from "./adminRouter.js";
241
+
242
+ /** 交给 mvframe 的 vueRouter.routes(菜单与注册同源,无需拆两份维护) */
243
+ export default [...baseRouter, ...adminRouter];
244
+ `,
245
+ );
246
+
247
+ write(
248
+ "src/config/index.js",
249
+ `/**
250
+ * 与 mvframe 内置 config 合并 → globalThis.$config
251
+ * @type {Record<string, unknown>}
252
+ */
253
+ export default {
254
+ iconfont: {
255
+ // url: "//at.alicdn.com/t/c/your_font.js",
256
+ // prefix: "ant",
257
+ },
258
+ table: {
259
+ summaryMetric: null,
260
+ },
261
+ };
262
+ `,
263
+ );
264
+
265
+ write(
266
+ "src/pinia/chip/app.js",
267
+ `/** 业务 Pinia chip:路径须为 pinia/chip/*.js,export default { state, actions } */
268
+ const state = () => ({
269
+ // demo: null,
270
+ });
271
+
272
+ const actions = {
273
+ saveData(key, value) {
274
+ this[key] = value;
275
+ },
276
+ };
277
+
278
+ export default {
279
+ state,
280
+ actions,
281
+ };
282
+ `,
283
+ );
284
+
285
+ write(
286
+ "src/api/index.js",
287
+ `/**
288
+ * 接口聚合:按需拆分 chip 并在业务中 import
289
+ * 例:export * from "./chip/user.js";
290
+ */
291
+ export {};
292
+ `,
293
+ );
294
+
295
+ write(
296
+ "src/assets/style/index.scss",
297
+ `/* 项目全局样式入口(已在 main.js 引入) */
298
+
299
+ /* MVFrame 工具类与变量:按安装方式选一种
300
+ * - monorepo / 源码依赖:*/
301
+ /* @import "mvframe/src/style/index.scss"; */
302
+
303
+ /* - 发布包全量样式:*/
304
+ /* @import "mvframe/style"; */
305
+
306
+ body {
307
+ margin: 0;
308
+ }
309
+ `,
310
+ );
311
+
312
+ write(
313
+ "src/views/Admin/Dashboard.vue",
314
+ `<template>
315
+ <Page title="Dashboard" subtitle="后台路由示例(adminRouter)">
316
+ <p>在 <code>src/router/adminRouter.js</code> 扩展;与 <code>baseRouter.js</code> 在 <code>index.js</code> 合并。</p>
317
+ </Page>
318
+ </template>
319
+ <script setup>
320
+ defineOptions({
321
+ name: "AdminDashboard",
322
+ inheritAttrs: false,
323
+ });
324
+ </script>
325
+ <style lang="scss" scoped>
326
+ code {
327
+ font-size: 0.875rem;
328
+ }
329
+ </style>
330
+ `,
331
+ );
332
+
333
+ write(
334
+ "src/views/Home/Home.vue",
335
+ `<template>
336
+ <Page title="Home" subtitle="MVFrame 雏形页,可从此扩展 views 模块">
337
+ <p>公开路由见 <code>src/router/baseRouter.js</code>;后台见 <code>adminRouter.js</code>,合并于 <code>index.js</code>。</p>
338
+ </Page>
339
+ </template>
340
+ <script setup>
341
+ defineOptions({
342
+ name: "HomeHome",
343
+ inheritAttrs: false,
344
+ });
345
+ </script>
346
+ <style lang="scss" scoped>
347
+ code {
348
+ font-size: 0.875rem;
349
+ }
350
+ </style>
351
+ `,
352
+ );
353
+
354
+ write("src/component/.gitkeep", "");
355
+ write("src/assets/img/.gitkeep", "");
356
+ write("src/composition/.gitkeep", "");
357
+
358
+ const idxHtml = path.join(target, "index.html");
359
+ if (!fs.existsSync(idxHtml) || FORCE) {
360
+ write(
361
+ "index.html",
362
+ `<!DOCTYPE html>
363
+ <html lang="zh-CN">
364
+ <head>
365
+ <meta charset="UTF-8" />
366
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
367
+ <title>App</title>
368
+ </head>
369
+ <body>
370
+ <div id="app"></div>
371
+ <script type="module" src="/src/main.js"></script>
372
+ </body>
373
+ </html>
374
+ `,
375
+ );
376
+ }
377
+
378
+ const viteConfig = path.join(target, "vite.config.js");
379
+ if (!fs.existsSync(viteConfig) || FORCE) {
380
+ write(
381
+ "vite.config.js",
382
+ `import { defineConfig } from "vite";
383
+ import vue from "@vitejs/plugin-vue";
384
+ import path from "path";
385
+ import { fileURLToPath } from "url";
386
+ import AutoImport from "unplugin-auto-import/vite";
387
+
388
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
389
+
390
+ export default defineConfig({
391
+ plugins: [
392
+ vue(),
393
+ AutoImport({
394
+ include: [/\\.vue$/, /\\.vue\\?vue/, /\\.js$/],
395
+ imports: ["vue", "vue-router"],
396
+ vueTemplate: true,
397
+ dts: true,
398
+ }),
399
+ ],
400
+ resolve: {
401
+ alias: {
402
+ "@": path.resolve(__dirname, "src"),
403
+ "@cps": path.resolve(__dirname, "src/composition"),
404
+ },
405
+ },
406
+ server: {
407
+ port: 5173,
408
+ },
409
+ });
410
+ `,
411
+ );
412
+ }
413
+
414
+ write(
415
+ "MVFRAME-SCAFFOLD.md",
416
+ `# MVFrame 雏形已生成
417
+
418
+ ## 依赖
419
+
420
+ 初始化脚本会**合并**(不整文件覆盖)\`package.json\` 的 \`dependencies\` / \`devDependencies\`:**已声明的包保留你的版本号**,仅补上缺失项。然后执行:
421
+
422
+ \`\`\`bash
423
+ yarn install
424
+ \`\`\`
425
+
426
+ 若需跳过对 \`package.json\` 的修改:\`node scripts/scaffold-app.js --no-package-json\`。
427
+
428
+ 自动生成的 \`vite.config.js\` 已包含 \`unplugin-auto-import\`;\`dts: true\` 时类型默认写在项目根 \`auto-imports.d.ts\`。
429
+
430
+ ## 样式
431
+
432
+ 在 \`src/assets/style/index.scss\` 中取消注释并指向实际 mvframe 样式路径(源码或 dist)。
433
+
434
+ ## 目录约定
435
+
436
+ | 路径 | 用途 |
437
+ |------|------|
438
+ | \`src/views\` | 主要页面(模块大驼峰 + \`Home.vue\`,见 mvframe views 规范) |
439
+ | \`src/component\` | 项目级公共组件(与 mvframe 全局 \`Mvc*\` 区分) |
440
+ | \`src/api\` | 接口 |
441
+ | \`src/assets/style\` | 样式入口,main.js 已 import |
442
+ | \`src/assets/img\` | 静态图 |
443
+ | \`src/router/baseRouter.js\` | 基础路由(公开页等) |
444
+ | \`src/router/adminRouter.js\` | 后台 / 业务路由(可按权限过滤后合并) |
445
+ | \`src/router/index.js\` | 合并导出 → \`vueRouter.routes\`(与 Frame \`menu.routes\` 同源) |
446
+ | \`src/pinia/chip/*.js\` | 业务 store → \`storeChips: import.meta.glob(...)\` |
447
+ | \`src/config/index.js\` | 合并进 \`globalThis.$config\` |
448
+ | \`src/composition\` | 可选,与 Vite 别名 \`@cps\` 对应 |
449
+
450
+ ## Cursor Skill(可选)
451
+
452
+ \`\`\`bash
453
+ yarn exec mvframe-install-cursor-skill
454
+ \`\`\`
455
+ `,
456
+ );
457
+
458
+ if (!NO_PKG) {
459
+ mergePackageJson(target);
460
+ }
461
+
462
+ console.log(
463
+ "[mvframe-init] 完成。请阅读 MVFRAME-SCAFFOLD.md 并在项目根执行 yarn install(或 npm install)。",
464
+ );
465
+ }
466
+
467
+ main();