@axewo/fin-micro 1.0.0 → 2.0.1

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.
Files changed (103) hide show
  1. package/README.md +181 -4
  2. package/cli/index.mjs +22 -6
  3. package/dist/browser.cjs +1 -1
  4. package/dist/browser.mjs +68 -66
  5. package/dist/core.cjs +1 -1
  6. package/dist/core.mjs +459 -441
  7. package/dist/index.cjs +1 -1
  8. package/dist/index.mjs +31 -24
  9. package/dist/layout.cjs +1 -0
  10. package/dist/layout.css +1 -0
  11. package/dist/layout.mjs +719 -0
  12. package/dist/{router-view-impl-C1KG6mxu.mjs → router-view-impl-B1NzCGKV.mjs} +18 -18
  13. package/dist/router-view-impl-BvOToV_Z.cjs +1 -0
  14. package/dist/runtime.cjs +1 -1
  15. package/dist/runtime.mjs +40 -39
  16. package/dist/sdk.cjs +1 -1
  17. package/dist/sdk.mjs +92 -56
  18. package/dist/types/browser/index.d.ts +4 -0
  19. package/dist/types/core/index.d.ts +1 -1
  20. package/dist/types/index.d.ts +2 -0
  21. package/dist/types/layout/HostShell.vue.d.ts +528 -0
  22. package/dist/types/layout/ShellHeader.vue.d.ts +46 -0
  23. package/dist/types/layout/ShellMenu.vue.d.ts +12 -0
  24. package/dist/types/layout/ShellTabs.vue.d.ts +23 -0
  25. package/dist/types/layout/StandaloneMenuItem.vue.d.ts +11 -0
  26. package/dist/types/layout/StandaloneShell.vue.d.ts +40 -0
  27. package/dist/types/layout/index.d.ts +5 -0
  28. package/dist/types/layout/types.d.ts +65 -0
  29. package/dist/types/runtime/lifecycle.d.ts +1 -1
  30. package/dist/types/sdk/index.d.ts +65 -4
  31. package/dist/types/vue/index.d.ts +13 -2
  32. package/dist/types/vue/router-view-impl.d.ts +1 -1
  33. package/dist/types/watermark/FinWatermark.vue.d.ts +16 -0
  34. package/dist/types/watermark/config.d.ts +36 -0
  35. package/dist/types/watermark/index.d.ts +4 -0
  36. package/dist/vue.cjs +1 -1
  37. package/dist/vue.mjs +49 -35
  38. package/dist/watermark.cjs +2 -0
  39. package/dist/watermark.mjs +119 -0
  40. package/package.json +29 -4
  41. package/scaffold/base/index.html +2 -1
  42. package/scaffold/base/package.json.template +5 -1
  43. package/scaffold/base/pnpm-workspace.yaml +6 -0
  44. package/scaffold/base/scripts/check-runtime.mjs +43 -0
  45. package/scaffold/base/src/locales/en.ts +12 -0
  46. package/scaffold/base/src/locales/index.ts +7 -1
  47. package/scaffold/base/src/locales/zh-CN.ts +12 -0
  48. package/scaffold/base/src/main.ts +13 -5
  49. package/scaffold/base/src/router/demo.ts +6 -0
  50. package/scaffold/base/src/router/index.ts +13 -0
  51. package/scaffold/base/src/stores/demo.ts +12 -0
  52. package/scaffold/base/src/stores/index.ts +11 -0
  53. package/scaffold/base/src/utils/appConfig.ts +49 -0
  54. package/scaffold/base/src/utils/errorUtils.ts +109 -0
  55. package/scaffold/base/src/utils/requestUtils.ts +32 -11
  56. package/scaffold/base/src/utils/watermarkConfig.ts +18 -0
  57. package/scaffold/base/src/views/demo/index.vue +26 -0
  58. package/scaffold/host/.env.example +11 -3
  59. package/scaffold/host/src/App.vue +1 -13
  60. package/scaffold/host/src/bootstrap.ts +40 -8
  61. package/scaffold/host/src/hooks/usePlatformSession.ts +40 -8
  62. package/scaffold/host/src/layout/HostHeader.vue +65 -5
  63. package/scaffold/host/src/layout/HostLayout.vue +65 -21
  64. package/scaffold/host/src/layout/HostMenu.vue +11 -2
  65. package/scaffold/host/src/layout/HostTabs.vue +91 -8
  66. package/scaffold/host/src/layout/HostWorkspace.vue +29 -7
  67. package/scaffold/host/src/localPages.ts +45 -0
  68. package/scaffold/host/src/platform.ts +51 -14
  69. package/scaffold/host/src/router/guards.ts +128 -0
  70. package/scaffold/host/src/router/index.ts +25 -0
  71. package/scaffold/host/src/router/targets.ts +30 -0
  72. package/scaffold/host/src/router/workspace.ts +63 -0
  73. package/scaffold/host/src/styles/shell.css +776 -0
  74. package/scaffold/host/src/utils/request.ts +30 -3
  75. package/scaffold/host/src/views/home/index.vue +19 -0
  76. package/scaffold/host/src/views/login/components/LoginForm.vue +83 -0
  77. package/scaffold/host/src/views/login/index.vue +16 -0
  78. package/scaffold/host/src/views/not-found/index.vue +1 -0
  79. package/scaffold/host/src/views/profile/index.vue +24 -0
  80. package/scaffold/host/src/views/workspace/index.vue +11 -0
  81. package/scaffold/layouts/standard/host/src/layout/HostLayout.vue +116 -0
  82. package/scaffold/layouts/standard/host/src/styles/shell.css +168 -0
  83. package/scaffold/layouts/standard/vue/src/layout/StandaloneLayout.vue +26 -0
  84. package/scaffold/vue/src/App.vue +13 -7
  85. package/scaffold/vue/src/bootstrap.ts +25 -24
  86. package/scaffold/vue/src/bridge/auth.ts +23 -0
  87. package/scaffold/vue/src/bridge/session.ts +2 -2
  88. package/scaffold/vue/src/layout/Header.vue +15 -0
  89. package/scaffold/vue/src/layout/Sidebar.vue +21 -0
  90. package/scaffold/vue/src/layout/SidebarItem.vue +21 -0
  91. package/scaffold/vue/src/layout/StandaloneLayout.vue +34 -0
  92. package/scaffold/vue/src/main.ts +5 -2
  93. package/scaffold/vue/src/router/index.ts +9 -4
  94. package/scaffold/vue/src/router/menu.ts +38 -0
  95. package/scaffold/vue/src/views/demo/configs/table.ts +24 -0
  96. package/scaffold/vue/src/views/demo/hooks/useRecordNavigation.ts +32 -0
  97. package/scaffold/vue/src/views/demo/index.vue +17 -0
  98. package/scaffold/vue/src/views/{DetailView.vue → demo/modules/detail/index.vue} +15 -6
  99. package/scaffold/vue/src/views/demo/types.ts +5 -0
  100. package/dist/router-view-impl-CgLEalx4.cjs +0 -1
  101. package/scaffold/host/src/views/LoginView.vue +0 -38
  102. package/scaffold/vue/src/views/ListView.vue +0 -51
  103. /package/scaffold/vue/src/views/{NotFoundView.vue → not-found/index.vue} +0 -0
