mvframe 1.0.4 → 1.0.5

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