@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.
Files changed (103) hide show
  1. package/CONTRACT.md +592 -0
  2. package/README.md +27 -0
  3. package/dist/atoms/app-button.d.ts +90 -0
  4. package/dist/atoms/app-button.js +74 -0
  5. package/dist/atoms/app-checkbox.d.ts +86 -0
  6. package/dist/atoms/app-checkbox.js +78 -0
  7. package/dist/atoms/app-icon-button.d.ts +79 -0
  8. package/dist/atoms/app-icon-button.js +54 -0
  9. package/dist/atoms/app-input.d.ts +118 -0
  10. package/dist/atoms/app-input.js +83 -0
  11. package/dist/atoms/app-layout.d.ts +348 -0
  12. package/dist/atoms/app-layout.js +102 -0
  13. package/dist/atoms/app-notice.d.ts +90 -0
  14. package/dist/atoms/app-notice.js +59 -0
  15. package/dist/atoms/app-query-state.d.ts +191 -0
  16. package/dist/atoms/app-query-state.js +161 -0
  17. package/dist/atoms/app-radio-card.d.ts +80 -0
  18. package/dist/atoms/app-radio-card.js +72 -0
  19. package/dist/atoms/app-search-box.d.ts +80 -0
  20. package/dist/atoms/app-search-box.js +64 -0
  21. package/dist/atoms/app-select.d.ts +106 -0
  22. package/dist/atoms/app-select.js +74 -0
  23. package/dist/atoms/app-switch.d.ts +72 -0
  24. package/dist/atoms/app-switch.js +66 -0
  25. package/dist/atoms/app-textarea.d.ts +89 -0
  26. package/dist/atoms/app-textarea.js +61 -0
  27. package/dist/atoms/app-tooltip.d.ts +70 -0
  28. package/dist/atoms/app-tooltip.js +48 -0
  29. package/dist/atoms/atoms.css +679 -0
  30. package/dist/atoms/state-illustration.d.ts +21 -0
  31. package/dist/atoms/state-illustration.js +101 -0
  32. package/dist/components/app-badge.d.ts +72 -0
  33. package/dist/components/app-badge.js +40 -0
  34. package/dist/components/app-dialog.d.ts +164 -0
  35. package/dist/components/app-dialog.js +121 -0
  36. package/dist/components/app-filter.d.ts +173 -0
  37. package/dist/components/app-filter.js +200 -0
  38. package/dist/components/app-form.d.ts +446 -0
  39. package/dist/components/app-form.js +159 -0
  40. package/dist/components/app-pagination.d.ts +78 -0
  41. package/dist/components/app-pagination.js +57 -0
  42. package/dist/components/app-panel.d.ts +399 -0
  43. package/dist/components/app-panel.js +213 -0
  44. package/dist/components/app-section.d.ts +68 -0
  45. package/dist/components/app-section.js +28 -0
  46. package/dist/components/app-steps.d.ts +115 -0
  47. package/dist/components/app-steps.js +108 -0
  48. package/dist/components/app-table.d.ts +415 -0
  49. package/dist/components/app-table.js +479 -0
  50. package/dist/components/app-tabs.d.ts +58 -0
  51. package/dist/components/app-tabs.js +54 -0
  52. package/dist/components/app-tree.d.ts +273 -0
  53. package/dist/components/app-tree.js +222 -0
  54. package/dist/components/components.css +597 -0
  55. package/dist/composables/use-client-pagination.d.ts +39 -0
  56. package/dist/composables/use-client-pagination.js +29 -0
  57. package/dist/entry/create-query-client.d.ts +8 -0
  58. package/dist/entry/create-query-client.js +14 -0
  59. package/dist/entry/create-sub-app.d.ts +45 -0
  60. package/dist/entry/create-sub-app.js +110 -0
  61. package/dist/entry/index.d.ts +4 -0
  62. package/dist/entry/index.js +12 -0
  63. package/dist/entry/initial-guard.d.ts +12 -0
  64. package/dist/entry/initial-guard.js +23 -0
  65. package/dist/index.d.ts +37 -0
  66. package/dist/index.js +67 -0
  67. package/dist/providers/setup-i18n.d.ts +19 -0
  68. package/dist/providers/setup-i18n.js +24 -0
  69. package/dist/services/app-container.d.ts +15 -0
  70. package/dist/services/app-container.js +7 -0
  71. package/dist/services/app-context.d.ts +4 -0
  72. package/dist/services/app-context.js +17 -0
  73. package/dist/services/index.d.ts +5 -0
  74. package/dist/services/loading.d.ts +16 -0
  75. package/dist/services/loading.js +27 -0
  76. package/dist/services/message-box.d.ts +84 -0
  77. package/dist/services/message-box.js +65 -0
  78. package/dist/services/modal.d.ts +51 -0
  79. package/dist/services/modal.js +51 -0
  80. package/dist/services/notify.d.ts +24 -0
  81. package/dist/services/notify.js +22 -0
  82. package/dist/shell/AppShell.d.ts +139 -0
  83. package/dist/shell/AppShell.js +171 -0
  84. package/dist/shell/shell.css +153 -0
  85. package/dist/styles/farris-bridge.css +129 -0
  86. package/dist/styles/index.css +16 -0
  87. package/dist/styles/markdown.css +85 -0
  88. package/dist/styles/reset.css +74 -0
  89. package/dist/styles/tokens.css +111 -0
  90. package/lint/__tests__/fixtures/clean-src/styles.css +14 -0
  91. package/lint/__tests__/fixtures/clean-src/views/good-page.tsx +15 -0
  92. package/lint/__tests__/fixtures/violations-src/styles.css +18 -0
  93. package/lint/__tests__/fixtures/violations-src/views/bad-page.tsx +38 -0
  94. package/lint/__tests__/fixtures/violations-src/views/no-shell-page.tsx +4 -0
  95. package/lint/__tests__/guardrails.spec.mjs +144 -0
  96. package/lint/component-audit.mjs +131 -0
  97. package/lint/guardrails.config.schema.json +74 -0
  98. package/lint/pre-commit.sample +26 -0
  99. package/lint/run-all.mjs +59 -0
  100. package/lint/shared.mjs +347 -0
  101. package/lint/structure-audit.mjs +254 -0
  102. package/lint/style-audit.mjs +200 -0
  103. package/package.json +78 -0
