mvframe 1.0.14 → 1.0.16
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/dist/index.js +2 -2
- package/dist/store-shared.js +162 -0
- package/dist/store.js +3 -160
- package/package.json +1 -1
- package/scripts/scaffold-app.js +316 -82
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { u as Ne } from "./util.js";
|
|
2
2
|
import { d as Ke } from "./directive.js";
|
|
3
|
-
import { s as Te, p as Ae, a as Fe } from "./store.js";
|
|
3
|
+
import { s as Te, p as Ae, a as Fe } from "./store-shared.js";
|
|
4
4
|
import { createRouter as De, createWebHistory as Be, useRoute as Me, useRouter as Ve } from "vue-router";
|
|
5
5
|
import { computed as O, openBlock as b, createElementBlock as M, mergeProps as oe, unref as n, Fragment as Y, renderList as se, normalizeClass as F, createCommentVNode as K, createTextVNode as W, toDisplayString as L, reactive as H, onUnmounted as ce, watch as ne, markRaw as me, resolveComponent as E, createVNode as z, withCtx as V, createElementVNode as r, renderSlot as R, createBlock as B, resolveDynamicComponent as Oe, nextTick as ge, getCurrentInstance as ae, ref as G, onMounted as ee, normalizeStyle as Le, defineComponent as Ie, cloneVNode as _e, h as we, inject as he, withModifiers as $e, defineAsyncComponent as Pe, resolveDirective as Re, normalizeProps as de, guardReactiveProps as fe, createSlots as ie, withDirectives as He, useSlots as Ue, Transition as Ge, withKeys as Je, createStaticVNode as qe, useAttrs as ve, isRef as je, onBeforeMount as We } from "vue";
|
|
6
6
|
/* empty css */
|
|
@@ -2983,7 +2983,7 @@ const Tt = Ie({
|
|
|
2983
2983
|
}, sl = {
|
|
2984
2984
|
name: "Matt Avias Frame",
|
|
2985
2985
|
copyright: "©2026",
|
|
2986
|
-
version: "1.0.
|
|
2986
|
+
version: "1.0.16",
|
|
2987
2987
|
author: "Matt Avias",
|
|
2988
2988
|
date: "2026-02-26",
|
|
2989
2989
|
/** 默认语言 key,与 `$getLang`、localStorage `lang` 一致;业务在 app.use(mvframe, { config }) 里覆盖 */
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { createPinia as p, defineStore as h } from "pinia";
|
|
2
|
+
const T = () => ({
|
|
3
|
+
lang: "en_us",
|
|
4
|
+
/** 路由切换 / 异步页面 chunk 加载期间为 true,由 router guard 维护 */
|
|
5
|
+
pageLoading: !1
|
|
6
|
+
}), y = {
|
|
7
|
+
saveData(t, e) {
|
|
8
|
+
this[t] = e;
|
|
9
|
+
},
|
|
10
|
+
setLang(t) {
|
|
11
|
+
this.lang = t;
|
|
12
|
+
},
|
|
13
|
+
setPageLoading(t) {
|
|
14
|
+
this.pageLoading = !!t;
|
|
15
|
+
}
|
|
16
|
+
}, $ = {
|
|
17
|
+
state: T,
|
|
18
|
+
actions: y
|
|
19
|
+
}, d = () => ({
|
|
20
|
+
useTab: !1,
|
|
21
|
+
// 是否使用tab
|
|
22
|
+
tabs: [],
|
|
23
|
+
ctab: {}
|
|
24
|
+
// current tab
|
|
25
|
+
}), S = {
|
|
26
|
+
saveData(t, e) {
|
|
27
|
+
this[t] = e;
|
|
28
|
+
},
|
|
29
|
+
saveCurrentTab(t) {
|
|
30
|
+
this.ctab = t, localStorage.setItem("ctab", JSON.stringify(t));
|
|
31
|
+
},
|
|
32
|
+
saveTab(t) {
|
|
33
|
+
const { data: e, index: s } = this.tabs.filter1((r) => r.name === t.name);
|
|
34
|
+
if (e)
|
|
35
|
+
if (JSON.stringify(e.params) === JSON.stringify(t.params) && JSON.stringify(e.query) === JSON.stringify(t.query)) {
|
|
36
|
+
this.saveCurrentTab(e);
|
|
37
|
+
return;
|
|
38
|
+
} else
|
|
39
|
+
this.closeTab(e, s);
|
|
40
|
+
const { fullPath: a, name: n, meta: i, params: c, query: u } = t;
|
|
41
|
+
if (n !== "Login") {
|
|
42
|
+
const r = a.split("/");
|
|
43
|
+
let l;
|
|
44
|
+
r[r.length - 1] === "Home" ? l = r[r.length - 2] : l = r[r.length - 1], i.icon && (l = i.icon);
|
|
45
|
+
const b = {
|
|
46
|
+
name: n,
|
|
47
|
+
meta: i,
|
|
48
|
+
path: r,
|
|
49
|
+
params: c,
|
|
50
|
+
query: u,
|
|
51
|
+
icon: l
|
|
52
|
+
}, { index: m } = this.tabs.filter1((g) => g.name === this.ctab.name);
|
|
53
|
+
this.tabs.splice(m + 1, 0, b), this.saveCurrentTab(b);
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
closeTab(t, e) {
|
|
57
|
+
var s;
|
|
58
|
+
this.tabs.splice(e, 1), t.name === this.ctab.name && ((s = globalThis.$router) == null || s.push({ name: this.tabs[e - 1 > 0 ? e - 1 : 0].name }));
|
|
59
|
+
},
|
|
60
|
+
closeRightTab(t, e) {
|
|
61
|
+
var n;
|
|
62
|
+
const s = [];
|
|
63
|
+
let a = !1;
|
|
64
|
+
this.tabs.forEach((i, c) => {
|
|
65
|
+
e >= c ? s.push(i) : i.name === this.ctab.name && (a = !0);
|
|
66
|
+
}), this.tabs = s, a && ((n = globalThis.$router) == null || n.push({ name: t.name }));
|
|
67
|
+
},
|
|
68
|
+
closeLeftTab(t, e) {
|
|
69
|
+
var n;
|
|
70
|
+
const s = [];
|
|
71
|
+
let a = !1;
|
|
72
|
+
this.tabs.forEach((i, c) => {
|
|
73
|
+
e <= c ? s.push(i) : i.name === this.ctab.name && (a = !0);
|
|
74
|
+
}), this.tabs = s, a && ((n = globalThis.$router) == null || n.push({ name: t.name }));
|
|
75
|
+
},
|
|
76
|
+
closeOtherTab(t, e) {
|
|
77
|
+
var s;
|
|
78
|
+
this.tabs = [t], t.name !== this.ctab.name && ((s = globalThis.$router) == null || s.push({ name: t.name }));
|
|
79
|
+
},
|
|
80
|
+
closeAllTab() {
|
|
81
|
+
this.tabs = [];
|
|
82
|
+
}
|
|
83
|
+
}, v = {}, w = {
|
|
84
|
+
state: d,
|
|
85
|
+
actions: S,
|
|
86
|
+
getters: v
|
|
87
|
+
}, O = () => ({
|
|
88
|
+
type: "",
|
|
89
|
+
visible: !1,
|
|
90
|
+
options: [],
|
|
91
|
+
el: null,
|
|
92
|
+
position: {
|
|
93
|
+
x: 0,
|
|
94
|
+
y: 0
|
|
95
|
+
}
|
|
96
|
+
}), L = {
|
|
97
|
+
saveData(t, e) {
|
|
98
|
+
this[t] = e;
|
|
99
|
+
},
|
|
100
|
+
show({ el: t, position: e }) {
|
|
101
|
+
this.visible = !0, e ? this.position = e : t && (this.el = t);
|
|
102
|
+
},
|
|
103
|
+
hide() {
|
|
104
|
+
this.visible = !1, this.el = null, this.position = {
|
|
105
|
+
x: 0,
|
|
106
|
+
y: 0
|
|
107
|
+
}, this.type = "";
|
|
108
|
+
}
|
|
109
|
+
}, N = {}, J = {
|
|
110
|
+
state: O,
|
|
111
|
+
actions: L,
|
|
112
|
+
getters: N
|
|
113
|
+
}, x = p(), o = {}, D = (t) => {
|
|
114
|
+
const e = t.match(/(?:^|[/\\])([^/\\]+)\.js$/);
|
|
115
|
+
return e ? e[1] : null;
|
|
116
|
+
}, E = (t) => {
|
|
117
|
+
Object.entries(t).forEach(([e, s]) => {
|
|
118
|
+
const a = D(e);
|
|
119
|
+
if (!a || !(s != null && s.default)) {
|
|
120
|
+
console.warn(`Invalid store module: ${e}`);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
try {
|
|
124
|
+
o[a] = h(a, s.default);
|
|
125
|
+
} catch (n) {
|
|
126
|
+
console.error(`Failed to register store module '${a}':`, n);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
};
|
|
130
|
+
o.init = h("init", $);
|
|
131
|
+
o.tab = h("tab", w);
|
|
132
|
+
o.rmenu = h("rmenu", J);
|
|
133
|
+
const f = (t) => {
|
|
134
|
+
try {
|
|
135
|
+
switch (window.$getType(t)) {
|
|
136
|
+
case "Array":
|
|
137
|
+
t.forEach(f);
|
|
138
|
+
break;
|
|
139
|
+
case "String":
|
|
140
|
+
const s = localStorage.getItem(t);
|
|
141
|
+
s && o.tab().saveData(t, JSON.parse(s));
|
|
142
|
+
break;
|
|
143
|
+
default:
|
|
144
|
+
t = [], console.warn(`Invalid key: ${t}`);
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
} catch (e) {
|
|
148
|
+
console.error(`Failed to parse '${t}' from localStorage:`, e);
|
|
149
|
+
}
|
|
150
|
+
}, F = (t, { storeChips: e, useTab: s } = {}) => {
|
|
151
|
+
try {
|
|
152
|
+
t.use(x).provide("store", o);
|
|
153
|
+
} catch (a) {
|
|
154
|
+
throw new Error("Failed to inject store into app:", a);
|
|
155
|
+
}
|
|
156
|
+
return s && (o.tab().saveData("useTab", !0), f(["tabs", "ctab"])), e && E(e), o;
|
|
157
|
+
};
|
|
158
|
+
export {
|
|
159
|
+
F as a,
|
|
160
|
+
x as p,
|
|
161
|
+
o as s
|
|
162
|
+
};
|
package/dist/store.js
CHANGED
|
@@ -1,162 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
const T = () => ({
|
|
3
|
-
lang: "en_us",
|
|
4
|
-
/** 路由切换 / 异步页面 chunk 加载期间为 true,由 router guard 维护 */
|
|
5
|
-
pageLoading: !1
|
|
6
|
-
}), y = {
|
|
7
|
-
saveData(t, e) {
|
|
8
|
-
this[t] = e;
|
|
9
|
-
},
|
|
10
|
-
setLang(t) {
|
|
11
|
-
this.lang = t;
|
|
12
|
-
},
|
|
13
|
-
setPageLoading(t) {
|
|
14
|
-
this.pageLoading = !!t;
|
|
15
|
-
}
|
|
16
|
-
}, $ = {
|
|
17
|
-
state: T,
|
|
18
|
-
actions: y
|
|
19
|
-
}, d = () => ({
|
|
20
|
-
useTab: !1,
|
|
21
|
-
// 是否使用tab
|
|
22
|
-
tabs: [],
|
|
23
|
-
ctab: {}
|
|
24
|
-
// current tab
|
|
25
|
-
}), S = {
|
|
26
|
-
saveData(t, e) {
|
|
27
|
-
this[t] = e;
|
|
28
|
-
},
|
|
29
|
-
saveCurrentTab(t) {
|
|
30
|
-
this.ctab = t, localStorage.setItem("ctab", JSON.stringify(t));
|
|
31
|
-
},
|
|
32
|
-
saveTab(t) {
|
|
33
|
-
const { data: e, index: s } = this.tabs.filter1((r) => r.name === t.name);
|
|
34
|
-
if (e)
|
|
35
|
-
if (JSON.stringify(e.params) === JSON.stringify(t.params) && JSON.stringify(e.query) === JSON.stringify(t.query)) {
|
|
36
|
-
this.saveCurrentTab(e);
|
|
37
|
-
return;
|
|
38
|
-
} else
|
|
39
|
-
this.closeTab(e, s);
|
|
40
|
-
const { fullPath: a, name: n, meta: i, params: c, query: u } = t;
|
|
41
|
-
if (n !== "Login") {
|
|
42
|
-
const r = a.split("/");
|
|
43
|
-
let l;
|
|
44
|
-
r[r.length - 1] === "Home" ? l = r[r.length - 2] : l = r[r.length - 1], i.icon && (l = i.icon);
|
|
45
|
-
const b = {
|
|
46
|
-
name: n,
|
|
47
|
-
meta: i,
|
|
48
|
-
path: r,
|
|
49
|
-
params: c,
|
|
50
|
-
query: u,
|
|
51
|
-
icon: l
|
|
52
|
-
}, { index: m } = this.tabs.filter1((g) => g.name === this.ctab.name);
|
|
53
|
-
this.tabs.splice(m + 1, 0, b), this.saveCurrentTab(b);
|
|
54
|
-
}
|
|
55
|
-
},
|
|
56
|
-
closeTab(t, e) {
|
|
57
|
-
var s;
|
|
58
|
-
this.tabs.splice(e, 1), t.name === this.ctab.name && ((s = globalThis.$router) == null || s.push({ name: this.tabs[e - 1 > 0 ? e - 1 : 0].name }));
|
|
59
|
-
},
|
|
60
|
-
closeRightTab(t, e) {
|
|
61
|
-
var n;
|
|
62
|
-
const s = [];
|
|
63
|
-
let a = !1;
|
|
64
|
-
this.tabs.forEach((i, c) => {
|
|
65
|
-
e >= c ? s.push(i) : i.name === this.ctab.name && (a = !0);
|
|
66
|
-
}), this.tabs = s, a && ((n = globalThis.$router) == null || n.push({ name: t.name }));
|
|
67
|
-
},
|
|
68
|
-
closeLeftTab(t, e) {
|
|
69
|
-
var n;
|
|
70
|
-
const s = [];
|
|
71
|
-
let a = !1;
|
|
72
|
-
this.tabs.forEach((i, c) => {
|
|
73
|
-
e <= c ? s.push(i) : i.name === this.ctab.name && (a = !0);
|
|
74
|
-
}), this.tabs = s, a && ((n = globalThis.$router) == null || n.push({ name: t.name }));
|
|
75
|
-
},
|
|
76
|
-
closeOtherTab(t, e) {
|
|
77
|
-
var s;
|
|
78
|
-
this.tabs = [t], t.name !== this.ctab.name && ((s = globalThis.$router) == null || s.push({ name: t.name }));
|
|
79
|
-
},
|
|
80
|
-
closeAllTab() {
|
|
81
|
-
this.tabs = [];
|
|
82
|
-
}
|
|
83
|
-
}, v = {}, w = {
|
|
84
|
-
state: d,
|
|
85
|
-
actions: S,
|
|
86
|
-
getters: v
|
|
87
|
-
}, O = () => ({
|
|
88
|
-
type: "",
|
|
89
|
-
visible: !1,
|
|
90
|
-
options: [],
|
|
91
|
-
el: null,
|
|
92
|
-
position: {
|
|
93
|
-
x: 0,
|
|
94
|
-
y: 0
|
|
95
|
-
}
|
|
96
|
-
}), L = {
|
|
97
|
-
saveData(t, e) {
|
|
98
|
-
this[t] = e;
|
|
99
|
-
},
|
|
100
|
-
show({ el: t, position: e }) {
|
|
101
|
-
this.visible = !0, e ? this.position = e : t && (this.el = t);
|
|
102
|
-
},
|
|
103
|
-
hide() {
|
|
104
|
-
this.visible = !1, this.el = null, this.position = {
|
|
105
|
-
x: 0,
|
|
106
|
-
y: 0
|
|
107
|
-
}, this.type = "";
|
|
108
|
-
}
|
|
109
|
-
}, N = {}, J = {
|
|
110
|
-
state: O,
|
|
111
|
-
actions: L,
|
|
112
|
-
getters: N
|
|
113
|
-
}, x = p(), o = {}, D = (t) => {
|
|
114
|
-
const e = t.match(/(?:^|[/\\])([^/\\]+)\.js$/);
|
|
115
|
-
return e ? e[1] : null;
|
|
116
|
-
}, E = (t) => {
|
|
117
|
-
Object.entries(t).forEach(([e, s]) => {
|
|
118
|
-
const a = D(e);
|
|
119
|
-
if (!a || !(s != null && s.default)) {
|
|
120
|
-
console.warn(`Invalid store module: ${e}`);
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
try {
|
|
124
|
-
o[a] = h(a, s.default);
|
|
125
|
-
} catch (n) {
|
|
126
|
-
console.error(`Failed to register store module '${a}':`, n);
|
|
127
|
-
}
|
|
128
|
-
});
|
|
129
|
-
};
|
|
130
|
-
o.init = h("init", $);
|
|
131
|
-
o.tab = h("tab", w);
|
|
132
|
-
o.rmenu = h("rmenu", J);
|
|
133
|
-
const f = (t) => {
|
|
134
|
-
try {
|
|
135
|
-
switch (window.$getType(t)) {
|
|
136
|
-
case "Array":
|
|
137
|
-
t.forEach(f);
|
|
138
|
-
break;
|
|
139
|
-
case "String":
|
|
140
|
-
const s = localStorage.getItem(t);
|
|
141
|
-
s && o.tab().saveData(t, JSON.parse(s));
|
|
142
|
-
break;
|
|
143
|
-
default:
|
|
144
|
-
t = [], console.warn(`Invalid key: ${t}`);
|
|
145
|
-
break;
|
|
146
|
-
}
|
|
147
|
-
} catch (e) {
|
|
148
|
-
console.error(`Failed to parse '${t}' from localStorage:`, e);
|
|
149
|
-
}
|
|
150
|
-
}, F = (t, { storeChips: e, useTab: s } = {}) => {
|
|
151
|
-
try {
|
|
152
|
-
t.use(x).provide("store", o);
|
|
153
|
-
} catch (a) {
|
|
154
|
-
throw new Error("Failed to inject store into app:", a);
|
|
155
|
-
}
|
|
156
|
-
return s && (o.tab().saveData("useTab", !0), f(["tabs", "ctab"])), e && E(e), o;
|
|
157
|
-
};
|
|
1
|
+
import { p as o, s as p } from "./store-shared.js";
|
|
158
2
|
export {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
o as s
|
|
3
|
+
o as pinia,
|
|
4
|
+
p as store
|
|
162
5
|
};
|
package/package.json
CHANGED
package/scripts/scaffold-app.js
CHANGED
|
@@ -106,8 +106,10 @@ function main() {
|
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
const dirs = [
|
|
109
|
-
"src/views/
|
|
110
|
-
"src/views/
|
|
109
|
+
"src/views/Launch",
|
|
110
|
+
"src/views/Overview",
|
|
111
|
+
"src/views/A",
|
|
112
|
+
"src/views/B",
|
|
111
113
|
"src/component",
|
|
112
114
|
"src/assets/img",
|
|
113
115
|
"src/assets/style",
|
|
@@ -128,7 +130,8 @@ import ElementPlus from "element-plus";
|
|
|
128
130
|
import "element-plus/dist/index.css";
|
|
129
131
|
import App from "./App.vue";
|
|
130
132
|
import mvframe from "mvframe";
|
|
131
|
-
import
|
|
133
|
+
import { pinia, store } from "mvframe/store";
|
|
134
|
+
import routes from "./router/routes.js";
|
|
132
135
|
import appConfig from "./config/index.js";
|
|
133
136
|
import "./assets/style/index.scss";
|
|
134
137
|
import "mvframe/style";
|
|
@@ -136,9 +139,42 @@ import "mvframe/style/cpt";
|
|
|
136
139
|
|
|
137
140
|
const app = createApp(App);
|
|
138
141
|
app.use(ElementPlus);
|
|
142
|
+
|
|
143
|
+
/** \`meta.public\` 为公开路由;已登录访问登录页则进首页。与 demo 一致:\`store.launch(pinia)\` 来自 \`mvframe/store\`(与插件注入的是同一单例)。 */
|
|
144
|
+
const launchRouteGuard = (to, from, next) => {
|
|
145
|
+
if (!store.launch) {
|
|
146
|
+
next();
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const launch = store.launch(pinia);
|
|
150
|
+
const authed = Boolean(launch.login);
|
|
151
|
+
const isPublic = to.matched.some((r) => r.meta?.public);
|
|
152
|
+
if (!authed && !isPublic) {
|
|
153
|
+
next({ name: "Entry", replace: true });
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
if (authed && to.name === "Entry") {
|
|
157
|
+
next({ name: "Overview_Home", replace: true });
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
next();
|
|
161
|
+
};
|
|
162
|
+
|
|
139
163
|
app.use(mvframe, {
|
|
140
164
|
vueRouter: {
|
|
141
165
|
routes,
|
|
166
|
+
guard: (router) => {
|
|
167
|
+
router.beforeEach(launchRouteGuard);
|
|
168
|
+
},
|
|
169
|
+
useAdmin: true,
|
|
170
|
+
adminPermission: () => Boolean(store.launch(pinia).login),
|
|
171
|
+
noaccess: (to, from, next) => {
|
|
172
|
+
if (from.path === "/") {
|
|
173
|
+
next();
|
|
174
|
+
} else {
|
|
175
|
+
next({ name: "Entry" });
|
|
176
|
+
}
|
|
177
|
+
},
|
|
142
178
|
},
|
|
143
179
|
pinia: {
|
|
144
180
|
useTab: true,
|
|
@@ -153,103 +189,106 @@ app.mount("#app");
|
|
|
153
189
|
write(
|
|
154
190
|
"src/App.vue",
|
|
155
191
|
`<template>
|
|
156
|
-
<
|
|
157
|
-
|
|
158
|
-
<span class="logo-placeholder">Logo</span>
|
|
159
|
-
</template>
|
|
160
|
-
<template #logomini>
|
|
161
|
-
<span class="logo-mini">L</span>
|
|
162
|
-
</template>
|
|
163
|
-
</Frame>
|
|
192
|
+
<AdminEntry v-if="isLoggedIn" />
|
|
193
|
+
<Entry v-else />
|
|
164
194
|
</template>
|
|
165
195
|
<script setup>
|
|
166
|
-
import
|
|
196
|
+
import Entry from "./views/Launch/Entry.vue";
|
|
197
|
+
import AdminEntry from "./views/Launch/AdminEntry.vue";
|
|
167
198
|
|
|
168
199
|
defineOptions({
|
|
169
200
|
name: "App",
|
|
170
201
|
inheritAttrs: false,
|
|
171
202
|
});
|
|
172
203
|
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
204
|
+
const store = inject("store");
|
|
205
|
+
const launch = store.launch();
|
|
206
|
+
|
|
207
|
+
const isLoggedIn = computed(() => Boolean(launch.login));
|
|
177
208
|
</script>
|
|
178
|
-
<style lang="scss" scoped>
|
|
179
|
-
.logo-placeholder {
|
|
180
|
-
display: inline-block;
|
|
181
|
-
min-width: 7.5rem;
|
|
182
|
-
padding: 0.25rem 0.5rem;
|
|
183
|
-
border-radius: 0.25rem;
|
|
184
|
-
background: var(--color-primary, #16b1ff);
|
|
185
|
-
color: #fff;
|
|
186
|
-
font-size: 0.875rem;
|
|
187
|
-
}
|
|
188
|
-
.logo-mini {
|
|
189
|
-
display: inline-flex;
|
|
190
|
-
width: 1.875rem;
|
|
191
|
-
height: 1.875rem;
|
|
192
|
-
align-items: center;
|
|
193
|
-
justify-content: center;
|
|
194
|
-
border-radius: 0.25rem;
|
|
195
|
-
background: var(--color-green, #20c997);
|
|
196
|
-
color: #fff;
|
|
197
|
-
font-size: 0.75rem;
|
|
198
|
-
}
|
|
199
|
-
</style>
|
|
209
|
+
<style lang="scss" scoped></style>
|
|
200
210
|
`,
|
|
201
211
|
);
|
|
202
212
|
|
|
203
213
|
write(
|
|
204
|
-
"src/router/
|
|
214
|
+
"src/router/routes.js",
|
|
205
215
|
`/**
|
|
206
|
-
*
|
|
207
|
-
*
|
|
216
|
+
* 与 mvframe \`demo/routes.js\` 一致:单文件导出全部路由;\`meta.public\` 为公开页(见 main.js 守卫)。
|
|
217
|
+
* B 嵌套使用 Layout + 子路由;日后若按权限裁剪菜单,可对本数组 \`filter\` 后再交给 Frame。
|
|
208
218
|
*/
|
|
209
219
|
export default [
|
|
210
220
|
{
|
|
211
221
|
path: "/",
|
|
212
|
-
name: "
|
|
213
|
-
component: () => import("@/views/
|
|
222
|
+
name: "Entry",
|
|
223
|
+
component: () => import("@/views/Launch/LoginPage.vue"),
|
|
214
224
|
meta: {
|
|
215
|
-
title: "
|
|
225
|
+
title: "Login",
|
|
226
|
+
public: true,
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
path: "/overview",
|
|
231
|
+
name: "Overview_Home",
|
|
232
|
+
component: () => import("@/views/Overview/Home.vue"),
|
|
233
|
+
meta: {
|
|
234
|
+
title: "Overview",
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
path: "/a",
|
|
239
|
+
name: "A_Home",
|
|
240
|
+
component: () => import("@/views/A/Home.vue"),
|
|
241
|
+
meta: {
|
|
242
|
+
title: "A",
|
|
243
|
+
icon: "im-swap-right",
|
|
216
244
|
},
|
|
217
245
|
},
|
|
218
|
-
];
|
|
219
|
-
`,
|
|
220
|
-
);
|
|
221
|
-
|
|
222
|
-
write(
|
|
223
|
-
"src/router/adminRouter.js",
|
|
224
|
-
`/**
|
|
225
|
-
* 后台 / 业务路由:可按接口权限过滤后追加,或整段替换。
|
|
226
|
-
* meta.admin 等字段可与 mvframe vueRouter.useAdmin / adminPermission 配合。
|
|
227
|
-
*/
|
|
228
|
-
export default [
|
|
229
246
|
{
|
|
230
|
-
path: "/
|
|
231
|
-
name: "
|
|
232
|
-
component: () => import("@/views/
|
|
247
|
+
path: "/b",
|
|
248
|
+
name: "B_Home",
|
|
249
|
+
component: () => import("@/views/B/Layout.vue"),
|
|
233
250
|
meta: {
|
|
234
|
-
title: "
|
|
235
|
-
icon: "im-
|
|
236
|
-
// admin: true,
|
|
251
|
+
title: "B",
|
|
252
|
+
icon: "im-swap-right",
|
|
237
253
|
},
|
|
254
|
+
children: [
|
|
255
|
+
{ path: "", redirect: { name: "B_First" } },
|
|
256
|
+
{
|
|
257
|
+
path: "first",
|
|
258
|
+
name: "B_First",
|
|
259
|
+
component: () => import("@/views/B/First.vue"),
|
|
260
|
+
meta: { title: "B_First" },
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
path: "second",
|
|
264
|
+
name: "B_Second",
|
|
265
|
+
component: () => import("@/views/B/Second.vue"),
|
|
266
|
+
meta: { title: "B_Second" },
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
path: "third",
|
|
270
|
+
name: "B_Third",
|
|
271
|
+
component: () => import("@/views/B/Third.vue"),
|
|
272
|
+
meta: { title: "B_Third" },
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
path: "fourth",
|
|
276
|
+
name: "B_Fourth",
|
|
277
|
+
component: () => import("@/views/B/Fourth.vue"),
|
|
278
|
+
meta: { title: "B_Fourth" },
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
path: "fifth",
|
|
282
|
+
name: "B_Fifth",
|
|
283
|
+
component: () => import("@/views/B/Fifth.vue"),
|
|
284
|
+
meta: { title: "B_Fifth" },
|
|
285
|
+
},
|
|
286
|
+
],
|
|
238
287
|
},
|
|
239
288
|
];
|
|
240
289
|
`,
|
|
241
290
|
);
|
|
242
291
|
|
|
243
|
-
write(
|
|
244
|
-
"src/router/index.js",
|
|
245
|
-
`import baseRouter from "./baseRouter.js";
|
|
246
|
-
import adminRouter from "./adminRouter.js";
|
|
247
|
-
|
|
248
|
-
/** 交给 mvframe 的 vueRouter.routes(菜单与注册同源,无需拆两份维护) */
|
|
249
|
-
export default [...baseRouter, ...adminRouter];
|
|
250
|
-
`,
|
|
251
|
-
);
|
|
252
|
-
|
|
253
292
|
write(
|
|
254
293
|
"src/config/index.js",
|
|
255
294
|
`/**
|
|
@@ -288,6 +327,136 @@ export default {
|
|
|
288
327
|
`,
|
|
289
328
|
);
|
|
290
329
|
|
|
330
|
+
write(
|
|
331
|
+
"src/pinia/chip/launch.js",
|
|
332
|
+
`/** 登录态:与 demo/pinia/launch.js、main.js launchRouteGuard、LoginPage 一致 */
|
|
333
|
+
export default {
|
|
334
|
+
state: () => ({
|
|
335
|
+
userinfo: {},
|
|
336
|
+
login: false,
|
|
337
|
+
}),
|
|
338
|
+
actions: {
|
|
339
|
+
/** 登录成功:合并写入 userinfo,并置 login: true */
|
|
340
|
+
setLogin(payload = {}) {
|
|
341
|
+
this.userinfo = {
|
|
342
|
+
...this.userinfo,
|
|
343
|
+
...payload,
|
|
344
|
+
};
|
|
345
|
+
this.login = true;
|
|
346
|
+
},
|
|
347
|
+
clearLogin() {
|
|
348
|
+
this.userinfo = {};
|
|
349
|
+
this.login = false;
|
|
350
|
+
},
|
|
351
|
+
},
|
|
352
|
+
};
|
|
353
|
+
`,
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
write(
|
|
357
|
+
"src/views/Launch/Entry.vue",
|
|
358
|
+
`<template>
|
|
359
|
+
<router-view />
|
|
360
|
+
</template>
|
|
361
|
+
<script setup>
|
|
362
|
+
defineOptions({
|
|
363
|
+
name: "LaunchEntry",
|
|
364
|
+
inheritAttrs: false,
|
|
365
|
+
});
|
|
366
|
+
</script>
|
|
367
|
+
`,
|
|
368
|
+
);
|
|
369
|
+
|
|
370
|
+
write(
|
|
371
|
+
"src/views/Launch/AdminEntry.vue",
|
|
372
|
+
`<template>
|
|
373
|
+
<Frame class="App wp100 vh100" :menu="frameMenu" :page="{}">
|
|
374
|
+
<template #logo>
|
|
375
|
+
<span class="logo-placeholder">Logo</span>
|
|
376
|
+
</template>
|
|
377
|
+
<template #logomini>
|
|
378
|
+
<span class="logo-mini">L</span>
|
|
379
|
+
</template>
|
|
380
|
+
</Frame>
|
|
381
|
+
</template>
|
|
382
|
+
<script setup>
|
|
383
|
+
import routes from "@/router/routes.js";
|
|
384
|
+
|
|
385
|
+
defineOptions({
|
|
386
|
+
name: "AdminEntry",
|
|
387
|
+
inheritAttrs: false,
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
const frameMenu = {
|
|
391
|
+
iconClass: "imicon",
|
|
392
|
+
routes: routes.filter((r) => r.meta?.public !== true),
|
|
393
|
+
};
|
|
394
|
+
</script>
|
|
395
|
+
<style lang="scss" scoped>
|
|
396
|
+
.logo-placeholder {
|
|
397
|
+
display: inline-block;
|
|
398
|
+
min-width: 7.5rem;
|
|
399
|
+
padding: 0.25rem 0.5rem;
|
|
400
|
+
border-radius: 0.25rem;
|
|
401
|
+
background: var(--color-primary, #16b1ff);
|
|
402
|
+
color: #fff;
|
|
403
|
+
font-size: 0.875rem;
|
|
404
|
+
}
|
|
405
|
+
.logo-mini {
|
|
406
|
+
display: inline-flex;
|
|
407
|
+
width: 1.875rem;
|
|
408
|
+
height: 1.875rem;
|
|
409
|
+
align-items: center;
|
|
410
|
+
justify-content: center;
|
|
411
|
+
border-radius: 0.25rem;
|
|
412
|
+
background: var(--color-green, #20c997);
|
|
413
|
+
color: #fff;
|
|
414
|
+
font-size: 0.75rem;
|
|
415
|
+
}
|
|
416
|
+
</style>
|
|
417
|
+
`,
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
write(
|
|
421
|
+
"src/views/Launch/LoginPage.vue",
|
|
422
|
+
`<template>
|
|
423
|
+
<div class="LoginPage wp100 vh100">
|
|
424
|
+
<Login
|
|
425
|
+
:form-fileds="formFileds"
|
|
426
|
+
:login-methods="[0]"
|
|
427
|
+
@submit="onSubmit"
|
|
428
|
+
/>
|
|
429
|
+
</div>
|
|
430
|
+
</template>
|
|
431
|
+
<script setup>
|
|
432
|
+
defineOptions({
|
|
433
|
+
name: "LoginPage",
|
|
434
|
+
inheritAttrs: false,
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
const router = useRouter();
|
|
438
|
+
const store = inject("store");
|
|
439
|
+
const launch = store.launch();
|
|
440
|
+
|
|
441
|
+
const onSubmit = (payload) => {
|
|
442
|
+
launch.setLogin(payload ?? {});
|
|
443
|
+
router.replace({ name: "Overview_Home" }).catch(() => {});
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
/** 与 mvframe demo 一致:自定义提交字段名 account / pwd */
|
|
447
|
+
const formFileds = {
|
|
448
|
+
username: { valueKey: "account" },
|
|
449
|
+
password: { valueKey: "pwd" },
|
|
450
|
+
};
|
|
451
|
+
</script>
|
|
452
|
+
<style lang="scss" scoped>
|
|
453
|
+
.LoginPage {
|
|
454
|
+
box-sizing: border-box;
|
|
455
|
+
}
|
|
456
|
+
</style>
|
|
457
|
+
`,
|
|
458
|
+
);
|
|
459
|
+
|
|
291
460
|
write(
|
|
292
461
|
"src/api/index.js",
|
|
293
462
|
`/**
|
|
@@ -311,15 +480,18 @@ body {
|
|
|
311
480
|
);
|
|
312
481
|
|
|
313
482
|
write(
|
|
314
|
-
"src/views/
|
|
483
|
+
"src/views/Overview/Home.vue",
|
|
315
484
|
`<template>
|
|
316
|
-
<Page title="
|
|
317
|
-
<p
|
|
485
|
+
<Page title="Overview" subtitle="登录后默认首页,可替换为业务概览">
|
|
486
|
+
<p>
|
|
487
|
+
路由与 mvframe <code>demo/routes.js</code> 对齐:<code>/overview</code>、<code>/a</code>、<code>/b/...</code>;公开入口为
|
|
488
|
+
<code>routes.js</code> 中的 <code>Entry</code>。
|
|
489
|
+
</p>
|
|
318
490
|
</Page>
|
|
319
491
|
</template>
|
|
320
492
|
<script setup>
|
|
321
493
|
defineOptions({
|
|
322
|
-
name: "
|
|
494
|
+
name: "OverviewHome",
|
|
323
495
|
inheritAttrs: false,
|
|
324
496
|
});
|
|
325
497
|
</script>
|
|
@@ -332,15 +504,15 @@ code {
|
|
|
332
504
|
);
|
|
333
505
|
|
|
334
506
|
write(
|
|
335
|
-
"src/views/
|
|
507
|
+
"src/views/A/Home.vue",
|
|
336
508
|
`<template>
|
|
337
|
-
<Page title="
|
|
338
|
-
<p
|
|
509
|
+
<Page title="A" subtitle="一级业务模块示例">
|
|
510
|
+
<p>对应路由 <code>A_Home</code>,路径 <code>/a</code>。</p>
|
|
339
511
|
</Page>
|
|
340
512
|
</template>
|
|
341
513
|
<script setup>
|
|
342
514
|
defineOptions({
|
|
343
|
-
name: "
|
|
515
|
+
name: "AHome",
|
|
344
516
|
inheritAttrs: false,
|
|
345
517
|
});
|
|
346
518
|
</script>
|
|
@@ -352,6 +524,52 @@ code {
|
|
|
352
524
|
`,
|
|
353
525
|
);
|
|
354
526
|
|
|
527
|
+
write(
|
|
528
|
+
"src/views/B/Layout.vue",
|
|
529
|
+
`<template>
|
|
530
|
+
<Page title="B" subtitle="嵌套路由父级,子页在下方出口渲染">
|
|
531
|
+
<router-view />
|
|
532
|
+
</Page>
|
|
533
|
+
</template>
|
|
534
|
+
<script setup>
|
|
535
|
+
defineOptions({
|
|
536
|
+
name: "BLayout",
|
|
537
|
+
inheritAttrs: false,
|
|
538
|
+
});
|
|
539
|
+
</script>
|
|
540
|
+
`,
|
|
541
|
+
);
|
|
542
|
+
|
|
543
|
+
const bSubPages = [
|
|
544
|
+
["First", "B_First"],
|
|
545
|
+
["Second", "B_Second"],
|
|
546
|
+
["Third", "B_Third"],
|
|
547
|
+
["Fourth", "B_Fourth"],
|
|
548
|
+
["Fifth", "B_Fifth"],
|
|
549
|
+
];
|
|
550
|
+
for (const [file, routeTitle] of bSubPages) {
|
|
551
|
+
write(
|
|
552
|
+
`src/views/B/${file}.vue`,
|
|
553
|
+
`<template>
|
|
554
|
+
<Page title="${routeTitle}" subtitle="B 模块子路由">
|
|
555
|
+
<p>组件 <code>${file}</code>,路由名 <code>${routeTitle}</code>。</p>
|
|
556
|
+
</Page>
|
|
557
|
+
</template>
|
|
558
|
+
<script setup>
|
|
559
|
+
defineOptions({
|
|
560
|
+
name: "B${file}",
|
|
561
|
+
inheritAttrs: false,
|
|
562
|
+
});
|
|
563
|
+
</script>
|
|
564
|
+
<style lang="scss" scoped>
|
|
565
|
+
code {
|
|
566
|
+
font-size: 0.875rem;
|
|
567
|
+
}
|
|
568
|
+
</style>
|
|
569
|
+
`,
|
|
570
|
+
);
|
|
571
|
+
}
|
|
572
|
+
|
|
355
573
|
write("src/component/.gitkeep", "");
|
|
356
574
|
write("src/assets/img/.gitkeep", "");
|
|
357
575
|
write("src/composition/.gitkeep", "");
|
|
@@ -428,6 +646,22 @@ yarn install
|
|
|
428
646
|
|
|
429
647
|
自动生成的 \`vite.config.js\` 已包含 \`unplugin-auto-import\`;\`dts: true\` 时类型默认写在项目根 \`auto-imports.d.ts\`。
|
|
430
648
|
|
|
649
|
+
## Launch(登录壳)
|
|
650
|
+
|
|
651
|
+
雏形与 **mvframe demo** 对齐:\`pinia/chip/launch.js\`(顶层 \`login\` + \`userinfo\`)、\`App.vue\`(\`<AdminEntry v-if />\` / \`<Entry v-else />\`)、\`main.js\` 内 \`import { store, pinia } from "mvframe/store"\` + \`launchRouteGuard\` + \`useAdmin\` / \`adminPermission\` / \`noaccess\`。未登录仅 \`meta.public\`;已登录进 \`Entry\` 会重定向到 \`Overview_Home\`(\`/overview\`)。若不需门禁,将 \`useAdmin\` 改为 \`false\` 并删除 \`adminPermission\` 与 \`noaccess\`。
|
|
652
|
+
|
|
653
|
+
## 路由(与 mvframe \`demo/routes.js\` 对齐)
|
|
654
|
+
|
|
655
|
+
| 路由名 | 路径 | 说明 |
|
|
656
|
+
|--------|------|------|
|
|
657
|
+
| \`Entry\` | \`/\` | 登录,\`meta.public\` |
|
|
658
|
+
| \`Overview_Home\` | \`/overview\` | 登录后默认页 |
|
|
659
|
+
| \`A_Home\` | \`/a\` | 一级模块示例 |
|
|
660
|
+
| \`B_Home\` | \`/b\` | 嵌套父级(\`Layout.vue\` + \`router-view\`) |
|
|
661
|
+
| \`B_First\` … \`B_Fifth\` | \`/b/first\` … | 子路由 |
|
|
662
|
+
|
|
663
|
+
路由集中在 \`src/router/routes.js\`(与 demo 单文件一致);\`Frame\` 的 \`menu.routes\` 由该表筛选掉 \`meta.public\` 后与注册同源。
|
|
664
|
+
|
|
431
665
|
### 报错 \`Could not resolve '@vue/shared'\`
|
|
432
666
|
|
|
433
667
|
这是 **Vue 3 自带的底层包**,一般不必手写;在 **pnpm / 严格 node_modules** 等环境下可能未被提升到可被 Vite 解析的位置。脚手架已在 \`dependencies\` 中合并 \`@vue/shared\`(与 \`vue\` 同主版本);老项目可手动执行 \`yarn add @vue/shared@^3.5\`。
|
|
@@ -453,9 +687,9 @@ yarn install
|
|
|
453
687
|
| \`src/api\` | 接口 |
|
|
454
688
|
| \`src/assets/style\` | 样式入口,main.js 已 import |
|
|
455
689
|
| \`src/assets/img\` | 静态图 |
|
|
456
|
-
| \`src/router/
|
|
457
|
-
| \`src/
|
|
458
|
-
| \`src/
|
|
690
|
+
| \`src/router/routes.js\` | 全部路由(与 \`demo/routes.js\` 结构一致)→ \`vueRouter.routes\` |
|
|
691
|
+
| \`src/views/Launch/*\` | 登录壳与登录页 |
|
|
692
|
+
| \`src/views/Overview\`、\`A\`、\`B\` | 与路由表一一对应的页面 |
|
|
459
693
|
| \`src/pinia/chip/*.js\` | 业务 store → \`storeChips: import.meta.glob(...)\` |
|
|
460
694
|
| \`src/config/index.js\` | 合并进 \`globalThis.$config\` |
|
|
461
695
|
| \`src/composition\` | 可选,与 Vite 别名 \`@cps\` 对应 |
|