@@ -0,0 +1,40 @@
1
+ import { ShellBrand, ShellMenuItem } from './types';
2
+ /** 独立外壳仅展示路由适配结果;不创建 router、认证或 bridge,也不自行判断嵌入环境。 */
3
+ type __VLS_Props = {
4
+ title: string;
5
+ menus: readonly ShellMenuItem[];
6
+ activeMenu: string;
7
+ brand?: ShellBrand;
8
+ };
9
+ /** 导航由项目处理并捕获失败;重置选中项,以真实路由而非点击结果为准。 */
10
+ declare function navigate(path: string): Promise<void>;
11
+ declare function __VLS_template(): {
12
+ attrs: Partial<{}>;
13
+ slots: {
14
+ brand?(_: {
15
+ brand: ShellBrand;
16
+ }): any;
17
+ 'header-actions'?(_: {}): any;
18
+ sidebar?(_: {
19
+ menus: readonly ShellMenuItem[];
20
+ navigate: typeof navigate;
21
+ }): any;
22
+ default?(_: {}): any;
23
+ 'workspace-overlay'?(_: {}): any;
24
+ };
25
+ refs: {};
26
+ rootEl: HTMLDivElement;
27
+ };
28
+ type __VLS_TemplateResult = ReturnType<typeof __VLS_template>;
29
+ declare const __VLS_component: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
30
+ navigate: (path: string) => any;
31
+ }, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{
32
+ onNavigate?: ((path: string) => any) | undefined;
33
+ }>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, HTMLDivElement>;
34
+ declare const _default: __VLS_WithTemplateSlots<typeof __VLS_component, __VLS_TemplateResult["slots"]>;
35
+ export default _default;
36
+ type __VLS_WithTemplateSlots<T, S> = T & {
37
+ new (): {
38
+ $slots: S;
39
+ };
40
+ };
@@ -0,0 +1,5 @@
1
+ export { default as HostShell } from './HostShell.vue';
2
+ export { default as StandaloneShell } from './StandaloneShell.vue';
3
+ export type { ShellBrand, ShellMenuItem, HostShellProps, HostShellActions, HostShellSlots } from './types';
4
+ /** 公共外壳模板协议,用于发布前避免新模板与旧 dist 混装。 */
5
+ export declare const layoutProtocol = 1;
@@ -0,0 +1,65 @@
1
+ import { Component, VNode } from 'vue';
2
+ import { MicroApp, MicroMenu, MicroTab, NavigationOperation } from '../sdk';
3
+ /** 公共外壳品牌配置;只改变展示,不注册应用、路由或认证能力。 */
4
+ export interface ShellBrand {
5
+ /** 产品名称。 */
6
+ title?: string;
7
+ /** 左上角短标识;复杂 Logo 请使用 brand 插槽。 */
8
+ mark?: string;
9
+ /** 面包屑前缀。 */
10
+ workspaceTitle?: string;
11
+ }
12
+ /** 独立应用菜单;由项目本地 router meta 转换,不是宿主权限凭据。 */
13
+ export interface ShellMenuItem {
14
+ /** 已解析的应用内完整路径,仅作为导航事件参数。 */
15
+ path: string;
16
+ title: string;
17
+ icon?: Component;
18
+ children: readonly ShellMenuItem[];
19
+ }
20
+ /** HostShell 输入全部来自项目唯一的核心状态;外壳不创建 host、router 或 session。 */
21
+ export interface HostShellProps {
22
+ apps: readonly MicroApp[];
23
+ tabs: readonly MicroTab[];
24
+ activeKey?: string;
25
+ userName: string;
26
+ pending?: boolean;
27
+ notice?: string;
28
+ brand?: ShellBrand;
29
+ /** 可选受控折叠状态;未传入时仅在当前组件内维护展示状态。 */
30
+ collapsed?: boolean;
31
+ /** 项目固定页签规则;仅控制按钮,真正的操作约束仍必须由核心适配器实施。 */
32
+ isFixedTab?: (tab: MicroTab) => boolean;
33
+ }
34
+ /** 所有插槽动作只发出事件;调用方必须继续通过原有授权和异步离页事务处理。 */
35
+ export interface HostShellActions {
36
+ open: (appId: string, menu: MicroMenu) => void;
37
+ operate: (operation: NavigationOperation) => void;
38
+ logout: () => void;
39
+ profile: () => void;
40
+ toggleCollapsed: () => void;
41
+ }
42
+ /** 替换整个区域表示自行维护该区域;未覆盖区域仍使用库内默认组件。 */
43
+ export interface HostShellSlots {
44
+ default: (scope: {
45
+ active?: MicroTab;
46
+ }) => VNode[];
47
+ brand?: (scope: {
48
+ brand: ShellBrand;
49
+ }) => VNode[];
50
+ 'header-actions'?: (scope: HostShellActions) => VNode[];
51
+ header?: (scope: HostShellActions & {
52
+ active?: MicroTab;
53
+ collapsed: boolean;
54
+ pending: boolean;
55
+ }) => VNode[];
56
+ sidebar?: (scope: HostShellActions & {
57
+ active?: MicroTab;
58
+ collapsed: boolean;
59
+ pending: boolean;
60
+ }) => VNode[];
61
+ tabs?: (scope: HostShellActions & {
62
+ active?: MicroTab;
63
+ pending: boolean;
64
+ }) => VNode[];
65
+ }
@@ -13,7 +13,7 @@ export interface WujieLifecycleOptions {
13
13
  */
14
14
  export declare function isMicroFrontend(): boolean;
15
15
  /**
16
- * 注册 Wujie 全局回调和 unload 清理;嵌入时通知 Wujie,独立时自动挂载。
16
+ * 注册 Wujie 全局回调和 pagehide 清理;嵌入时通知 Wujie,独立时自动挂载。
17
17
  * 重复 setup 释放旧注册;返回的 dispose 幂等,可交给 import.meta.hot.dispose。
18
18
  * 错误通过 console.error 报告(Wujie 可能忽略返回值),显式全局调用也可等待 Promise 接收失败。
19
19
  * 卸载不等待挂起的 mount;新挂载等待上轮 unmount,旧 mount 的迟到结果不再触发清理。
@@ -5,6 +5,8 @@ export interface MicroMenu {
5
5
  menuId: string;
6
6
  /** 菜单展示名称,同时作为入口页签的默认标题。 */
7
7
  name: string;
8
+ /** 可选展示图标标识(如 HomeFilled、User、Folder、List);仅布局层解析,不接收组件、HTML 或 URL。 */
9
+ icon?: string;
8
10
  /** 子菜单;分组不允许同时配置 target。 */
9
11
  children?: readonly MicroMenu[];
10
12
  /** 入口导航信息;tabId 仍须在整个宿主唯一。 */
@@ -20,16 +22,26 @@ export interface MicroMenuRoute {
20
22
  /** 该应用内可导航叶节点的 menuId。 */
21
23
  menuId: string;
22
24
  }
23
- /** 宿主登记的应用元信息;entry 必须为 HTTP(S) 地址,菜单配置在创建宿主时递归复制冻结。 */
24
- export interface MicroApp {
25
+ /** 应用公共元信息;菜单配置在创建宿主时递归复制冻结。 */
26
+ interface MicroAppMetadata {
25
27
  appId: string;
26
28
  name: string;
27
- entry: string;
28
29
  /** 可选多级菜单;不配置时保持原有无菜单导航行为。 */
29
30
  menus?: readonly MicroMenu[];
30
31
  /** 隐藏页面的菜单归属规则;入口 target 路径也自动参与匹配。 */
31
32
  menuRoutes?: readonly MicroMenuRoute[];
32
33
  }
34
+ /**
35
+ * 明确区分远端与宿主本地应用;省略 kind 保持原有远端协议,entry 必须为 HTTP(S)。
36
+ * local 只登记导航身份,不具有执行地址;由宿主自己的 SFC 容器渲染,不能交给 Wujie。
37
+ */
38
+ export type MicroApp = MicroAppMetadata & ({
39
+ kind?: 'remote';
40
+ entry: string;
41
+ } | {
42
+ kind: 'local';
43
+ entry?: never;
44
+ });
33
45
  /** 打开页面的导航元信息;tabId 在宿主内唯一,route 为应用内绝对路径,不应存放敏感信息。 */
34
46
  export interface OpenTab {
35
47
  appId: string;
@@ -154,8 +166,12 @@ export type NavigationOperation =
154
166
  type: 'open';
155
167
  input: OpenTab;
156
168
  } | {
157
- type: 'activate' | 'refresh' | 'close';
169
+ type: 'activate' | 'refresh';
158
170
  target: TabRef;
171
+ } | {
172
+ type: 'close';
173
+ target: TabRef;
174
+ closeFallback?: TabRef;
159
175
  } | {
160
176
  type: 'closeMany';
161
177
  scope: 'all' | 'others' | 'left' | 'right';
@@ -166,6 +182,8 @@ export type NavigationOperation =
166
182
  * 排除项不参与关闭确认,不会扩大页面 SDK 的应用权限;省略时完全保持原有范围选择行为。
167
183
  */
168
184
  exclude?: readonly TabRef[];
185
+ /** 活动页被关闭时优先激活仍存活的指定生命周期;不存在时沿用相邻页回落,不创建页面。 */
186
+ closeFallback?: TabRef;
169
187
  } | {
170
188
  type: 'back';
171
189
  target?: TabRef;
@@ -326,6 +344,48 @@ export declare function createTokenCapability(options: {
326
344
  apps: readonly string[];
327
345
  handler(context: HostCallContext): Promise<TokenResult>;
328
346
  };
347
+ /** 单窗口宿主签发的不透明请求票据;只作内存关联,不含 Token,不得序列化或持久化。 */
348
+ export interface AuthRequestTicket {
349
+ readonly request: string;
350
+ }
351
+ /** 正式登录失效能力契约;与 getToken 同样必须显式注册并配置可信应用白名单。 */
352
+ export interface AuthCapabilities extends TokenCapabilities {
353
+ /** 请求发送前签发原对象票据;只关联宿主私有版本,不接受子应用自报版本或身份。 */
354
+ beginAuthRequest(): AuthRequestTicket;
355
+ /**
356
+ * 上报该请求的认证失效;首次有效上报同步撤销会话并返回 true。
357
+ * 伪造、复制、跨应用、已消费或旧版本票据返回 false;能力越权及生命周期释放仍由 callHost 拒绝。
358
+ * 本返回值不是业务请求结果,不得据此自动重发请求或恢复身份。
359
+ */
360
+ reportAuthFailure(ticket: AuthRequestTicket): boolean;
361
+ }
362
+ /**
363
+ * 创建会话失效协调能力,复用现有 callHost 白名单与生命周期,不建立事件总线。
364
+ * @param options apps 是可信应用白名单;getRevision 必须读取宿主私有版本,每次身份/凭据变化递增。
365
+ * invalidate 必须同步撤销身份并推进版本,不能等待 beforeLeave 或远程注销;抛错仍由 core 隔离。
366
+ * @returns 两个正式能力配置;票据同时绑定可信 context.appId、对象身份和私有版本,不接受子应用自报 revision。
367
+ * @remarks 仅支持同窗口对象桥,白名单不是 XSS 沙箱;后台仍须鉴权,不自动刷新 Token 或重放请求。
368
+ */
369
+ export declare function createAuthFailureCapabilities(options: {
370
+ apps: readonly string[];
371
+ getRevision(): number;
372
+ invalidate(): void;
373
+ }): {
374
+ beginAuthRequest: {
375
+ apps: readonly string[];
376
+ handler(context: HostCallContext): AuthRequestTicket;
377
+ };
378
+ reportAuthFailure: {
379
+ apps: readonly string[];
380
+ handler(context: HostCallContext, ticket: AuthRequestTicket): boolean;
381
+ };
382
+ };
383
+ /** 未登录或空权限输入一律拒绝;权限名区分大小写,不进行通配符或隐式角色映射。 */
384
+ export declare function hasPermission(session: SessionInput | null | undefined, permission: string): boolean;
385
+ /** 至少一个明确权限匹配才允许;空数组拒绝,不将空条件解释成公共访问。 */
386
+ export declare function hasAny(session: SessionInput | null | undefined, permissions: readonly string[]): boolean;
387
+ /** 所有明确权限均匹配才允许;空数组或包含空权限名均拒绝。 */
388
+ export declare function hasAll(session: SessionInput | null | undefined, permissions: readonly string[]): boolean;
329
389
  /** Wujie 注入约定;可显式传入测试窗口,SDK 本身不导入 Wujie。 */
330
390
  export interface MicroWindow extends Window {
331
391
  $wujie?: {
@@ -336,3 +396,4 @@ export interface MicroWindow extends Window {
336
396
  }
337
397
  /** 获取 target 上的桥;默认浏览器 window,独立运行或 Node/SSR 无 window 时返回 undefined,不创建宿主。 */
338
398
  export declare function getMicroBridge<C extends CapabilityContract<C> = HostCapabilities>(target?: MicroWindow): AppBridge<C> | undefined;
399
+ export {};
@@ -1,7 +1,18 @@
1
- import { PropType } from 'vue';
1
+ import { PropType, MaybeRefOrGetter } from 'vue';
2
+ import { SessionSnapshot, AppBridge, MicroTab, PageSDK, CapabilityContract } from '../sdk';
2
3
  import { MicroHost } from '../core';
3
4
  import { MicroRuntime } from '../runtime';
4
- import { AppBridge, MicroTab, PageSDK, CapabilityContract } from '../sdk';
5
+ /**
6
+ * 根据响应式公开会话消费权限;不读取 Token、不绕过核心准入或后台鉴权。
7
+ * @param session 当前 SessionSnapshot 的 ref/getter,未连接或未登录安全拒绝。
8
+ * @returns 三种判断函数;在 computed/模板中调用会追踪最新权限,can 用于直接生成权限计算值。
9
+ */
10
+ export declare function usePermission(session: MaybeRefOrGetter<SessionSnapshot | null | undefined>): {
11
+ hasPermission: (permission: string) => boolean;
12
+ hasAny: (permissions: readonly string[]) => boolean;
13
+ hasAll: (permissions: readonly string[]) => boolean;
14
+ can: (permission: MaybeRefOrGetter<string>) => import('vue').ComputedRef<boolean>;
15
+ };
5
16
  export { useMicroTab } from './tab-context';
6
17
  export { MicroRouteView, type MicroRouteErrorCode } from './router-view';
7
18
  /** 应用共享容器:appId 对应一个 runtime 实例;挂载失败显示错误,组件卸载触发异步 unmount。不是页级缓存组件,不应在每次切 tab 时销毁。 */
@@ -8,7 +8,7 @@ import { MicroRouteErrorCode } from './router-view';
8
8
  *
9
9
  * 这里只解析与加载页面,不启动导航事务、不执行 Vue Router 导航守卫、不修改宿主页签和权限。
10
10
  * redirect 不替代宿主 canOpen;业务必须保证重定向目标属于该应用已授权页面,鉴权仍由宿主/core 负责。
11
- * 页面定位等价于从 START_LOCATION 发起解析:相对 redirect 以应用根为基准,函数 redirect 的 from 为 START_LOCATION。
11
+ * 页面定位等价于从 START_LOCATION 发起解析:相对 redirect 以应用根为基准,函数 redirect 按公开契约只接收目标 route。
12
12
  * 建议 redirect 使用绝对 path 或 name;不会把其他页签的全局 currentRoute 当作本页的导航来源。
13
13
  * 页面跳转和离页确认应使用页面 SDK;不要通过 RouterLink/router.push 把全局路由当作宿主页签导航。
14
14
  * 懒加载无法取消网络请求;卸载后忽略迟到结果,不再写入组件状态。模块加载后的组件 setup/render 异常由 Vue 错误边界处理。
@@ -0,0 +1,16 @@
1
+ import { WatermarkConfig } from './config';
2
+ /**
3
+ * 仅叠加水印,不包装业务 DOM。直接父容器必须是 position:relative 的稳定视口。
4
+ * 防篡改只提高常见删除/样式修改成本,不是安全边界;祖先隐藏或脚本被停用无法保证恢复。
5
+ */
6
+ type __VLS_Props = {
7
+ config?: WatermarkConfig;
8
+ };
9
+ declare const _default: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
10
+ error: (error: Error) => any;
11
+ }, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{
12
+ onError?: ((error: Error) => any) | undefined;
13
+ }>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {
14
+ layer: HTMLDivElement;
15
+ }, HTMLDivElement>;
16
+ export default _default;
@@ -0,0 +1,36 @@
1
+ /** 水印配置;接口接入方应只传业务文字,不传 Token 等敏感凭据。 */
2
+ export interface WatermarkConfig {
3
+ /** 是否绘制;默认开启,禁用会清理观察器和检查定时器。 */
4
+ enabled?: boolean;
5
+ /** 自定义文字或多行文字;最多四行,每行最多 120 字。 */
6
+ text?: string | readonly string[];
7
+ /** Canvas 文字颜色;默认 #64748b。 */
8
+ color?: string;
9
+ /** 文字透明度,范围 0~1。 */
10
+ opacity?: number;
11
+ /** 顺时针旋转角度,范围 -180~180。 */
12
+ rotate?: number;
13
+ /** 字号,单位 px,范围 10~64。 */
14
+ fontSize?: number;
15
+ /** Canvas 字体族;不接受 CSS 声明串。 */
16
+ fontFamily?: string;
17
+ /** 水平、垂直文字间距,单位 px,范围 16~600;越小越密。 */
18
+ gap?: readonly [number, number];
19
+ }
20
+ /** 标准化后的不可变快照,不跨组件共享可变配置。 */
21
+ export interface NormalizedWatermarkConfig {
22
+ enabled: boolean;
23
+ text: readonly string[];
24
+ color: string;
25
+ opacity: number;
26
+ rotate: number;
27
+ fontSize: number;
28
+ fontFamily: string;
29
+ gap: readonly [number, number];
30
+ }
31
+ /**
32
+ * 将前端或接口配置转换为有界快照;非法数字使用默认值,超范围数字截断。
33
+ * @param input 可选的水印配置;运行时 null/非法对象也安全降级。
34
+ * @returns 不可变配置,文字/间距复制后冻结;不操作 DOM,不抛校验异常。
35
+ */
36
+ export declare function normalizeWatermarkConfig(input?: WatermarkConfig | null): NormalizedWatermarkConfig;
@@ -0,0 +1,4 @@
1
+ /** 可选水印子路径;核心入口不加载 Canvas、Vue 展示组件或观察器。 */
2
+ export { default as FinWatermark } from './FinWatermark.vue';
3
+ export { normalizeWatermarkConfig } from './config';
4
+ export type { WatermarkConfig, NormalizedWatermarkConfig } from './config';
package/dist/vue.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("vue"),s=Symbol("micro-tab");function d(){const t=e.inject(s);if(!t)throw new Error("useMicroTab 必须在 MicroTabCache 内调用");return t}const m=e.defineComponent({name:"MicroRouteView",props:{name:{type:String,default:"default"}},slots:Object,setup(t,{slots:n}){const i=d(),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-CgLEalx4.cjs"));u||(a.value=l.MicroRouteViewImpl)}catch{u||(r.value=!0)}finally{c=!1}}}return e.onBeforeUnmount(()=>{u=!0}),o(),()=>{var l,f;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},"重新加载")]):((f=n.loading)==null?void 0:f.call(n,{tab:i}))??e.h("p",{role:"status"},"页面加载中…")}}}),h=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(o){r||(i.value=String(o))}finally{r||(a.value=!1)}}}return e.onMounted(()=>{u(!0)}),e.onBeforeUnmount(()=>{r=!0,t.runtime.unmount(t.appId).catch(console.error)}),()=>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 v(t){const n=e.shallowRef(t.getState()),i=t.subscribe(a=>{n.value=a});return e.onBeforeUnmount(i),n}const b=e.defineComponent({name:"MicroTabBoundary",props:{tab:{type:Object,required:!0},active:Boolean},setup(t,{slots:n}){const i=Object.freeze({...t.tab});e.provide(s,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})])}}),p=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(b,{key:a.key,tab:a,active:a.key===t.activeKey},n))}});function M(t,n,i=console.error){let a=!1,r,u=!1,c=0;async function o(){if(!u){u=!0;try{let f=-1;for(;!a&&r&&f!==c;){f=c;try{await n.replace(r.route,r)}catch(y){i(y)}}}finally{u=!1}}}const l=t.ready(f=>{r=f,c++,o()});return()=>{a=!0,l()}}function w(t,n){const i=t.page(n.key);return e.onBeforeUnmount(i.dispose),i}exports.MicroAppContainer=h;exports.MicroRouteView=m;exports.MicroTabCache=p;exports.connectMicroRouter=M;exports.useMicroHost=v;exports.useMicroPage=w;exports.useMicroTab=d;
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 CHANGED
@@ -1,22 +1,23 @@
1
- import { inject as b, defineComponent as s, shallowRef as d, onBeforeUnmount as y, h as u, onMounted as p, provide as h, KeepAlive as M, watch as g } from "vue";
2
- const m = Symbol("micro-tab");
3
- function k() {
4
- const e = b(m);
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);
5
6
  if (!e) throw new Error("useMicroTab 必须在 MicroTabCache 内调用");
6
7
  return e;
7
8
  }
8
- const A = s({
9
+ const O = y({
9
10
  name: "MicroRouteView",
10
11
  props: { name: { type: String, default: "default" } },
11
12
  slots: Object,
12
13
  setup(e, { slots: t }) {
13
- const r = k(), a = d(), n = d(!1);
14
+ const r = D(), a = d(), n = d(!1);
14
15
  let o = !1, c = !1;
15
16
  async function i() {
16
17
  if (!(o || c)) {
17
18
  c = !0, n.value = !1;
18
19
  try {
19
- const l = await import("./router-view-impl-C1KG6mxu.mjs");
20
+ const l = await import("./router-view-impl-B1NzCGKV.mjs");
20
21
  o || (a.value = l.MicroRouteViewImpl);
21
22
  } catch {
22
23
  o || (n.value = !0);
@@ -25,7 +26,7 @@ const A = s({
25
26
  }
26
27
  }
27
28
  }
28
- return y(() => {
29
+ return m(() => {
29
30
  o = !0;
30
31
  }), i(), () => {
31
32
  var l, f;
@@ -35,7 +36,16 @@ const A = s({
35
36
  ]) : ((f = t.loading) == null ? void 0 : f.call(t, { tab: r })) ?? u("p", { role: "status" }, "页面加载中…");
36
37
  };
37
38
  }
38
- }), C = s({
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({
39
49
  name: "MicroAppContainer",
40
50
  props: {
41
51
  appId: { type: String, required: !0 },
@@ -50,17 +60,17 @@ const A = s({
50
60
  a.value = !0, r.value = "";
51
61
  try {
52
62
  await e.runtime.mount(e.appId, t.value);
53
- } catch (i) {
54
- n || (r.value = String(i));
63
+ } catch {
64
+ n || (r.value = "子应用加载失败,请重试或联系管理员");
55
65
  } finally {
56
66
  n || (a.value = !1);
57
67
  }
58
68
  }
59
69
  }
60
- return p(() => {
70
+ return M(() => {
61
71
  o(!0);
62
- }), y(() => {
63
- n = !0, e.runtime.unmount(e.appId).catch(console.error);
72
+ }), m(() => {
73
+ n = !0, e.runtime.unmount(e.appId).catch(() => console.error("[fin-micro] 子应用资源释放失败"));
64
74
  }), () => u("section", { style: { height: "100%", minHeight: "420px" }, "aria-busy": a.value }, [
65
75
  r.value ? u("div", { role: "alert" }, [
66
76
  u("p", r.value),
@@ -73,13 +83,13 @@ const A = s({
73
83
  ]);
74
84
  }
75
85
  });
76
- function D(e) {
86
+ function j(e) {
77
87
  const t = d(e.getState()), r = e.subscribe((a) => {
78
88
  t.value = a;
79
89
  });
80
- return y(r), t;
90
+ return m(r), t;
81
91
  }
82
- const w = s({
92
+ const T = y({
83
93
  name: "MicroTabBoundary",
84
94
  props: {
85
95
  tab: { type: Object, required: !0 },
@@ -87,16 +97,16 @@ const w = s({
87
97
  },
88
98
  setup(e, { slots: t }) {
89
99
  const r = Object.freeze({ ...e.tab });
90
- h(m, r);
91
- const a = s({ name: "MicroTabContent", setup: () => () => {
100
+ g(b, r);
101
+ const a = y({ name: "MicroTabContent", setup: () => () => {
92
102
  var n;
93
103
  return (n = t.default) == null ? void 0 : n.call(t, { tab: r });
94
104
  } });
95
105
  return () => u("section", { hidden: !e.active, "data-micro-tab": r.key }, [
96
- u(M, null, { default: () => e.active ? u(a) : null })
106
+ u(k, null, { default: () => e.active ? u(a) : null })
97
107
  ]);
98
108
  }
99
- }), T = s({
109
+ }), B = y({
100
110
  name: "MicroTabCache",
101
111
  props: {
102
112
  tabs: { type: Array, required: !0 },
@@ -105,7 +115,7 @@ const w = s({
105
115
  },
106
116
  setup(e, { slots: t }) {
107
117
  const r = d([]);
108
- return g(() => [e.tabs, e.activeKey, e.max], () => {
118
+ return A(() => [e.tabs, e.activeKey, e.max], () => {
109
119
  const a = new Set(e.tabs.map((i) => i.key));
110
120
  let n = r.value.filter((i) => a.has(i.key));
111
121
  const o = e.tabs.find((i) => i.key === e.activeKey);
@@ -117,14 +127,14 @@ const w = s({
117
127
  n.splice(i, 1);
118
128
  }
119
129
  r.value = n;
120
- }, { immediate: !0, flush: "sync" }), () => r.value.map((a) => u(w, {
130
+ }, { immediate: !0, flush: "sync" }), () => r.value.map((a) => u(T, {
121
131
  key: a.key,
122
132
  tab: a,
123
133
  active: a.key === e.activeKey
124
134
  }, t));
125
135
  }
126
136
  });
127
- function I(e, t, r = console.error) {
137
+ function R(e, t, r = () => console.error("[fin-micro] 路由同步失败")) {
128
138
  let a = !1, n, o = !1, c = 0;
129
139
  async function i() {
130
140
  if (!o) {
@@ -135,8 +145,11 @@ function I(e, t, r = console.error) {
135
145
  f = c;
136
146
  try {
137
147
  await t.replace(n.route, n);
138
- } catch (v) {
139
- r(v);
148
+ } catch (h) {
149
+ try {
150
+ r(h);
151
+ } catch {
152
+ }
140
153
  }
141
154
  }
142
155
  } finally {
@@ -151,16 +164,17 @@ function I(e, t, r = console.error) {
151
164
  a = !0, l();
152
165
  };
153
166
  }
154
- function K(e, t) {
167
+ function q(e, t) {
155
168
  const r = e.page(t.key);
156
- return y(r.dispose), r;
169
+ return m(r.dispose), r;
157
170
  }
158
171
  export {
159
- C as MicroAppContainer,
160
- A as MicroRouteView,
161
- T as MicroTabCache,
162
- I as connectMicroRouter,
163
- D as useMicroHost,
164
- K as useMicroPage,
165
- k as useMicroTab
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
166
180
  };
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const f=require("vue");function y(s,e,u,m){return typeof s=="number"&&Number.isFinite(s)?Math.min(m,Math.max(u,s)):e}function F(s){var r,i;const e=s&&typeof s=="object"?s:{},m=(typeof e.text=="string"?e.text.split(`
2
+ `):Array.isArray(e.text)?e.text:["内部资料"]).filter(l=>typeof l=="string").slice(0,4).map(l=>l.slice(0,120)),c=typeof e.fontFamily=="string"&&e.fontFamily.length<=160&&!/[;{}\\\n\r]/.test(e.fontFamily)?e.fontFamily:"sans-serif";return Object.freeze({enabled:e.enabled!==!1,text:Object.freeze(m),color:typeof e.color=="string"&&e.color.length<=80?e.color:"#64748b",opacity:y(e.opacity,.12,0,1),rotate:y(e.rotate,-22,-180,180),fontSize:y(e.fontSize,16,10,64),fontFamily:c,gap:Object.freeze([y((r=e.gap)==null?void 0:r[0],100,16,600),y((i=e.gap)==null?void 0:i[1],80,16,600)])})}const D=f.defineComponent({__name:"FinWatermark",props:{config:{}},emits:["error"],setup(s,{emit:e}){const u=s,m=e,c=f.ref();let r=null,i,l,g="",d=!1,M=!1;function A(){i==null||i.disconnect(),i=void 0,l!==void 0&&clearInterval(l),l=void 0}function W(t){var O;const a=F(u.config),o=t.ownerDocument.createElement("canvas"),n=o.getContext("2d");if(!n)throw new Error("当前环境不支持 Canvas 2D,无法绘制水印。");const b=`${a.fontSize}px ${a.fontFamily}`;n.font=b;const p=Math.min(1e3,Math.max(1,...a.text.map(T=>n.measureText(T).width))),x=a.fontSize*1.5,w=Math.max(1,a.text.length)*x,h=a.rotate*Math.PI/180,z=Math.ceil(Math.abs(p*Math.cos(h))+Math.abs(w*Math.sin(h))+a.gap[0]),S=Math.ceil(Math.abs(p*Math.sin(h))+Math.abs(w*Math.cos(h))+a.gap[1]),v=Math.min(2,Math.max(1,((O=t.ownerDocument.defaultView)==null?void 0:O.devicePixelRatio)??1));o.width=Math.ceil(z*v),o.height=Math.ceil(S*v),n.scale(v,v),n.translate(z/2,S/2),n.rotate(h),n.font=b,n.fillStyle="#64748b",n.fillStyle=a.color,n.globalAlpha=a.opacity,n.textAlign="center",n.textBaseline="middle",a.text.forEach((T,j)=>n.fillText(T,0,(j-(a.text.length-1)/2)*x,1e3));const E=o.toDataURL("image/png");if(!E.startsWith("data:image/png"))throw new Error("Canvas 未生成有效水印图像。");return{url:E,width:z,height:S}}function _(t){i==null||i.observe(t,{attributes:!0}),r&&(i==null||i.observe(r,{childList:!0}))}function k(){var a;const t=c.value;if(!(!d||M||!t||!r)){M=!0,i==null||i.disconnect();try{t.parentNode!==r&&r.appendChild(t),t.style.cssText!==g&&(t.style.cssText=g),t.className&&t.removeAttribute("class"),t.hasAttribute("hidden")&&t.removeAttribute("hidden"),t.setAttribute("aria-hidden","true");const o=(a=t.ownerDocument.defaultView)==null?void 0:a.getComputedStyle(t);o&&(o.display==="none"||o.visibility!=="visible"||Number(o.opacity)===0)&&(t.style.cssText=g)}finally{M=!1,d&&_(t)}}}function C(){var o;A();const t=c.value;if(!t||!d)return;t.style.cssText="display:none !important;";const a=F(u.config);if(!(!a.enabled||!a.text.some(Boolean)))try{const n=W(t),b={position:"absolute",inset:"0",width:"100%",height:"100%",margin:"0",padding:"0",border:"0",display:"block",visibility:"visible",opacity:"1","pointer-events":"none","z-index":"2000",transform:"none",filter:"none","clip-path":"none","background-color":"transparent","background-image":`url("${n.url}")`,"background-size":`${n.width}px ${n.height}px`,"background-repeat":"repeat","background-position":"0 0","box-sizing":"border-box"};t.style.cssText="";for(const[x,w]of Object.entries(b))t.style.setProperty(x,w,"important");g=t.style.cssText;const p=(o=t.ownerDocument.defaultView)==null?void 0:o.MutationObserver;p&&(i=new p(k)),k(),l=setInterval(k,1e3)}catch(n){t.style.cssText="display:none !important;",m("error",n instanceof Error?n:new Error("水印绘制失败。")),console.warn("[fin-micro/watermark] 水印绘制失败,本次检查已停止。",n)}}return f.onMounted(()=>{var t;d=!0,r=((t=c.value)==null?void 0:t.parentElement)??null,C()}),f.watch(()=>u.config,C,{deep:!0}),f.onBeforeUnmount(()=>{var t;d=!1,A(),(t=c.value)==null||t.remove(),r=null}),(t,a)=>(f.openBlock(),f.createElementBlock("div",{ref_key:"layer",ref:c,"aria-hidden":"true","data-fin-watermark":""},null,512))}});exports.FinWatermark=D;exports.normalizeWatermarkConfig=F;