mvframe 1.0.13 → 1.0.15
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 +195 -42
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.15",
|
|
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
|
@@ -128,6 +128,7 @@ import ElementPlus from "element-plus";
|
|
|
128
128
|
import "element-plus/dist/index.css";
|
|
129
129
|
import App from "./App.vue";
|
|
130
130
|
import mvframe from "mvframe";
|
|
131
|
+
import { pinia, store } from "mvframe/store";
|
|
131
132
|
import routes from "./router/index.js";
|
|
132
133
|
import appConfig from "./config/index.js";
|
|
133
134
|
import "./assets/style/index.scss";
|
|
@@ -136,9 +137,42 @@ import "mvframe/style/cpt";
|
|
|
136
137
|
|
|
137
138
|
const app = createApp(App);
|
|
138
139
|
app.use(ElementPlus);
|
|
140
|
+
|
|
141
|
+
/** \`meta.public\` 为公开路由;已登录访问登录页则进首页。与 demo 一致:\`store.launch(pinia)\` 来自 \`mvframe/store\`(与插件注入的是同一单例)。 */
|
|
142
|
+
const launchRouteGuard = (to, from, next) => {
|
|
143
|
+
if (!store.launch) {
|
|
144
|
+
next();
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
const launch = store.launch(pinia);
|
|
148
|
+
const authed = Boolean(launch.login);
|
|
149
|
+
const isPublic = to.matched.some((r) => r.meta?.public);
|
|
150
|
+
if (!authed && !isPublic) {
|
|
151
|
+
next({ name: "Entry", replace: true });
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
if (authed && to.name === "Entry") {
|
|
155
|
+
next({ name: "Home_Home", replace: true });
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
next();
|
|
159
|
+
};
|
|
160
|
+
|
|
139
161
|
app.use(mvframe, {
|
|
140
162
|
vueRouter: {
|
|
141
163
|
routes,
|
|
164
|
+
guard: (router) => {
|
|
165
|
+
router.beforeEach(launchRouteGuard);
|
|
166
|
+
},
|
|
167
|
+
useAdmin: true,
|
|
168
|
+
adminPermission: () => Boolean(store.launch(pinia).login),
|
|
169
|
+
noaccess: (to, from, next) => {
|
|
170
|
+
if (from.path === "/") {
|
|
171
|
+
next();
|
|
172
|
+
} else {
|
|
173
|
+
next({ name: "Entry" });
|
|
174
|
+
}
|
|
175
|
+
},
|
|
142
176
|
},
|
|
143
177
|
pinia: {
|
|
144
178
|
useTab: true,
|
|
@@ -153,66 +187,40 @@ app.mount("#app");
|
|
|
153
187
|
write(
|
|
154
188
|
"src/App.vue",
|
|
155
189
|
`<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>
|
|
190
|
+
<AdminEntry v-if="isLoggedIn" />
|
|
191
|
+
<Entry v-else />
|
|
164
192
|
</template>
|
|
165
193
|
<script setup>
|
|
166
|
-
import
|
|
194
|
+
import Entry from "./views/Launch/Entry.vue";
|
|
195
|
+
import AdminEntry from "./views/Launch/AdminEntry.vue";
|
|
167
196
|
|
|
168
197
|
defineOptions({
|
|
169
198
|
name: "App",
|
|
170
199
|
inheritAttrs: false,
|
|
171
200
|
});
|
|
172
201
|
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
202
|
+
const store = inject("store");
|
|
203
|
+
const launch = store.launch();
|
|
204
|
+
|
|
205
|
+
const isLoggedIn = computed(() => Boolean(launch.login));
|
|
177
206
|
</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>
|
|
207
|
+
<style lang="scss" scoped></style>
|
|
200
208
|
`,
|
|
201
209
|
);
|
|
202
210
|
|
|
203
211
|
write(
|
|
204
212
|
"src/router/baseRouter.js",
|
|
205
213
|
`/**
|
|
206
|
-
*
|
|
207
|
-
* 权限接口返回后若要合并,可在本文件导出函数内拼接,或与 adminRouter 在 index 中组合。
|
|
214
|
+
* 基础路由:登录等 \`meta.public\` 公开页(不写 public 则需登录,由 main.js 内 launch 守卫处理)。
|
|
208
215
|
*/
|
|
209
216
|
export default [
|
|
210
217
|
{
|
|
211
218
|
path: "/",
|
|
212
|
-
name: "
|
|
213
|
-
component: () => import("@/views/
|
|
219
|
+
name: "Entry",
|
|
220
|
+
component: () => import("@/views/Launch/LoginPage.vue"),
|
|
214
221
|
meta: {
|
|
215
|
-
title: "
|
|
222
|
+
title: "Login",
|
|
223
|
+
public: true,
|
|
216
224
|
},
|
|
217
225
|
},
|
|
218
226
|
];
|
|
@@ -222,10 +230,18 @@ export default [
|
|
|
222
230
|
write(
|
|
223
231
|
"src/router/adminRouter.js",
|
|
224
232
|
`/**
|
|
225
|
-
* 后台 /
|
|
233
|
+
* 后台 / 业务路由(需登录后访问):可按接口权限过滤后追加,或整段替换。
|
|
226
234
|
* meta.admin 等字段可与 mvframe vueRouter.useAdmin / adminPermission 配合。
|
|
227
235
|
*/
|
|
228
236
|
export default [
|
|
237
|
+
{
|
|
238
|
+
path: "/home",
|
|
239
|
+
name: "Home_Home",
|
|
240
|
+
component: () => import("@/views/Home/Home.vue"),
|
|
241
|
+
meta: {
|
|
242
|
+
title: "Home",
|
|
243
|
+
},
|
|
244
|
+
},
|
|
229
245
|
{
|
|
230
246
|
path: "/admin",
|
|
231
247
|
name: "Admin_Dashboard",
|
|
@@ -288,6 +304,136 @@ export default {
|
|
|
288
304
|
`,
|
|
289
305
|
);
|
|
290
306
|
|
|
307
|
+
write(
|
|
308
|
+
"src/pinia/chip/launch.js",
|
|
309
|
+
`/** 登录态:与 demo/pinia/launch.js、main.js launchRouteGuard、LoginPage 一致 */
|
|
310
|
+
export default {
|
|
311
|
+
state: () => ({
|
|
312
|
+
userinfo: {},
|
|
313
|
+
login: false,
|
|
314
|
+
}),
|
|
315
|
+
actions: {
|
|
316
|
+
/** 登录成功:合并写入 userinfo,并置 login: true */
|
|
317
|
+
setLogin(payload = {}) {
|
|
318
|
+
this.userinfo = {
|
|
319
|
+
...this.userinfo,
|
|
320
|
+
...payload,
|
|
321
|
+
};
|
|
322
|
+
this.login = true;
|
|
323
|
+
},
|
|
324
|
+
clearLogin() {
|
|
325
|
+
this.userinfo = {};
|
|
326
|
+
this.login = false;
|
|
327
|
+
},
|
|
328
|
+
},
|
|
329
|
+
};
|
|
330
|
+
`,
|
|
331
|
+
);
|
|
332
|
+
|
|
333
|
+
write(
|
|
334
|
+
"src/views/Launch/Entry.vue",
|
|
335
|
+
`<template>
|
|
336
|
+
<router-view />
|
|
337
|
+
</template>
|
|
338
|
+
<script setup>
|
|
339
|
+
defineOptions({
|
|
340
|
+
name: "LaunchEntry",
|
|
341
|
+
inheritAttrs: false,
|
|
342
|
+
});
|
|
343
|
+
</script>
|
|
344
|
+
`,
|
|
345
|
+
);
|
|
346
|
+
|
|
347
|
+
write(
|
|
348
|
+
"src/views/Launch/AdminEntry.vue",
|
|
349
|
+
`<template>
|
|
350
|
+
<Frame class="App wp100 vh100" :menu="frameMenu" :page="{}">
|
|
351
|
+
<template #logo>
|
|
352
|
+
<span class="logo-placeholder">Logo</span>
|
|
353
|
+
</template>
|
|
354
|
+
<template #logomini>
|
|
355
|
+
<span class="logo-mini">L</span>
|
|
356
|
+
</template>
|
|
357
|
+
</Frame>
|
|
358
|
+
</template>
|
|
359
|
+
<script setup>
|
|
360
|
+
import routes from "@/router/index.js";
|
|
361
|
+
|
|
362
|
+
defineOptions({
|
|
363
|
+
name: "AdminEntry",
|
|
364
|
+
inheritAttrs: false,
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
const frameMenu = {
|
|
368
|
+
iconClass: "imicon",
|
|
369
|
+
routes: routes.filter((r) => r.meta?.public !== true),
|
|
370
|
+
};
|
|
371
|
+
</script>
|
|
372
|
+
<style lang="scss" scoped>
|
|
373
|
+
.logo-placeholder {
|
|
374
|
+
display: inline-block;
|
|
375
|
+
min-width: 7.5rem;
|
|
376
|
+
padding: 0.25rem 0.5rem;
|
|
377
|
+
border-radius: 0.25rem;
|
|
378
|
+
background: var(--color-primary, #16b1ff);
|
|
379
|
+
color: #fff;
|
|
380
|
+
font-size: 0.875rem;
|
|
381
|
+
}
|
|
382
|
+
.logo-mini {
|
|
383
|
+
display: inline-flex;
|
|
384
|
+
width: 1.875rem;
|
|
385
|
+
height: 1.875rem;
|
|
386
|
+
align-items: center;
|
|
387
|
+
justify-content: center;
|
|
388
|
+
border-radius: 0.25rem;
|
|
389
|
+
background: var(--color-green, #20c997);
|
|
390
|
+
color: #fff;
|
|
391
|
+
font-size: 0.75rem;
|
|
392
|
+
}
|
|
393
|
+
</style>
|
|
394
|
+
`,
|
|
395
|
+
);
|
|
396
|
+
|
|
397
|
+
write(
|
|
398
|
+
"src/views/Launch/LoginPage.vue",
|
|
399
|
+
`<template>
|
|
400
|
+
<div class="LoginPage wp100 vh100">
|
|
401
|
+
<Login
|
|
402
|
+
:form-fileds="formFileds"
|
|
403
|
+
:login-methods="[0]"
|
|
404
|
+
@submit="onSubmit"
|
|
405
|
+
/>
|
|
406
|
+
</div>
|
|
407
|
+
</template>
|
|
408
|
+
<script setup>
|
|
409
|
+
defineOptions({
|
|
410
|
+
name: "LoginPage",
|
|
411
|
+
inheritAttrs: false,
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
const router = useRouter();
|
|
415
|
+
const store = inject("store");
|
|
416
|
+
const launch = store.launch();
|
|
417
|
+
|
|
418
|
+
const onSubmit = (payload) => {
|
|
419
|
+
launch.setLogin(payload ?? {});
|
|
420
|
+
router.replace({ name: "Home_Home" }).catch(() => {});
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
/** 与 mvframe demo 一致:自定义提交字段名 account / pwd */
|
|
424
|
+
const formFileds = {
|
|
425
|
+
username: { valueKey: "account" },
|
|
426
|
+
password: { valueKey: "pwd" },
|
|
427
|
+
};
|
|
428
|
+
</script>
|
|
429
|
+
<style lang="scss" scoped>
|
|
430
|
+
.LoginPage {
|
|
431
|
+
box-sizing: border-box;
|
|
432
|
+
}
|
|
433
|
+
</style>
|
|
434
|
+
`,
|
|
435
|
+
);
|
|
436
|
+
|
|
291
437
|
write(
|
|
292
438
|
"src/api/index.js",
|
|
293
439
|
`/**
|
|
@@ -335,7 +481,10 @@ code {
|
|
|
335
481
|
"src/views/Home/Home.vue",
|
|
336
482
|
`<template>
|
|
337
483
|
<Page title="Home" subtitle="MVFrame 雏形页,可从此扩展 views 模块">
|
|
338
|
-
<p
|
|
484
|
+
<p>
|
|
485
|
+
登录入口为 <code>baseRouter</code> 的 <code>Entry</code>(<code>meta.public</code>);业务路由见
|
|
486
|
+
<code>adminRouter.js</code>,合并于 <code>router/index.js</code>,与侧栏菜单同源。
|
|
487
|
+
</p>
|
|
339
488
|
</Page>
|
|
340
489
|
</template>
|
|
341
490
|
<script setup>
|
|
@@ -428,6 +577,10 @@ yarn install
|
|
|
428
577
|
|
|
429
578
|
自动生成的 \`vite.config.js\` 已包含 \`unplugin-auto-import\`;\`dts: true\` 时类型默认写在项目根 \`auto-imports.d.ts\`。
|
|
430
579
|
|
|
580
|
+
## Launch(登录壳)
|
|
581
|
+
|
|
582
|
+
雏形与 **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\` 会重定向到 \`Home_Home\`。若不需门禁,将 \`useAdmin\` 改为 \`false\` 并删除 \`adminPermission\` 与 \`noaccess\`。
|
|
583
|
+
|
|
431
584
|
### 报错 \`Could not resolve '@vue/shared'\`
|
|
432
585
|
|
|
433
586
|
这是 **Vue 3 自带的底层包**,一般不必手写;在 **pnpm / 严格 node_modules** 等环境下可能未被提升到可被 Vite 解析的位置。脚手架已在 \`dependencies\` 中合并 \`@vue/shared\`(与 \`vue\` 同主版本);老项目可手动执行 \`yarn add @vue/shared@^3.5\`。
|