flame-plus 1.5.0 → 1.5.3
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/flameDist/flame-plus.css +1 -1
- package/flameDist/flame-plus.es.js +18150 -17589
- package/flameDist/flame-plus.es.js.map +1 -1
- package/flameDist/flame-plus.umd.js +128 -128
- package/flameDist/flame-plus.umd.js.map +1 -1
- package/package.json +1 -1
- package/.claude/settings.json +0 -11
- package/.claude/settings.local.json +0 -42
- package/cursor_modelvalue_prop_type_check_issue.md +0 -102
- package/docs/current-page-full.png +0 -0
- package/docs/flame-plus-attrfields-onchange-support.md +0 -201
- package/docs/formslot-test.png +0 -0
- package/docs/framework-dynamic-form-value-retrieval.md +0 -153
- package/docs/framework-external-components.md +0 -182
- package/docs/page-structure.md +0 -165
package/package.json
CHANGED
package/.claude/settings.json
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"permissions": {
|
|
3
|
-
"allow": [
|
|
4
|
-
"Read(//Users/albert/Workspaces/gmp/gmp-frontend/gmp-platform/node_modules/.pnpm/flame-plus@1.3.26_@element-plus+icons-vue@2.3.2_vue@3.3.4__@interactjs+core@1.10.27_@in_3c4e5d5dc5fd652459cb45b714b2aa3c/node_modules/flame-plus/flameDist/**)",
|
|
5
|
-
"Bash(pnpm run:*)"
|
|
6
|
-
]
|
|
7
|
-
},
|
|
8
|
-
"enabledPlugins": {
|
|
9
|
-
"chrome-devtools-mcp@claude-plugins-official": true
|
|
10
|
-
}
|
|
11
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"permissions": {
|
|
3
|
-
"allow": [
|
|
4
|
-
"Bash(python3:*)",
|
|
5
|
-
"Bash(npm run:*)",
|
|
6
|
-
"mcp__ide__getDiagnostics",
|
|
7
|
-
"Bash(cat /Users/albert/Workspaces/poba/flame-plus/node_modules/flame-types/lib/*.d.ts 2>/dev/null | head -30)",
|
|
8
|
-
"Bash(wc -l /Users/albert/Workspaces/poba/flame-plus/src/packages/components/framework/*.ts /Users/albert/Workspaces/poba/flame-plus/src/packages/components/framework/*.vue 2>/dev/null | head -20)",
|
|
9
|
-
"Bash(ls -la /Users/albert/Workspaces/spark/flame-types/src/ 2>/dev/null || find /Users/albert/Workspaces/spark/flame-types -name \"*.ts\" -o -name \"*.d.ts\" | head -30)",
|
|
10
|
-
"Bash(cd /Users/albert/Workspaces/spark/flame-types && npm install 2>&1)",
|
|
11
|
-
"Bash(npm pack:*)",
|
|
12
|
-
"Bash(cd /Users/albert/Workspaces/poba/flame-plus && npm install /Users/albert/Workspaces/spark/flame-types/flame-types-1.1.7.tgz 2>&1)",
|
|
13
|
-
"Bash(npm install:*)",
|
|
14
|
-
"Bash(npx vue-tsc:*)",
|
|
15
|
-
"Bash(grep -l \"framework.vue\" /Users/albert/.claude/plans/*.md 2>/dev/null | head -5)",
|
|
16
|
-
"Bash(npx run:*)",
|
|
17
|
-
"Bash(wc -l /Users/albert/Workspaces/poba/flame-plus/src/packages/components/framework/framework.vue /Users/albert/Workspaces/poba/flame-plus/src/packages/components/framework/utils/*.ts /Users/albert/Workspaces/poba/flame-plus/src/packages/components/framework/components/DetailSettingsSlot.vue)",
|
|
18
|
-
"Bash(wc -l /Users/albert/Workspaces/poba/flame-plus/src/packages/components/framework/types.ts /Users/albert/Workspaces/poba/flame-plus/src/packages/components/framework/utils/*.ts /Users/albert/Workspaces/poba/flame-plus/src/packages/components/framework/composables/*.ts /Users/albert/Workspaces/poba/flame-plus/src/packages/components/framework/components/*.vue /Users/albert/Workspaces/poba/flame-plus/src/packages/components/framework/*.ts /Users/albert/Workspaces/poba/flame-plus/src/packages/components/framework/*.vue)",
|
|
19
|
-
"Bash(npx vite:*)",
|
|
20
|
-
"Bash(kill %1)",
|
|
21
|
-
"Bash(curl -s http://localhost:5173/)",
|
|
22
|
-
"Bash(curl -s 'http://localhost:5173/src/packages/components/framework/framework.vue?vue&type=style&index=0&lang.scss')",
|
|
23
|
-
"Bash(jobs -l)",
|
|
24
|
-
"Bash(npx skills:*)",
|
|
25
|
-
"mcp__plugin_chrome-devtools-mcp_chrome-devtools__list_pages",
|
|
26
|
-
"mcp__plugin_chrome-devtools-mcp_chrome-devtools__navigate_page",
|
|
27
|
-
"mcp__plugin_chrome-devtools-mcp_chrome-devtools__take_snapshot",
|
|
28
|
-
"mcp__plugin_chrome-devtools-mcp_chrome-devtools__take_screenshot",
|
|
29
|
-
"mcp__plugin_chrome-devtools-mcp_chrome-devtools__list_console_messages",
|
|
30
|
-
"mcp__plugin_chrome-devtools-mcp_chrome-devtools__get_console_message",
|
|
31
|
-
"mcp__plugin_chrome-devtools-mcp_chrome-devtools__evaluate_script",
|
|
32
|
-
"mcp__plugin_chrome-devtools-mcp_chrome-devtools__click",
|
|
33
|
-
"mcp__plugin_chrome-devtools-mcp_chrome-devtools__press_key",
|
|
34
|
-
"mcp__plugin_chrome-devtools-mcp_chrome-devtools__drag",
|
|
35
|
-
"mcp__plugin_chrome-devtools-mcp_chrome-devtools__wait_for",
|
|
36
|
-
"Bash(curl -s http://localhost:5173)"
|
|
37
|
-
]
|
|
38
|
-
},
|
|
39
|
-
"enabledPlugins": {
|
|
40
|
-
"chrome-devtools-mcp@claude-plugins-official": true
|
|
41
|
-
}
|
|
42
|
-
}
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
# ModelValue prop type check issue
|
|
2
|
-
_Exported on 2026/4/1 at GMT+8 15:24:02 from Cursor (2.6.22)_
|
|
3
|
-
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
**User**
|
|
7
|
-
|
|
8
|
-
排查问题
|
|
9
|
-
|
|
10
|
-
MasterFormSwitch.vue?t=1775015461216:215 Invalid prop: type check failed for prop "modelValue". Expected Number | Null, got String with value "12".
|
|
11
|
-
Proxy(Object) {…}
|
|
12
|
-
at <ElInputNumber>
|
|
13
|
-
at <FlmInputNumber>
|
|
14
|
-
at <ElFormItem>
|
|
15
|
-
at <ElForm>
|
|
16
|
-
at <Anonymous>
|
|
17
|
-
at <BaseTransition>
|
|
18
|
-
at <Transition>
|
|
19
|
-
at <ElCollapseTransition>
|
|
20
|
-
at <ElCollapseItem>
|
|
21
|
-
at <ElCollapse>
|
|
22
|
-
at <FlmCollapse>
|
|
23
|
-
at <FlmDynamicForm>
|
|
24
|
-
at <ElFocusTrap>
|
|
25
|
-
at <ElOverlay>
|
|
26
|
-
at <BaseTransition>
|
|
27
|
-
at <Transition>
|
|
28
|
-
at <Teleport>
|
|
29
|
-
at <ElDrawer>
|
|
30
|
-
at <MasterFormSwitch>
|
|
31
|
-
at <KeepAlive>
|
|
32
|
-
at <RouterView>
|
|
33
|
-
at <AppView>
|
|
34
|
-
at <ElScrollbar>
|
|
35
|
-
at <Layout>
|
|
36
|
-
at <RouterView>
|
|
37
|
-
at <ElConfigProvider>
|
|
38
|
-
at <ConfigGlobal>
|
|
39
|
-
at <App>
|
|
40
|
-
(28) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}]
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
---
|
|
45
|
-
|
|
46
|
-
**Cursor**
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
正在排查:`ElInputNumber` 的 `modelValue` 需要 `number | null`,但收到了字符串 `"12"`。正在查看 `MasterFormSwitch.vue` 与 `FlmInputNumber` 的数据流。
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
## 结论
|
|
55
|
-
|
|
56
|
-
这是 **Element Plus `el-input-number` 的类型校验**:`modelValue` 只能是 `number | null | undefined`,但当前收到的是字符串 **`"12"`**。
|
|
57
|
-
组件链是:`MasterFormSwitch` → `flm-dynamic-form` → `FlmInputNumber` → `ElInputNumber`。问题出在 **喂给动态表单的 model 里数字字段是字符串**,不是 `MasterFormSwitch` 模板里某一行写错类型。
|
|
58
|
-
|
|
59
|
-
控制台里 `MasterFormSwitch.vue:215` 多半是 **Vite 带 query 的编译映射**,和源码行号不一定一致;真正相关的逻辑在下面这段。
|
|
60
|
-
|
|
61
|
-
## 数据从哪来
|
|
62
|
-
|
|
63
|
-
编辑时 `dynamicFormModel` 来自详情接口解析结果,或退化为表格行:
|
|
64
|
-
|
|
65
|
-
```353:356:/Users/albert/Workspaces/gmp/gmp-frontend/gmp-platform/src/views/SPARKFORM/MasterFormSwitch/MasterFormSwitch.vue
|
|
66
|
-
dynamicFormModel.value =
|
|
67
|
-
Object.keys(fromApi).length > 0
|
|
68
|
-
? { ...fromApi }
|
|
69
|
-
: { ...(data && typeof data === 'object' ? data : {}) }
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
`fromApi` 由 `parseDynamicFormGetDtlItems` 从 `dynamicFormGetDtl` 响应里拆出来,**不会做数字类型转换**,后端/JSON 里经常是 `"12"` 这种字符串:
|
|
73
|
-
|
|
74
|
-
```289:317:/Users/albert/Workspaces/gmp/gmp-frontend/gmp-platform/src/views/SPARKFORM/MasterFormSwitch/MasterFormSwitch.vue
|
|
75
|
-
const parseDynamicFormGetDtlItems = (res: any): Record<string, any> => {
|
|
76
|
-
// ...
|
|
77
|
-
if (typeof items === 'object') {
|
|
78
|
-
const row = (items as any).record ?? (items as any).row ?? items
|
|
79
|
-
if (row && typeof row === 'object' && !Array.isArray(row)) {
|
|
80
|
-
return { ...row }
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
return {}
|
|
84
|
-
}
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
新增时 `dynamicFormModel` 先是 `{}`,一般 **不会** 立刻报这个错;**编辑**(或详情为空、走了表格行 `data` 回显)最容易复现。
|
|
88
|
-
|
|
89
|
-
## 与 flame-plus 的关系
|
|
90
|
-
|
|
91
|
-
你们仓库里的说明已经对齐根因:`flm-dynamic-form` / grid 把 model 里的值原样塞给控件,`FlmInputNumber` 又原样传给 `ElInputNumber`,**没有**把字符串数字转成 `number`。见 `docs/flame-plus-fix-input-number-string-modelValue.md`。
|
|
92
|
-
|
|
93
|
-
## 处理思路(择一即可)
|
|
94
|
-
|
|
95
|
-
1. **业务侧(本页)**:在赋给 `dynamicFormModel` 之前,按表单配置或字段元数据,对 `flmInputNumber` 对应字段做归一(空串/`null` 与 `Number()` 的规则见该文档表格)。
|
|
96
|
-
2. **组件库侧**:在 flame-plus 的 `FlmInputNumber`(或 grid 赋值处)统一归一 `modelValue`,升级依赖或用 `pnpm patch`。
|
|
97
|
-
3. **后端**:保证动态详情/列表里数值字段以 JSON number 返回(改动面取决于接口约定)。
|
|
98
|
-
|
|
99
|
-
若只想先确认,可在 `editData` 里 `console.log(fromApi)` 或打断点,看对应数字字段是否为字符串 `"12"`。
|
|
100
|
-
|
|
101
|
-
(当前为 Ask 模式,我无法直接改代码;若要我在项目里加归一或 patch,需要切换到 Agent 模式。)
|
|
102
|
-
|
|
Binary file
|
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
# FlameFramework attrFields onChange 支持修改文档
|
|
2
|
-
|
|
3
|
-
## 问题描述
|
|
4
|
-
|
|
5
|
-
FlameFramework 的 `attrFields` 配置中的 `props.onChange` 回调无法被触发,导致级联选择(项目 → 表 → 字段)无法在属性面板中实现。
|
|
6
|
-
|
|
7
|
-
## 问题根因
|
|
8
|
-
|
|
9
|
-
### 文件:`/Users/albert/Workspaces/poba/flame-plus/src/packages/components/complex/flmForm/flmForm.vue`
|
|
10
|
-
|
|
11
|
-
在 `controlDom` 函数(约第 215-266 行)中:
|
|
12
|
-
|
|
13
|
-
```javascript
|
|
14
|
-
const controlDom = (
|
|
15
|
-
prop: string,
|
|
16
|
-
controlType: ControlTypes,
|
|
17
|
-
controlConfig: ControlConfig
|
|
18
|
-
) => {
|
|
19
|
-
const config: FormModel = { ...controlConfig }
|
|
20
|
-
const event = controlType === ControlTypes['flmInput'] ? 'onInput' : 'onChange'
|
|
21
|
-
config[event] = (value: any) => formChange(value, prop) // ⚠️ 这里覆盖了原有的 onChange
|
|
22
|
-
config['modelValue'] = formModel[prop]
|
|
23
|
-
// ...
|
|
24
|
-
}
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
**问题**:`flmForm` 在渲染控件时,**强制覆盖**了 `onChange`/`onInput` 事件处理器,导致 `attrFields.props` 中传入的 `onChange` 回调被替换,永远不会被调用。
|
|
28
|
-
|
|
29
|
-
## 修改方案
|
|
30
|
-
|
|
31
|
-
### 方案一:保留原有 onChange 并链式调用(推荐)
|
|
32
|
-
|
|
33
|
-
修改 `flmForm.vue` 中的 `controlDom` 函数:
|
|
34
|
-
|
|
35
|
-
```javascript
|
|
36
|
-
const controlDom = (
|
|
37
|
-
prop: string,
|
|
38
|
-
controlType: ControlTypes,
|
|
39
|
-
controlConfig: ControlConfig
|
|
40
|
-
) => {
|
|
41
|
-
const config: FormModel = { ...controlConfig }
|
|
42
|
-
const event = controlType === ControlTypes['flmInput'] ? 'onInput' : 'onChange'
|
|
43
|
-
|
|
44
|
-
// 保存原有的 onChange/onInput 回调
|
|
45
|
-
const originalHandler = controlConfig[event]
|
|
46
|
-
|
|
47
|
-
// 链式调用:先调用内部 formChange,再调用原有回调
|
|
48
|
-
config[event] = (value: any) => {
|
|
49
|
-
formChange(value, prop)
|
|
50
|
-
if (typeof originalHandler === 'function') {
|
|
51
|
-
originalHandler(value)
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
config['modelValue'] = formModel[prop]
|
|
56
|
-
|
|
57
|
-
// ... 后续代码保持不变
|
|
58
|
-
}
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
### 方案二:新增 `onValueChange` 专用回调属性
|
|
62
|
-
|
|
63
|
-
在 `types.ts` 中扩展 `AttrFieldConfig`:
|
|
64
|
-
|
|
65
|
-
```typescript
|
|
66
|
-
// /Users/albert/Workspaces/poba/flame-plus/src/packages/components/framework/types.ts
|
|
67
|
-
|
|
68
|
-
export interface AttrFieldConfig {
|
|
69
|
-
key: string
|
|
70
|
-
label: string
|
|
71
|
-
component: AttrFieldComponent
|
|
72
|
-
props?: Record<string, unknown>
|
|
73
|
-
/** 值变化时的回调,不同于 props.onChange,此回调不会被覆盖 */
|
|
74
|
-
onValueChange?: (value: unknown, model: Record<string, unknown>) => void
|
|
75
|
-
visible?: (model: Record<string, unknown>) => boolean
|
|
76
|
-
}
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
然后在 `FrameworkAttrFieldsForm.vue` 中处理:
|
|
80
|
-
|
|
81
|
-
```javascript
|
|
82
|
-
// FrameworkAttrFieldsForm.vue
|
|
83
|
-
|
|
84
|
-
const formItems = computed(() => {
|
|
85
|
-
return visibleFields.value
|
|
86
|
-
.filter((f) => componentToControlType[f.component])
|
|
87
|
-
.map((f) => {
|
|
88
|
-
const controlType = componentToControlType[f.component] as ControlTypes
|
|
89
|
-
return {
|
|
90
|
-
prop: f.key,
|
|
91
|
-
label: f.label,
|
|
92
|
-
controlType,
|
|
93
|
-
controlConfig: {
|
|
94
|
-
...(f.props ?? {}),
|
|
95
|
-
'model-value': shadowModel[f.key],
|
|
96
|
-
// 传递 onValueChange 回调
|
|
97
|
-
_onValueChange: f.onValueChange
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
})
|
|
101
|
-
})
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
然后在 `flmForm.vue` 中调用:
|
|
105
|
-
|
|
106
|
-
```javascript
|
|
107
|
-
config[event] = (value: any) => {
|
|
108
|
-
formChange(value, prop)
|
|
109
|
-
// 调用自定义回调
|
|
110
|
-
if (typeof controlConfig._onValueChange === 'function') {
|
|
111
|
-
controlConfig._onValueChange(value, formModel)
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
## 推荐方案:方案一
|
|
117
|
-
|
|
118
|
-
方案一修改最小,只需修改 `flmForm.vue` 一个文件,且保持向后兼容。
|
|
119
|
-
|
|
120
|
-
## 修改步骤
|
|
121
|
-
|
|
122
|
-
### Step 1: 修改 flmForm.vue
|
|
123
|
-
|
|
124
|
-
文件路径:`/Users/albert/Workspaces/poba/flame-plus/src/packages/components/complex/flmForm/flmForm.vue`
|
|
125
|
-
|
|
126
|
-
找到 `controlDom` 函数(约第 215 行),修改如下:
|
|
127
|
-
|
|
128
|
-
```javascript
|
|
129
|
-
const controlDom = (
|
|
130
|
-
prop: string,
|
|
131
|
-
controlType: ControlTypes,
|
|
132
|
-
controlConfig: ControlConfig
|
|
133
|
-
) => {
|
|
134
|
-
const config: FormModel = { ...controlConfig }
|
|
135
|
-
const event = controlType === ControlTypes['flmInput'] ? 'onInput' : 'onChange'
|
|
136
|
-
|
|
137
|
-
// ====== 新增:保存并链式调用原有回调 ======
|
|
138
|
-
const originalHandler = controlConfig[event]
|
|
139
|
-
config[event] = (value: any) => {
|
|
140
|
-
// 1. 内部表单状态更新
|
|
141
|
-
formChange(value, prop)
|
|
142
|
-
// 2. 调用外部传入的回调(如果有)
|
|
143
|
-
if (typeof originalHandler === 'function') {
|
|
144
|
-
originalHandler(value)
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
// ====== 修改结束 ======
|
|
148
|
-
|
|
149
|
-
config['modelValue'] = formModel[prop]
|
|
150
|
-
|
|
151
|
-
// ... 后续代码保持不变
|
|
152
|
-
}
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
### Step 2: 重新构建 flame-plus
|
|
156
|
-
|
|
157
|
-
```bash
|
|
158
|
-
cd /Users/albert/Workspaces/poba/flame-plus
|
|
159
|
-
pnpm run build
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
### Step 3: 更新 gmp-frontend 依赖
|
|
163
|
-
|
|
164
|
-
```bash
|
|
165
|
-
cd /Users/albert/Workspaces/gmp/gmp-frontend/gmp-platform
|
|
166
|
-
pnpm update flame-plus
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
## 验证方式
|
|
170
|
-
|
|
171
|
-
修改完成后,在 `MasterForm.vue` 的 `attrFields` 中使用 `onChange`:
|
|
172
|
-
|
|
173
|
-
```typescript
|
|
174
|
-
attrFields: [
|
|
175
|
-
{
|
|
176
|
-
key: 'config.projectName',
|
|
177
|
-
label: '项目',
|
|
178
|
-
component: 'select',
|
|
179
|
-
props: {
|
|
180
|
-
placeholder: '请选择项目',
|
|
181
|
-
filterable: true,
|
|
182
|
-
options: PROJECT_OPTIONS,
|
|
183
|
-
onChange: (val: string) => {
|
|
184
|
-
console.log('项目已选择:', val)
|
|
185
|
-
if (val) loadTableOptions(val)
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
},
|
|
189
|
-
// ...
|
|
190
|
-
]
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
当用户在属性面板中选择项目时,控制台应输出 `项目已选择: xxx`,并且 `loadTableOptions` 应被调用。
|
|
194
|
-
|
|
195
|
-
## 相关文件
|
|
196
|
-
|
|
197
|
-
| 文件 | 说明 |
|
|
198
|
-
|------|------|
|
|
199
|
-
| `/Users/albert/Workspaces/poba/flame-plus/src/packages/components/complex/flmForm/flmForm.vue` | 核心修改文件 |
|
|
200
|
-
| `/Users/albert/Workspaces/poba/flame-plus/src/packages/components/framework/components/FrameworkAttrFieldsForm.vue` | attrFields 渲染组件 |
|
|
201
|
-
| `/Users/albert/Workspaces/poba/flame-plus/src/packages/components/framework/types.ts` | 类型定义 |
|
package/docs/formslot-test.png
DELETED
|
Binary file
|
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
# 动态表单取值与提交数据详解
|
|
2
|
-
|
|
3
|
-
本文说明 **`flmDynamicForm` 运行时**如何汇总各分组数据、数据在 **`flmGridForm`** 中如何维护,以及 **`componentList` 自定义控件**为何必须遵守 `onChange` / `onInput` 契约才能出现在提交结果中。
|
|
4
|
-
|
|
5
|
-
与外部组件注册、设计器配置形态相关的说明见:[framework-external-components.md](./framework-external-components.md)。
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## 1. 总览:数据从哪来
|
|
10
|
-
|
|
11
|
-
```
|
|
12
|
-
┌─────────────────────────────────────────────────────────────────┐
|
|
13
|
-
│ flmDynamicForm (config.groups + config.buttons) │
|
|
14
|
-
│ ├─ type === 'form' → flmGridForm (每个分组一个实例) │
|
|
15
|
-
│ │ └─ 内部 reactive: formModel { [prop]: value } │
|
|
16
|
-
│ └─ type === 'subForm' → flm-sub-form + 本组件内 subFormData │
|
|
17
|
-
│ │
|
|
18
|
-
│ 底部按钮 submit/cancel/reset → getDynamicFormData() → emit │
|
|
19
|
-
└─────────────────────────────────────────────────────────────────┘
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
**要点**:
|
|
23
|
-
|
|
24
|
-
- 动态表单**不会**遍历 DOM 或调用各控件的「取值 API」;**只读 `flmGridForm` 暴露的 `formModel` 与子表单的 `subFormData`**。
|
|
25
|
-
- 任一表单项的值要进提交 payload,必须在用户交互时通过 `flmGridForm` 已注入的回调写进 `formModel[prop]`(见第 3 节)。
|
|
26
|
-
|
|
27
|
-
---
|
|
28
|
-
|
|
29
|
-
## 2. 提交时:`getDynamicFormData` 行为
|
|
30
|
-
|
|
31
|
-
源码:`src/packages/components/complex/flmDynamicForm/flmDynamicForm.vue`。
|
|
32
|
-
|
|
33
|
-
### 2.1 `type === 'form'` 分组
|
|
34
|
-
|
|
35
|
-
1. 用分组 **`id`** 在 `formRefs` 里取对应的 **`flmGridForm` 实例**(通过 `ref` 回调挂载)。
|
|
36
|
-
2. 从实例上解析 **`formModel`**(兼容多种 Vue 3 实例形态):
|
|
37
|
-
|
|
38
|
-
- `inst.formModel`
|
|
39
|
-
- `inst.exposed.formModel`
|
|
40
|
-
- `inst.$.exposed.formModel`
|
|
41
|
-
- `inst.$.setupState.formModel`
|
|
42
|
-
|
|
43
|
-
3. 若当前 `id` 取不到 `formModel`,会**兜底**:在 `formRefs` 里找**第一个**能 `pickFormModel` 成功的实例(多表单分组时若 `id` 不一致可能出现合并错乱,应保证配置里 **`group.id` 与 ref 使用的 id 一致**)。
|
|
44
|
-
4. 将 `formModel` **深拷贝为普通对象**后 **`Object.assign` 到结果根对象**,即:**同一动态表单里多个 `form` 分组的字段会摊平到同一层**;若不同分组出现相同 `prop` 键,**后遍历的分组会覆盖先遍历的**。
|
|
45
|
-
|
|
46
|
-
### 2.2 `type === 'subForm'` 分组
|
|
47
|
-
|
|
48
|
-
- 数据来自 **`subFormData[id]`**(与 `flm-sub-form` 的 `v-model` 同步),在结果中为 **`payload[groupId] = 行数组`**,**不摊平到根对象**。
|
|
49
|
-
|
|
50
|
-
### 2.3 底部按钮与事件参数
|
|
51
|
-
|
|
52
|
-
- `submit` / `cancel` / `reset`:`emit(event, getDynamicFormData())`,即上述合并后的 **payload**。
|
|
53
|
-
- 其他按钮:`emit('customEvent', { event, data: btn })`,**不带**自动汇总的表单数据;若业务需要当前表单值,需自行 `ref` 动态表单或在外层监听并缓存。
|
|
54
|
-
|
|
55
|
-
---
|
|
56
|
-
|
|
57
|
-
## 3. `flmGridForm` 内:`formModel` 如何写入
|
|
58
|
-
|
|
59
|
-
源码:`src/packages/components/complex/flmGirdForm/flmGridForm.vue`。
|
|
60
|
-
|
|
61
|
-
### 3.1 核心结构
|
|
62
|
-
|
|
63
|
-
- `formModel`:`reactive<Record<string, any>>({})`,通过 **`expose({ formModel, reset })`** 暴露给父级。
|
|
64
|
-
- 任意字段更新统一走 **`formChange(value, prop)`** → **`formModel[prop] = value`**。
|
|
65
|
-
|
|
66
|
-
### 3.2 与配置的双向同步
|
|
67
|
-
|
|
68
|
-
- **`props.config.model`** 变化时:`updateFormModel` 把外部 model 合并进 `formModel`;`updateControl` 把 `formModel` 同步回各 `controlConfig` 的展示值(`modelValue` 或 `model-value`,由 `isInputControl` 决定)。
|
|
69
|
-
- **`formModel` 变化时:`watch(formModel, …)` → `updateControl`,保证控件 config 上的当前值与模型一致。
|
|
70
|
-
|
|
71
|
-
### 3.3 表单项 `prop` 即 `formModel` 的键
|
|
72
|
-
|
|
73
|
-
每个 `items[]` 项有 **`prop`(字符串)**。`controlDom` 里注入回调时使用的就是这个 **`prop`**:
|
|
74
|
-
|
|
75
|
-
```ts
|
|
76
|
-
;(config as any)[event] = (value: any) => formChange(value, prop)
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
因此 **提交对象里字段名 = 各表单项的 `prop`**。设计器保存时,`FlameFramework` 通常用控件内部 id 作为 `prop`(如 `ctrl_xxx_时间戳`),业务若需要固定业务字段名,需在**加载配置后映射**或改设计器生成规则(见外部组件指南第 5 节)。
|
|
80
|
-
|
|
81
|
-
---
|
|
82
|
-
|
|
83
|
-
## 4. 控件渲染分支与「写回」方式
|
|
84
|
-
|
|
85
|
-
`controlDom` 对不同类型处理不同,**只有写回路径接上了 `formChange`,值才会进 `formModel`**。
|
|
86
|
-
|
|
87
|
-
| 类型 / 分支 | 写回方式 |
|
|
88
|
-
|-------------|----------|
|
|
89
|
-
| **`flmInput`** | `config.onInput` → `formChange(value, prop)`(注意不是 `onChange`) |
|
|
90
|
-
| **`flmSearchSelect`** | 单独渲染,使用 `onUpdate` 等,内部再 `formChange` |
|
|
91
|
-
| **`flmDatePicker`** | `onUpdate:modelValue` → `formChange` |
|
|
92
|
-
| **`flmImage` / 上传** | `onUpload` 等 → `formChange` |
|
|
93
|
-
| **`FlmEditor`** | `onUpdate` → `formChange` |
|
|
94
|
-
| **其余(含 `resolveComponent` 自定义)** | 拷贝后的 `config` 上挂 **`onChange`**(非 flmInput)→ `formChange`;渲染 **`<DynamicComp config={config} />`** |
|
|
95
|
-
|
|
96
|
-
**自定义组件(`componentList` 拖入、保存为某 `controlType`)**落在最后一行:**必须**在用户改值时调用传入的 **`config.onChange(newValue)`**。若错误地只使用 `emit('update:modelValue')` 而父级未绑定,**不会**更新 `formModel`。
|
|
97
|
-
|
|
98
|
-
若某自定义控件**错误地把 `controlType` 设成 `flmInput`**,则框架会注入 **`onInput`**;此时组件内应调用 **`config.onInput`**,而不是 `onChange`。
|
|
99
|
-
|
|
100
|
-
---
|
|
101
|
-
|
|
102
|
-
## 5. 单独使用 `flmGridForm` 时的取值
|
|
103
|
-
|
|
104
|
-
未包在 `flmDynamicForm` 里时:
|
|
105
|
-
|
|
106
|
-
- **提交类按钮**:`flmGridForm` 内在校验通过后 **`emit('submit', formModel)`**(引用同一 reactive 对象)。
|
|
107
|
-
- **程序化读取**:父组件通过 **`ref` + `expose`** 拿到子组件实例,读取 **`formModel`**(与 `flmDynamicForm` 的 `pickFormModel` 目标一致)。
|
|
108
|
-
|
|
109
|
-
`flmDynamicForm` 正是依赖子级 **`expose` 的 `formModel`** 做聚合,而不是依赖 `submit` 事件参数。
|
|
110
|
-
|
|
111
|
-
---
|
|
112
|
-
|
|
113
|
-
## 6. 常见问题与排障
|
|
114
|
-
|
|
115
|
-
### 6.1 基础控件有值,自定义控件提交没有
|
|
116
|
-
|
|
117
|
-
1. 确认运行时 **`resolveComponent(controlType)`** 能解析到正确组件(全局注册名与 JSON 中 `controlType` 一致)。
|
|
118
|
-
2. 在自定义组件内确认用户操作后是否调用了 **`config.onChange`**(或 **`config.onInput`** 当类型为 `flmInput`)。
|
|
119
|
-
3. 确认没有把 `formModel` 当作只读:应通过回调写回,而不是仅本地 `ref`。
|
|
120
|
-
|
|
121
|
-
### 6.2 多个 `form` 分组字段「串了」或只有一部分有数据
|
|
122
|
-
|
|
123
|
-
- 检查每个 **`form` 分组的 `id`** 是否与 `flmDynamicForm` 里 `setFormRef(id, el)` 使用的 id 一致。
|
|
124
|
-
- 若 `id` 缺失或重复,`pickFormModel` 可能走到兜底逻辑,导致读到错误或单个实例。
|
|
125
|
-
|
|
126
|
-
### 6.3 `prop` 为设计器 id,与后端字段不一致
|
|
127
|
-
|
|
128
|
-
- 属**配置形态**问题:在加载 JSON 后批量改写 `items[].prop`,或在保存/加载管线做映射;不在 `getDynamicFormData` 内自动转换。
|
|
129
|
-
|
|
130
|
-
### 6.4 `subForm` 有数据,`form` 没有
|
|
131
|
-
|
|
132
|
-
- `subForm` 走 **`subFormData`**;`form` 走 **`flmGridForm.formModel`**。两套机制独立,勿混用。
|
|
133
|
-
|
|
134
|
-
---
|
|
135
|
-
|
|
136
|
-
## 7. 相关源码索引
|
|
137
|
-
|
|
138
|
-
| 主题 | 路径 |
|
|
139
|
-
|------|------|
|
|
140
|
-
| 汇总提交、`formRefs`、`pickFormModel`、`getDynamicFormData` | `src/packages/components/complex/flmDynamicForm/flmDynamicForm.vue` |
|
|
141
|
-
| `formModel`、`formChange`、`controlDom`、各特殊分支、`expose` | `src/packages/components/complex/flmGirdForm/flmGridForm.vue` |
|
|
142
|
-
| 保存时 `items[].prop` / `controlType` / `controlConfig` | `src/packages/components/framework/framework.vue` → `buildFrameworkConfig` |
|
|
143
|
-
| 自定义控件契约(注册与设计器) | [framework-external-components.md](./framework-external-components.md) |
|
|
144
|
-
|
|
145
|
-
---
|
|
146
|
-
|
|
147
|
-
## 8. 自检清单(取值相关)
|
|
148
|
-
|
|
149
|
-
- [ ] 自定义控件在变更时调用 **`config.onChange`**(或 **`config.onInput`** 当 `controlType === 'flmInput'`)
|
|
150
|
-
- [ ] 从 **`config.modelValue` / `config['model-value']`** 读取当前值并与展示同步
|
|
151
|
-
- [ ] 多 **`form` 分组**时 **`group.id` 唯一且稳定**,与 ref 键一致
|
|
152
|
-
- [ ] 需要固定业务字段名时,已对 **`items[].prop`** 或 payload 做映射
|
|
153
|
-
- [ ] **`subForm`** 数据在 payload 中为 **`payload[groupId]`** 数组,而非根级扁平字段
|