@axewo/fin-micro 0.0.0-stage → 2.0.0
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 +223 -2
- package/cli/index.mjs +240 -0
- package/dist/browser.cjs +1 -0
- package/dist/browser.mjs +169 -0
- package/dist/core.cjs +1 -0
- package/dist/core.mjs +654 -0
- package/dist/iframe.cjs +1 -0
- package/dist/iframe.mjs +146 -0
- package/dist/index.cjs +1 -0
- package/dist/index.mjs +35 -0
- package/dist/router-view-impl-B1NzCGKV.mjs +76 -0
- package/dist/router-view-impl-BvOToV_Z.cjs +1 -0
- package/dist/runtime.cjs +1 -0
- package/dist/runtime.mjs +149 -0
- package/dist/sdk.cjs +1 -0
- package/dist/sdk.mjs +118 -0
- package/dist/types/browser/index.d.ts +46 -0
- package/dist/types/core/index.d.ts +63 -0
- package/dist/types/iframe/index.d.ts +77 -0
- package/dist/types/index.d.ts +8 -0
- package/dist/types/runtime/index.d.ts +21 -0
- package/dist/types/runtime/lifecycle.d.ts +23 -0
- package/dist/types/sdk/index.d.ts +397 -0
- package/dist/types/vue/index.d.ts +102 -0
- package/dist/types/vue/router-view-impl.d.ts +41 -0
- package/dist/types/vue/router-view.d.ts +42 -0
- package/dist/types/vue/tab-context.d.ts +6 -0
- package/dist/vue.cjs +1 -0
- package/dist/vue.mjs +180 -0
- package/package.json +91 -3
- package/scaffold/base/index.html +12 -0
- package/scaffold/base/package.json.template +33 -0
- package/scaffold/base/scripts/check-runtime.mjs +43 -0
- package/scaffold/base/src/components/.gitkeep +1 -0
- package/scaffold/base/src/components/AppProviders.vue +25 -0
- package/scaffold/base/src/hooks/.gitkeep +1 -0
- package/scaffold/base/src/layout/.gitkeep +1 -0
- package/scaffold/base/src/locales/.gitkeep +1 -0
- package/scaffold/base/src/locales/en.ts +12 -0
- package/scaffold/base/src/locales/index.ts +13 -0
- package/scaffold/base/src/locales/zh-CN.ts +12 -0
- package/scaffold/base/src/main.ts +22 -0
- package/scaffold/base/src/router/.gitkeep +1 -0
- package/scaffold/base/src/router/demo.ts +6 -0
- package/scaffold/base/src/router/index.ts +13 -0
- package/scaffold/base/src/stores/.gitkeep +1 -0
- package/scaffold/base/src/stores/demo.ts +12 -0
- package/scaffold/base/src/stores/index.ts +11 -0
- package/scaffold/base/src/styles/index.css +18 -0
- package/scaffold/base/src/types/.gitkeep +1 -0
- package/scaffold/base/src/utils/.gitkeep +1 -0
- package/scaffold/base/src/utils/appConfig.ts +49 -0
- package/scaffold/base/src/utils/errorUtils.ts +92 -0
- package/scaffold/base/src/utils/requestUtils.ts +144 -0
- package/scaffold/base/src/views/.gitkeep +1 -0
- package/scaffold/base/src/views/DemoView.vue +26 -0
- package/scaffold/base/tsconfig.json +16 -0
- package/scaffold/base/vite.config.ts +8 -0
- package/scaffold/host/.env.example +13 -0
- package/scaffold/host/src/App.vue +4 -0
- package/scaffold/host/src/bootstrap.ts +50 -0
- package/scaffold/host/src/hooks/usePlatformSession.ts +91 -0
- package/scaffold/host/src/layout/HostHeader.vue +69 -0
- package/scaffold/host/src/layout/HostLayout.vue +168 -0
- package/scaffold/host/src/layout/HostMenu.vue +15 -0
- package/scaffold/host/src/layout/HostTabs.vue +47 -0
- package/scaffold/host/src/layout/HostWorkspace.vue +26 -0
- package/scaffold/host/src/localPages.ts +45 -0
- package/scaffold/host/src/platform.ts +239 -0
- package/scaffold/host/src/router/guards.ts +128 -0
- package/scaffold/host/src/router/index.ts +25 -0
- package/scaffold/host/src/router/targets.ts +30 -0
- package/scaffold/host/src/router/workspace.ts +63 -0
- package/scaffold/host/src/styles/shell.css +761 -0
- package/scaffold/host/src/types/platform.ts +12 -0
- package/scaffold/host/src/utils/request.ts +42 -0
- package/scaffold/host/src/views/HomeView.vue +19 -0
- package/scaffold/host/src/views/LoginRoute.vue +16 -0
- package/scaffold/host/src/views/LoginView.vue +88 -0
- package/scaffold/host/src/views/NotFoundView.vue +1 -0
- package/scaffold/host/src/views/ProfileView.vue +24 -0
- package/scaffold/host/src/views/WorkspaceRoute.vue +11 -0
- package/scaffold/vue/src/App.vue +24 -0
- package/scaffold/vue/src/bootstrap.ts +67 -0
- package/scaffold/vue/src/bridge/auth.ts +23 -0
- package/scaffold/vue/src/bridge/session.ts +16 -0
- package/scaffold/vue/src/main.ts +13 -0
- package/scaffold/vue/src/router/index.ts +16 -0
- package/scaffold/vue/src/views/DetailView.vue +57 -0
- package/scaffold/vue/src/views/ListView.vue +55 -0
- package/scaffold/vue/src/views/NotFoundView.vue +1 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { SlotsType } from 'vue';
|
|
2
|
+
import { MicroTab } from '../sdk';
|
|
3
|
+
import { MicroRouteErrorCode } from './router-view';
|
|
4
|
+
/**
|
|
5
|
+
* 页签内的 Vue Router 页面出口,必须位于 MicroTabCache 内,并已通过 app.use(router) 安装 Vue Router 4.5+。
|
|
6
|
+
* 固定使用创建时的 tab.key/route;支持路由 props、嵌套 RouterView、命名视图及 useRoute 独立上下文。
|
|
7
|
+
* loading 插槽接收 { tab },error 插槽接收 { tab, code, retry };不提供插槽时也会展示加载/错误与重试。
|
|
8
|
+
*
|
|
9
|
+
* 这里只解析与加载页面,不启动导航事务、不执行 Vue Router 导航守卫、不修改宿主页签和权限。
|
|
10
|
+
* redirect 不替代宿主 canOpen;业务必须保证重定向目标属于该应用已授权页面,鉴权仍由宿主/core 负责。
|
|
11
|
+
* 页面定位等价于从 START_LOCATION 发起解析:相对 redirect 以应用根为基准,函数 redirect 按公开契约只接收目标 route。
|
|
12
|
+
* 建议 redirect 使用绝对 path 或 name;不会把其他页签的全局 currentRoute 当作本页的导航来源。
|
|
13
|
+
* 页面跳转和离页确认应使用页面 SDK;不要通过 RouterLink/router.push 把全局路由当作宿主页签导航。
|
|
14
|
+
* 懒加载无法取消网络请求;卸载后忽略迟到结果,不再写入组件状态。模块加载后的组件 setup/render 异常由 Vue 错误边界处理。
|
|
15
|
+
*/
|
|
16
|
+
export declare const MicroRouteViewImpl: import('vue').DefineComponent<import('vue').ExtractPropTypes<{
|
|
17
|
+
name: {
|
|
18
|
+
type: StringConstructor;
|
|
19
|
+
default: string;
|
|
20
|
+
};
|
|
21
|
+
}>, () => import('vue').VNode<import('vue').RendererNode, import('vue').RendererElement, {
|
|
22
|
+
[key: string]: any;
|
|
23
|
+
}> | import('vue').VNode<import('vue').RendererNode, import('vue').RendererElement, {
|
|
24
|
+
[key: string]: any;
|
|
25
|
+
}>[], {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<import('vue').ExtractPropTypes<{
|
|
26
|
+
name: {
|
|
27
|
+
type: StringConstructor;
|
|
28
|
+
default: string;
|
|
29
|
+
};
|
|
30
|
+
}>> & Readonly<{}>, {
|
|
31
|
+
name: string;
|
|
32
|
+
}, SlotsType<{
|
|
33
|
+
loading: {
|
|
34
|
+
tab: MicroTab;
|
|
35
|
+
};
|
|
36
|
+
error: {
|
|
37
|
+
tab: MicroTab;
|
|
38
|
+
code: MicroRouteErrorCode;
|
|
39
|
+
retry: () => Promise<void>;
|
|
40
|
+
};
|
|
41
|
+
}>, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, any>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { SlotsType } from 'vue';
|
|
2
|
+
import { MicroTab } from '../sdk';
|
|
3
|
+
/** 稳定的脱敏错误分类;不包含模块地址、原始异常消息或堆栈。 */
|
|
4
|
+
export type MicroRouteErrorCode = 'NOT_FOUND' | 'LOAD_FAILED' | 'REDIRECT_LOOP' | 'RESOLVE_FAILED';
|
|
5
|
+
/**
|
|
6
|
+
* 固定页签的 Vue Router 页面出口,需放在 MicroTabCache 内,并已安装 Vue Router 4.5+。
|
|
7
|
+
* name 指定命名视图,默认 default;loading 插槽接收 { tab },error 接收 { tab, code, retry }。
|
|
8
|
+
* 不提供插槽时仍显示加载、错误和重试按钮;retry 返回 Promise<void>,重复点击不会并发加载。
|
|
9
|
+
*
|
|
10
|
+
* 仅实际渲染本组件时才动态加载路由实现;旧的 /vue 缓存和 SDK 用户不必安装可选 vue-router peer。
|
|
11
|
+
* 内部组件以同步 setup 注入固定 tab 和路由上下文,不依赖 async setup 或 Suspense。
|
|
12
|
+
* 页面只解析与加载,不执行 router.push/replace 或导航守卫;导航、canOpen 和离页确认仍由宿主/core/SDK负责。
|
|
13
|
+
* redirect 不替代 canOpen,也不会更改宿主 tab.route;相对 redirect 与函数 from 均以 START_LOCATION 为来源。
|
|
14
|
+
* 支持 Composition API useRoute 的独立快照;Options API this.$route / 模板 $route 仍读全局路由,不支持页签隔离。
|
|
15
|
+
* 懒加载请求不可取消,卸载后忽略迟到结果;组件 setup/render 异常交由 Vue 错误边界处理。
|
|
16
|
+
*/
|
|
17
|
+
export declare const MicroRouteView: import('vue').DefineComponent<import('vue').ExtractPropTypes<{
|
|
18
|
+
name: {
|
|
19
|
+
type: StringConstructor;
|
|
20
|
+
default: string;
|
|
21
|
+
};
|
|
22
|
+
}>, () => import('vue').VNode<import('vue').RendererNode, import('vue').RendererElement, {
|
|
23
|
+
[key: string]: any;
|
|
24
|
+
}> | import('vue').VNode<import('vue').RendererNode, import('vue').RendererElement, {
|
|
25
|
+
[key: string]: any;
|
|
26
|
+
}>[], {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<import('vue').ExtractPropTypes<{
|
|
27
|
+
name: {
|
|
28
|
+
type: StringConstructor;
|
|
29
|
+
default: string;
|
|
30
|
+
};
|
|
31
|
+
}>> & Readonly<{}>, {
|
|
32
|
+
name: string;
|
|
33
|
+
}, SlotsType<{
|
|
34
|
+
loading: {
|
|
35
|
+
tab: MicroTab;
|
|
36
|
+
};
|
|
37
|
+
error: {
|
|
38
|
+
tab: MicroTab;
|
|
39
|
+
code: MicroRouteErrorCode;
|
|
40
|
+
retry: () => Promise<void>;
|
|
41
|
+
};
|
|
42
|
+
}>, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, any>;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { InjectionKey } from 'vue';
|
|
2
|
+
import { MicroTab } from '../sdk';
|
|
3
|
+
/** 页签边界注入创建时的身份,不会随宿主活动页改变。 */
|
|
4
|
+
export declare const microTabKey: InjectionKey<MicroTab>;
|
|
5
|
+
/** 在缓存页面 setup 中取得创建时的页签快照;必须位于 MicroTabCache 默认插槽内。 */
|
|
6
|
+
export declare function useMicroTab(): MicroTab;
|
package/dist/vue.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("vue"),f=require("./sdk.cjs"),d=Symbol("micro-tab");function m(){const t=e.inject(d);if(!t)throw new Error("useMicroTab 必须在 MicroTabCache 内调用");return t}const y=e.defineComponent({name:"MicroRouteView",props:{name:{type:String,default:"default"}},slots:Object,setup(t,{slots:n}){const i=m(),a=e.shallowRef(),r=e.shallowRef(!1);let u=!1,c=!1;async function o(){if(!(u||c)){c=!0,r.value=!1;try{const l=await Promise.resolve().then(()=>require("./router-view-impl-BvOToV_Z.cjs"));u||(a.value=l.MicroRouteViewImpl)}catch{u||(r.value=!0)}finally{c=!1}}}return e.onBeforeUnmount(()=>{u=!0}),o(),()=>{var l,s;return a.value?e.h(a.value,{name:t.name},n):r.value?((l=n.error)==null?void 0:l.call(n,{tab:i,code:"LOAD_FAILED",retry:o}))??e.h("div",{role:"alert","data-micro-route-error":"LOAD_FAILED"},[e.h("p","页面加载失败(LOAD_FAILED)"),e.h("button",{type:"button",onClick:o},"重新加载")]):((s=n.loading)==null?void 0:s.call(n,{tab:i}))??e.h("p",{role:"status"},"页面加载中…")}}});function v(t){return{hasPermission:n=>f.hasPermission(e.toValue(t),n),hasAny:n=>f.hasAny(e.toValue(t),n),hasAll:n=>f.hasAll(e.toValue(t),n),can:n=>e.computed(()=>f.hasPermission(e.toValue(t),e.toValue(n)))}}const b=e.defineComponent({name:"MicroAppContainer",props:{appId:{type:String,required:!0},runtime:{type:Object,required:!0},retryDisabled:Boolean},setup(t){const n=e.shallowRef(),i=e.shallowRef(""),a=e.shallowRef(!1);let r=!1;async function u(c=!1){if(!(r||a.value||!c&&t.retryDisabled)){a.value=!0,i.value="";try{await t.runtime.mount(t.appId,n.value)}catch{r||(i.value="子应用加载失败,请重试或联系管理员")}finally{r||(a.value=!1)}}}return e.onMounted(()=>{u(!0)}),e.onBeforeUnmount(()=>{r=!0,t.runtime.unmount(t.appId).catch(()=>console.error("[fin-micro] 子应用资源释放失败"))}),()=>e.h("section",{style:{height:"100%",minHeight:"420px"},"aria-busy":a.value},[i.value?e.h("div",{role:"alert"},[e.h("p",i.value),e.h("button",{type:"button",disabled:t.retryDisabled||a.value,onClick:()=>{u()}},"重新加载")]):null,a.value?e.h("p",{role:"status"},"子应用加载中…"):null,e.h("div",{ref:n,style:{height:"100%",minHeight:"420px"}})])}});function p(t){const n=e.shallowRef(t.getState()),i=t.subscribe(a=>{n.value=a});return e.onBeforeUnmount(i),n}const M=e.defineComponent({name:"MicroTabBoundary",props:{tab:{type:Object,required:!0},active:Boolean},setup(t,{slots:n}){const i=Object.freeze({...t.tab});e.provide(d,i);const a=e.defineComponent({name:"MicroTabContent",setup:()=>()=>{var r;return(r=n.default)==null?void 0:r.call(n,{tab:i})}});return()=>e.h("section",{hidden:!t.active,"data-micro-tab":i.key},[e.h(e.KeepAlive,null,{default:()=>t.active?e.h(a):null})])}}),w=e.defineComponent({name:"MicroTabCache",props:{tabs:{type:Array,required:!0},activeKey:String,max:{type:Number,default:20}},setup(t,{slots:n}){const i=e.shallowRef([]);return e.watch(()=>[t.tabs,t.activeKey,t.max],()=>{const a=new Set(t.tabs.map(o=>o.key));let r=i.value.filter(o=>a.has(o.key));const u=t.tabs.find(o=>o.key===t.activeKey);u&&(r=[...r.filter(o=>o.key!==u.key),u]);const c=Number.isFinite(t.max)?Math.max(1,Math.floor(t.max)):20;for(;r.length>c;){const o=r.findIndex(l=>l.key!==t.activeKey);if(o<0)break;r.splice(o,1)}i.value=r},{immediate:!0,flush:"sync"}),()=>i.value.map(a=>e.h(M,{key:a.key,tab:a,active:a.key===t.activeKey},n))}});function g(t,n,i=()=>console.error("[fin-micro] 路由同步失败")){let a=!1,r,u=!1,c=0;async function o(){if(!u){u=!0;try{let s=-1;for(;!a&&r&&s!==c;){s=c;try{await n.replace(r.route,r)}catch(h){try{i(h)}catch{}}}}finally{u=!1}}}const l=t.ready(s=>{r=s,c++,o()});return()=>{a=!0,l()}}function k(t,n){const i=t.page(n.key);return e.onBeforeUnmount(i.dispose),i}exports.MicroAppContainer=b;exports.MicroRouteView=y;exports.MicroTabCache=w;exports.connectMicroRouter=g;exports.useMicroHost=p;exports.useMicroPage=k;exports.useMicroTab=m;exports.usePermission=v;
|
package/dist/vue.mjs
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { inject as p, defineComponent as y, shallowRef as d, onBeforeUnmount as m, h as u, onMounted as M, provide as g, KeepAlive as k, watch as A, computed as w, toValue as s } from "vue";
|
|
2
|
+
import { hasPermission as v, hasAll as x, hasAny as C } from "./sdk.mjs";
|
|
3
|
+
const b = Symbol("micro-tab");
|
|
4
|
+
function D() {
|
|
5
|
+
const e = p(b);
|
|
6
|
+
if (!e) throw new Error("useMicroTab 必须在 MicroTabCache 内调用");
|
|
7
|
+
return e;
|
|
8
|
+
}
|
|
9
|
+
const O = y({
|
|
10
|
+
name: "MicroRouteView",
|
|
11
|
+
props: { name: { type: String, default: "default" } },
|
|
12
|
+
slots: Object,
|
|
13
|
+
setup(e, { slots: t }) {
|
|
14
|
+
const r = D(), a = d(), n = d(!1);
|
|
15
|
+
let o = !1, c = !1;
|
|
16
|
+
async function i() {
|
|
17
|
+
if (!(o || c)) {
|
|
18
|
+
c = !0, n.value = !1;
|
|
19
|
+
try {
|
|
20
|
+
const l = await import("./router-view-impl-B1NzCGKV.mjs");
|
|
21
|
+
o || (a.value = l.MicroRouteViewImpl);
|
|
22
|
+
} catch {
|
|
23
|
+
o || (n.value = !0);
|
|
24
|
+
} finally {
|
|
25
|
+
c = !1;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return m(() => {
|
|
30
|
+
o = !0;
|
|
31
|
+
}), i(), () => {
|
|
32
|
+
var l, f;
|
|
33
|
+
return a.value ? u(a.value, { name: e.name }, t) : n.value ? ((l = t.error) == null ? void 0 : l.call(t, { tab: r, code: "LOAD_FAILED", retry: i })) ?? u("div", { role: "alert", "data-micro-route-error": "LOAD_FAILED" }, [
|
|
34
|
+
u("p", "页面加载失败(LOAD_FAILED)"),
|
|
35
|
+
u("button", { type: "button", onClick: i }, "重新加载")
|
|
36
|
+
]) : ((f = t.loading) == null ? void 0 : f.call(t, { tab: r })) ?? u("p", { role: "status" }, "页面加载中…");
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
function L(e) {
|
|
41
|
+
return {
|
|
42
|
+
hasPermission: (t) => v(s(e), t),
|
|
43
|
+
hasAny: (t) => C(s(e), t),
|
|
44
|
+
hasAll: (t) => x(s(e), t),
|
|
45
|
+
can: (t) => w(() => v(s(e), s(t)))
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
const S = y({
|
|
49
|
+
name: "MicroAppContainer",
|
|
50
|
+
props: {
|
|
51
|
+
appId: { type: String, required: !0 },
|
|
52
|
+
runtime: { type: Object, required: !0 },
|
|
53
|
+
retryDisabled: Boolean
|
|
54
|
+
},
|
|
55
|
+
setup(e) {
|
|
56
|
+
const t = d(), r = d(""), a = d(!1);
|
|
57
|
+
let n = !1;
|
|
58
|
+
async function o(c = !1) {
|
|
59
|
+
if (!(n || a.value || !c && e.retryDisabled)) {
|
|
60
|
+
a.value = !0, r.value = "";
|
|
61
|
+
try {
|
|
62
|
+
await e.runtime.mount(e.appId, t.value);
|
|
63
|
+
} catch {
|
|
64
|
+
n || (r.value = "子应用加载失败,请重试或联系管理员");
|
|
65
|
+
} finally {
|
|
66
|
+
n || (a.value = !1);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return M(() => {
|
|
71
|
+
o(!0);
|
|
72
|
+
}), m(() => {
|
|
73
|
+
n = !0, e.runtime.unmount(e.appId).catch(() => console.error("[fin-micro] 子应用资源释放失败"));
|
|
74
|
+
}), () => u("section", { style: { height: "100%", minHeight: "420px" }, "aria-busy": a.value }, [
|
|
75
|
+
r.value ? u("div", { role: "alert" }, [
|
|
76
|
+
u("p", r.value),
|
|
77
|
+
u("button", { type: "button", disabled: e.retryDisabled || a.value, onClick: () => {
|
|
78
|
+
o();
|
|
79
|
+
} }, "重新加载")
|
|
80
|
+
]) : null,
|
|
81
|
+
a.value ? u("p", { role: "status" }, "子应用加载中…") : null,
|
|
82
|
+
u("div", { ref: t, style: { height: "100%", minHeight: "420px" } })
|
|
83
|
+
]);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
function j(e) {
|
|
87
|
+
const t = d(e.getState()), r = e.subscribe((a) => {
|
|
88
|
+
t.value = a;
|
|
89
|
+
});
|
|
90
|
+
return m(r), t;
|
|
91
|
+
}
|
|
92
|
+
const T = y({
|
|
93
|
+
name: "MicroTabBoundary",
|
|
94
|
+
props: {
|
|
95
|
+
tab: { type: Object, required: !0 },
|
|
96
|
+
active: Boolean
|
|
97
|
+
},
|
|
98
|
+
setup(e, { slots: t }) {
|
|
99
|
+
const r = Object.freeze({ ...e.tab });
|
|
100
|
+
g(b, r);
|
|
101
|
+
const a = y({ name: "MicroTabContent", setup: () => () => {
|
|
102
|
+
var n;
|
|
103
|
+
return (n = t.default) == null ? void 0 : n.call(t, { tab: r });
|
|
104
|
+
} });
|
|
105
|
+
return () => u("section", { hidden: !e.active, "data-micro-tab": r.key }, [
|
|
106
|
+
u(k, null, { default: () => e.active ? u(a) : null })
|
|
107
|
+
]);
|
|
108
|
+
}
|
|
109
|
+
}), B = y({
|
|
110
|
+
name: "MicroTabCache",
|
|
111
|
+
props: {
|
|
112
|
+
tabs: { type: Array, required: !0 },
|
|
113
|
+
activeKey: String,
|
|
114
|
+
max: { type: Number, default: 20 }
|
|
115
|
+
},
|
|
116
|
+
setup(e, { slots: t }) {
|
|
117
|
+
const r = d([]);
|
|
118
|
+
return A(() => [e.tabs, e.activeKey, e.max], () => {
|
|
119
|
+
const a = new Set(e.tabs.map((i) => i.key));
|
|
120
|
+
let n = r.value.filter((i) => a.has(i.key));
|
|
121
|
+
const o = e.tabs.find((i) => i.key === e.activeKey);
|
|
122
|
+
o && (n = [...n.filter((i) => i.key !== o.key), o]);
|
|
123
|
+
const c = Number.isFinite(e.max) ? Math.max(1, Math.floor(e.max)) : 20;
|
|
124
|
+
for (; n.length > c; ) {
|
|
125
|
+
const i = n.findIndex((l) => l.key !== e.activeKey);
|
|
126
|
+
if (i < 0) break;
|
|
127
|
+
n.splice(i, 1);
|
|
128
|
+
}
|
|
129
|
+
r.value = n;
|
|
130
|
+
}, { immediate: !0, flush: "sync" }), () => r.value.map((a) => u(T, {
|
|
131
|
+
key: a.key,
|
|
132
|
+
tab: a,
|
|
133
|
+
active: a.key === e.activeKey
|
|
134
|
+
}, t));
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
function R(e, t, r = () => console.error("[fin-micro] 路由同步失败")) {
|
|
138
|
+
let a = !1, n, o = !1, c = 0;
|
|
139
|
+
async function i() {
|
|
140
|
+
if (!o) {
|
|
141
|
+
o = !0;
|
|
142
|
+
try {
|
|
143
|
+
let f = -1;
|
|
144
|
+
for (; !a && n && f !== c; ) {
|
|
145
|
+
f = c;
|
|
146
|
+
try {
|
|
147
|
+
await t.replace(n.route, n);
|
|
148
|
+
} catch (h) {
|
|
149
|
+
try {
|
|
150
|
+
r(h);
|
|
151
|
+
} catch {
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
} finally {
|
|
156
|
+
o = !1;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
const l = e.ready((f) => {
|
|
161
|
+
n = f, c++, i();
|
|
162
|
+
});
|
|
163
|
+
return () => {
|
|
164
|
+
a = !0, l();
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
function q(e, t) {
|
|
168
|
+
const r = e.page(t.key);
|
|
169
|
+
return m(r.dispose), r;
|
|
170
|
+
}
|
|
171
|
+
export {
|
|
172
|
+
S as MicroAppContainer,
|
|
173
|
+
O as MicroRouteView,
|
|
174
|
+
B as MicroTabCache,
|
|
175
|
+
R as connectMicroRouter,
|
|
176
|
+
j as useMicroHost,
|
|
177
|
+
q as useMicroPage,
|
|
178
|
+
D as useMicroTab,
|
|
179
|
+
L as usePermission
|
|
180
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,94 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axewo/fin-micro",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.cjs",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/types/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/types/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.cjs"
|
|
13
|
+
},
|
|
14
|
+
"./browser": {
|
|
15
|
+
"types": "./dist/types/browser/index.d.ts",
|
|
16
|
+
"import": "./dist/browser.mjs",
|
|
17
|
+
"require": "./dist/browser.cjs"
|
|
18
|
+
},
|
|
19
|
+
"./iframe": {
|
|
20
|
+
"types": "./dist/types/iframe/index.d.ts",
|
|
21
|
+
"import": "./dist/iframe.mjs",
|
|
22
|
+
"require": "./dist/iframe.cjs"
|
|
23
|
+
},
|
|
24
|
+
"./sdk": {
|
|
25
|
+
"types": "./dist/types/sdk/index.d.ts",
|
|
26
|
+
"import": "./dist/sdk.mjs",
|
|
27
|
+
"require": "./dist/sdk.cjs"
|
|
28
|
+
},
|
|
29
|
+
"./core": {
|
|
30
|
+
"types": "./dist/types/core/index.d.ts",
|
|
31
|
+
"import": "./dist/core.mjs",
|
|
32
|
+
"require": "./dist/core.cjs"
|
|
33
|
+
},
|
|
34
|
+
"./vue": {
|
|
35
|
+
"types": "./dist/types/vue/index.d.ts",
|
|
36
|
+
"import": "./dist/vue.mjs",
|
|
37
|
+
"require": "./dist/vue.cjs"
|
|
38
|
+
},
|
|
39
|
+
"./runtime": {
|
|
40
|
+
"types": "./dist/types/runtime/index.d.ts",
|
|
41
|
+
"import": "./dist/runtime.mjs",
|
|
42
|
+
"require": "./dist/runtime.cjs"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"bin": {
|
|
46
|
+
"fin-micro": "./cli/index.mjs"
|
|
47
|
+
},
|
|
48
|
+
"files": [
|
|
49
|
+
"dist/**/*.mjs",
|
|
50
|
+
"dist/**/*.cjs",
|
|
51
|
+
"dist/**/*.d.ts",
|
|
52
|
+
"cli/index.mjs",
|
|
53
|
+
"scaffold"
|
|
54
|
+
],
|
|
55
|
+
"engines": {
|
|
56
|
+
"node": ">=18.0.0"
|
|
57
|
+
},
|
|
58
|
+
"publishConfig": {
|
|
59
|
+
"access": "public"
|
|
60
|
+
},
|
|
61
|
+
"sideEffects": false,
|
|
62
|
+
"peerDependencies": {
|
|
63
|
+
"vue": "^3.4.0",
|
|
64
|
+
"vue-router": "^4.5.0"
|
|
65
|
+
},
|
|
66
|
+
"peerDependenciesMeta": {
|
|
67
|
+
"vue": {
|
|
68
|
+
"optional": true
|
|
69
|
+
},
|
|
70
|
+
"vue-router": {
|
|
71
|
+
"optional": true
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
"dependencies": {
|
|
75
|
+
"wujie": "2.1.0"
|
|
76
|
+
},
|
|
77
|
+
"devDependencies": {
|
|
78
|
+
"vue-router": "^4.5.0",
|
|
79
|
+
"vue": "^3.5.0",
|
|
80
|
+
"vite": "^5.4.0",
|
|
81
|
+
"vite-plugin-dts": "^4.3.0",
|
|
82
|
+
"vitest": "^2.1.0",
|
|
83
|
+
"vue-tsc": "^2.1.0",
|
|
84
|
+
"typescript": "^5.6.0"
|
|
85
|
+
},
|
|
86
|
+
"license": "MIT",
|
|
87
|
+
"scripts": {
|
|
88
|
+
"build": "vite build",
|
|
89
|
+
"test": "node ../../node_modules/vitest/vitest.mjs run",
|
|
90
|
+
"typecheck": "vue-tsc --noEmit -p tsconfig.json",
|
|
91
|
+
"test:cli": "node --test cli/create.test.mjs cli/scaffold.test.mjs",
|
|
92
|
+
"check:package": "node scripts/check-package.mjs"
|
|
93
|
+
}
|
|
6
94
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>__PROJECT_NAME__</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="app"></div>
|
|
10
|
+
<script type="module" src="/src/main.ts"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "__PROJECT_NAME__",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"check:runtime": "node scripts/check-runtime.mjs",
|
|
8
|
+
"predev": "npm run check:runtime",
|
|
9
|
+
"prebuild": "npm run check:runtime",
|
|
10
|
+
"dev": "vite",
|
|
11
|
+
"build": "vue-tsc --noEmit && vite build",
|
|
12
|
+
"preview": "vite preview",
|
|
13
|
+
"typecheck": "vue-tsc --noEmit"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@axewo/fin-core": "2.1.1",
|
|
17
|
+
"@axewo/fin-micro": "__FIN_MICRO_VERSION__",
|
|
18
|
+
"@axewo/fin-ui": "__FIN_UI_VERSION__",
|
|
19
|
+
"@axewo/fin-editor": "2.0.1",
|
|
20
|
+
"@element-plus/icons-vue": "2.3.2",
|
|
21
|
+
"element-plus": "2.14.2",
|
|
22
|
+
"pinia": "2.3.1",
|
|
23
|
+
"vue": "3.5.13",
|
|
24
|
+
"vue-i18n": "^9.14.0",
|
|
25
|
+
"vue-router": "4.5.0"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@vitejs/plugin-vue": "5.2.1",
|
|
29
|
+
"typescript": "5.6.3",
|
|
30
|
+
"vite": "5.4.14",
|
|
31
|
+
"vue-tsc": "2.2.0"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { readdir, readFile } from 'node:fs/promises'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
import { fileURLToPath } from 'node:url'
|
|
4
|
+
|
|
5
|
+
// 构建前检查安装包的实际导出,避免 Vite 链接失败被误认为登录或网络问题。
|
|
6
|
+
const imports = new Map()
|
|
7
|
+
let hostRouter = false
|
|
8
|
+
async function scan(directory) {
|
|
9
|
+
for (const entry of await readdir(directory, { withFileTypes: true })) {
|
|
10
|
+
const file = join(directory, entry.name)
|
|
11
|
+
if (entry.isDirectory()) await scan(file)
|
|
12
|
+
else if (/\.(ts|vue)$/.test(entry.name)) {
|
|
13
|
+
const source = await readFile(file, 'utf8')
|
|
14
|
+
if (source.includes('connectWorkspaceRouter')) hostRouter = true
|
|
15
|
+
for (const match of source.matchAll(/import\s*\{([^}]+)\}\s*from\s*['"](@axewo\/[^'"]+)['"]/g)) {
|
|
16
|
+
const names = imports.get(match[2]) ?? new Set()
|
|
17
|
+
for (const part of match[1].split(',')) {
|
|
18
|
+
const name = part.trim()
|
|
19
|
+
if (name && !name.startsWith('type ')) names.add(name.split(/\s+as\s+/)[0])
|
|
20
|
+
}
|
|
21
|
+
imports.set(match[2], names)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export async function checkRuntime() {
|
|
27
|
+
await scan(fileURLToPath(new URL('../src', import.meta.url)))
|
|
28
|
+
const problems = []
|
|
29
|
+
for (const [specifier, names] of imports) {
|
|
30
|
+
try {
|
|
31
|
+
const module = await import(specifier)
|
|
32
|
+
for (const name of names) if (!(name in module)) problems.push(`${specifier} 缺少导出 ${name}`)
|
|
33
|
+
if (specifier === '@axewo/fin-micro' && hostRouter && module.hostRouterProtocol !== 1) {
|
|
34
|
+
problems.push('@axewo/fin-micro 缺少 hostRouterProtocol=1(真实 host 路由、local 页面和 url:false 连接器能力)')
|
|
35
|
+
}
|
|
36
|
+
} catch { problems.push(`${specifier} 无法加载,请检查安装版本及其依赖(不输出原始异常)`) }
|
|
37
|
+
}
|
|
38
|
+
if (problems.length) {
|
|
39
|
+
console.error('启动依赖能力检查失败:\n' + problems.join('\n') + '\n请安装与生成器配套构建的包,并重新运行 npm run check:runtime;不会自动改依赖。')
|
|
40
|
+
process.exitCode = 1
|
|
41
|
+
} else console.log('启动依赖能力检查通过')
|
|
42
|
+
}
|
|
43
|
+
await checkRuntime()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# 预留可复用业务组件目录;宿主专属结构组件位于 layout。
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import { FConfigProvider, useFinLocale, zhCN, en, type FinConfig } from '@axewo/fin-ui'
|
|
4
|
+
import { ElConfigProvider } from 'element-plus'
|
|
5
|
+
import elZhCN from 'element-plus/es/locale/lang/zh-cn'
|
|
6
|
+
import elEn from 'element-plus/es/locale/lang/en'
|
|
7
|
+
|
|
8
|
+
const props = withDefaults(defineProps<{ size?: FinConfig['size'] }>(), { size: 'default' })
|
|
9
|
+
// bootstrap 已为当前应用安装 i18n;切换全局 locale 时,两套组件库同步更新。
|
|
10
|
+
const { locale } = useFinLocale()
|
|
11
|
+
const config = computed<FinConfig>(() => ({
|
|
12
|
+
locale: locale.value === 'en' ? en : zhCN,
|
|
13
|
+
size: props.size,
|
|
14
|
+
}))
|
|
15
|
+
const elementLocale = computed(() => locale.value === 'en' ? elEn : elZhCN)
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<template>
|
|
19
|
+
<!-- config.locale 仅注入语言包,不能代替 app 安装 vue-i18n。 -->
|
|
20
|
+
<FConfigProvider :config="config">
|
|
21
|
+
<ElConfigProvider :locale="elementLocale" :size="config.size">
|
|
22
|
+
<slot />
|
|
23
|
+
</ElConfigProvider>
|
|
24
|
+
</FConfigProvider>
|
|
25
|
+
</template>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# 预留业务组合函数目录;不要在此复制核心导航、权限或页签状态机。
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# 预留布局组件目录;仅在页面有实际布局职责时添加组件。
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# 预留语言资源目录;当前模板未启用国际化,也未引入国际化依赖。
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// 与中文保持相同的业务词条结构。
|
|
2
|
+
export default {
|
|
3
|
+
demo: {
|
|
4
|
+
title: 'Locale and state demo',
|
|
5
|
+
description: 'Switch language, increment or reset. State belongs only to this app.',
|
|
6
|
+
count: 'Count: {count}',
|
|
7
|
+
doubled: 'Double count: {count}',
|
|
8
|
+
increment: 'Increment',
|
|
9
|
+
reset: 'Reset',
|
|
10
|
+
switchLanguage: 'Switch language',
|
|
11
|
+
},
|
|
12
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { App } from 'vue'
|
|
2
|
+
import { installFinI18n } from '@axewo/fin-ui'
|
|
3
|
+
import zhCN from './zh-CN'
|
|
4
|
+
import en from './en'
|
|
5
|
+
|
|
6
|
+
/** 每次 createApp 后独立安装,不在模块级共享可变 i18n 实例。 */
|
|
7
|
+
export function installAppLocale(app: App): void {
|
|
8
|
+
// 工厂递归合并业务词条,不覆盖 fin-ui 内置语言包。
|
|
9
|
+
installFinI18n(app, {
|
|
10
|
+
locale: 'zh-CN', fallbackLocale: 'zh-CN', legacy: false,
|
|
11
|
+
messages: { 'zh-CN': zhCN, en },
|
|
12
|
+
})
|
|
13
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import 'element-plus/dist/index.css'
|
|
2
|
+
import '@axewo/fin-ui/style.css'
|
|
3
|
+
import './styles/index.css'
|
|
4
|
+
import { createStartupLifecycle } from './utils/errorUtils'
|
|
5
|
+
|
|
6
|
+
// 动态导入使配置解析等模块求值错误也进入脱敏启动边界。
|
|
7
|
+
const { mount, unmount } = createStartupLifecycle(() => import('./bootstrap'))
|
|
8
|
+
|
|
9
|
+
// 普通应用直接启动;Vue 子应用模板覆盖本入口,接入微前端生命周期。
|
|
10
|
+
// fin-ui 公开根入口依赖 editor 与 icons,依赖已在公共 package.json 中显式声明。
|
|
11
|
+
void mount()
|
|
12
|
+
function onPageHide(event: PageTransitionEvent) {
|
|
13
|
+
// BFCache 返回时继续使用同一应用;保留监听以处理之后的普通离开。
|
|
14
|
+
if (!event.persisted) dispose()
|
|
15
|
+
}
|
|
16
|
+
function dispose() {
|
|
17
|
+
// 基座只清理自身实例,不注册 Wujie 子应用全局回调。
|
|
18
|
+
window.removeEventListener('pagehide', onPageHide)
|
|
19
|
+
unmount()
|
|
20
|
+
}
|
|
21
|
+
window.addEventListener('pagehide', onPageHide)
|
|
22
|
+
if (import.meta.hot) import.meta.hot.dispose(dispose)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# 路由目录由对应模板提供;宿主地址由 connectMicroBrowser 管理,不创建第二个 Vue Router。
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { createMemoryHistory, createRouter, type RouteRecordRaw, type Router } from 'vue-router'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 每轮挂载创建独立路由,默认使用空路由表,业务可通过参数传入本地页面。
|
|
5
|
+
* 此通用工厂不拥有地址栏;host overlay 用真实 history 路由覆盖本文件,vue overlay 按嵌入模式选历史。
|
|
6
|
+
* 独立使用本工厂时须由调用方负责 URL 适配;本地路由不替代平台认证及 canOpen 检查。
|
|
7
|
+
* @param routes 本地页面路由表;省略时为空,不自动添加业务页面。
|
|
8
|
+
* @returns 独立 Router;调用方安装到 App,并在卸载时销毁 history。
|
|
9
|
+
* @throws 无效路由配置导致的 Vue Router 初始化异常原样抛出。
|
|
10
|
+
*/
|
|
11
|
+
export function createAppRouter(routes: RouteRecordRaw[] = []): Router {
|
|
12
|
+
return createRouter({ history: createMemoryHistory(), routes })
|
|
13
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# 预留跨页面业务状态目录;当前模板没有需要共享的业务状态,未引入状态管理依赖。
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { computed, ref } from 'vue'
|
|
2
|
+
import { defineStore } from 'pinia'
|
|
3
|
+
|
|
4
|
+
// 只定义 store,不在模块顶层实例化;状态由当前应用安装的 Pinia 隔离。
|
|
5
|
+
export const useDemoStore = defineStore('demo', () => {
|
|
6
|
+
const count = ref(0)
|
|
7
|
+
const doubled = computed(() => count.value * 2)
|
|
8
|
+
function increment(): void { count.value++ }
|
|
9
|
+
// setup store 显式提供重置 action,不使用 options store 的默认 $reset。
|
|
10
|
+
function reset(): void { count.value = 0 }
|
|
11
|
+
return { count, doubled, increment, reset }
|
|
12
|
+
})
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { createPinia, type Pinia } from 'pinia'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 每个 Vue App、每轮挂载独立创建状态容器,禁止导出跨应用共享的模块级单例。
|
|
5
|
+
* @returns 未安装的独立 Pinia;调用方通过 app.use 安装,卸载时通过 disposePinia 释放。
|
|
6
|
+
* @throws 不额外包装底层初始化异常。
|
|
7
|
+
* @remarks 无参数;业务 store 需在安装后使用,组件外调用须显式传入所属 Pinia。
|
|
8
|
+
*/
|
|
9
|
+
export function createAppPinia(): Pinia {
|
|
10
|
+
return createPinia()
|
|
11
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
:root { font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif; color: #243247; background: #f4f6fa; font-synthesis: none; }
|
|
2
|
+
* { box-sizing: border-box; }
|
|
3
|
+
body { margin: 0; }
|
|
4
|
+
button, input { font: inherit; }
|
|
5
|
+
#app { min-height: 100vh; }
|
|
6
|
+
.page { padding: 24px; }
|
|
7
|
+
.panel { background: white; border-radius: 10px; padding: 24px; }
|
|
8
|
+
.actions { display: flex; align-items: center; gap: 12px; margin: 16px 0; flex-wrap: wrap; }
|
|
9
|
+
.feedback { color: #b45309; padding: 12px 24px; }
|
|
10
|
+
.login { max-width: 440px; margin: 14vh auto; }
|
|
11
|
+
.shell { display: grid; grid-template: 64px 1fr / 240px minmax(0, 1fr); height: 100vh; }
|
|
12
|
+
.shell-header { grid-column: 1 / -1; display: flex; align-items: center; justify-content: space-between; padding: 0 24px; background: white; border-bottom: 1px solid #e5e7eb; }
|
|
13
|
+
.sidebar { overflow: auto; background: white; border-right: 1px solid #e5e7eb; }
|
|
14
|
+
.workspace { min-width: 0; overflow: auto; }
|
|
15
|
+
.tabbar { display: flex; gap: 6px; padding: 12px; background: #fff; overflow: auto; }
|
|
16
|
+
.tabbar .tab { display: flex; align-items: center; gap: 4px; }
|
|
17
|
+
.micro-workspace { padding: 16px; }
|
|
18
|
+
@media (max-width: 760px) { .shell { grid-template-columns: 170px minmax(0, 1fr); } .page { padding: 12px; } }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# 预留跨模块业务类型目录;SDK 类型直接从对应依赖导入。
|