@@ -0,0 +1,51 @@
1
+ import type { VNodeChild } from 'vue';
2
+ export interface AppModalButton {
3
+ /** 按钮标识(disableButtons 按此引用) */
4
+ name: string;
5
+ text: string;
6
+ /** primary=主按钮;缺省浅色普通按钮 */
7
+ variant?: 'primary' | 'default';
8
+ disabled?: boolean;
9
+ /** 按钮内 loading 态(spinner 图标 + 禁点) */
10
+ loading?: boolean;
11
+ /** 点击处理;返回 false 阻止关闭(farris 契约),异步失败须自行恢复按钮状态 */
12
+ handle?: () => boolean | void | Promise<boolean | void>;
13
+ }
14
+ export interface AppModalOptions {
15
+ title: string;
16
+ width?: number;
17
+ fitContent?: boolean;
18
+ showButtons?: boolean;
19
+ buttons?: AppModalButton[];
20
+ /** 弹窗内容渲染函数 */
21
+ content: {
22
+ render: () => VNodeChild;
23
+ };
24
+ }
25
+ /** farris 模态句柄的对外形态(destroy=销毁弹窗,close=触发关闭流程,update=换按钮/配置,disableButtons=批量禁用) */
26
+ export interface AppModalHandle {
27
+ destroy: () => void;
28
+ close: () => void;
29
+ update: (options: Partial<AppModalOptions>) => void;
30
+ disableButtons: (names: string[], disabled?: boolean) => void;
31
+ }
32
+ /**
33
+ * 命令式弹窗(收敛 inject('FModalService'))。
34
+ *
35
+ * farris 契约在内部翻译:按钮 variant→btn class、loading→spinner 图标类;
36
+ * 句柄保留 destroy/close/update/disableButtons 语义(farris 命令式弹窗的既有用法)。
37
+ *
38
+ * @example
39
+ * const modal = modalService.open({
40
+ * title: '新增分类',
41
+ * width: 480,
42
+ * buttons: [
43
+ * { name: 'cancel', text: '取消', handle: () => { modal.destroy(); return true } },
44
+ * { name: 'submit', text: '确定', variant: 'primary', handle: handleSubmit },
45
+ * ],
46
+ * content: { render: () => <TopicForm ... /> },
47
+ * })
48
+ */
49
+ export declare const modalService: {
50
+ open(options: AppModalOptions): AppModalHandle;
51
+ };
@@ -0,0 +1,51 @@
1
+ import { inject } from "vue";
2
+ import { getCurrentApp } from "./app-context.js";
3
+ import { resolveAppContainer } from "./app-container.js";
4
+ function translateButton(button) {
5
+ return {
6
+ name: button.name,
7
+ text: button.text,
8
+ class: button.variant === "primary" ? "btn btn-primary" : "btn btn-light",
9
+ disabled: button.disabled ?? false,
10
+ iconClass: button.loading ? "ak-modal-btn-spinner" : "",
11
+ handle: button.handle
12
+ };
13
+ }
14
+ function translateOptions(options) {
15
+ return {
16
+ title: options.title,
17
+ width: options.width,
18
+ fitContent: options.fitContent ?? true,
19
+ showButtons: options.showButtons ?? true,
20
+ buttons: options.buttons?.map(translateButton),
21
+ content: options.content,
22
+ // 与 AppDialog 同口径:farris 缺省投到 body,令牌与应用侧样式都会失效(见 resolveAppContainer)
23
+ host: resolveAppContainer()
24
+ };
25
+ }
26
+ function getModalService() {
27
+ const app = getCurrentApp();
28
+ if (!app) {
29
+ throw new Error("[app-kit] modal 服务必须在 createSubApp 挂载后使用(无活跃的 app 上下文)");
30
+ }
31
+ const service = app.runWithContext(() => inject("FModalService"));
32
+ if (!service) {
33
+ throw new Error("[app-kit] FModalService 未注册:请确认经 createSubApp 启动(内部执行 app.use(Farris))");
34
+ }
35
+ return service;
36
+ }
37
+ const modalService = {
38
+ open(options) {
39
+ const service = getModalService();
40
+ const handle = service.open(translateOptions(options));
41
+ return {
42
+ destroy: handle.destroy,
43
+ close: handle.close,
44
+ update: (next) => handle.update(translateOptions({ ...options, ...next })),
45
+ disableButtons: handle.disableButtons
46
+ };
47
+ }
48
+ };
49
+ export {
50
+ modalService
51
+ };
@@ -0,0 +1,24 @@
1
+ /**
2
+ * 提示位置。取值与 farris 的 `NotifyPosition` 一致,但**在本包内重新声明**:
3
+ * farris 类型一律不对外转发(本包把 farris 挡在边界内)。
4
+ */
5
+ export type NotifyPosition = 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right' | 'center-center';
6
+ /**
7
+ * 单条提示的覆盖项(不传则用全局配置)。
8
+ *
9
+ * 对应 farris 的 `ShowNotifyParams`:`title` / `position` / `timeout` / `showCloseButton`。
10
+ */
11
+ export interface NotifyOverride {
12
+ title?: string;
13
+ /** 通知位置,如 'top-center' */
14
+ position?: NotifyPosition;
15
+ /** 延时关闭时间(ms) */
16
+ timeout?: number;
17
+ showCloseButton?: boolean;
18
+ }
19
+ export declare const notify: {
20
+ success: (message: string, override?: NotifyOverride) => import("vue").App<any>;
21
+ warning: (message: string, override?: NotifyOverride) => import("vue").App<any>;
22
+ error: (message: string, override?: NotifyOverride) => import("vue").App<any>;
23
+ info: (message: string, override?: NotifyOverride) => import("vue").App<any>;
24
+ };
@@ -0,0 +1,22 @@
1
+ import { FNotifyService } from "@farris/ui-vue";
2
+ let service = null;
3
+ function getService() {
4
+ if (!service) {
5
+ service = new FNotifyService();
6
+ service.globalConfig = { position: "top-center", timeout: 5e3, showCloseButton: true };
7
+ }
8
+ return service;
9
+ }
10
+ const show = (kind, message, override) => {
11
+ const sink = getService();
12
+ return override ? sink[kind]({ message, ...override }) : sink[kind](message);
13
+ };
14
+ const notify = {
15
+ success: (message, override) => show("success", message, override),
16
+ warning: (message, override) => show("warning", message, override),
17
+ error: (message, override) => show("error", message, override),
18
+ info: (message, override) => show("info", message, override)
19
+ };
20
+ export {
21
+ notify
22
+ };
@@ -0,0 +1,139 @@
1
+ import { type PropType, type SlotsType, type VNodeChild } from 'vue';
2
+ export interface AppShellHeaderProps {
3
+ /** 传入 title 走内置布局;不传则纯 slot 直出(自定义页头出口) */
4
+ title?: string;
5
+ /**
6
+ * 标题左侧图标:**svg 节点或返回节点的函数**皆可。缺省渲染设计稿默认图标
7
+ * (MH后台-0911 node 0:3904:26×26 圆角方块 #2660FF)。
8
+ */
9
+ icon?: VNodeChild | (() => VNodeChild);
10
+ /**
11
+ * Header 右侧工具条(唯一可自定义位):搜索框 / 按钮 / 下拉等,水平排列。
12
+ * 传节点或返回节点的函数。布局(水平 / 垂直居中 / 间距)由标准样式锁死。
13
+ */
14
+ toolbar?: unknown;
15
+ /** 页头下方页签区,传参同 toolbar */
16
+ tabs?: unknown;
17
+ }
18
+ export interface AppShellBodyProps {
19
+ /** table=由 AppTable 内部滚;scroll=自身滚;plain=不滚(向导页) */
20
+ mode?: 'table' | 'scroll' | 'plain';
21
+ }
22
+ export interface AppShellSplitProps {
23
+ sidebar?: {
24
+ width?: number;
25
+ content?: () => VNodeChild;
26
+ };
27
+ }
28
+ /**
29
+ * Object.assign 复合件。两个注意点:
30
+ * 1. Object.assign 后不要再 defineComponent 二次包装,否则 props 类型丢失;
31
+ * 2. 每个子件必须显式声明 slots(SlotsType),否则 JSX 下 slot 参数类型退化为 any。
32
+ */
33
+ export declare const AppShell: {
34
+ new (...args: any[]): import("vue").CreateComponentPublicInstanceWithMixins<Readonly<{}> & Readonly<{}>, () => import("vue/jsx-runtime").JSX.Element, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, import("vue").PublicProps, {}, true, {}, SlotsType<{
35
+ default?: () => VNodeChild;
36
+ }>, import("vue").GlobalComponents, import("vue").GlobalDirectives, string, {}, any, import("vue").ComponentProvideOptions, {
37
+ P: {};
38
+ B: {};
39
+ D: {};
40
+ C: {};
41
+ M: {};
42
+ Defaults: {};
43
+ }, Readonly<{}> & Readonly<{}>, () => import("vue/jsx-runtime").JSX.Element, {}, {}, {}, {}>;
44
+ __isFragment?: never;
45
+ __isTeleport?: never;
46
+ __isSuspense?: never;
47
+ } & import("vue").ComponentOptionsBase<Readonly<{}> & Readonly<{}>, () => import("vue/jsx-runtime").JSX.Element, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, {}, {}, string, SlotsType<{
48
+ default?: () => VNodeChild;
49
+ }>, import("vue").GlobalComponents, import("vue").GlobalDirectives, string, import("vue").ComponentProvideOptions> & import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps & {
50
+ Filter: import("vue").DefineComponent<{}, () => import("vue/jsx-runtime").JSX.Element, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, SlotsType<{
51
+ default?: () => VNodeChild;
52
+ }>, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
53
+ Header: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
54
+ title: {
55
+ type: StringConstructor;
56
+ default: undefined;
57
+ };
58
+ icon: {
59
+ type: PropType<any>;
60
+ default: undefined;
61
+ };
62
+ toolbar: {
63
+ type: PropType<any>;
64
+ default: undefined;
65
+ };
66
+ tabs: {
67
+ type: PropType<any>;
68
+ default: undefined;
69
+ };
70
+ }>, () => string | number | boolean | import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
71
+ [key: string]: any;
72
+ }> | import("vue").VNodeArrayChildren | null, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
73
+ title: {
74
+ type: StringConstructor;
75
+ default: undefined;
76
+ };
77
+ icon: {
78
+ type: PropType<any>;
79
+ default: undefined;
80
+ };
81
+ toolbar: {
82
+ type: PropType<any>;
83
+ default: undefined;
84
+ };
85
+ tabs: {
86
+ type: PropType<any>;
87
+ default: undefined;
88
+ };
89
+ }>> & Readonly<{}>, {
90
+ title: string;
91
+ icon: any;
92
+ toolbar: any;
93
+ tabs: any;
94
+ }, SlotsType<{
95
+ default?: () => VNodeChild;
96
+ toolbar?: () => VNodeChild;
97
+ tabs?: () => VNodeChild;
98
+ }>, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
99
+ Toolbar: import("vue").DefineComponent<{}, () => import("vue/jsx-runtime").JSX.Element, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, SlotsType<{
100
+ default?: () => VNodeChild;
101
+ }>, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
102
+ Body: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
103
+ mode: {
104
+ type: () => NonNullable<AppShellBodyProps["mode"]>;
105
+ default: string;
106
+ };
107
+ }>, () => import("vue/jsx-runtime").JSX.Element, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
108
+ mode: {
109
+ type: () => NonNullable<AppShellBodyProps["mode"]>;
110
+ default: string;
111
+ };
112
+ }>> & Readonly<{}>, {
113
+ mode: NonNullable<"table" | "scroll" | "plain" | undefined>;
114
+ }, SlotsType<{
115
+ default?: () => VNodeChild;
116
+ }>, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
117
+ Footer: import("vue").DefineComponent<{}, () => import("vue/jsx-runtime").JSX.Element, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, SlotsType<{
118
+ default?: () => VNodeChild;
119
+ }>, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
120
+ Split: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
121
+ sidebar: {
122
+ type: () => AppShellSplitProps["sidebar"];
123
+ default: undefined;
124
+ };
125
+ }>, () => import("vue/jsx-runtime").JSX.Element, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
126
+ sidebar: {
127
+ type: () => AppShellSplitProps["sidebar"];
128
+ default: undefined;
129
+ };
130
+ }>> & Readonly<{}>, {
131
+ sidebar: {
132
+ width?: number;
133
+ content?: () => VNodeChild;
134
+ } | undefined;
135
+ }, SlotsType<{
136
+ default?: () => VNodeChild;
137
+ }>, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
138
+ };
139
+ export type AppShellCompound = typeof AppShell;
@@ -0,0 +1,171 @@
1
+ import { defineComponent, createVNode } from "vue";
2
+ import { Icon } from "@manohub/icon";
3
+ const AppShellRoot = /* @__PURE__ */ defineComponent({
4
+ name: "AppShell",
5
+ slots: Object,
6
+ setup(_, {
7
+ slots
8
+ }) {
9
+ return () => createVNode("div", {
10
+ "class": "ak-shell"
11
+ }, [slots.default?.()]);
12
+ }
13
+ });
14
+ const DefaultHeaderIcon = () => createVNode(Icon, {
15
+ "name": "page-title",
16
+ "size": 26
17
+ }, null);
18
+ const AppShellFilter = /* @__PURE__ */ defineComponent({
19
+ name: "AppShellFilter",
20
+ inheritAttrs: false,
21
+ slots: Object,
22
+ setup(_, {
23
+ slots,
24
+ attrs
25
+ }) {
26
+ return () => createVNode("div", {
27
+ "class": ["ak-shell-filter", attrs.class].filter(Boolean).join(" ")
28
+ }, [slots.default?.()]);
29
+ }
30
+ });
31
+ const AppShellHeader = /* @__PURE__ */ defineComponent({
32
+ name: "AppShellHeader",
33
+ inheritAttrs: false,
34
+ props: {
35
+ title: {
36
+ type: String,
37
+ default: void 0
38
+ },
39
+ icon: {
40
+ type: [Function, Object, Array, String],
41
+ default: void 0
42
+ },
43
+ toolbar: {
44
+ type: [Function, Object, Array, String],
45
+ default: void 0
46
+ },
47
+ tabs: {
48
+ type: [Function, Object, Array, String],
49
+ default: void 0
50
+ }
51
+ },
52
+ slots: Object,
53
+ setup(props, {
54
+ slots
55
+ }) {
56
+ const renderAction = (slotFn, propValue) => {
57
+ if (slotFn) return slotFn();
58
+ if (typeof propValue === "function") return propValue();
59
+ return propValue;
60
+ };
61
+ const renderIcon = () => {
62
+ const icon = props.icon;
63
+ if (typeof icon === "function") return icon();
64
+ return icon ?? DefaultHeaderIcon();
65
+ };
66
+ return () => {
67
+ if (!props.title) {
68
+ return slots.default?.() ?? null;
69
+ }
70
+ const toolbar = renderAction(slots.toolbar, props.toolbar);
71
+ const tabs = renderAction(slots.tabs, props.tabs);
72
+ return createVNode("header", {
73
+ "class": ["ak-shell-header", "ak-shell-header--titled"]
74
+ }, [createVNode("div", {
75
+ "class": "ak-shell-header-inner"
76
+ }, [createVNode("div", {
77
+ "class": "ak-shell-header-text"
78
+ }, [createVNode("div", {
79
+ "class": "ak-shell-header-title-row"
80
+ }, [createVNode("span", {
81
+ "class": "ak-shell-header-icon"
82
+ }, [renderIcon()]), createVNode("h1", {
83
+ "class": "ak-shell-header-title"
84
+ }, [props.title])])]), toolbar ? createVNode("div", {
85
+ "class": "ak-shell-header-toolbar"
86
+ }, [toolbar]) : null]), tabs ? createVNode("div", {
87
+ "class": "ak-shell-header-tabs"
88
+ }, [tabs]) : null]);
89
+ };
90
+ }
91
+ });
92
+ const AppShellToolbar = /* @__PURE__ */ defineComponent({
93
+ name: "AppShellToolbar",
94
+ inheritAttrs: false,
95
+ slots: Object,
96
+ setup(_, {
97
+ slots
98
+ }) {
99
+ return () => createVNode("div", {
100
+ "class": "ak-shell-toolbar"
101
+ }, [slots.default?.()]);
102
+ }
103
+ });
104
+ const AppShellBody = /* @__PURE__ */ defineComponent({
105
+ name: "AppShellBody",
106
+ inheritAttrs: false,
107
+ props: {
108
+ mode: {
109
+ type: String,
110
+ default: "plain"
111
+ }
112
+ },
113
+ slots: Object,
114
+ setup(props, {
115
+ slots
116
+ }) {
117
+ return () => createVNode("div", {
118
+ "class": ["ak-shell-body", `ak-shell-body--${props.mode}`]
119
+ }, [slots.default?.()]);
120
+ }
121
+ });
122
+ const AppShellFooter = /* @__PURE__ */ defineComponent({
123
+ name: "AppShellFooter",
124
+ inheritAttrs: false,
125
+ slots: Object,
126
+ setup(_, {
127
+ slots
128
+ }) {
129
+ return () => createVNode("footer", {
130
+ "class": "ak-shell-footer"
131
+ }, [createVNode("div", {
132
+ "class": "ak-shell-footer-inner"
133
+ }, [slots.default?.()])]);
134
+ }
135
+ });
136
+ const AppShellSplit = /* @__PURE__ */ defineComponent({
137
+ name: "AppShellSplit",
138
+ inheritAttrs: false,
139
+ props: {
140
+ sidebar: {
141
+ type: Object,
142
+ default: void 0
143
+ }
144
+ },
145
+ slots: Object,
146
+ setup(props, {
147
+ slots
148
+ }) {
149
+ return () => createVNode("div", {
150
+ "class": "ak-shell-split"
151
+ }, [createVNode("aside", {
152
+ "class": "ak-shell-split-sidebar",
153
+ "style": props.sidebar?.width ? {
154
+ width: `${props.sidebar.width}px`
155
+ } : void 0
156
+ }, [props.sidebar?.content?.()]), createVNode("div", {
157
+ "class": "ak-shell-split-main"
158
+ }, [slots.default?.()])]);
159
+ }
160
+ });
161
+ const AppShell = Object.assign(AppShellRoot, {
162
+ Filter: AppShellFilter,
163
+ Header: AppShellHeader,
164
+ Toolbar: AppShellToolbar,
165
+ Body: AppShellBody,
166
+ Footer: AppShellFooter,
167
+ Split: AppShellSplit
168
+ });
169
+ export {
170
+ AppShell
171
+ };
@@ -0,0 +1,153 @@
1
+ /**
2
+ * AppShell 布局契约(滚动归属写死,调用方无法违反):
3
+ * - .ak-shell:height 100%,纵向 flex,自身不滚
4
+ * - Header / Toolbar / Footer:flex-shrink 0,不滚
5
+ * - Body mode="table":不滚,由 AppTable 内部滚动(表头固定)
6
+ * - Body mode="scroll":自身 overflow-y auto(详情页长内容)
7
+ * - Body mode="plain":不滚(向导页,内容自带滚动)
8
+ * - Split:自身不滚,栏内滚动由内容承担(双栏内用 AppPanel 的 Body 滚)
9
+ *
10
+ * 统一 height:100% 语义(不用 100vh):子应用被注入宿主容器,
11
+ * 100vh 取 window 视口高度,宿主有顶栏即溢出。
12
+ */
13
+
14
+ .ak-shell {
15
+ height: 100%;
16
+ display: flex;
17
+ flex-direction: column;
18
+ overflow: hidden;
19
+ }
20
+
21
+ .ak-shell-header {
22
+ flex-shrink: 0;
23
+ /* 页头条规格依据 MH后台-0911 设计稿 node 0:3888:条高 56(14+28+13+1px 分隔线)、左右 20 */
24
+ padding: 14px 20px 13px;
25
+ border-bottom: 1px solid var(--ui-base-300);
26
+ }
27
+
28
+ .ak-shell-header-inner {
29
+ display: flex;
30
+ align-items: flex-end;
31
+ justify-content: space-between;
32
+ gap: var(--ui-space-4);
33
+ }
34
+
35
+ .ak-shell-header-text {
36
+ min-width: 0;
37
+ flex: 1;
38
+ }
39
+
40
+ .ak-shell-header-title-row {
41
+ display: flex;
42
+ align-items: center;
43
+ /* 设计稿:图标 26 宽 + 10 间距 → 文字起点 36 */
44
+ gap: 10px;
45
+ }
46
+
47
+ .ak-shell-header-icon {
48
+ display: inline-flex;
49
+ align-items: center;
50
+ color: var(--ui-primary);
51
+ /* 设计稿图标 26×26(svg 或 f-icon 字体类均按此渲染) */
52
+ font-size: 26px;
53
+ }
54
+
55
+ .ak-shell-header-icon svg {
56
+ width: 26px;
57
+ height: 26px;
58
+ }
59
+
60
+ /* 页头右侧工具条(Header 唯一可自定义位):搜索框 / 按钮 / 下拉等,水平排列;
61
+ 间距与垂直对齐由标准样式锁死,内容(搜索框、按钮)由消费方提供 */
62
+ .ak-shell-header-toolbar {
63
+ flex-shrink: 0;
64
+ display: flex;
65
+ align-items: center;
66
+ gap: var(--ui-space-3);
67
+ }
68
+
69
+ .ak-shell-header-tabs {
70
+ margin-top: var(--ui-space-3);
71
+ }
72
+
73
+ .ak-shell-header-title {
74
+ margin: 0;
75
+ font-size: var(--ui-font-title);
76
+ font-weight: 600;
77
+ line-height: 28px;
78
+ color: var(--ui-base-content);
79
+ }
80
+
81
+ /* 筛选区块(Header 上方的第一子块):query-solution / 筛选条。
82
+ 无边框;padding-bottom 恒为 0 —— 与 Header 的间距由 Header 自身的 top padding
83
+ (14px)提供,避免两块 padding 叠加成双倍间距 */
84
+ .ak-shell-filter {
85
+ flex-shrink: 0;
86
+ padding: 14px 20px 0;
87
+ }
88
+
89
+ .ak-shell-toolbar {
90
+ flex-shrink: 0;
91
+ padding: var(--ui-space-3) 20px;
92
+ }
93
+
94
+ .ak-shell-body {
95
+ flex: 1;
96
+ min-height: 0;
97
+ /* 页面内距 20px(与页头左边距对齐,同设计稿) */
98
+ padding: 0 20px;
99
+ }
100
+
101
+ .ak-shell-body--scroll {
102
+ overflow-y: auto;
103
+ }
104
+
105
+ .ak-shell-body--table,
106
+ .ak-shell-body--plain {
107
+ overflow: hidden;
108
+ }
109
+
110
+ .ak-shell-footer {
111
+ flex-shrink: 0;
112
+ padding: var(--ui-space-3) 20px;
113
+ }
114
+
115
+ .ak-shell-footer-inner {
116
+ display: flex;
117
+ justify-content: flex-end;
118
+ }
119
+
120
+ /* ===== Split 双栏 =====
121
+ 中间 1px 竖直分隔线由 Split 统一提供(线色与页头下边线同源 --ui-base-300),
122
+ 线右侧(主栏)留 --ui-space-4(16px) 内距与线拉开间距;**侧栏不额外加内距**(内容自带)。
123
+ 业务侧不得再给栏的子元素加竖直边框。
124
+
125
+ 滚动:**Split 自身不滚,栏内滚动由内容承担**(双栏内用 AppPanel,其 Body overflow:auto)。
126
+ 两栏 overflow:hidden 是为了避免「外层先滚、内层滚不到」的双层滚动容器。
127
+
128
+ 侧栏宽度语义(`sidebar.width`):**内容宽**(content-box),
129
+ 1px 边线与 16px 右内距加在框外 —— 消费方不必手算补偿值。 */
130
+ .ak-shell-split {
131
+ height: 100%;
132
+ display: flex;
133
+ gap: 0;
134
+ overflow: hidden;
135
+ }
136
+
137
+ .ak-shell-split-sidebar {
138
+ flex-shrink: 0;
139
+ /* width 语义是内容宽 → 1px 边线加在内容框外 */
140
+ box-sizing: content-box;
141
+ border-right: 1px solid var(--ui-base-300);
142
+ /* **有意不留右内距**:侧栏内容(面板 / 树)自带内距,再加一段会让线与内容脱节。
143
+ 反向锁定见 __tests__/app-shell.spec.ts,别再「顺手补齐」。 */
144
+ overflow: hidden;
145
+ }
146
+
147
+ .ak-shell-split-main {
148
+ flex: 1;
149
+ min-width: 0;
150
+ /* 线右侧 16px 内距:把内容与分隔线拉开 */
151
+ padding-left: var(--ui-space-4);
152
+ overflow: hidden;
153
+ }