@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,273 @@
|
|
|
1
|
+
import { type PropType, type SlotsType, type VNodeChild } from 'vue';
|
|
2
|
+
/** 树节点:至少要有主键与标签,子节点字段名可用 `childrenKey` 指定 */
|
|
3
|
+
export interface AppTreeNode {
|
|
4
|
+
[key: string]: unknown;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* 树(**自建**,不封装 farris)。
|
|
8
|
+
*
|
|
9
|
+
* ## 视觉与几何规范来源
|
|
10
|
+
*
|
|
11
|
+
* 对齐 `@aihub/theme` 的 Tree(`src/components/tree.css` 令牌 + `playground/components/tree.html` 标记):
|
|
12
|
+
* 节点行高 30(紧凑档 28)、圆角 6(`--ui-radius-control`)、字号 13、滑过 Ne11、点击瞬态 #F4F5F9、
|
|
13
|
+
* 选中 Au02 底 + Th03 字、箭头 8px(收起态转 −90°)、缩进 = 基础内距 8 + level × 步进 10(**无上限**)。
|
|
14
|
+
*
|
|
15
|
+
* 两处与主题包的**有意差异**(不改视觉,只改承载方式):
|
|
16
|
+
* 1. **缩进步进由 CSS 变量承载**:主题包用 typed `attr(data-level type(<number>))` 一步算出;
|
|
17
|
+
* 为兼容尚无该能力的引擎,本组件同时下发 `data-level`(数据、供 CSS/测试钩子)与
|
|
18
|
+
* `--ak-tree-level`(公式仍在 CSS 内,见 components.css)。不用行内 paddingLeft —— 那样
|
|
19
|
+
* 步进值就散落在 JS 里,改了令牌对不上。
|
|
20
|
+
* 2. **交互分工**:主题包的约定是「点行即展开/收拢,选中由行内选择件驱动」;本组件没有内嵌选择件,
|
|
21
|
+
* 故沿用「点箭头展开/收拢、点行选中」(`onSelect`)—— 消费方靠 `onSelect` 拿选中,展开态受控。
|
|
22
|
+
*
|
|
23
|
+
* ## 为什么自建(对照计划登记的退路)
|
|
24
|
+
*
|
|
25
|
+
* farris `FTreeView` 实测结论:① 是列模板式网格(节点内容可用 `columnTemplate` 承载);
|
|
26
|
+
* ② **只在挂载时填充一次可见行,data 变化不刷新**(仓内唯一的适配器
|
|
27
|
+
* `apps/skill/src/features/skill/farris-file-tree.tsx` 正是用 `key` 强制重挂绕行);
|
|
28
|
+
* ③ 层级缩进不可控、节点图标走 farris 图标字体类。
|
|
29
|
+
*
|
|
30
|
+
* 而本组件的硬需求是「数据更新时**不丢**用户已展开的节点 / 滚动位置 / 焦点」——
|
|
31
|
+
* 与 ② 直接冲突(重挂必然丢展开态);缩进又要按设计稿精确控制(③)。因此按计划
|
|
32
|
+
* 登记的退路改为自建:契约完整(含受控展开态)、零 farris 耦合、可在无浏览器环境测试。
|
|
33
|
+
*
|
|
34
|
+
* ## 与数据变化的关系
|
|
35
|
+
*
|
|
36
|
+
* 自建后**不需要**「nodes 变就重建」这种逃生舱:渲染直接跟随 `nodes`,
|
|
37
|
+
* 展开态由 `expandedKeys`(受控)或内部状态(非受控)持有,数据刷新不会改变展开态。
|
|
38
|
+
* `treeKey` 仅作为极端场景的强制重挂出口保留。
|
|
39
|
+
*
|
|
40
|
+
* @example 受控展开 + 计数徽标
|
|
41
|
+
* <AppTree nodes={treeNodes.value} rowKey="id" labelKey="name"
|
|
42
|
+
* selected={selectedId.value} onSelect={(n) => select(n.id)}
|
|
43
|
+
* expandedKeys={expandedKeys.value} onExpandChange={(k) => (expandedKeys.value = k)}
|
|
44
|
+
* renderNode={(n) => <AppBadge>{countOfDomain(n.id)}</AppBadge>} />
|
|
45
|
+
*/
|
|
46
|
+
export declare const AppTree: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
|
47
|
+
nodes: {
|
|
48
|
+
type: PropType<AppTreeNode[]>;
|
|
49
|
+
required: true;
|
|
50
|
+
};
|
|
51
|
+
/** 节点主键:字段名或取值函数 */
|
|
52
|
+
rowKey: {
|
|
53
|
+
type: PropType<string | ((node: AppTreeNode) => string)>;
|
|
54
|
+
required: true;
|
|
55
|
+
};
|
|
56
|
+
/** 标签字段名(默认 'label') */
|
|
57
|
+
labelKey: {
|
|
58
|
+
type: StringConstructor;
|
|
59
|
+
default: string;
|
|
60
|
+
};
|
|
61
|
+
/** 子节点字段名(默认 'children') */
|
|
62
|
+
childrenKey: {
|
|
63
|
+
type: StringConstructor;
|
|
64
|
+
default: string;
|
|
65
|
+
};
|
|
66
|
+
/** 受控选中(行主键值) */
|
|
67
|
+
selected: {
|
|
68
|
+
type: StringConstructor;
|
|
69
|
+
default: undefined;
|
|
70
|
+
};
|
|
71
|
+
onSelect: {
|
|
72
|
+
type: PropType<(node: AppTreeNode) => void>;
|
|
73
|
+
default: undefined;
|
|
74
|
+
};
|
|
75
|
+
/** 节点附加内容(计数徽标等),渲染在标签之后 */
|
|
76
|
+
renderNode: {
|
|
77
|
+
type: PropType<(node: AppTreeNode) => unknown>;
|
|
78
|
+
default: undefined;
|
|
79
|
+
};
|
|
80
|
+
/** 尺寸档:md 舒适(行高 30)/ sm 紧凑(行高 28),与主题包 Tree 的 M/S 档一致 */
|
|
81
|
+
size: {
|
|
82
|
+
type: PropType<"md" | "sm">;
|
|
83
|
+
default: string;
|
|
84
|
+
};
|
|
85
|
+
/** 受控展开集(传了就完全受控,内部不再自持) */
|
|
86
|
+
expandedKeys: {
|
|
87
|
+
type: PropType<string[]>;
|
|
88
|
+
default: undefined;
|
|
89
|
+
};
|
|
90
|
+
/** 非受控展开初值 */
|
|
91
|
+
defaultExpandedKeys: {
|
|
92
|
+
type: PropType<string[]>;
|
|
93
|
+
default: () => never[];
|
|
94
|
+
};
|
|
95
|
+
onExpandChange: {
|
|
96
|
+
type: PropType<(keys: string[]) => void>;
|
|
97
|
+
default: undefined;
|
|
98
|
+
};
|
|
99
|
+
loading: {
|
|
100
|
+
type: BooleanConstructor;
|
|
101
|
+
default: boolean;
|
|
102
|
+
};
|
|
103
|
+
/** 空态文案 */
|
|
104
|
+
empty: {
|
|
105
|
+
type: StringConstructor;
|
|
106
|
+
default: string;
|
|
107
|
+
};
|
|
108
|
+
/** 空态操作按钮(如「新建」);与 `onEmptyAction` 同时传才渲染 */
|
|
109
|
+
emptyActionText: {
|
|
110
|
+
type: StringConstructor;
|
|
111
|
+
default: undefined;
|
|
112
|
+
};
|
|
113
|
+
onEmptyAction: {
|
|
114
|
+
type: PropType<() => void>;
|
|
115
|
+
default: undefined;
|
|
116
|
+
};
|
|
117
|
+
/**
|
|
118
|
+
* 错误态(truthy 即触发)。
|
|
119
|
+
*
|
|
120
|
+
* **构造器顺序同 `AppTable.error`:`String` 必须排在 `Boolean` 前** —— 顺序写反时,成功态传的
|
|
121
|
+
* 空字符串会被 Vue 的布尔转换判成 `true`,树永远停在错误态。
|
|
122
|
+
*
|
|
123
|
+
* 有它之前,消费方只能把错误文案塞进 `empty`:错误被渲染成空态语义(无错误插画、无重试入口),
|
|
124
|
+
* 用户看不出「是没数据还是加载挂了」。
|
|
125
|
+
*/
|
|
126
|
+
error: {
|
|
127
|
+
type: PropType<any>;
|
|
128
|
+
default: undefined;
|
|
129
|
+
};
|
|
130
|
+
/** 错误态标题(默认「加载失败」);字符串错误原文会自动作为副文案显示 */
|
|
131
|
+
errorTitle: {
|
|
132
|
+
type: StringConstructor;
|
|
133
|
+
default: string;
|
|
134
|
+
};
|
|
135
|
+
/** 错误态操作按钮(如「重试」);与 `onErrorAction` 同时传才渲染 */
|
|
136
|
+
errorActionText: {
|
|
137
|
+
type: StringConstructor;
|
|
138
|
+
default: undefined;
|
|
139
|
+
};
|
|
140
|
+
onErrorAction: {
|
|
141
|
+
type: PropType<() => void>;
|
|
142
|
+
default: undefined;
|
|
143
|
+
};
|
|
144
|
+
/** 强制重挂逃生舱(正常数据刷新不需要它) */
|
|
145
|
+
treeKey: {
|
|
146
|
+
type: PropType<string | number>;
|
|
147
|
+
default: undefined;
|
|
148
|
+
};
|
|
149
|
+
}>, () => import("vue/jsx-runtime").JSX.Element, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
|
150
|
+
nodes: {
|
|
151
|
+
type: PropType<AppTreeNode[]>;
|
|
152
|
+
required: true;
|
|
153
|
+
};
|
|
154
|
+
/** 节点主键:字段名或取值函数 */
|
|
155
|
+
rowKey: {
|
|
156
|
+
type: PropType<string | ((node: AppTreeNode) => string)>;
|
|
157
|
+
required: true;
|
|
158
|
+
};
|
|
159
|
+
/** 标签字段名(默认 'label') */
|
|
160
|
+
labelKey: {
|
|
161
|
+
type: StringConstructor;
|
|
162
|
+
default: string;
|
|
163
|
+
};
|
|
164
|
+
/** 子节点字段名(默认 'children') */
|
|
165
|
+
childrenKey: {
|
|
166
|
+
type: StringConstructor;
|
|
167
|
+
default: string;
|
|
168
|
+
};
|
|
169
|
+
/** 受控选中(行主键值) */
|
|
170
|
+
selected: {
|
|
171
|
+
type: StringConstructor;
|
|
172
|
+
default: undefined;
|
|
173
|
+
};
|
|
174
|
+
onSelect: {
|
|
175
|
+
type: PropType<(node: AppTreeNode) => void>;
|
|
176
|
+
default: undefined;
|
|
177
|
+
};
|
|
178
|
+
/** 节点附加内容(计数徽标等),渲染在标签之后 */
|
|
179
|
+
renderNode: {
|
|
180
|
+
type: PropType<(node: AppTreeNode) => unknown>;
|
|
181
|
+
default: undefined;
|
|
182
|
+
};
|
|
183
|
+
/** 尺寸档:md 舒适(行高 30)/ sm 紧凑(行高 28),与主题包 Tree 的 M/S 档一致 */
|
|
184
|
+
size: {
|
|
185
|
+
type: PropType<"md" | "sm">;
|
|
186
|
+
default: string;
|
|
187
|
+
};
|
|
188
|
+
/** 受控展开集(传了就完全受控,内部不再自持) */
|
|
189
|
+
expandedKeys: {
|
|
190
|
+
type: PropType<string[]>;
|
|
191
|
+
default: undefined;
|
|
192
|
+
};
|
|
193
|
+
/** 非受控展开初值 */
|
|
194
|
+
defaultExpandedKeys: {
|
|
195
|
+
type: PropType<string[]>;
|
|
196
|
+
default: () => never[];
|
|
197
|
+
};
|
|
198
|
+
onExpandChange: {
|
|
199
|
+
type: PropType<(keys: string[]) => void>;
|
|
200
|
+
default: undefined;
|
|
201
|
+
};
|
|
202
|
+
loading: {
|
|
203
|
+
type: BooleanConstructor;
|
|
204
|
+
default: boolean;
|
|
205
|
+
};
|
|
206
|
+
/** 空态文案 */
|
|
207
|
+
empty: {
|
|
208
|
+
type: StringConstructor;
|
|
209
|
+
default: string;
|
|
210
|
+
};
|
|
211
|
+
/** 空态操作按钮(如「新建」);与 `onEmptyAction` 同时传才渲染 */
|
|
212
|
+
emptyActionText: {
|
|
213
|
+
type: StringConstructor;
|
|
214
|
+
default: undefined;
|
|
215
|
+
};
|
|
216
|
+
onEmptyAction: {
|
|
217
|
+
type: PropType<() => void>;
|
|
218
|
+
default: undefined;
|
|
219
|
+
};
|
|
220
|
+
/**
|
|
221
|
+
* 错误态(truthy 即触发)。
|
|
222
|
+
*
|
|
223
|
+
* **构造器顺序同 `AppTable.error`:`String` 必须排在 `Boolean` 前** —— 顺序写反时,成功态传的
|
|
224
|
+
* 空字符串会被 Vue 的布尔转换判成 `true`,树永远停在错误态。
|
|
225
|
+
*
|
|
226
|
+
* 有它之前,消费方只能把错误文案塞进 `empty`:错误被渲染成空态语义(无错误插画、无重试入口),
|
|
227
|
+
* 用户看不出「是没数据还是加载挂了」。
|
|
228
|
+
*/
|
|
229
|
+
error: {
|
|
230
|
+
type: PropType<any>;
|
|
231
|
+
default: undefined;
|
|
232
|
+
};
|
|
233
|
+
/** 错误态标题(默认「加载失败」);字符串错误原文会自动作为副文案显示 */
|
|
234
|
+
errorTitle: {
|
|
235
|
+
type: StringConstructor;
|
|
236
|
+
default: string;
|
|
237
|
+
};
|
|
238
|
+
/** 错误态操作按钮(如「重试」);与 `onErrorAction` 同时传才渲染 */
|
|
239
|
+
errorActionText: {
|
|
240
|
+
type: StringConstructor;
|
|
241
|
+
default: undefined;
|
|
242
|
+
};
|
|
243
|
+
onErrorAction: {
|
|
244
|
+
type: PropType<() => void>;
|
|
245
|
+
default: undefined;
|
|
246
|
+
};
|
|
247
|
+
/** 强制重挂逃生舱(正常数据刷新不需要它) */
|
|
248
|
+
treeKey: {
|
|
249
|
+
type: PropType<string | number>;
|
|
250
|
+
default: undefined;
|
|
251
|
+
};
|
|
252
|
+
}>> & Readonly<{}>, {
|
|
253
|
+
onSelect: (node: AppTreeNode) => void;
|
|
254
|
+
size: "sm" | "md";
|
|
255
|
+
loading: boolean;
|
|
256
|
+
error: any;
|
|
257
|
+
empty: string;
|
|
258
|
+
errorTitle: string;
|
|
259
|
+
emptyActionText: string;
|
|
260
|
+
onEmptyAction: () => void;
|
|
261
|
+
errorActionText: string;
|
|
262
|
+
onErrorAction: () => void;
|
|
263
|
+
labelKey: string;
|
|
264
|
+
childrenKey: string;
|
|
265
|
+
selected: string;
|
|
266
|
+
renderNode: (node: AppTreeNode) => unknown;
|
|
267
|
+
expandedKeys: string[];
|
|
268
|
+
defaultExpandedKeys: string[];
|
|
269
|
+
onExpandChange: (keys: string[]) => void;
|
|
270
|
+
treeKey: string | number;
|
|
271
|
+
}, SlotsType<{
|
|
272
|
+
default?: () => VNodeChild;
|
|
273
|
+
}>, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { defineComponent, ref, computed, watch, createVNode } from "vue";
|
|
2
|
+
import { AppQueryState } from "../atoms/app-query-state.js";
|
|
3
|
+
const AppTree = /* @__PURE__ */ defineComponent({
|
|
4
|
+
name: "AppTree",
|
|
5
|
+
inheritAttrs: false,
|
|
6
|
+
props: {
|
|
7
|
+
nodes: {
|
|
8
|
+
type: Array,
|
|
9
|
+
required: true
|
|
10
|
+
},
|
|
11
|
+
/** 节点主键:字段名或取值函数 */
|
|
12
|
+
rowKey: {
|
|
13
|
+
type: [String, Function],
|
|
14
|
+
required: true
|
|
15
|
+
},
|
|
16
|
+
/** 标签字段名(默认 'label') */
|
|
17
|
+
labelKey: {
|
|
18
|
+
type: String,
|
|
19
|
+
default: "label"
|
|
20
|
+
},
|
|
21
|
+
/** 子节点字段名(默认 'children') */
|
|
22
|
+
childrenKey: {
|
|
23
|
+
type: String,
|
|
24
|
+
default: "children"
|
|
25
|
+
},
|
|
26
|
+
/** 受控选中(行主键值) */
|
|
27
|
+
selected: {
|
|
28
|
+
type: String,
|
|
29
|
+
default: void 0
|
|
30
|
+
},
|
|
31
|
+
onSelect: {
|
|
32
|
+
type: Function,
|
|
33
|
+
default: void 0
|
|
34
|
+
},
|
|
35
|
+
/** 节点附加内容(计数徽标等),渲染在标签之后 */
|
|
36
|
+
renderNode: {
|
|
37
|
+
type: Function,
|
|
38
|
+
default: void 0
|
|
39
|
+
},
|
|
40
|
+
/** 尺寸档:md 舒适(行高 30)/ sm 紧凑(行高 28),与主题包 Tree 的 M/S 档一致 */
|
|
41
|
+
size: {
|
|
42
|
+
type: String,
|
|
43
|
+
default: "md"
|
|
44
|
+
},
|
|
45
|
+
/** 受控展开集(传了就完全受控,内部不再自持) */
|
|
46
|
+
expandedKeys: {
|
|
47
|
+
type: Array,
|
|
48
|
+
default: void 0
|
|
49
|
+
},
|
|
50
|
+
/** 非受控展开初值 */
|
|
51
|
+
defaultExpandedKeys: {
|
|
52
|
+
type: Array,
|
|
53
|
+
default: () => []
|
|
54
|
+
},
|
|
55
|
+
onExpandChange: {
|
|
56
|
+
type: Function,
|
|
57
|
+
default: void 0
|
|
58
|
+
},
|
|
59
|
+
loading: {
|
|
60
|
+
type: Boolean,
|
|
61
|
+
default: false
|
|
62
|
+
},
|
|
63
|
+
/** 空态文案 */
|
|
64
|
+
empty: {
|
|
65
|
+
type: String,
|
|
66
|
+
default: "暂无数据"
|
|
67
|
+
},
|
|
68
|
+
/** 空态操作按钮(如「新建」);与 `onEmptyAction` 同时传才渲染 */
|
|
69
|
+
emptyActionText: {
|
|
70
|
+
type: String,
|
|
71
|
+
default: void 0
|
|
72
|
+
},
|
|
73
|
+
onEmptyAction: {
|
|
74
|
+
type: Function,
|
|
75
|
+
default: void 0
|
|
76
|
+
},
|
|
77
|
+
/**
|
|
78
|
+
* 错误态(truthy 即触发)。
|
|
79
|
+
*
|
|
80
|
+
* **构造器顺序同 `AppTable.error`:`String` 必须排在 `Boolean` 前** —— 顺序写反时,成功态传的
|
|
81
|
+
* 空字符串会被 Vue 的布尔转换判成 `true`,树永远停在错误态。
|
|
82
|
+
*
|
|
83
|
+
* 有它之前,消费方只能把错误文案塞进 `empty`:错误被渲染成空态语义(无错误插画、无重试入口),
|
|
84
|
+
* 用户看不出「是没数据还是加载挂了」。
|
|
85
|
+
*/
|
|
86
|
+
error: {
|
|
87
|
+
type: [String, Boolean, Object, Number, Array],
|
|
88
|
+
default: void 0
|
|
89
|
+
},
|
|
90
|
+
/** 错误态标题(默认「加载失败」);字符串错误原文会自动作为副文案显示 */
|
|
91
|
+
errorTitle: {
|
|
92
|
+
type: String,
|
|
93
|
+
default: "加载失败"
|
|
94
|
+
},
|
|
95
|
+
/** 错误态操作按钮(如「重试」);与 `onErrorAction` 同时传才渲染 */
|
|
96
|
+
errorActionText: {
|
|
97
|
+
type: String,
|
|
98
|
+
default: void 0
|
|
99
|
+
},
|
|
100
|
+
onErrorAction: {
|
|
101
|
+
type: Function,
|
|
102
|
+
default: void 0
|
|
103
|
+
},
|
|
104
|
+
/** 强制重挂逃生舱(正常数据刷新不需要它) */
|
|
105
|
+
treeKey: {
|
|
106
|
+
type: [String, Number],
|
|
107
|
+
default: void 0
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
slots: Object,
|
|
111
|
+
setup(props, {
|
|
112
|
+
slots,
|
|
113
|
+
attrs
|
|
114
|
+
}) {
|
|
115
|
+
const nodeKey = (node) => typeof props.rowKey === "function" ? props.rowKey(node) : String(node[props.rowKey] ?? "");
|
|
116
|
+
const childrenOf = (node) => {
|
|
117
|
+
const children = node[props.childrenKey];
|
|
118
|
+
return Array.isArray(children) ? children : [];
|
|
119
|
+
};
|
|
120
|
+
const innerExpanded = ref([...props.defaultExpandedKeys]);
|
|
121
|
+
const expandedSet = computed(() => new Set(props.expandedKeys ?? innerExpanded.value));
|
|
122
|
+
const isExpanded = (key) => expandedSet.value.has(key);
|
|
123
|
+
const toggleExpand = (key) => {
|
|
124
|
+
const next = new Set(expandedSet.value);
|
|
125
|
+
if (next.has(key)) next.delete(key);
|
|
126
|
+
else next.add(key);
|
|
127
|
+
const list = [...next];
|
|
128
|
+
if (props.expandedKeys === void 0) innerExpanded.value = list;
|
|
129
|
+
props.onExpandChange?.(list);
|
|
130
|
+
};
|
|
131
|
+
watch(() => props.defaultExpandedKeys, (next) => {
|
|
132
|
+
if (props.expandedKeys === void 0) innerExpanded.value = [...next];
|
|
133
|
+
});
|
|
134
|
+
const handleSelect = (node) => {
|
|
135
|
+
props.onSelect?.(node);
|
|
136
|
+
};
|
|
137
|
+
const flatten = () => {
|
|
138
|
+
const rows = [];
|
|
139
|
+
const walk = (list, depth) => {
|
|
140
|
+
for (const node of list) {
|
|
141
|
+
const key = nodeKey(node);
|
|
142
|
+
rows.push({
|
|
143
|
+
node,
|
|
144
|
+
depth,
|
|
145
|
+
key
|
|
146
|
+
});
|
|
147
|
+
if (isExpanded(key)) walk(childrenOf(node), depth + 1);
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
walk(props.nodes, 0);
|
|
151
|
+
return rows;
|
|
152
|
+
};
|
|
153
|
+
return () => {
|
|
154
|
+
if (props.loading) {
|
|
155
|
+
return createVNode(AppQueryState, {
|
|
156
|
+
"loading": true,
|
|
157
|
+
"skeletonRows": 6
|
|
158
|
+
}, null);
|
|
159
|
+
}
|
|
160
|
+
if (props.error) {
|
|
161
|
+
return createVNode(AppQueryState, {
|
|
162
|
+
"error": props.error,
|
|
163
|
+
"errorTitle": props.errorTitle,
|
|
164
|
+
"errorDescription": typeof props.error === "string" ? props.error : void 0,
|
|
165
|
+
"errorActionText": props.errorActionText,
|
|
166
|
+
"onErrorAction": props.onErrorAction
|
|
167
|
+
}, null);
|
|
168
|
+
}
|
|
169
|
+
if (props.nodes.length === 0) {
|
|
170
|
+
return createVNode(AppQueryState, {
|
|
171
|
+
"empty": props.empty,
|
|
172
|
+
"emptyActionText": props.emptyActionText,
|
|
173
|
+
"onEmptyAction": props.onEmptyAction
|
|
174
|
+
}, null);
|
|
175
|
+
}
|
|
176
|
+
const rows = flatten().map(({
|
|
177
|
+
node,
|
|
178
|
+
depth,
|
|
179
|
+
key
|
|
180
|
+
}) => {
|
|
181
|
+
const children = childrenOf(node);
|
|
182
|
+
const hasChildren = children.length > 0;
|
|
183
|
+
const expanded = isExpanded(key);
|
|
184
|
+
const selected = props.selected !== void 0 && props.selected === key;
|
|
185
|
+
return createVNode("div", {
|
|
186
|
+
"class": "ak-tree__row",
|
|
187
|
+
"key": key,
|
|
188
|
+
"role": "treeitem",
|
|
189
|
+
"aria-selected": selected,
|
|
190
|
+
"aria-expanded": hasChildren ? expanded : void 0,
|
|
191
|
+
"data-selected": selected ? "true" : void 0,
|
|
192
|
+
"data-level": depth,
|
|
193
|
+
"style": {
|
|
194
|
+
"--ak-tree-level": depth
|
|
195
|
+
},
|
|
196
|
+
"onClick": () => handleSelect(node)
|
|
197
|
+
}, [hasChildren ? createVNode("span", {
|
|
198
|
+
"class": "ak-tree__caret",
|
|
199
|
+
"data-expanded": expanded ? "true" : "false",
|
|
200
|
+
"onClick": (event) => {
|
|
201
|
+
event.stopPropagation();
|
|
202
|
+
toggleExpand(key);
|
|
203
|
+
}
|
|
204
|
+
}, null) : createVNode("span", {
|
|
205
|
+
"class": "ak-tree__caret ak-tree__caret--empty"
|
|
206
|
+
}, null), createVNode("span", {
|
|
207
|
+
"class": "ak-tree__label"
|
|
208
|
+
}, [String(node[props.labelKey] ?? "")]), props.renderNode ? createVNode("span", {
|
|
209
|
+
"class": "ak-tree__extra"
|
|
210
|
+
}, [props.renderNode(node)]) : null]);
|
|
211
|
+
});
|
|
212
|
+
return createVNode("div", {
|
|
213
|
+
"key": props.treeKey,
|
|
214
|
+
"class": [attrs.class, "ak-tree", props.size === "sm" ? "ak-tree--compact" : ""].filter(Boolean).join(" "),
|
|
215
|
+
"role": "tree"
|
|
216
|
+
}, [rows, slots.default?.()]);
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
export {
|
|
221
|
+
AppTree
|
|
222
|
+
};
|