@done-coding/admin-core 0.27.0 → 0.27.1-alpha.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/es/bridge/index.mjs +11 -3
- package/es/components/app-layout/AppBody.vue.mjs +1 -1
- package/es/components/app-layout/AppBody.vue2.mjs +14 -5
- package/es/components/app-layout/AppHeader.vue.mjs +1 -1
- package/es/components/app-layout/AppHeader.vue2.mjs +7 -5
- package/es/components/app-layout/AppLayout.vue.mjs +1 -1
- package/es/components/app-layout/AppLayout.vue2.mjs +2 -1
- package/es/components/app-layout/AppPage.vue.mjs +1 -1
- package/es/components/app-layout/AppPage.vue2.mjs +35 -32
- package/es/components/app-layout/app-page-geometry.mjs +16 -16
- package/es/components/app-layout/app-page-recommended.mjs +10 -0
- package/es/components/data-view/DataGridView.vue.mjs +1 -1
- package/es/components/data-view/DataGridView.vue2.mjs +29 -17
- package/es/components/data-view/DataListView.vue.mjs +1 -1
- package/es/components/data-view/DataListView.vue2.mjs +10 -0
- package/es/components/data-view/InfiniteListView.vue.mjs +1 -1
- package/es/components/data-view/InfiniteListView.vue2.mjs +9 -3
- package/es/components/data-view/use-active-scroll.mjs +15 -0
- package/es/components/display/DragFloat.vue.mjs +7 -0
- package/es/components/display/DragFloat.vue2.mjs +109 -0
- package/es/components/display/TabsHeader.vue.mjs +1 -1
- package/es/components/display/TabsHeader.vue2.mjs +9 -10
- package/es/components/display/TabsMain.vue.mjs +22 -7
- package/es/components/display/tabs-variant.mjs +14 -0
- package/es/components/form/FormDivider.vue.mjs +7 -0
- package/es/components/form/FormDivider.vue2.mjs +14 -0
- package/es/components/form/FormGroupTitle.vue.mjs +7 -0
- package/es/components/form/FormGroupTitle.vue2.mjs +59 -0
- package/es/components/form/FormItemNestForm.vue.mjs +1 -1
- package/es/components/form/FormItemNestFormList.vue.mjs +1 -1
- package/es/components/form/FormMain.vue.mjs +1 -1
- package/es/components/form/FormMain.vue2.mjs +69 -9
- package/es/components/form/FormSearch.vue.mjs +1 -1
- package/es/components/form/FormSearch.vue2.mjs +15 -3
- package/es/components/form/FormSubmitPanel.vue.mjs +1 -1
- package/es/components/form/FormSubmitPanel.vue2.mjs +14 -1
- package/es/components/form/ai-schema.mjs +139 -0
- package/es/components/form/group.mjs +81 -0
- package/es/components/form/use-ai-fill.mjs +71 -0
- package/es/components/form/utils.mjs +15 -4
- package/es/components/list-layout/ListLayout.vue.mjs +1 -1
- package/es/components/list-layout/ListLayout.vue2.mjs +103 -62
- package/es/components/modal/ImagePreviewTrigger.vue.mjs +5 -1
- package/es/components/modal/ModalConfirm.vue.mjs +1 -1
- package/es/components/modal/ModalConfirm.vue2.mjs +8 -5
- package/es/components/modal/VideoPreviewTrigger.vue.mjs +1 -1
- package/es/components/modal/VideoPreviewTrigger.vue2.mjs +2 -1
- package/es/components/page-layout/AppPageDetail.vue.mjs +1 -1
- package/es/components/page-layout/AppPageDetail.vue2.mjs +17 -13
- package/es/components/page-layout/AppPageListDetailLayout.vue.mjs +7 -4
- package/es/components/page-layout/AppPageListDetailSheet.vue.mjs +12 -4
- package/es/components/page-layout/AppPageListDetailSplit.vue.mjs +1 -1
- package/es/components/page-layout/AppPageListDetailSplit.vue2.mjs +10 -3
- package/es/components/table/TableMain.vue.mjs +1 -1
- package/es/components/table/TableMain.vue2.mjs +45 -25
- package/es/components/view-layout/ViewLayout.vue.mjs +1 -1
- package/es/components/view-layout/ViewLayout.vue2.mjs +46 -15
- package/es/config/slot-region.mjs +1 -0
- package/es/hooks/use-active-record.mjs +5 -1
- package/es/hooks/use-surface.mjs +27 -0
- package/es/hooks/use-theme-apply.mjs +52 -18
- package/es/index.mjs +81 -59
- package/es/inject/key.mjs +4 -0
- package/es/store/app.mjs +51 -17
- package/es/style.css +361 -184
- package/es/utils/dom.mjs +7 -1
- package/es/utils/gap-scale.mjs +25 -0
- package/es/utils/theme-scale.mjs +27 -0
- package/package.json +2 -2
- package/src/bridge/docs/README.md +30 -0
- package/src/components/app-layout/docs/README-AppBody.md +21 -0
- package/src/components/app-layout/docs/README-AppPage.md +20 -3
- package/src/components/data-view/docs/README-DataGridView.md +15 -0
- package/src/components/data-view/docs/README-DataListView.md +13 -0
- package/src/components/data-view/docs/README-InfiniteListView.md +15 -0
- package/src/components/display/README.md +1 -0
- package/src/components/display/docs/README-DragFloat.md +68 -0
- package/src/components/form/README.md +4 -2
- package/src/components/form/docs/README-FormItemNestForm.md +5 -3
- package/src/components/form/docs/README-FormItemNestFormList.md +7 -1
- package/src/components/form/docs/README-FormMain.md +102 -1
- package/src/components/form/docs/README-FormSearch.md +5 -0
- package/src/components/list-layout/docs/README-ListLayout.md +61 -1
- package/src/components/modal/docs/README-ImagePreviewTrigger.md +2 -0
- package/src/components/modal/docs/README-VideoPreviewTrigger.md +4 -1
- package/src/components/page-layout/docs/README-AppPageDetail.md +11 -1
- package/src/components/page-layout/docs/README-AppPageListDetailLayout.md +64 -6
- package/src/components/table/docs/README-TableMain.md +22 -0
- package/src/components/view-layout/docs/README-ViewLayout.md +27 -2
- package/src/hooks/docs/README.md +30 -1
- package/types/bridge/index.d.ts +69 -10
- package/types/components/app-layout/AppPage.vue.d.ts +15 -7
- package/types/components/app-layout/app-page-geometry.d.ts +19 -10
- package/types/components/app-layout/app-page-recommended.d.ts +12 -0
- package/types/components/app-layout/index.d.ts +1 -0
- package/types/components/data-view/types.d.ts +18 -0
- package/types/components/data-view/use-active-scroll.d.ts +30 -0
- package/types/components/display/BadgeMark.vue.d.ts +2 -2
- package/types/components/display/DragFloat.vue.d.ts +35 -0
- package/types/components/display/TabsHeader.vue.d.ts +7 -0
- package/types/components/display/index.d.ts +8 -1
- package/types/components/display/tabs-variant.d.ts +17 -0
- package/types/components/display/types.d.ts +6 -0
- package/types/components/form/FormDivider.vue.d.ts +2 -0
- package/types/components/form/FormGroupTitle.vue.d.ts +22 -0
- package/types/components/form/FormItemNestForm.vue.d.ts +2 -0
- package/types/components/form/ai-schema.d.ts +24 -0
- package/types/components/form/group.d.ts +36 -0
- package/types/components/form/index.d.ts +2 -0
- package/types/components/form/types.d.ts +94 -3
- package/types/components/form/use-ai-fill.d.ts +59 -0
- package/types/components/form/utils.d.ts +19 -0
- package/types/components/list-layout/ListLayout.vue.d.ts +3 -0
- package/types/components/list-layout/types.d.ts +28 -0
- package/types/components/modal/ModalConfirm.vue.d.ts +1 -1
- package/types/components/modal/VideoPreviewTrigger.vue.d.ts +1 -1
- package/types/components/page-layout/AppPageDetail.vue.d.ts +2 -2
- package/types/components/page-layout/AppPageListDetailLayout.vue.d.ts +3 -7
- package/types/components/page-layout/AppPageListDetailSheet.vue.d.ts +4 -8
- package/types/components/page-layout/AppPageListDetailSplit.vue.d.ts +4 -8
- package/types/components/page-layout/types.d.ts +42 -0
- package/types/components/panel/PanelItemNestForm.vue.d.ts +2 -0
- package/types/components/table/types.d.ts +9 -0
- package/types/components/view-layout/ViewLayout.vue.d.ts +18 -0
- package/types/components/view-layout/types.d.ts +15 -0
- package/types/config/slot-region.d.ts +10 -1
- package/types/hooks/use-surface.d.ts +34 -0
- package/types/inject/key.d.ts +10 -0
- package/types/injectInfo.json.d.ts +1 -1
- package/types/store/app.d.ts +3 -0
- package/types/utils/dom.d.ts +31 -0
- package/types/utils/gap-scale.d.ts +46 -0
- package/types/utils/index.d.ts +1 -0
- package/types/utils/theme-scale.d.ts +33 -0
package/es/utils/dom.mjs
CHANGED
|
@@ -20,7 +20,13 @@ function domClosestStickyScrollport(el) {
|
|
|
20
20
|
}
|
|
21
21
|
return void 0;
|
|
22
22
|
}
|
|
23
|
+
function domScrollIntoViewport(target, options = {}) {
|
|
24
|
+
if (!target || typeof target.scrollIntoView !== "function") return;
|
|
25
|
+
const { behavior = "auto", block = "nearest", inline = "nearest" } = options;
|
|
26
|
+
target.scrollIntoView({ behavior, block, inline });
|
|
27
|
+
}
|
|
23
28
|
export {
|
|
24
29
|
domClosestScrollableAncestor,
|
|
25
|
-
domClosestStickyScrollport
|
|
30
|
+
domClosestStickyScrollport,
|
|
31
|
+
domScrollIntoViewport
|
|
26
32
|
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const GAP_SCALE_RUNG = {
|
|
2
|
+
/** 外圈:内容 ↔ chrome、内容 ↔ viewport 边、悬浮插槽 ↔ 默认内容 */
|
|
3
|
+
outer: 2,
|
|
4
|
+
/** 卡片内边距(呼吸) */
|
|
5
|
+
breath: 1.5,
|
|
6
|
+
/** 模块 / 卡之间 */
|
|
7
|
+
module: 1,
|
|
8
|
+
/** 同一张卡内的分段 */
|
|
9
|
+
section: 0.5
|
|
10
|
+
};
|
|
11
|
+
const DENSITY_FACTOR = {
|
|
12
|
+
compact: 0.75,
|
|
13
|
+
cozy: 1,
|
|
14
|
+
comfortable: 1.5
|
|
15
|
+
};
|
|
16
|
+
const gapScaleBase = (size, density) => {
|
|
17
|
+
const factor = (density && DENSITY_FACTOR[density]) ?? DENSITY_FACTOR.cozy;
|
|
18
|
+
return Math.round(size * factor);
|
|
19
|
+
};
|
|
20
|
+
const gapScaleValue = (size, rung, density) => Math.round(gapScaleBase(size, density) * GAP_SCALE_RUNG[rung]);
|
|
21
|
+
export {
|
|
22
|
+
GAP_SCALE_RUNG,
|
|
23
|
+
gapScaleBase,
|
|
24
|
+
gapScaleValue
|
|
25
|
+
};
|
package/es/utils/theme-scale.mjs
CHANGED
|
@@ -37,6 +37,24 @@ const themeScaleToRgba = (hex, alpha) => {
|
|
|
37
37
|
const a = Math.min(1, Math.max(0, alpha));
|
|
38
38
|
return `rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, ${a})`;
|
|
39
39
|
};
|
|
40
|
+
const ELEVATION_STEP_CH = 10;
|
|
41
|
+
const PAGE_STEP_CH = 18;
|
|
42
|
+
const ELEVATION_REL_MAX = 0.5;
|
|
43
|
+
const themeScalePageColor = (bodyColor) => {
|
|
44
|
+
const rgb = themeScaleParseHex(bodyColor);
|
|
45
|
+
if (!rgb) return bodyColor;
|
|
46
|
+
const maxCh = Math.max(...rgb);
|
|
47
|
+
const k = maxCh === 0 ? 0 : Math.min(ELEVATION_REL_MAX, PAGE_STEP_CH / maxCh);
|
|
48
|
+
return themeScaleMix(bodyColor, "#000000", 1 - k);
|
|
49
|
+
};
|
|
50
|
+
const themeScaleSurfaceColor = (bodyColor, mode) => {
|
|
51
|
+
if (mode === "light") return bodyColor;
|
|
52
|
+
const rgb = themeScaleParseHex(bodyColor);
|
|
53
|
+
if (!rgb) return bodyColor;
|
|
54
|
+
const headroom = 255 - Math.max(...rgb);
|
|
55
|
+
const k = headroom === 0 ? 0 : Math.min(ELEVATION_REL_MAX, ELEVATION_STEP_CH / headroom);
|
|
56
|
+
return themeScaleMix(bodyColor, "#ffffff", 1 - k);
|
|
57
|
+
};
|
|
40
58
|
const themeScaleDerive = (base, mode) => {
|
|
41
59
|
const lightMix = mode === "light" ? "#ffffff" : "#141414";
|
|
42
60
|
const darkMix = mode === "light" ? "#000000" : "#ffffff";
|
|
@@ -51,9 +69,18 @@ const themeScaleDerive = (base, mode) => {
|
|
|
51
69
|
"dark-2": themeScaleMix(darkMix, base, 0.2)
|
|
52
70
|
};
|
|
53
71
|
};
|
|
72
|
+
const NEAR_BLACK_MAX_CH = 12;
|
|
73
|
+
const themeScaleIsNearBlack = (bodyColor) => {
|
|
74
|
+
const rgb = themeScaleParseHex(bodyColor);
|
|
75
|
+
if (!rgb) return false;
|
|
76
|
+
return Math.max(...rgb) < NEAR_BLACK_MAX_CH;
|
|
77
|
+
};
|
|
54
78
|
export {
|
|
55
79
|
themeScaleDerive,
|
|
80
|
+
themeScaleIsNearBlack,
|
|
56
81
|
themeScaleMix,
|
|
82
|
+
themeScalePageColor,
|
|
57
83
|
themeScaleParseHex,
|
|
84
|
+
themeScaleSurfaceColor,
|
|
58
85
|
themeScaleToRgba
|
|
59
86
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@done-coding/admin-core",
|
|
3
|
-
"version": "0.27.0",
|
|
3
|
+
"version": "0.27.1-alpha.0",
|
|
4
4
|
"description": "内部前端库",
|
|
5
5
|
"private": false,
|
|
6
6
|
"main": "es/index.mjs",
|
|
@@ -83,5 +83,5 @@
|
|
|
83
83
|
"dependencies": {
|
|
84
84
|
"@tanstack/vue-virtual": "^3.13.35"
|
|
85
85
|
},
|
|
86
|
-
"gitHead": "
|
|
86
|
+
"gitHead": "2aee5a4919080501a29f96053a84ca98cf02d5b3"
|
|
87
87
|
}
|
|
@@ -203,6 +203,36 @@ appCoreBridge.update("APP_LAYOUT_SIDEBAR_CONFIG", { menuGroupMode: true });
|
|
|
203
203
|
- `userInfoAccess`:token / refreshToken / permission 三字段访问器(get + set),createCoreBridge 时注入
|
|
204
204
|
- 接线样板:`apps/reference/src/config/bridge.ts`(userInfoAccess + auth facade + 8 path 配置全量实例化)
|
|
205
205
|
|
|
206
|
+
### AI 填充触发组件(`aiFillTrigger`,批次 e9dzn4 → gf9z3w 控制反转终态)
|
|
207
|
+
|
|
208
|
+
表单 AI 填充的**主通道**:应用级注册一个**触发组件**,全站根表单可用并驱动入口显隐。
|
|
209
|
+
core 出插座 + 落表,触发组件出全部 AI 交互 UI(popup / prompt / LLM 调用 / 错误 /
|
|
210
|
+
撤销按钮),应用层 bridge 初始化时组装——**core 与触发组件提供方(如 forge-agent 的
|
|
211
|
+
`LlmJsonTrigger`)互不知晓**。
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
// 应用层:薄 wrapper 预绑定传输参数后注册(组件来源任意,duck 契约即可)
|
|
215
|
+
const AppAiFillTrigger = (props) =>
|
|
216
|
+
h(LlmJsonTrigger, { ...props, sseUrl: "/dev-ai/chat", model: "..." });
|
|
217
|
+
bridge.register({ aiFillTrigger: AppAiFillTrigger });
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
- `bridge.aiFillTrigger`(**响应式** shallowRef 只读):挂载后注册也能驱动入口出现
|
|
221
|
+
([MUST NOT] 学 auth 槽位的裸闭包 let)。
|
|
222
|
+
- **duck 契约(core 渲染时喂给触发组件的 props)**:
|
|
223
|
+
`getSchema: () => Promise<schema>`(= `getAiSchema()` 产出,含表单目标 description)
|
|
224
|
+
+ `onData: (data) => { appliedCount, undo }`(core 落表 + 高亮 + 建撤销快照后回执;
|
|
225
|
+
撤销**按钮**由触发组件展示、**机制**归 core)。
|
|
226
|
+
- 表单侧 `FormMain` 的 `aiFillTrigger` prop 是**局部覆盖**(某表单换触发器),
|
|
227
|
+
bridge 注册仍是主通道——[MUST NOT] 逐表单 props 接线当主路径。
|
|
228
|
+
- **LLM 选型归应用层**:core 对模型 / 服务商 / 凭据零感知、不依赖 forge-agent;
|
|
229
|
+
触发组件也不必来自 forge-agent——满足 duck 契约的任意组件皆可(reference demo
|
|
230
|
+
内 20 行 mock trigger 即证明)。
|
|
231
|
+
- **description 策略位** `register({ aiFillRequireDescription: true })`(默认
|
|
232
|
+
`false`):表单未配 `description` 时不予出触点——严肃交付场景强管控用。默认不拦
|
|
233
|
+
(实证 description 是映射质量增强项而非可用性前提:零描述表单靠字段 label/类型/
|
|
234
|
+
enum 照样 4/4 填对),仅 dev 提示一次倒逼补描述;`FormSearch` 自报家门天然豁免。
|
|
235
|
+
|
|
206
236
|
### 请求实例(@done-coding/request-axios)
|
|
207
237
|
|
|
208
238
|
请求层**不在 core 包内**——消费方用 `@done-coding/request-axios` 的 `createRequest` 自建全局实例(peer 依赖):
|
|
@@ -40,6 +40,27 @@ AppBody 由 AppLayout 自动装配,无需手动挂载——总装配见 AppLay
|
|
|
40
40
|
- **KeepAlive 承接是自动行为,零配置**:开不开缓存由路由 `meta.keepAlive` + 真叶子判定声明(缓存上限读 bridge config)——本组件无开关,页面按需声明即可。
|
|
41
41
|
- `menuFlatList`:AppLayout 内部已喂入(守高度链)——消费方通常不需要自己传;仅直接挂载 AppBody(非经 AppLayout)时才需要。
|
|
42
42
|
- `#footer` 槽:默认已由 AppLayout 组装 shell 吐出的 Footer 组件填好(内置吐 AppFooter)——仅自供 shell 时才需要接管。
|
|
43
|
+
- **底色:`.app-body` 缺省不铺任何背景**(`akw76r`)。它是**容器**,容器一律透明 ——
|
|
44
|
+
布局底由**全局 `html` 一处**铺(core 在主题 `<style>` 的 `:root{}` 里挂
|
|
45
|
+
`background: var(--el-bg-color-page)`),一路透到这里。
|
|
46
|
+
header / footer / sidebar / aside 四个模块的面色仍取 `bodyColor`(面)—— 底与面恒差一档,
|
|
47
|
+
**模块间距(`APP_LAYOUT_GAP_CONFIG`,核内默认 `size: 8`)的缝、以及 header 上方的滚动遮挡条,
|
|
48
|
+
露出的就是全局那层底**。
|
|
49
|
+
- `bridge.APP_LAYOUT_BODY_CONFIG.background` **仍是逃生口**:显式给值即在 `.app-body` 这一层
|
|
50
|
+
铺一块面(渐变 / 背景图 / 纯色皆可),只影响内容区那一块,不影响 chrome 模块与外框。
|
|
51
|
+
不给值 = 透明(这是新的缺省)。
|
|
52
|
+
- 🔴 [MUST NOT] 在业务侧用 `!important` 压 `.app-body` 的背景;更 [MUST NOT] 拿它去铺一层
|
|
53
|
+
**"与全局底同色"** 的面 —— 那是把一个会变的值复制到第二处,换底 / 换主题时必留补丁
|
|
54
|
+
(同 AppPage 文档那条)。要改底色,改的是**主题的 `bodyColor`**(底由它派生),不是在这里补色。
|
|
55
|
+
- **内容四周留白 = 间距阶梯的「外圈档」**(`gap × 2`,默认 16)。它**接替了已废弃的
|
|
56
|
+
`APP_LAYOUT_BODY_CONFIG.shimPadding`** —— 此前这段空白由 `gap + shimPadding` 两个来源
|
|
57
|
+
各给一半,`akw76r` 让容器层露底后两者在视觉上就是同一段空白,冗余由此产生。
|
|
58
|
+
- `.app-body-shim` **不再铺 padding**(恒 0)。
|
|
59
|
+
- 内容偏移恒 = `模块档 + [chrome 高] + 外圈档`,**有无 chrome 一个样**(`.app-body` 四面恒让)。
|
|
60
|
+
- `shimPadding` 配了非 0 值只会得到一条 `console.warn`,不影响渲染。要调留白 ⇒ 改
|
|
61
|
+
`APP_LAYOUT_GAP_CONFIG.size`(一个旋钮按比例缩放四档)。
|
|
62
|
+
- 🔴 **chrome 模块彼此**(header ↔ sidebar ↔ footer ↔ 外框)仍取**模块档 ×1** ——
|
|
63
|
+
只有「内容对外」才 ×2。[MUST NOT] 把两者拉平:拉平后 sidebar 会被一起推开,chrome 的节奏就散了。
|
|
43
64
|
- **完整能力演示**(NoBreadcrumb/NoFooter/NoHeader/NoSidebar 显隐变体):`apps/reference/src/pages/app-layout/layout/`
|
|
44
65
|
|
|
45
66
|
## API
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
- **何时不用**:无需容器几何(纯组件局部)时直接用裸组件
|
|
10
10
|
- 默认 slot 内容经 ModalShelf page 层包裹(弹层挂载点)
|
|
11
11
|
- 四向悬浮插槽为 fixed 定位(z-index `APP_PAGE_SLOT_Z_INDEX`=1),top/bottom 通栏量高、left/right 量宽,尺寸几何由 `app-page-geometry` 纯函数计算
|
|
12
|
+
- **让位量恒为 `槽尺寸 + 外圈档`,三处同式**:① 默认内容(`app-page-shim` padding)② `left`/`right` 竖向让过 `top`/`bottom`(槽 ↔ 槽)③ 对后代重 provide 的可用高 / 宽。无该方向插槽时恒 0([MUST NOT] 给不存在的方向留间隔)。🔴 改其中任一处 [MUST] 三处同步——只改一处会出现「内容让了、槽之间没让」这种同页两套口径
|
|
12
13
|
- `contentCentered` 开启后默认 slot 内容限宽居中(超宽屏适配,默认 1200px)——**四向悬浮槽与 `background` 仍通栏**(chrome / 底色通栏、只有内容列限宽,超宽下两侧留白露出页面底色)
|
|
13
14
|
|
|
14
15
|
## 快速上手(最小可用)
|
|
@@ -41,7 +42,8 @@
|
|
|
41
42
|
|
|
42
43
|
- **四向悬浮插槽(`#top`/`#bottom`/`#left`/`#right`):默认全关、常规页面不需要。** 仅当页面需要「悬浮于内容之上的固定补充区」(通栏操作条 / 侧边抽屉 / 浮动提示)才开对应槽——判据:内容必须 fixed 悬浮且不参与文档流(top/bottom 通栏量高 → padding 让位,left/right 量宽)。列表/表单/详情常规页一律不开。**`#left`/`#right` 内放列表时用 scope 的 `listLayoutRecommended`(见 API)拿窄栏推荐配置,[MUST NOT] 自行拍脑袋配窄栏列表形态。**
|
|
43
44
|
- **`contentCentered`(超宽屏限宽居中):默认关,按页面类型开。** 表单页 / 详情页 / 阅读型页开(超宽屏下内容整行铺满,label↔控件、行首↔行尾视线跨度过长);**列表 / 表格页 [MUST NOT] 开**——表格列本就挤,限宽后更挤(业界同解:GitHub 对 diff / 表格页保留全宽)。`contentMaxWidth` 仅在需要非 1200 时设。
|
|
44
|
-
- `topBg`/`bottomBg`/`leftBg`/`rightBg
|
|
45
|
+
- `topBg`/`bottomBg`/`leftBg`/`rightBg`:**默认已是面色**(`--el-bg-color`),不用设。
|
|
46
|
+
只在要**换色**(品牌色条)或要**关掉面**(`"transparent"`,让底透上来)时才传。
|
|
45
47
|
- `*ObserveResize`:仅当槽内内容有 CSS 过渡折叠 / 异步撑开(不触发重渲染的尺寸变化)才开对应 RO;普通槽内容(v-if/v-show/静态)不开。
|
|
46
48
|
- `fullMode`:默认 `"min-height"` 即够用;`"height"` 仅当页面内子级依赖硬高撑满(如顶级自撑 TabsMain)时选。
|
|
47
49
|
- **完整能力演示**(四槽全开 + 背景 + RO 互动面板):`apps/reference/src/pages/app-layout/page-slots/`——能力展示,非推荐默认。
|
|
@@ -59,8 +61,23 @@
|
|
|
59
61
|
| `background` | `string` | 无 | 根盒背景(通栏,不受限宽影响) |
|
|
60
62
|
| `contentCentered` | `boolean` | `false` | 默认 slot 内容限宽居中(超宽屏适配);同步收窄对后代公布的内容几何(见下) |
|
|
61
63
|
| `contentMaxWidth` | `number` | `1200` | 限宽值(px),仅 `contentCentered` 开启时生效 |
|
|
62
|
-
| `gap` | `number` | `8` |
|
|
63
|
-
| `topBg` / `bottomBg` / `leftBg` / `rightBg` | `string` |
|
|
64
|
+
| `gap` | `number` | `8` | 间距基数 px(缺省读 bridge `APP_LAYOUT_GAP_CONFIG.size`)。**槽 ↔ 内容**与**槽 ↔ 槽**都取由它派生的「外圈档」(`基数 × 2`,随密度缩放),两处同式同源 |
|
|
65
|
+
| `topBg` / `bottomBg` / `leftBg` / `rightBg` | `string` | `var(--el-bg-color)`(面色) | 各槽 shim 背景;传 `"transparent"` 可关 |
|
|
66
|
+
|
|
67
|
+
> **五个背景 prop 的契约(分两组,判据是「块」还是「容器」)**
|
|
68
|
+
>
|
|
69
|
+
> | prop | 默认 | 为什么 |
|
|
70
|
+
> |---|---|---|
|
|
71
|
+
> | 四个 `*Bg`(悬浮槽) | **面色 `--el-bg-color`** | 槽是 **chrome 小块**,本就该浮在底上成一块面 |
|
|
72
|
+
> | `background`(页面根) | **无(透明)** | 根是**容器**,容器一律不铺底 —— 底由全局 `html` 一处接手 |
|
|
73
|
+
>
|
|
74
|
+
> 🔴 **[MUST NOT] 用任何一个去「对齐全局底色」**。给**块**上面色,和给**缝 / 容器**补一层
|
|
75
|
+
> 「与底同色」的面,是两件事:后者把一个会变的值复制到第二处 —— 今天两处同色看不出来,
|
|
76
|
+
> 哪天全局底换成渐变 / 背景图 / 换主题,那层补色就成了糊在上面的一块实色补丁 ——
|
|
77
|
+
> **不报错、不红、没有任何测试会失败,只是变丑**。
|
|
78
|
+
> 同理 [MUST NOT] 要求「给缝隙上色」的 prop:**缝不是一块要上色的面,是真空**。
|
|
79
|
+
>
|
|
80
|
+
> **默认插槽恒透明**,且没有给它上色的 prop —— 它是主内容区,必须让底透上来。
|
|
64
81
|
| `topObserveResize` / `bottomObserveResize` / `leftObserveResize` / `rightObserveResize` | `boolean` | 均 `false` | 逐槽 WatchSize RO 观测(CSS 过渡折叠等非重渲染尺寸变化时开) |
|
|
65
82
|
|
|
66
83
|
### Emits
|
|
@@ -69,6 +69,21 @@
|
|
|
69
69
|
- **激活刷新条件化**:`onActivated` 自动刷新仅「已发生过查询」后生效(`hasRequested` 自判),与 TableMain 同语义。
|
|
70
70
|
- **完整能力演示**:`apps/reference/src/pages/data-view/grid/`。
|
|
71
71
|
|
|
72
|
+
## 自持面(`surface`)
|
|
73
|
+
|
|
74
|
+
本组件**自己持有那块面**(面色 `--el-bg-color` + 内呼吸 + 圆角),不依赖外层容器
|
|
75
|
+
伸手来刷 —— 单独使用时观感一致。
|
|
76
|
+
|
|
77
|
+
🔴 **内呼吸计入可用高**:`surface` 开启时自身 padding 会从数据区可用高里扣掉。
|
|
78
|
+
样式与高度账在同一个组件内闭环,[MUST NOT] 退回「外层刷面、内层算高」——
|
|
79
|
+
那样每页恒漏一个 padding(甲方 2026-09-15 报障的根因)。
|
|
80
|
+
|
|
81
|
+
**弹窗内不二次成面**:置于 `ModalConfirm` 正文时经 `useContextDefaults` 自动取
|
|
82
|
+
`false`。亮色下面色与 overlay 同为 `#fff` 看不出区别,**暗色**下才会露出
|
|
83
|
+
「弹窗里凹下去一块」——[MUST NOT] 只用亮色验收。
|
|
84
|
+
|
|
85
|
+
⚠️ 只读**直系父**:弹窗正文里若隔了一层容器,[MUST] 显式传 `:surface="false"`。
|
|
86
|
+
|
|
72
87
|
## API
|
|
73
88
|
> ⚠️ API 以 types.ts 与组件源码为真相源;与文档冲突时以源码为准。
|
|
74
89
|
|
|
@@ -38,6 +38,19 @@
|
|
|
38
38
|
- **`rowKey` / `columns` / `getRenderCtxParams` 必填契约**:行唯一键、列配置、列 render 上下文缺一不可。
|
|
39
39
|
- **完整能力演示**(选中框列 + 行号列 + 字段列 + 操作列全接线):`apps/reference/src/pages/data-view/custom-view/Index.vue`——能力展示,非推荐默认。
|
|
40
40
|
|
|
41
|
+
## 当前项跟随滚动(`activeScroll`,默认开)
|
|
42
|
+
|
|
43
|
+
`activeId` 变化后把对应项滚进视口 —— 上/下一条导航、深链直达当前项等场景全靠它。
|
|
44
|
+
|
|
45
|
+
用 `nearest` 语义(`utils` 的 `domScrollIntoViewport`):
|
|
46
|
+
- **已在视口内则零动作** —— 用户自己滚着看时点某项不会被拽走;
|
|
47
|
+
- 避免「越级滚动」:原生默认 `block: "start"` 会把整个页面滚了。
|
|
48
|
+
|
|
49
|
+
🔴 **瞬时而非 smooth**:这是**跟随**不是**导航**。本仓 `TabsTile` 自研 smooth 滚动
|
|
50
|
+
曾被 v-model 回流重渲染打断(真机复现),故此处 [MUST NOT] 默认 smooth。
|
|
51
|
+
|
|
52
|
+
关掉传 `:active-scroll="false"`。
|
|
53
|
+
|
|
41
54
|
## API
|
|
42
55
|
> ⚠️ API 以 types.ts 与组件源码为真相源;与文档冲突时以源码为准。
|
|
43
56
|
|
|
@@ -47,6 +47,21 @@
|
|
|
47
47
|
- `#error` / `#end` / `#loading-more` 插槽可覆写底部各态默认渲染。
|
|
48
48
|
- `#empty`:`data` 为空时显示(默认 `<el-empty />`)。
|
|
49
49
|
|
|
50
|
+
## 自持面(`surface`)
|
|
51
|
+
|
|
52
|
+
本组件**自己持有那块面**(面色 `--el-bg-color` + 内呼吸 + 圆角),不依赖外层容器
|
|
53
|
+
伸手来刷 —— 单独使用时观感一致。
|
|
54
|
+
|
|
55
|
+
🔴 **内呼吸计入可用高**:`surface` 开启时自身 padding 会从数据区可用高里扣掉。
|
|
56
|
+
样式与高度账在同一个组件内闭环,[MUST NOT] 退回「外层刷面、内层算高」——
|
|
57
|
+
那样每页恒漏一个 padding(甲方 2026-09-15 报障的根因)。
|
|
58
|
+
|
|
59
|
+
**弹窗内不二次成面**:置于 `ModalConfirm` 正文时经 `useContextDefaults` 自动取
|
|
60
|
+
`false`。亮色下面色与 overlay 同为 `#fff` 看不出区别,**暗色**下才会露出
|
|
61
|
+
「弹窗里凹下去一块」——[MUST NOT] 只用亮色验收。
|
|
62
|
+
|
|
63
|
+
⚠️ 只读**直系父**:弹窗正文里若隔了一层容器,[MUST] 显式传 `:surface="false"`。
|
|
64
|
+
|
|
50
65
|
## API
|
|
51
66
|
> ⚠️ API 以 types.ts 与组件源码为真相源;与文档冲突时以源码为准。
|
|
52
67
|
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
- [HeightProvider](./docs/README-HeightProvider.md) — 纯数值高度预算节点(available = viewportHeight − reserve)
|
|
14
14
|
- [BooleanTag](./docs/README-BooleanTag.md) — boolean 只读彩色标签
|
|
15
15
|
- [ShadowClone](./docs/README-ShadowClone.md) — 影分身(零 DOM 输出双渲染位:本体就地 + 分身 teleport,目标晚渲染自动等待)
|
|
16
|
+
- [DragFloat](./docs/README-DragFloat.md) — 可拖拽悬浮容器(宿主容器内约束 + 点击拖拽消歧 + 呼吸光影;首个消费者 = FormMain AI 填充触发器)
|
|
16
17
|
|
|
17
18
|
## 关键类型
|
|
18
19
|
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# DragFloat — 可拖拽悬浮容器
|
|
2
|
+
|
|
3
|
+
> 通用悬浮触点原语(批次 trsf36):把任意内容(默认插槽)变成**宿主容器内可拖拽**的
|
|
4
|
+
> 悬浮块,自带呼吸光影。第一个消费者 = FormMain 的 AI 填充触发器。
|
|
5
|
+
|
|
6
|
+
## 定位
|
|
7
|
+
|
|
8
|
+
固定角落的悬浮入口都可能遮内容——本组件让用户自己拖开。机制(定位 / 容器内约束 /
|
|
9
|
+
点击拖拽消歧 / 呼吸光影 / 位置记忆)归组件,内容归插槽。
|
|
10
|
+
|
|
11
|
+
- 拖拽为**手搓 pointer events**(轮子阶梯记录:EP `useDraggable` 缺容器约束与触屏、
|
|
12
|
+
fork 不算复用;`@vueuse/core` 功能满足但为一个 hook 给发布库引 runtime 依赖不值,
|
|
13
|
+
几十行可替代——DNA #7)。
|
|
14
|
+
|
|
15
|
+
## 快速上手(最小可用)
|
|
16
|
+
|
|
17
|
+
```vue
|
|
18
|
+
<div style="position: relative"><!-- 宿主 [MUST] 有定位(组件按 offsetParent 约束) -->
|
|
19
|
+
<DragFloat placement="top-right">
|
|
20
|
+
<ElButton circle :icon="MagicStick" />
|
|
21
|
+
</DragFloat>
|
|
22
|
+
</div>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## 能力边界 / 按需使用
|
|
26
|
+
|
|
27
|
+
- **约束 = offsetParent 矩形**:拖不出宿主容器;宿主 [MUST] `position: relative/absolute`
|
|
28
|
+
(aiFill 场景 `.dc-form-main` 已是)。
|
|
29
|
+
- **点击拖拽消歧**:位移超阈值(3px)的一次按压不触发 click(capture 期拦截)——
|
|
30
|
+
插槽里的按钮 / popover 触发器不会被拖拽误触。
|
|
31
|
+
- **位置会话内记忆**(组件实例态,KeepAlive 存活即保留);不持久化。
|
|
32
|
+
- **呼吸光影**:默认开(`breathing`),主色系缓慢明暗(~2.6s 周期),
|
|
33
|
+
`prefers-reduced-motion` 下静止;拖拽中暂停。
|
|
34
|
+
- 初始位置只给四角 + offset(YAGNI:自由初始坐标等真实需求出现再加)。
|
|
35
|
+
|
|
36
|
+
## API
|
|
37
|
+
> ⚠️ API 以 types.ts 与组件源码为真相源;与文档冲突时以源码为准。
|
|
38
|
+
|
|
39
|
+
### Props
|
|
40
|
+
|
|
41
|
+
| name | type | 默认 | 语义 |
|
|
42
|
+
| --- | --- | --- | --- |
|
|
43
|
+
| `placement` | `"top-right" \| "top-left" \| "bottom-right" \| "bottom-left"` | `"top-right"` | 初始停靠角(拖动后失效,以拖后位置为准) |
|
|
44
|
+
| `offset` | `number` | `0` | 初始角内缩偏移 px(负值 = 微溢出宿主角) |
|
|
45
|
+
| `breathing` | `boolean` | `true` | 呼吸光影开关 |
|
|
46
|
+
|
|
47
|
+
### Slots
|
|
48
|
+
|
|
49
|
+
| 槽 | scope | 语义 |
|
|
50
|
+
| --- | --- | --- |
|
|
51
|
+
| default | — | 悬浮内容(按钮 / 图标 / 任意) |
|
|
52
|
+
|
|
53
|
+
### Emits
|
|
54
|
+
|
|
55
|
+
无(拖拽是纯视图态;需要感知位置的场景出现再加)。
|
|
56
|
+
|
|
57
|
+
## 反模式 / 注意
|
|
58
|
+
|
|
59
|
+
- [MUST NOT] 把它当 fixed 全局悬浮球用——约束是 offsetParent,不是视口;
|
|
60
|
+
全局悬浮球是另一个需求。
|
|
61
|
+
- 宿主无定位时 offsetParent 会逃逸到更外层容器(拖拽范围变大而非报错)——
|
|
62
|
+
消费方自查宿主定位。
|
|
63
|
+
- 插槽内容自己的 click 逻辑无需感知拖拽(消歧在组件内做完)。
|
|
64
|
+
|
|
65
|
+
## 关联
|
|
66
|
+
|
|
67
|
+
- 消费者:`FormMain` AI 填充触发器(`use-ai-fill.ts` + FormMain 模板挂点)。
|
|
68
|
+
- demo:`/display/drag-float`(组件自身)+ `/form/ai-fill`(消费点)。
|
|
@@ -27,9 +27,11 @@
|
|
|
27
27
|
|
|
28
28
|
## 关键类型与 helper
|
|
29
29
|
|
|
30
|
-
- 类型:`FormItemConfig` / `FormItemConfigList` / `FormMainInstance` / `FormSearchInstance` / `FormScope`(全部经 `./types` 导出)
|
|
30
|
+
- 类型:`FormItemConfig` / `FormItemConfigList` / `FormMainInstance` / `FormSearchInstance` / `FormScope` / `FormAiSchema`(全部经 `./types` 导出)
|
|
31
31
|
- helper:`nestFormItem`(C1)/ `nestFormItemList`(C4)/ `useNestForm` / `useNestFormList` / `useNestLayoutScale`
|
|
32
|
-
- 工具:`generateFormData` / `parseFormData` / `stringifyFormData` / `swiftFormItemConfig`
|
|
32
|
+
- 工具:`generateFormData` / `parseFormData` / `stringifyFormData` / `patchFormData` / `swiftFormItemConfig` / `checkFormItemIsIgnore`
|
|
33
|
+
- 分组 / 分割线 / 折叠(伪 item):`swiftFormItemGroup` / `swiftFormItemDivider`——数据层被忽略、视觉照常渲染,折叠复用 `hide` 语义,见 [FormMain 文档「分组 / 分割线 / 折叠」](./docs/README-FormMain.md)
|
|
34
|
+
- AI 填充数据层:`FormItemConfig` 可选 `type` / `description` / `aiIgnore` + `FormMainInstance` 的 `getAiSchema()`(OpenAI strict 裁剪 JSON Schema,`ai-schema.ts` 投影)/ `setValues()`(按 key 就地 patch),见 [FormMain 文档「AI 填充(数据层)」](./docs/README-FormMain.md)
|
|
33
35
|
- 内部不导出:`FormItem`(仅 FormMain 内部)、`use-form-submit`、`use-layout-by-container`
|
|
34
36
|
|
|
35
37
|
## 范式页(apps/reference)
|
|
@@ -38,7 +38,9 @@ const data = ref(generateFormData(list));
|
|
|
38
38
|
- **默认即够用:`nestFormItem({ key, label, list })` 即完成嵌套**(render / init / stringify / parse / parentSpan 自动织入,级联校验零接线)。以下全部按需开:
|
|
39
39
|
- `layout` / `rowGutter`:默认未设 = 吃父级布局;仅当子表单需要独立布局才设。
|
|
40
40
|
- `rebase`:默认 `true` 叠本层嵌套布局 rebase(配合 `parentSpan` 算因子);仅当不需要层级缩放(layout 原样直透)才设 `false`。
|
|
41
|
-
-
|
|
41
|
+
- **视觉 = 去框化**(批次 e9dzn4):容器 = page 档凹陷井(`--el-bg-color-page`),零描边
|
|
42
|
+
零投影——嵌套层级靠明度差表达(在面档父容器上凹一档),[MUST NOT] 回补描边+投影。
|
|
43
|
+
- `shadowColor`:默认零投影(去框化);显式传值才内联加外环阴影(消费方自负跨主题适配)。
|
|
42
44
|
- 多行同构子表(一对多)→ 不走本组件,用 `FormItemNestFormList`(C4)。
|
|
43
45
|
- **完整能力演示**(C1 单独场景 / C4 行内套 C1):`apps/reference/src/pages/form/nest-form/showcase/`、`pages/form/nest-form-list/showcase/`——能力展示,非推荐默认。
|
|
44
46
|
|
|
@@ -53,7 +55,7 @@ const data = ref(generateFormData(list));
|
|
|
53
55
|
| `list` | `FormItemConfigList`(必填) | — | 子表单配置 |
|
|
54
56
|
| `layout` | `Partial<ColProps>` | 未设 | 子 FormMain 布局 |
|
|
55
57
|
| `rowGutter` | `number` | 未设 | 子行间距 |
|
|
56
|
-
| `shadowColor` | `string` |
|
|
58
|
+
| `shadowColor` | `string` | 未设 | 外环阴影色(默认零投影·去框化;显式传才内联加投影) |
|
|
57
59
|
| `parentSpan` | `number` | 未设 | 父 item 代表 span(P3 rebase 算因子) |
|
|
58
60
|
| `rebase` | `boolean` | `true` | 是否叠本层嵌套布局 rebase(`false`=layout 原样直透) |
|
|
59
61
|
|
|
@@ -75,7 +77,7 @@ const data = ref(generateFormData(list));
|
|
|
75
77
|
|
|
76
78
|
- **手写嵌套子表单配置**:直接手写 render 挂子 FormMain 会绕过注册机制,级联校验断裂——[MUST] 用 `nestFormItem` / 本组件
|
|
77
79
|
- **nestKey 与父 key 不一致**:nestKey 是父 item key,注册索引对不上则级联失效
|
|
78
|
-
- **shadowColor
|
|
80
|
+
- **shadowColor 显式传才内联覆盖**:默认零投影(去框化,层级靠背景明度差);除非确需跨主题固定投影,别传
|
|
79
81
|
- **rebase 语义**:默认 `true` 叠本层嵌套布局 rebase(配合 `parentSpan` 算因子);`false` 时 layout 原样直透
|
|
80
82
|
|
|
81
83
|
## 关联
|
|
@@ -33,6 +33,12 @@ const data = ref(generateFormData(list));
|
|
|
33
33
|
- `nestFormItemList` 自动织入 render / init / stringify / parse + `rules`(min / max 长度规则与自动织入合并)——`generateFormData(list)` 即按配置产出完整嵌套结构
|
|
34
34
|
- 两层防线各管一段:min / max 硬约束(禁删 / 禁新增)在交互层,长度硬校验走父 item `rules`(不补数据)
|
|
35
35
|
- `removeConfirm` 默认开:行删除二次确认(`true`=默认 popconfirm「确认删除该项?」;`false`=直删;对象=自定义)
|
|
36
|
+
- **视觉 = 去框化双档背景分层**(批次 e9dzn4):容器 = page 档凹陷井(`--el-bg-color-page`)、
|
|
37
|
+
每行 = 面档卡片(`--el-bg-color`),零描边零投影——嵌套层级靠明度差表达,多层嵌套时
|
|
38
|
+
井/卡自然交替,[MUST NOT] 回补每层描边+投影(框中框反模式)。`shadowColor` 显式传值
|
|
39
|
+
仍走内联覆盖(消费方自负跨主题适配)。
|
|
40
|
+
- **删除按钮不占行内列**:绝对定位行卡片右上角(卡片顶部预留按钮带),行内容体吃满整行宽
|
|
41
|
+
(验收判据:行容器宽 − 内容体宽 ≤ 2px)。
|
|
36
42
|
|
|
37
43
|
## 能力边界 / 按需使用
|
|
38
44
|
|
|
@@ -62,7 +68,7 @@ const data = ref(generateFormData(list));
|
|
|
62
68
|
| `addText` / `removeText` | `string` | `"新增"` / `"删除"` | 按钮文案 |
|
|
63
69
|
| `headerRender` | `({ count, max }) => VNodeChild` | 未设 | 顶部自定义渲染(render-fn,非 slot) |
|
|
64
70
|
| `footerRender` | `({ count, max, canAdd, add }) => VNodeChild` | 未设 | 底部自定义渲染(缺省=内置新增按钮) |
|
|
65
|
-
| `shadowColor` | `string` |
|
|
71
|
+
| `shadowColor` | `string` | 未设 | 外环 + 每行阴影色(默认零投影·去框化;显式传值才内联加投影) |
|
|
66
72
|
| `parentSpan` / `rebase` | — | rebase `true` | 同 C1(父 item 代表 span / 是否叠嵌套 rebase) |
|
|
67
73
|
| `removeConfirm` | `boolean \| ActionBtnConfirmConfig` | `true` | 行删除二次确认(`true`=默认 popconfirm「确认删除该项?」;`false`=直删;对象=自定义) |
|
|
68
74
|
|
|
@@ -65,6 +65,14 @@ const submit = async () => {
|
|
|
65
65
|
| `visibleChange` | `boolean` | 显隐(hide 驱动)变化 |
|
|
66
66
|
| `submit` | `FormItemSubmitType` | 表单项触发提交(`"blur"` / `"change"` / `"enter"`) |
|
|
67
67
|
|
|
68
|
+
> 🔴 **原生表单提交恒被阻止**(`ElForm` 上 `@submit.prevent`)。
|
|
69
|
+
> `ElForm` 渲染的是真 `<form>`,而 HTML 的**隐式提交**规则是「表单里恰好只有一个文本类输入控件时,
|
|
70
|
+
> 在该控件按 Enter 即提交表单」—— 不拦就是**整页刷新 + 用户输入全丢**,且只在「恰好一个输入框」
|
|
71
|
+
> 的表单上出现,多字段表单看着一切正常(这正是它能长期潜伏的原因)。
|
|
72
|
+
> 本组件的提交管线(`use-form-submit`)本就全走 JS,从不依赖原生提交。
|
|
73
|
+
> 需要「回车即提交」时,由业务在输入控件上自行接 `@keyup.enter`,或用表单项的 `submit` 事件
|
|
74
|
+
> (载荷含 `"enter"`)—— [MUST NOT] 指望原生提交。
|
|
75
|
+
|
|
68
76
|
### Slots
|
|
69
77
|
|
|
70
78
|
| 槽 | scope | 语义 |
|
|
@@ -76,15 +84,108 @@ const submit = async () => {
|
|
|
76
84
|
|
|
77
85
|
`validate(): Promise<void>`(本层 + 隐藏项过滤 + 嵌套子表单级联)、`resetFields()`、`clearValidate(key?)`、`generate(): PO`、`parse(stringifyData): PO`、`stringify()`(序列化对象)——三方法 round-trip。
|
|
78
86
|
|
|
87
|
+
AI 填充数据层两方法(详见下节「AI 填充(数据层)」):
|
|
88
|
+
|
|
89
|
+
- `getAiSchema(): Promise<FormAiSchema>` —— 导出本表单的机器可读描述(按 OpenAI strict 限制裁剪的 JSON Schema)。async 因 `getOptions` 型选项需异步拉取后并入 enum。
|
|
90
|
+
- `setValues(partial: Partial<PO>): void` —— 按 key **就地 patch** 表单值。[MUST NOT] 换 `data` 容器引用(PO 容器父子同引用贯通,换引用当场断链);只写配置内非 ignore 的 key,`undefined` / `null` 跳过(= 留空语义)。
|
|
91
|
+
|
|
79
92
|
### 关键类型
|
|
80
93
|
|
|
81
|
-
- `FormItemConfig<PO, SO, PK, PV, SV>`:单表单项配置——`key` / `label` / `labelHide` / `tip` / `layout` / `wrapProps` / `props` / `render` / `rules` / `init` / `parse` / `stringify` / `ignore` / `hide` / `beLink` / `extra`
|
|
94
|
+
- `FormItemConfig<PO, SO, PK, PV, SV>`:单表单项配置——`key` / `label` / `labelHide` / `tip` / `layout` / `wrapProps` / `props` / `render` / `rules` / `init` / `parse` / `stringify` / `ignore` / `hide` / `beLink` / `extra`,以及 AI 填充三个可选字段 `type`(数据类型)/ `description`(选型描述,复用 `CoreConfigDescription`)/ `aiIgnore`(schema 导出排除)
|
|
82
95
|
- `FormItemConfigList<PO, SO>`:表单项配置数组(`FormMain.list` 的类型)
|
|
83
96
|
- `FormItemConfigExtra`:`extra` 快捷项——`isInput` / `isSelect`(OnlyOneKey 互斥)+ `enterSubmit` / `blurSubmit` / `changeSubmit`
|
|
84
97
|
- `FormItemLinkConfig<K, BV, OV>`:`beLink` 联动配置——`key` + `getValue(hostValue, ownValue, attachInfo)`
|
|
85
98
|
- `FormScope<PO, SO, ...>`:渲染 / 函数式配置的作用域——`{ data, config }`
|
|
86
99
|
- `ExtractFormStringifyFromObject` / `FromList`:从 `PO`+`SO` 提取序列化提交形态(类型级提取,剔除 symbol / never 键)
|
|
87
100
|
|
|
101
|
+
## AI 填充(数据层)
|
|
102
|
+
|
|
103
|
+
> 给「自然语言 → 自动填表」提供的机器可读出入口。**core 只做数据层**:schema 导出 + 值写回;
|
|
104
|
+
> 与模型对话 / 按钮入口 / 建议态与撤销等交互层归 bridge 注册的能力方(另期)。
|
|
105
|
+
|
|
106
|
+
### 配置字段(全部可选,存量配置零改动即可用)
|
|
107
|
+
|
|
108
|
+
| 字段 | 类型 | 语义 |
|
|
109
|
+
| --- | --- | --- |
|
|
110
|
+
| `type` | `"string" \| "number" \| "integer" \| "boolean" \| "array"` | 数据类型。解析优先级:**显式 > `init` typeof 推导 > 兜底 `"string"`**。语义类型(人名/金额)写进 `description`,[MUST NOT] 另开字段 |
|
|
111
|
+
| `description` | `CoreConfigDescription`(`{suitable?, unsuitable?}`,复用主题那套) | 选型描述精调位,拼进 schema property 的 description |
|
|
112
|
+
| `aiIgnore` | `boolean` | 本项不出现在导出 schema(隐私面:label / 选项可能携客户名单、内部编码) |
|
|
113
|
+
|
|
114
|
+
### `getAiSchema()` 导出规则(OpenAI strict 裁剪)
|
|
115
|
+
|
|
116
|
+
- **property key = 配置 key**(key 是身份,description 是语义;`setValues` 按同一 key 写回,零映射环节)。
|
|
117
|
+
- **所有字段进 `required`**;每字段形如 `anyOf: [载荷, { type: "null" }]`——`null` = 留空通道(模型不确定就留空,[MUST NOT] 幻觉填值)。
|
|
118
|
+
- `additionalProperties: false`;嵌套 ≤5 层、属性 ≤100(超出截断 + console.warn)。
|
|
119
|
+
- **description 拼装** = `label`(必有)+ `tip`(**仅 string 形态**,`() => VNode` 序列化不了)+ `description.suitable / unsuitable` + 选项 label 清单。
|
|
120
|
+
- **选项三形态**(读取解析后的 item `props`):`options`(静态)与 `getOptions`(异步一次性,故本方法 async)→ 导出 `enum` + 选项 label;`remoteMethod`(搜索驱动)无法穷举 → **不导出 enum**,退化为带 description 的 string。enum 仅在选项值类型均一(全 string / 全 number)时挂。
|
|
121
|
+
- **过滤**:`ignore`(含 `__` 全大写 key 约定,判据同 `checkFormItemIsIgnore` 单一真相源)、`hide` 为真(隐藏字段用户看不见,AI 填值 = 用户不知情提交)、`aiIgnore` 为真的项均不导出。
|
|
122
|
+
- **期一只支持标量 + 字符串数组**:显式 `type:"array"` 或 init 推导为非空全字符串数组 → `items:{type:"string"}`;嵌套结构(`FormItemNestForm` / `FormItemNestFormList`,init 为对象 / 对象数组)**暂不支持**、不导出。
|
|
123
|
+
- 🔴 `pattern` / `format` / `minLength` 等校验关键字**不被模型执行** ⇒ 校验 [MUST NOT] 指望 schema,一律回落表单自己的 `rules`(AI 填完照常走 `validate()`)。
|
|
124
|
+
|
|
125
|
+
### `setValues(partial)` 写回规则
|
|
126
|
+
|
|
127
|
+
- 按 key **就地 patch**:`data[key] = value` 逐 key 写,[MUST NOT] 整体替换 `data` 容器(引用贯通命脉)。
|
|
128
|
+
- 只写配置内非 ignore 的 key(AI 幻觉出的未知 key 不落容器);`undefined` / `null` 跳过 = 留空语义(要清空传该字段类型的空值 `""` / `[]`)。
|
|
129
|
+
- 写回后 [MUST NOT] 自动提交——校验 / 提交仍由消费方显式触发。
|
|
130
|
+
|
|
131
|
+
## AI 填充(交互层,批次 e9dzn4)
|
|
132
|
+
|
|
133
|
+
数据层(`getAiSchema` / `setValues`)之上的开箱交互。**gf9z3w 控制反转终态**:
|
|
134
|
+
core 只出「插座 + 落表」——挂点(DragFloat 可拖拽)+ 显隐 + `getSchema` 组装 +
|
|
135
|
+
`onData` 落表/高亮/撤销机制;**全部 AI 交互 UI(popup / prompt / LLM 调用 / 错误 /
|
|
136
|
+
撤销按钮)归 bridge 注册的触发组件**(如 forge-agent `LlmJsonTrigger`,或任何满足
|
|
137
|
+
duck 契约的组件)。
|
|
138
|
+
|
|
139
|
+
- **入口**:bridge 注册的触发组件经 `DragFloat` 挂载在根表单右上——**可在表单矩形内
|
|
140
|
+
拖拽避让**(trsf36)、自带呼吸光影。显隐 = 「触发组件可用(bridge `aiFillTrigger`
|
|
141
|
+
响应式 ∥ `aiFillTrigger` prop 局部覆盖)&& 非嵌套实例 && `aiFill !== false`」。
|
|
142
|
+
- **嵌套实例恒关**(C1 / C4 内的子 FormMain 经父 registry 自判)——嵌套结构本就
|
|
143
|
+
不进 schema,出 N 个入口只会误导。
|
|
144
|
+
- **`FormSearch` 自 trsf36 起默认同自动档**(api 可用即出;透传时自动拼接
|
|
145
|
+
「筛选条件表单」形态语义),`aiFill: false` 可关;填充后 [MUST NOT] 自动触发搜索。
|
|
146
|
+
- 组件级 `aiFill: false` 局部否决。
|
|
147
|
+
- **description 策略位**:bridge `aiFillRequireDescription: true` 时,未配
|
|
148
|
+
`description` 的表单不出触点(默认 false = 照出 + console.warn 一次提示补描述;
|
|
149
|
+
`FormSearch` 自报家门天然豁免)。
|
|
150
|
+
- **表单目标描述 `description`**(trsf36):`string | CoreConfigDescription`——
|
|
151
|
+
告诉 AI 这个表单是做什么的,落 `getAiSchema()` 产出的 **schema 顶层 `description`**
|
|
152
|
+
(string 直用;对象形态拼「适用:…/不适用:…」;缺省不输出该字段)。
|
|
153
|
+
`FormSearch` 同名 prop 透传并**自报家门**(组件补形态语义,用户描述管业务)。
|
|
154
|
+
- **duck 契约(core 喂给触发组件)**:`getSchema`(= `getAiSchema()`,含 description
|
|
155
|
+
组装)+ `onData(data) → { appliedCount, undo }`——core 按 `setValues` 同判据落表、
|
|
156
|
+
实际写入项背景闪烁高亮、建撤销快照后回执;撤销按钮由触发组件展示。
|
|
157
|
+
- **填充 = 直接写入 + 高亮 + 整体撤销**(甲方拍定;逐字段建议态不做)。**绝不自动提交**。
|
|
158
|
+
- 通道详见 bridge 文档「AI 填充触发组件」;demo:`/form/ai-fill`。
|
|
159
|
+
|
|
160
|
+
## 分组 / 分割线 / 折叠(伪 item,批次 e9dzn4)
|
|
161
|
+
|
|
162
|
+
**机制 = 伪 item**:`ignore` 语义(key `__` 前缀全大写天然命中 `checkFormItemIsIgnore`)让它
|
|
163
|
+
**数据层被忽略**(不进 generate / parse / stringify / AI schema),视觉照常渲染。
|
|
164
|
+
配置数组里**标题与组内项同级扁平**,仅视觉呈父子——转换全在工具方法层,不改数据结构。
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
import { swiftFormItemGroup, swiftFormItemDivider } from "@done-coding/admin-core";
|
|
168
|
+
|
|
169
|
+
const list = [
|
|
170
|
+
...基础字段,
|
|
171
|
+
swiftFormItemDivider(),
|
|
172
|
+
...swiftFormItemGroup("高级选项", [advA, advB], { defaultCollapsed: true }),
|
|
173
|
+
];
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
- `swiftFormItemGroup(title, items, options?)` → `[标题伪 item, ...items]`(扁平展开)。
|
|
177
|
+
- 标题伪 item **恒 `span: 24` 不可配**;分割线同理。
|
|
178
|
+
- `options`:`defaultCollapsed`(默认 `false`)/ `collapsible`(默认 `true`)/ `key`
|
|
179
|
+
(伪 item key 覆盖,[MUST] `__` 前缀全大写;缺省内部自增 `__GROUP_<n>`)。
|
|
180
|
+
- **折叠 = 组内项 `hide` 置真**(复用「隐藏 + 跳校验」完整语义:折叠区必填项不拦提交、
|
|
181
|
+
错误自动清除),[MUST NOT] 另造 display 显隐通道。成员原有 `hide`(布尔 / 函数)与
|
|
182
|
+
折叠态**或**合成。
|
|
183
|
+
- 折叠态**按 FormMain 实例隔离**(内部按 `data` 对象身份建态)——同一份配置喂多个表单
|
|
184
|
+
(如 C4 行列表)互不串态;非受控为主,无受控口。
|
|
185
|
+
- **嵌套分组(组里再套组)一期不支持。**
|
|
186
|
+
- `swiftFormItemDivider()` → 分割线伪 item(`ElDivider`,恒整行)。
|
|
187
|
+
- 伪 item 已调掉表单项校验预留(`margin-bottom` 收敛)与 label 占位。
|
|
188
|
+
|
|
88
189
|
## 反模式 / 注意
|
|
89
190
|
|
|
90
191
|
- **手写嵌套子表单 render**:嵌套子表单(C1 / C4)与 `nestFormItem` / `nestFormItemList` helper 是既定通道,`FormItem` 未导出(仅 FormMain 内部)——[MUST NOT] 手写内联子表单 render 绕开注册机制(会失去级联 validate / clear / reset)
|
|
@@ -34,6 +34,11 @@
|
|
|
34
34
|
- `compact`:默认关;仅当搜索区落入受限窄栏(独立侧栏搜索)才开——窄栏场景优先容器断点系(`compact` / `layoutByContainer`),而非给单项 layout 写死 span。
|
|
35
35
|
- `layoutByContainer`:默认关;仅当容器断点折叠几何需要切到容器档才开。
|
|
36
36
|
- expose `triggerSearch()` / `triggerReset()` / `toggleCollapse()`:默认不需要(操作区按钮内置);仅当外部需程序化触发(如 ListLayout 列插槽注入)才用。
|
|
37
|
+
- **AI 填充(trsf36 起默认自动档)**:api 可用(bridge 注册 `aiFillApi` ∥ `aiFillApi` prop)
|
|
38
|
+
即出悬浮触点;`aiFill: false` 关(ListLayout 经 `formSearchProps` 透传)。`description`
|
|
39
|
+
prop 描述本搜索面向的业务(如「用户列表的筛选条件」),组件透传时**自报家门**——
|
|
40
|
+
自动拼接「列表筛选条件表单:把用户意图映射为筛选条件字段,未提及的条件留空」形态语义。
|
|
41
|
+
填充只落条件值,[MUST NOT] 自动触发搜索(查询仍由用户点)。
|
|
37
42
|
- **完整能力演示**(staticQuery + 多 slot 覆盖 + 折叠回调):`apps/reference/src/pages/menu/showcase/`——能力展示,非推荐默认。
|
|
38
43
|
|
|
39
44
|
## API
|