@manohub/app-kit 0.1.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/CONTRACT.md +592 -0
- package/README.md +27 -0
- package/dist/atoms/app-button.d.ts +90 -0
- package/dist/atoms/app-button.js +74 -0
- package/dist/atoms/app-checkbox.d.ts +86 -0
- package/dist/atoms/app-checkbox.js +78 -0
- package/dist/atoms/app-icon-button.d.ts +79 -0
- package/dist/atoms/app-icon-button.js +54 -0
- package/dist/atoms/app-input.d.ts +118 -0
- package/dist/atoms/app-input.js +83 -0
- package/dist/atoms/app-layout.d.ts +348 -0
- package/dist/atoms/app-layout.js +102 -0
- package/dist/atoms/app-notice.d.ts +90 -0
- package/dist/atoms/app-notice.js +59 -0
- package/dist/atoms/app-query-state.d.ts +191 -0
- package/dist/atoms/app-query-state.js +161 -0
- package/dist/atoms/app-radio-card.d.ts +80 -0
- package/dist/atoms/app-radio-card.js +72 -0
- package/dist/atoms/app-search-box.d.ts +80 -0
- package/dist/atoms/app-search-box.js +64 -0
- package/dist/atoms/app-select.d.ts +106 -0
- package/dist/atoms/app-select.js +74 -0
- package/dist/atoms/app-switch.d.ts +72 -0
- package/dist/atoms/app-switch.js +66 -0
- package/dist/atoms/app-textarea.d.ts +89 -0
- package/dist/atoms/app-textarea.js +61 -0
- package/dist/atoms/app-tooltip.d.ts +70 -0
- package/dist/atoms/app-tooltip.js +48 -0
- package/dist/atoms/atoms.css +679 -0
- package/dist/atoms/state-illustration.d.ts +21 -0
- package/dist/atoms/state-illustration.js +101 -0
- package/dist/components/app-badge.d.ts +72 -0
- package/dist/components/app-badge.js +40 -0
- package/dist/components/app-dialog.d.ts +164 -0
- package/dist/components/app-dialog.js +121 -0
- package/dist/components/app-filter.d.ts +173 -0
- package/dist/components/app-filter.js +200 -0
- package/dist/components/app-form.d.ts +446 -0
- package/dist/components/app-form.js +159 -0
- package/dist/components/app-pagination.d.ts +78 -0
- package/dist/components/app-pagination.js +57 -0
- package/dist/components/app-panel.d.ts +399 -0
- package/dist/components/app-panel.js +213 -0
- package/dist/components/app-section.d.ts +68 -0
- package/dist/components/app-section.js +28 -0
- package/dist/components/app-steps.d.ts +115 -0
- package/dist/components/app-steps.js +108 -0
- package/dist/components/app-table.d.ts +415 -0
- package/dist/components/app-table.js +479 -0
- package/dist/components/app-tabs.d.ts +58 -0
- package/dist/components/app-tabs.js +54 -0
- package/dist/components/app-tree.d.ts +273 -0
- package/dist/components/app-tree.js +222 -0
- package/dist/components/components.css +597 -0
- package/dist/composables/use-client-pagination.d.ts +39 -0
- package/dist/composables/use-client-pagination.js +29 -0
- package/dist/entry/create-query-client.d.ts +8 -0
- package/dist/entry/create-query-client.js +14 -0
- package/dist/entry/create-sub-app.d.ts +45 -0
- package/dist/entry/create-sub-app.js +110 -0
- package/dist/entry/index.d.ts +4 -0
- package/dist/entry/index.js +12 -0
- package/dist/entry/initial-guard.d.ts +12 -0
- package/dist/entry/initial-guard.js +23 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.js +67 -0
- package/dist/providers/setup-i18n.d.ts +19 -0
- package/dist/providers/setup-i18n.js +24 -0
- package/dist/services/app-container.d.ts +15 -0
- package/dist/services/app-container.js +7 -0
- package/dist/services/app-context.d.ts +4 -0
- package/dist/services/app-context.js +17 -0
- package/dist/services/index.d.ts +5 -0
- package/dist/services/loading.d.ts +16 -0
- package/dist/services/loading.js +27 -0
- package/dist/services/message-box.d.ts +84 -0
- package/dist/services/message-box.js +65 -0
- package/dist/services/modal.d.ts +51 -0
- package/dist/services/modal.js +51 -0
- package/dist/services/notify.d.ts +24 -0
- package/dist/services/notify.js +22 -0
- package/dist/shell/AppShell.d.ts +139 -0
- package/dist/shell/AppShell.js +171 -0
- package/dist/shell/shell.css +153 -0
- package/dist/styles/farris-bridge.css +129 -0
- package/dist/styles/index.css +16 -0
- package/dist/styles/markdown.css +85 -0
- package/dist/styles/reset.css +74 -0
- package/dist/styles/tokens.css +111 -0
- package/lint/__tests__/fixtures/clean-src/styles.css +14 -0
- package/lint/__tests__/fixtures/clean-src/views/good-page.tsx +15 -0
- package/lint/__tests__/fixtures/violations-src/styles.css +18 -0
- package/lint/__tests__/fixtures/violations-src/views/bad-page.tsx +38 -0
- package/lint/__tests__/fixtures/violations-src/views/no-shell-page.tsx +4 -0
- package/lint/__tests__/guardrails.spec.mjs +144 -0
- package/lint/component-audit.mjs +131 -0
- package/lint/guardrails.config.schema.json +74 -0
- package/lint/pre-commit.sample +26 -0
- package/lint/run-all.mjs +59 -0
- package/lint/shared.mjs +347 -0
- package/lint/structure-audit.mjs +254 -0
- package/lint/style-audit.mjs +200 -0
- package/package.json +78 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { QueryClient } from '@tanstack/vue-query';
|
|
2
|
+
import { type App as VueApp, type Component, type Plugin } from 'vue';
|
|
3
|
+
import { type RouteRecordRaw } from 'vue-router';
|
|
4
|
+
export interface CreateSubAppOptions {
|
|
5
|
+
rootComponent: Component;
|
|
6
|
+
routes: RouteRecordRaw[];
|
|
7
|
+
i18n?: {
|
|
8
|
+
resources: Record<string, Record<string, unknown>>;
|
|
9
|
+
fallbackLng?: string;
|
|
10
|
+
};
|
|
11
|
+
/** 缺省由 createQueryClient() 提供;带全局错误缓存/自定义 retry 的应用自建后注入 */
|
|
12
|
+
queryClient?: QueryClient;
|
|
13
|
+
/** 额外插件(如 ContextMenu) */
|
|
14
|
+
extraPlugins?: Plugin[];
|
|
15
|
+
/** 额外样式(如 highlight.js 主题),mount 前动态加载 */
|
|
16
|
+
extraStyles?: () => Promise<unknown>;
|
|
17
|
+
/** 首帧路由重置到根路径(默认 true,刷新时不恢复 micro-app 传入的路由状态) */
|
|
18
|
+
resetToRootOnBoot?: boolean;
|
|
19
|
+
/** 允许的初始路径别名(宿主兼容前缀),命中不重置 */
|
|
20
|
+
rootPathAliases?: string[];
|
|
21
|
+
onReady?: (app: VueApp) => void;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* 统一子应用入口工厂,替代各 app 手写的 main.ts 样板。
|
|
25
|
+
*
|
|
26
|
+
* 固定流程(顺序即契约):
|
|
27
|
+
* createApp(外层自动包裹 .app-container)→ pinia → Farris → i18n → VueQuery
|
|
28
|
+
* → router → mount(__MICRO_APP_CONTAINER__ || '#app') → 初始守卫 → window 挂载
|
|
29
|
+
*
|
|
30
|
+
* `.app-container` 包裹保证 reset / tokens / farris-bridge 的作用域在所有 app 一致。
|
|
31
|
+
*
|
|
32
|
+
* farris 插件(app.use(Farris))注册在本工厂内部 —— farris 只允许存在于 app-kit,
|
|
33
|
+
* 应用侧不得 import '@farris/ui-vue'(oxlint 护栏)。
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* export const { mount, unmount } = createSubApp({
|
|
37
|
+
* rootComponent: Root,
|
|
38
|
+
* routes,
|
|
39
|
+
* i18n: { resources: { en: { translation: en }, zh: { translation: zh } } },
|
|
40
|
+
* })
|
|
41
|
+
*/
|
|
42
|
+
export declare function createSubApp(options: CreateSubAppOptions): {
|
|
43
|
+
mount: () => void;
|
|
44
|
+
unmount: () => void;
|
|
45
|
+
};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import Farris from "@farris/ui-vue";
|
|
2
|
+
import { VueQueryPlugin } from "@tanstack/vue-query";
|
|
3
|
+
import { createPinia } from "pinia";
|
|
4
|
+
import { defineComponent, ref, provide, h, createApp } from "vue";
|
|
5
|
+
import { createRouter, createWebHistory } from "vue-router";
|
|
6
|
+
import { installInitialGuard } from "./initial-guard.js";
|
|
7
|
+
import { createQueryClient } from "./create-query-client.js";
|
|
8
|
+
import { setupI18n } from "../providers/setup-i18n.js";
|
|
9
|
+
import { setCurrentApp } from "../services/app-context.js";
|
|
10
|
+
function createSubApp(options) {
|
|
11
|
+
const {
|
|
12
|
+
rootComponent,
|
|
13
|
+
routes,
|
|
14
|
+
i18n,
|
|
15
|
+
queryClient,
|
|
16
|
+
extraPlugins = [],
|
|
17
|
+
extraStyles,
|
|
18
|
+
resetToRootOnBoot = true,
|
|
19
|
+
rootPathAliases = [],
|
|
20
|
+
onReady
|
|
21
|
+
} = options;
|
|
22
|
+
let app = null;
|
|
23
|
+
let router = null;
|
|
24
|
+
let pinia = null;
|
|
25
|
+
let stopInitialGuard = null;
|
|
26
|
+
let removeGlobalDataListener = null;
|
|
27
|
+
let i18nInstance = null;
|
|
28
|
+
const mount = () => {
|
|
29
|
+
router = createRouter({ history: createWebHistory(), routes });
|
|
30
|
+
const ContainerRoot = defineComponent({
|
|
31
|
+
name: "AppKitRoot",
|
|
32
|
+
setup() {
|
|
33
|
+
const subAppContext = ref({});
|
|
34
|
+
provide("subAppContext", subAppContext);
|
|
35
|
+
return () => h("div", { class: "app-container" }, [h(rootComponent)]);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
app = createApp(ContainerRoot);
|
|
39
|
+
pinia = createPinia();
|
|
40
|
+
app.use(pinia);
|
|
41
|
+
app.use(Farris);
|
|
42
|
+
if (i18n) {
|
|
43
|
+
i18nInstance = setupI18n(app, i18n);
|
|
44
|
+
}
|
|
45
|
+
app.use(VueQueryPlugin, { queryClient: queryClient ?? createQueryClient() });
|
|
46
|
+
for (const plugin of extraPlugins) {
|
|
47
|
+
app.use(plugin);
|
|
48
|
+
}
|
|
49
|
+
app.use(router);
|
|
50
|
+
const container = window.__MICRO_APP_CONTAINER__ || "#app";
|
|
51
|
+
app.mount(container);
|
|
52
|
+
const mountTarget = typeof container === "string" ? document.querySelector(container) : container;
|
|
53
|
+
setCurrentApp(app, mountTarget?.querySelector(".app-container") ?? null);
|
|
54
|
+
if (window.__MICRO_APP_ENVIRONMENT__) {
|
|
55
|
+
const microAppWindow = window;
|
|
56
|
+
const onGlobalData = (data) => {
|
|
57
|
+
if (data.language && i18nInstance) {
|
|
58
|
+
const normalizedLang = String(data.language).split("-")[0] === "zh" ? "zh" : "en";
|
|
59
|
+
if (normalizedLang !== i18nInstance.language) {
|
|
60
|
+
void i18nInstance.changeLanguage(normalizedLang);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
microAppWindow.microApp.addGlobalDataListener(onGlobalData, true);
|
|
65
|
+
removeGlobalDataListener = () => microAppWindow.microApp.removeGlobalDataListener(onGlobalData);
|
|
66
|
+
}
|
|
67
|
+
if (resetToRootOnBoot && router) {
|
|
68
|
+
stopInitialGuard = installInitialGuard(router, rootPathAliases);
|
|
69
|
+
}
|
|
70
|
+
if (extraStyles) {
|
|
71
|
+
void extraStyles();
|
|
72
|
+
}
|
|
73
|
+
onReady?.(app);
|
|
74
|
+
};
|
|
75
|
+
const unmount = () => {
|
|
76
|
+
if (stopInitialGuard) {
|
|
77
|
+
stopInitialGuard();
|
|
78
|
+
stopInitialGuard = null;
|
|
79
|
+
}
|
|
80
|
+
if (removeGlobalDataListener) {
|
|
81
|
+
removeGlobalDataListener();
|
|
82
|
+
removeGlobalDataListener = null;
|
|
83
|
+
}
|
|
84
|
+
if (app) {
|
|
85
|
+
app.unmount();
|
|
86
|
+
app = null;
|
|
87
|
+
}
|
|
88
|
+
setCurrentApp(null);
|
|
89
|
+
router = null;
|
|
90
|
+
pinia = null;
|
|
91
|
+
};
|
|
92
|
+
exposeToHost(mount, unmount);
|
|
93
|
+
if (!window.__MICRO_APP_ENVIRONMENT__) {
|
|
94
|
+
mount();
|
|
95
|
+
}
|
|
96
|
+
return { mount, unmount };
|
|
97
|
+
}
|
|
98
|
+
function exposeToHost(mount, unmount) {
|
|
99
|
+
if (typeof window === "undefined") return;
|
|
100
|
+
const w = window;
|
|
101
|
+
w.mount = mount;
|
|
102
|
+
w.unmount = unmount;
|
|
103
|
+
w.__MICRO_APP__ = true;
|
|
104
|
+
w.microApp = w.microApp ?? {};
|
|
105
|
+
w.microApp.mount = mount;
|
|
106
|
+
w.microApp.unmount = unmount;
|
|
107
|
+
}
|
|
108
|
+
export {
|
|
109
|
+
createSubApp
|
|
110
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { createSubApp, type CreateSubAppOptions } from './create-sub-app';
|
|
2
|
+
export { installInitialGuard } from './initial-guard';
|
|
3
|
+
export { createQueryClient } from './create-query-client';
|
|
4
|
+
export { setupI18n, i18n, type SetupI18nOptions } from '../providers/setup-i18n';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createSubApp } from "./create-sub-app.js";
|
|
2
|
+
import { installInitialGuard } from "./initial-guard.js";
|
|
3
|
+
import { createQueryClient } from "./create-query-client.js";
|
|
4
|
+
import { setupI18n } from "../providers/setup-i18n.js";
|
|
5
|
+
import { default as default2 } from "i18next";
|
|
6
|
+
export {
|
|
7
|
+
createQueryClient,
|
|
8
|
+
createSubApp,
|
|
9
|
+
default2 as i18n,
|
|
10
|
+
installInitialGuard,
|
|
11
|
+
setupI18n
|
|
12
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Router } from 'vue-router';
|
|
2
|
+
/**
|
|
3
|
+
* 「首次导航重置」守卫:刷新浏览器时,micro-app 会从 URL 参数恢复子应用路由状态,
|
|
4
|
+
* 该守卫在首次非根导航发生前拦截并重定向到根路径,确保每次打开子应用都是初始状态。
|
|
5
|
+
* 导航在产生历史记录前即被取消,避免 afterEach 事后 replace 在快速连续导航时残留多余历史。
|
|
6
|
+
* 只执行一次,执行后立即移除自身,不影响后续正常导航。
|
|
7
|
+
*
|
|
8
|
+
* @param rootPathAliases 额外允许的初始路径(宿主兼容前缀,如 ['/aihub-skill']);
|
|
9
|
+
* 命中别名的首次导航不重置
|
|
10
|
+
* @returns 清理函数:应用卸载时调用,销毁守卫,防止内存泄漏
|
|
11
|
+
*/
|
|
12
|
+
export declare function installInitialGuard(router: Router, rootPathAliases?: string[]): () => void;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
function installInitialGuard(router, rootPathAliases = []) {
|
|
2
|
+
const allowed = /* @__PURE__ */ new Set(["/", ...rootPathAliases]);
|
|
3
|
+
let hasAppliedInitialReset = false;
|
|
4
|
+
let stop = null;
|
|
5
|
+
stop = router.beforeEach((to) => {
|
|
6
|
+
if (hasAppliedInitialReset) return true;
|
|
7
|
+
hasAppliedInitialReset = true;
|
|
8
|
+
stop?.();
|
|
9
|
+
if (!allowed.has(to.path)) {
|
|
10
|
+
return { path: "/", query: {} };
|
|
11
|
+
}
|
|
12
|
+
return true;
|
|
13
|
+
});
|
|
14
|
+
return () => {
|
|
15
|
+
if (stop) {
|
|
16
|
+
stop();
|
|
17
|
+
stop = null;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export {
|
|
22
|
+
installInitialGuard
|
|
23
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @manohub/app-kit 对外导出白名单。
|
|
3
|
+
*
|
|
4
|
+
* 禁止 export *;禁止从 '@farris/ui-vue' re-export 任何东西(类型也不行)——
|
|
5
|
+
* farris 必须被挡在边界内(方案 §5.4 / 护栏 12)。
|
|
6
|
+
*/
|
|
7
|
+
export { AppShell, type AppShellCompound, type AppShellHeaderProps, type AppShellBodyProps, type AppShellSplitProps } from './shell/AppShell';
|
|
8
|
+
export { AppButton, type AppButtonTone, type AppButtonShape, type AppControlSize } from './atoms/app-button';
|
|
9
|
+
export { AppIconButton } from './atoms/app-icon-button';
|
|
10
|
+
export { AppInput } from './atoms/app-input';
|
|
11
|
+
export { AppSwitch } from './atoms/app-switch';
|
|
12
|
+
export { AppCheckbox } from './atoms/app-checkbox';
|
|
13
|
+
export { AppRadioCard, type AppRadioCardItem, type AppRadioCardValue } from './atoms/app-radio-card';
|
|
14
|
+
export { AppNotice, type AppNoticeTone } from './atoms/app-notice';
|
|
15
|
+
export { AppQueryState, type AppQueryStateKind, type AppQueryStateProps } from './atoms/app-query-state';
|
|
16
|
+
export { AppStateIllustrationEmpty, AppStateIllustrationError } from './atoms/state-illustration';
|
|
17
|
+
export { AppSelect, type AppSelectOption, type AppSelectValue } from './atoms/app-select';
|
|
18
|
+
export { AppSearchBox } from './atoms/app-search-box';
|
|
19
|
+
export { AppTextarea } from './atoms/app-textarea';
|
|
20
|
+
export { AppTooltip, type AppTooltipPlacement, type AppTooltipTrigger } from './atoms/app-tooltip';
|
|
21
|
+
export { AppPanel, type AppPanelProps, type AppPanelPadding, type AppPanelCompound, } from './components/app-panel';
|
|
22
|
+
export { AppTree, type AppTreeNode } from './components/app-tree';
|
|
23
|
+
export { AppTable, type AppTableColumn, type AppTableProps, type AppTableSelection } from './components/app-table';
|
|
24
|
+
export { AppPagination } from './components/app-pagination';
|
|
25
|
+
export { AppBadge, type AppBadgeTone, type AppBadgeShape, type AppBadgeSize, type AppBadgeDot } from './components/app-badge';
|
|
26
|
+
export { AppDialog } from './components/app-dialog';
|
|
27
|
+
export { AppTabs, type AppTabItem } from './components/app-tabs';
|
|
28
|
+
export { AppForm, type AppFormProps, type AppFormItemProps, type AppFormLabelAlign, type AppFormCompound, } from './components/app-form';
|
|
29
|
+
export { AppSection, type AppSectionProps } from './components/app-section';
|
|
30
|
+
export { AppLayout, type AppLayoutGap, type AppLayoutAlign, type AppLayoutJustify, type AppLayoutColumns, type AppLayoutSpan, } from './atoms/app-layout';
|
|
31
|
+
export { AppSteps, type AppStepItem } from './components/app-steps';
|
|
32
|
+
export { AppFilter, appFilterSelect, appFilterInput, type AppFilterField, type AppFilterValues, type AppFilterEventMeta, } from './components/app-filter';
|
|
33
|
+
export { useClientPagination, type UseClientPagination, type UseClientPaginationOptions, } from './composables/use-client-pagination';
|
|
34
|
+
export { notify, type NotifyOverride, type NotifyPosition } from './services/notify';
|
|
35
|
+
export { messageBox, type AppMessageBoxShowOptions, type AppMessageBoxConfirmOptions } from './services/message-box';
|
|
36
|
+
export { loading } from './services/loading';
|
|
37
|
+
export { modalService, type AppModalOptions, type AppModalButton, type AppModalHandle } from './services/modal';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { AppShell } from "./shell/AppShell.js";
|
|
2
|
+
import { AppButton } from "./atoms/app-button.js";
|
|
3
|
+
import { AppIconButton } from "./atoms/app-icon-button.js";
|
|
4
|
+
import { AppInput } from "./atoms/app-input.js";
|
|
5
|
+
import { AppSwitch } from "./atoms/app-switch.js";
|
|
6
|
+
import { AppCheckbox } from "./atoms/app-checkbox.js";
|
|
7
|
+
import { AppRadioCard } from "./atoms/app-radio-card.js";
|
|
8
|
+
import { AppNotice } from "./atoms/app-notice.js";
|
|
9
|
+
import { AppQueryState } from "./atoms/app-query-state.js";
|
|
10
|
+
import { AppStateIllustrationEmpty, AppStateIllustrationError } from "./atoms/state-illustration.js";
|
|
11
|
+
import { AppSelect } from "./atoms/app-select.js";
|
|
12
|
+
import { AppSearchBox } from "./atoms/app-search-box.js";
|
|
13
|
+
import { AppTextarea } from "./atoms/app-textarea.js";
|
|
14
|
+
import { AppTooltip } from "./atoms/app-tooltip.js";
|
|
15
|
+
import { AppPanel } from "./components/app-panel.js";
|
|
16
|
+
import { AppTree } from "./components/app-tree.js";
|
|
17
|
+
import { AppTable } from "./components/app-table.js";
|
|
18
|
+
import { AppPagination } from "./components/app-pagination.js";
|
|
19
|
+
import { AppBadge } from "./components/app-badge.js";
|
|
20
|
+
import { AppDialog } from "./components/app-dialog.js";
|
|
21
|
+
import { AppTabs } from "./components/app-tabs.js";
|
|
22
|
+
import { AppForm } from "./components/app-form.js";
|
|
23
|
+
import { AppSection } from "./components/app-section.js";
|
|
24
|
+
import { AppLayout } from "./atoms/app-layout.js";
|
|
25
|
+
import { AppSteps } from "./components/app-steps.js";
|
|
26
|
+
import { AppFilter, appFilterInput, appFilterSelect } from "./components/app-filter.js";
|
|
27
|
+
import { useClientPagination } from "./composables/use-client-pagination.js";
|
|
28
|
+
import { notify } from "./services/notify.js";
|
|
29
|
+
import { messageBox } from "./services/message-box.js";
|
|
30
|
+
import { loading } from "./services/loading.js";
|
|
31
|
+
import { modalService } from "./services/modal.js";
|
|
32
|
+
export {
|
|
33
|
+
AppBadge,
|
|
34
|
+
AppButton,
|
|
35
|
+
AppCheckbox,
|
|
36
|
+
AppDialog,
|
|
37
|
+
AppFilter,
|
|
38
|
+
AppForm,
|
|
39
|
+
AppIconButton,
|
|
40
|
+
AppInput,
|
|
41
|
+
AppLayout,
|
|
42
|
+
AppNotice,
|
|
43
|
+
AppPagination,
|
|
44
|
+
AppPanel,
|
|
45
|
+
AppQueryState,
|
|
46
|
+
AppRadioCard,
|
|
47
|
+
AppSearchBox,
|
|
48
|
+
AppSection,
|
|
49
|
+
AppSelect,
|
|
50
|
+
AppShell,
|
|
51
|
+
AppStateIllustrationEmpty,
|
|
52
|
+
AppStateIllustrationError,
|
|
53
|
+
AppSteps,
|
|
54
|
+
AppSwitch,
|
|
55
|
+
AppTable,
|
|
56
|
+
AppTabs,
|
|
57
|
+
AppTextarea,
|
|
58
|
+
AppTooltip,
|
|
59
|
+
AppTree,
|
|
60
|
+
appFilterInput,
|
|
61
|
+
appFilterSelect,
|
|
62
|
+
loading,
|
|
63
|
+
messageBox,
|
|
64
|
+
modalService,
|
|
65
|
+
notify,
|
|
66
|
+
useClientPagination
|
|
67
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import i18next from 'i18next';
|
|
2
|
+
import type { App } from 'vue';
|
|
3
|
+
export interface SetupI18nOptions {
|
|
4
|
+
/** i18next resources 形态:{ en: { translation: {...} }, zh: { translation: {...} } } */
|
|
5
|
+
resources: Record<string, Record<string, unknown>>;
|
|
6
|
+
fallbackLng?: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* i18n 初始化(每子应用一个 i18next 单例)。
|
|
10
|
+
*
|
|
11
|
+
* 各子应用以 IIFE 独立 bundle 加载,包内各持一份 i18next 模块实例,
|
|
12
|
+
* 单例模式天然按应用隔离。消费方(如 api-error 处理)可从本模块直接取 i18n 单例。
|
|
13
|
+
*
|
|
14
|
+
* ⚠️ micro-app 宿主语言切换监听不在本函数内,由 createSubApp 统一注册,
|
|
15
|
+
* 以保证「监听器在应用初始化前注册」的时序约束。
|
|
16
|
+
*/
|
|
17
|
+
export declare function setupI18n(app: App, options: SetupI18nOptions): import("i18next").i18n;
|
|
18
|
+
/** i18n 单例出口:供组件树外取 t()/exists()/changeLanguage()(如 api-error 处理器) */
|
|
19
|
+
export { i18next as i18n };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import i18next from "i18next";
|
|
2
|
+
import { default as default2 } from "i18next";
|
|
3
|
+
import LanguageDetector from "i18next-browser-languagedetector";
|
|
4
|
+
import i18nextVue from "i18next-vue";
|
|
5
|
+
function setupI18n(app, options) {
|
|
6
|
+
const { resources, fallbackLng = "en" } = options;
|
|
7
|
+
if (!i18next.isInitialized) {
|
|
8
|
+
i18next.use(LanguageDetector).init({
|
|
9
|
+
resources,
|
|
10
|
+
fallbackLng,
|
|
11
|
+
interpolation: { escapeValue: false },
|
|
12
|
+
detection: {
|
|
13
|
+
order: ["localStorage", "navigator"],
|
|
14
|
+
caches: ["localStorage"]
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
app.use(i18nextVue, { i18next });
|
|
19
|
+
return i18next;
|
|
20
|
+
}
|
|
21
|
+
export {
|
|
22
|
+
default2 as i18n,
|
|
23
|
+
setupI18n
|
|
24
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 子应用容器(`.app-container`)解析 —— `createSubApp` 统一包裹的作用域锚点。
|
|
3
|
+
*
|
|
4
|
+
* 令牌(`--ui-*`)、farris 主题桥接(`--f-theme-*`)与 reset 都挂在它上面;
|
|
5
|
+
* 微前端下应用侧 CSS 还会被 scopecss 前缀限定在容器内。
|
|
6
|
+
*
|
|
7
|
+
* 因此**任何 Teleport 出组件树的内容都必须落在这里**:
|
|
8
|
+
* 落到 `body` 上的节点既拿不到令牌(`var(--ui-*)` 全部失效 ⇒ 组件「样式全丢」),
|
|
9
|
+
* 也匹配不到被前缀化的应用侧样式(farris 弹窗的默认 host 就是 `"body"`,故调用方需要改写)。
|
|
10
|
+
*
|
|
11
|
+
* 取值优先级:**当前应用挂载时记录的容器**(精确,门户里多应用并存时不会串到别的应用)
|
|
12
|
+
* → 文档里第一个 `.app-container`(未经 createSubApp 的裸挂载场景)
|
|
13
|
+
* → `document.body`(兜底,行为与改写前一致,不会更差)。
|
|
14
|
+
*/
|
|
15
|
+
export declare function resolveAppContainer(): Element;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
let currentApp = null;
|
|
2
|
+
let currentContainer = null;
|
|
3
|
+
function setCurrentApp(app, container = null) {
|
|
4
|
+
currentApp = app;
|
|
5
|
+
currentContainer = container;
|
|
6
|
+
}
|
|
7
|
+
function getCurrentApp() {
|
|
8
|
+
return currentApp;
|
|
9
|
+
}
|
|
10
|
+
function getCurrentAppContainer() {
|
|
11
|
+
return currentContainer;
|
|
12
|
+
}
|
|
13
|
+
export {
|
|
14
|
+
getCurrentApp,
|
|
15
|
+
getCurrentAppContainer,
|
|
16
|
+
setCurrentApp
|
|
17
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { notify } from './notify';
|
|
2
|
+
export { messageBox, type AppMessageBoxShowOptions, type AppMessageBoxConfirmOptions } from './message-box';
|
|
3
|
+
export { loading } from './loading';
|
|
4
|
+
export { modalService, type AppModalOptions, type AppModalButton, type AppModalHandle } from './modal';
|
|
5
|
+
export { resolveAppContainer } from './app-container';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 全局加载遮罩(收敛 FLoadingService 的 show/hide 成对 API,防漏关)。
|
|
3
|
+
*
|
|
4
|
+
* 推荐用 withLoading 自动配对;手动场景 open() 返回带 close 的句柄。
|
|
5
|
+
* 单例语义:重复 open 覆盖前一个(与现状各 app 行为一致,全局同时只有一个遮罩)。
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* await loading.withLoading(() => mutation.mutateAsync(request), '提交中…')
|
|
9
|
+
* const mask = loading.open('导出中…'); try { ... } finally { mask.close() }
|
|
10
|
+
*/
|
|
11
|
+
export declare const loading: {
|
|
12
|
+
open(message?: string): {
|
|
13
|
+
close: () => void;
|
|
14
|
+
};
|
|
15
|
+
withLoading<T>(fn: () => Promise<T>, message?: string): Promise<T>;
|
|
16
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { FLoadingService } from "@farris/ui-vue";
|
|
2
|
+
let current = null;
|
|
3
|
+
function show(message) {
|
|
4
|
+
current = FLoadingService.show({ message });
|
|
5
|
+
return current;
|
|
6
|
+
}
|
|
7
|
+
function close() {
|
|
8
|
+
current?.value?.close();
|
|
9
|
+
current = null;
|
|
10
|
+
}
|
|
11
|
+
const loading = {
|
|
12
|
+
open(message) {
|
|
13
|
+
show(message);
|
|
14
|
+
return { close };
|
|
15
|
+
},
|
|
16
|
+
async withLoading(fn, message) {
|
|
17
|
+
show(message);
|
|
18
|
+
try {
|
|
19
|
+
return await fn();
|
|
20
|
+
} finally {
|
|
21
|
+
close();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
export {
|
|
26
|
+
loading
|
|
27
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
export interface AppMessageBoxButton {
|
|
2
|
+
/** 按钮标识(消费方无需再引用,仅用于内部区分) */
|
|
3
|
+
name: string;
|
|
4
|
+
text: string;
|
|
5
|
+
/** 视觉角色;缺省为浅色普通按钮 */
|
|
6
|
+
variant?: 'primary' | 'default' | 'danger';
|
|
7
|
+
/** 点击处理;返回 false 阻止关闭(farris 契约) */
|
|
8
|
+
handle?: () => boolean | void | Promise<boolean | void>;
|
|
9
|
+
}
|
|
10
|
+
export interface AppMessageBoxShowOptions {
|
|
11
|
+
type?: 'info' | 'success' | 'warning' | 'error' | 'question';
|
|
12
|
+
/** 主文案(对话框第一行)。**传文本**;多行用 `\n`(换行在弹窗里生效) */
|
|
13
|
+
title: string;
|
|
14
|
+
/**
|
|
15
|
+
* 补充说明(主文案下方的灰色小字)。**传文本**,多行用 `\n`(服务内部先转义再交底层渲染,
|
|
16
|
+
* 所以文本里的 `<` `&` 不会被当标签解析)。
|
|
17
|
+
*
|
|
18
|
+
* HTML 只留给**静态长文说明**(「值映射说明」这种要分段/强调的须知):这时置 `richText: true`,
|
|
19
|
+
* 内容必须来自静态语言包,**禁止拼接用户输入**。提示/确认/报错一律用文本,不要为了分段拼 `<div>`。
|
|
20
|
+
*/
|
|
21
|
+
detail?: string;
|
|
22
|
+
/** `detail` 是否按 HTML 渲染;**缺省 false**(按文本:内部转义,`\n` 换行) */
|
|
23
|
+
richText?: boolean;
|
|
24
|
+
/** 弹窗宽度(px);不传走底层默认宽度。长正文建议显式给宽(如 720)避免挤成窄条 */
|
|
25
|
+
width?: number;
|
|
26
|
+
/** 「确定」按钮文案;不传走底层默认(「确定」)。多语言场景应由调用方传入当前语言文案 */
|
|
27
|
+
okButtonText?: string;
|
|
28
|
+
/** 自定义按钮组(farris 多按钮弹窗);不传即单「确定」按钮 */
|
|
29
|
+
buttons?: AppMessageBoxButton[];
|
|
30
|
+
}
|
|
31
|
+
export interface AppMessageBoxConfirmOptions {
|
|
32
|
+
/** 主文案:确认主题(如「确认删除」);整句问句也可以放这里 */
|
|
33
|
+
title: string;
|
|
34
|
+
/** 补充说明:影响/后果这类补充(**文本**,可多行) */
|
|
35
|
+
description?: string;
|
|
36
|
+
/** 「确定」按钮文案;不传走底层默认(「确定」) */
|
|
37
|
+
confirmText?: string;
|
|
38
|
+
/** 「取消」按钮文案;不传走底层默认(「取消」) */
|
|
39
|
+
cancelText?: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* 消息框(收敛 FMessageBoxService 的静态回调式 API)。
|
|
43
|
+
*
|
|
44
|
+
* ## 文案口径:**用文本传,尽量不用 HTML**
|
|
45
|
+
*
|
|
46
|
+
* farris 的字段落位:`title` → `.toast-msg-title`(第一行主文案)、`detail` → `.toast-msg-detail`
|
|
47
|
+
* (主文案下方的灰色小字)。两者底层都是 `innerHTML`,但**本服务默认把传入内容当文本**:
|
|
48
|
+
* 内部先转义、再交给底层(`\n` 已放开换行,见 farris-bridge.css 的 `white-space: pre-line`)。
|
|
49
|
+
* 于是"换行"用 `\n`、"嵌业务值"直接拼字符串即可 —— 不再需要调用方自己 `escapeHtml` 或拼 `<div>`;
|
|
50
|
+
* 拼 HTML 的老写法既容易漏转义(编码里的尖括号会被当标签),又把文案与排版耦合在代码里。
|
|
51
|
+
* 唯一例外是**静态长文说明**(如值映射说明的四段须知)需要分段/强调:显式 `richText: true`,
|
|
52
|
+
* 内容必须来自静态语言包,**禁止拼接用户输入**。
|
|
53
|
+
*
|
|
54
|
+
* - `confirm` 把确认语义 Promise 化(`resolve(true/false)`)
|
|
55
|
+
* - `show` 支持 `buttons` 自定义按钮组(如「应用 / 丢弃 / 取消」三按钮确认),
|
|
56
|
+
* 按钮角色在内部翻译为 farris 类名,消费方不写 `btn btn-*`
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* const ok = await messageBox.confirm({ title: '删除分类', description: `确定删除「${name}」?` })
|
|
60
|
+
* if (ok) { ... }
|
|
61
|
+
* messageBox.show({ type: 'error', title: '删除失败', detail: error.message })
|
|
62
|
+
*
|
|
63
|
+
* @example 三按钮确认
|
|
64
|
+
* messageBox.show({ type: 'question', title: '有未应用的更改', detail: '是否先应用?',
|
|
65
|
+
* buttons: [
|
|
66
|
+
* { name: 'save', text: '立即应用', variant: 'primary', handle: () => finish('save') },
|
|
67
|
+
* { name: 'discard', text: '丢弃', handle: () => finish('discard') },
|
|
68
|
+
* { name: 'cancel', text: '取消', handle: () => finish('cancel') },
|
|
69
|
+
* ] })
|
|
70
|
+
*/
|
|
71
|
+
export declare const messageBox: {
|
|
72
|
+
show(options: AppMessageBoxShowOptions): void;
|
|
73
|
+
/**
|
|
74
|
+
* 确认框:`resolve(true)` = 点确定,`resolve(false)` = 点取消。
|
|
75
|
+
*
|
|
76
|
+
* 走 `show`(而不是 `question`):`question` 把按钮文案写死成「确定 / 取消」,
|
|
77
|
+
* 而确认框经常要给具体动作名(「继续编辑 / 确定退出」这类)。
|
|
78
|
+
* 字段落位与 `show` 一致:`title` → 主文案、`description` → 补充小字,都是**文本**(`\n` 生效)。
|
|
79
|
+
*
|
|
80
|
+
* 注意:右上角 ✕ / Esc 关闭只关窗、不触发回调(farris 行为,`show` / `question` 都一样),
|
|
81
|
+
* 此时 Promise 不落定 —— 调用方按"未确认"处理即可(`await` 之后什么都不做)。要拿到 false 得点「取消」。
|
|
82
|
+
*/
|
|
83
|
+
confirm(options: AppMessageBoxConfirmOptions): Promise<boolean>;
|
|
84
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { FMessageBoxService } from "@farris/ui-vue";
|
|
2
|
+
function escapeHtml(text) {
|
|
3
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
4
|
+
}
|
|
5
|
+
function toRenderable(text, richText) {
|
|
6
|
+
const value = text ?? "";
|
|
7
|
+
return richText ? value : escapeHtml(value);
|
|
8
|
+
}
|
|
9
|
+
const BUTTON_CLASS = {
|
|
10
|
+
primary: "btn btn-primary",
|
|
11
|
+
default: "btn btn-secondary",
|
|
12
|
+
danger: "btn btn-danger"
|
|
13
|
+
};
|
|
14
|
+
const messageBox = {
|
|
15
|
+
show(options) {
|
|
16
|
+
FMessageBoxService.show({
|
|
17
|
+
type: options.type ?? "info",
|
|
18
|
+
title: toRenderable(options.title),
|
|
19
|
+
detail: toRenderable(options.detail, options.richText),
|
|
20
|
+
/*
|
|
21
|
+
* `show` 是**信息型**弹窗(无确认语义),固定单按钮。
|
|
22
|
+
* 底层 `show` 默认带「取消」(farris 组件的 showCancelButton 默认为 true),不显式关掉会多出一颗
|
|
23
|
+
* 无意义的取消按钮;需要确认语义请用 `confirm`。
|
|
24
|
+
*/
|
|
25
|
+
showCancelButton: false,
|
|
26
|
+
...options.width ? { width: options.width } : {},
|
|
27
|
+
...options.okButtonText ? { okButtonText: options.okButtonText } : {},
|
|
28
|
+
...options.buttons ? {
|
|
29
|
+
buttons: options.buttons.map((button) => ({
|
|
30
|
+
name: button.name,
|
|
31
|
+
text: button.text,
|
|
32
|
+
class: BUTTON_CLASS[button.variant ?? "default"],
|
|
33
|
+
handle: button.handle
|
|
34
|
+
}))
|
|
35
|
+
} : {}
|
|
36
|
+
});
|
|
37
|
+
},
|
|
38
|
+
/**
|
|
39
|
+
* 确认框:`resolve(true)` = 点确定,`resolve(false)` = 点取消。
|
|
40
|
+
*
|
|
41
|
+
* 走 `show`(而不是 `question`):`question` 把按钮文案写死成「确定 / 取消」,
|
|
42
|
+
* 而确认框经常要给具体动作名(「继续编辑 / 确定退出」这类)。
|
|
43
|
+
* 字段落位与 `show` 一致:`title` → 主文案、`description` → 补充小字,都是**文本**(`\n` 生效)。
|
|
44
|
+
*
|
|
45
|
+
* 注意:右上角 ✕ / Esc 关闭只关窗、不触发回调(farris 行为,`show` / `question` 都一样),
|
|
46
|
+
* 此时 Promise 不落定 —— 调用方按"未确认"处理即可(`await` 之后什么都不做)。要拿到 false 得点「取消」。
|
|
47
|
+
*/
|
|
48
|
+
confirm(options) {
|
|
49
|
+
return new Promise((resolve) => {
|
|
50
|
+
FMessageBoxService.show({
|
|
51
|
+
type: "question",
|
|
52
|
+
title: toRenderable(options.title),
|
|
53
|
+
detail: toRenderable(options.description),
|
|
54
|
+
showCancelButton: true,
|
|
55
|
+
...options.confirmText ? { okButtonText: options.confirmText } : {},
|
|
56
|
+
...options.cancelText ? { cancelButtonText: options.cancelText } : {},
|
|
57
|
+
acceptCallback: () => resolve(true),
|
|
58
|
+
rejectCallback: () => resolve(false)
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
export {
|
|
64
|
+
messageBox
|
|
65
|
+
};
|