flame-plus 1.3.27 → 1.3.29

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.
@@ -0,0 +1,8 @@
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
+ }
@@ -0,0 +1,18 @@
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
+ ]
17
+ }
18
+ }
@@ -0,0 +1,102 @@
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
+
@@ -0,0 +1,201 @@
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` | 类型定义 |
@@ -0,0 +1,153 @@
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]`** 数组,而非根级扁平字段
@@ -0,0 +1,182 @@
1
+ # FlameFramework 外部组件接入指南
2
+
3
+ 本文说明**外部系统**如何开发(或封装)Vue 组件,并通过 `componentList` 传入 `FlameFramework`(表单设计器),使设计结果在 **`flmDynamicForm` / `flmGridForm` 运行时**中正确渲染。
4
+
5
+ **运行时如何汇总表单值、为何自定义控件必须 `onChange`/`onInput`:** 见 [framework-dynamic-form-value-retrieval.md](./framework-dynamic-form-value-retrieval.md)。
6
+
7
+ ---
8
+
9
+ ## 1. 整体流程
10
+
11
+ 1. 在宿主应用中**全局注册**组件,**注册名**必须与表单项里的 `controlType` 字符串**完全一致**(例如 `flmSysUserSelect`)。
12
+ 2. 通过 `app.use(FlamePlus, …, [YourComponent])` 或 `app.component(name, YourComponent)` 完成注册(见第 3 节)。
13
+ 3. 将预设条目数组传给 `<FlameFramework :component-list="…" />`。
14
+ 4. 设计器保存后,配置中的 `items[].controlType` 即为上述名称;运行时由 `flmGridForm` 使用 `resolveComponent(controlType)` 解析。
15
+
16
+ ---
17
+
18
+ ## 2. `componentList` 数据结构
19
+
20
+ `FlameFramework` 接收可选 prop:`componentList`。每一项在设计器左栏「**组件库**」中显示为可拖拽预设,拖入表单后会与内置默认配置合并。
21
+
22
+ | 字段 | 类型 | 必填 | 说明 |
23
+ |------|------|------|------|
24
+ | `key` | `string` | 是 | 列表唯一标识(如 `sys-user-select`),便于外部列表渲染与去重 |
25
+ | `label` | `string` | 是 | 左栏显示名称(如「人员单选」) |
26
+ | `type` | `string` | 是 | **控件类型名**,写入保存结果的 `controlType`,须与全局注册的组件名一致 |
27
+ | `icon` | `string` | 否 | 左栏图标,默认 `🧩` |
28
+ | `defaultConfig` | `Record<string, unknown>` | 否 | 与 `getDefaultConfig(type)` 结果**浅合并**,覆盖默认 `controlConfig` |
29
+ | `defaultFormItem` | 对象 | 否 | 覆盖默认表单项元数据(如 `label`、`required`、`rules`) |
30
+
31
+ `defaultFormItem` 常用字段与 `FormItemSettings` 对齐,例如:`label`、`required`、`rules`、`labelWidth` 等。
32
+
33
+ **注意**:源码中 `FrameworkComponentItem.type` 目前标注为 `BasicControlType`(仅内置基础控件)。传入 **`flmSearchSelect`、自定义 `flmXxx`** 等在运行时通常仍可用,但 TypeScript 可能需要 `as any` 或本地扩展类型声明,直至类型定义放宽。
34
+
35
+ ---
36
+
37
+ ## 3. 在宿主应用中注册组件
38
+
39
+ ### 3.1 使用 Flame-Plus `install` 第三参数合并
40
+
41
+ `install` 会将自定义组件与内置表合并后统一 `app.component(name, component)`:
42
+
43
+ ```ts
44
+ import { createApp } from 'vue'
45
+ import FlamePlus from 'flame-plus'
46
+ import FlmSysUserSelect from './components/FlmSysUserSelect.vue'
47
+
48
+ const app = createApp(App)
49
+ app.use(FlamePlus, undefined, { FlmSysUserSelect }) // 键名即全局组件名
50
+ ```
51
+
52
+ 对象形式下,**对象的 key** 为组件名(须与 `componentList[].type`、`controlType` 一致)。
53
+
54
+ ### 3.2 单独注册
55
+
56
+ ```ts
57
+ app.component('FlmSysUserSelect', FlmSysUserSelect)
58
+ ```
59
+
60
+ 未全局注册的 `controlType` 在运行时会解析失败或无法渲染。
61
+
62
+ ---
63
+
64
+ ## 4. 传入 `FlameFramework`
65
+
66
+ ```vue
67
+ <template>
68
+ <FlameFramework
69
+ :component-list="systemComponentPresets"
70
+ @save="onSave"
71
+ />
72
+ </template>
73
+
74
+ <script setup lang="ts">
75
+ const systemComponentPresets = [
76
+ {
77
+ key: 'sys-user-single',
78
+ label: '人员(弹窗单选)',
79
+ icon: '👤',
80
+ type: 'flmSearchSelect',
81
+ defaultConfig: {
82
+ service_name: 'your-service',
83
+ fk_table_name: 'your_person_table',
84
+ placeholder: '请选择人员'
85
+ },
86
+ defaultFormItem: { label: '人员' }
87
+ },
88
+ {
89
+ key: 'sys-dept-tree',
90
+ label: '部门(下拉)',
91
+ type: 'flmSelect',
92
+ defaultConfig: {
93
+ placeholder: '请选择部门',
94
+ options: [],
95
+ clearable: true
96
+ },
97
+ defaultFormItem: { label: '部门' }
98
+ }
99
+ ]
100
+ </script>
101
+ ```
102
+
103
+ `defaultConfig` 的内容即落库后的 `controlConfig` 初始值(设计器内还可继续改)。
104
+
105
+ ---
106
+
107
+ ## 5. 保存结果中的表单项形态
108
+
109
+ 设计器 `save` 事件抛出配置,其中表单分组下的 `items` 与 `FormConfig['items']` 一致。每个控件对应一项,核心字段包括:
110
+
111
+ - `prop`:设计器生成的控件 id(字符串),**业务侧若需固定字段名,应在加载配置后做映射或在二次开发设计器中改生成规则**。
112
+ - `controlType`:与 `componentList[].type` / 全局组件名一致。
113
+ - `controlConfig`:控件配置快照(含 `model-value` 等)。
114
+ - `gridX` / `gridY` / `gridW` / `gridH`:栅格布局(默认 24 列)。
115
+
116
+ 运行时由 `flmDynamicForm` → `flmGridForm` 根据 `controlType` 渲染。
117
+
118
+ ---
119
+
120
+ ## 6. 自定义控件与 `flmGridForm` 的契约
121
+
122
+ `flmGridForm` 对**大部分**控件采用统一方式(非 `flmInput`、`flmSearchSelect`、`flmDatePicker`、`flmImage`、`FlmEditor` 等特殊分支):
123
+
124
+ - 渲染为:`<YourComponent config={mergedConfig} />`
125
+ - `mergedConfig` 在原有 `controlConfig` 基础上会注入:
126
+ - `modelValue`:来自 `controlConfig['model-value']`
127
+ - **`onChange`**:表单写入用(`flmInput` 类型使用 **`onInput`** 而非 `onChange`)
128
+
129
+ 因此,**推荐**自定义控件与内置 `flmSelect` 等保持一致:
130
+
131
+ 1. **Props**:声明 `config: Object`(或具体类型)。
132
+ 2. **取值**:从 `config['model-value']` 或 `config.modelValue` 读取当前值(以你方 `filterConfig` 习惯为准)。
133
+ 3. **改值**:用户操作后调用 **`config.onChange?.(newValue)`**(若为输入类且走 `flmInput` 分支则调用 **`config.onInput`**——自定义类型一般不会走 `flmInput` 分支,用 `onChange` 即可)。
134
+
135
+ 若控件需要 `isSearch` 等额外 props,需在 `flmGridForm` 增加分支或复用现有特殊类型模式;**默认动态分支不会传递 `isSearch`**。
136
+
137
+ ### 6.1 与 `flmSearchSelect` 对齐的特殊协议
138
+
139
+ 若 `controlType` 为 `flmSearchSelect`,`flmGridForm` 会单独传 `onUpdate`、合并 `ref` 等,**不能**仅靠通用 `config` 协议实现,需直接使用或封装 `flmSearchSelect`。
140
+
141
+ ---
142
+
143
+ ## 7. 表单设计器画布(设计态)限制
144
+
145
+ 设计区内预览使用 `FormDesignArea` 内置的 `controlComponents` 映射:**仅内置基础控件有真实预览**。未在映射表中的 `type` 会**回退为 `flmInput` 占位**,不影响已保存 JSON 在 **`flmGridForm` 运行态**的解析,但设计时视觉与内置控件不一致。
146
+
147
+ 可选改进方向(需改本库):
148
+
149
+ - 为 `flmSearchSelect` 等补充映射;或
150
+ - 为 `FormDesignArea` 增加可选 prop,合并外部传入的 `type → 组件` 表。
151
+
152
+ ---
153
+
154
+ ## 8. 与 `flame-types` / `ControlTypes`
155
+
156
+ 消费端若使用 TypeScript 维护表单配置,内置枚举见包内 `ControlTypes`。**自定义 `controlType` 不会出现在枚举中**,可:
157
+
158
+ - 使用 `string` 扩展类型;或
159
+ - 在业务侧声明:`type ControlType = ControlTypes | 'FlmSysUserSelect' | …`。
160
+
161
+ ---
162
+
163
+ ## 9. 自检清单
164
+
165
+ - [ ] 全局注册名与 `componentList[].type`、`items[].controlType` 一致
166
+ - [ ] 自定义组件实现 `config` + `onChange`(或对齐 `flmSearchSelect` 等特殊协议)
167
+ - [ ] 宿主已安装 `flame-request`、`element-plus` 等依赖(若组件内部使用)
168
+ - [ ] 接受设计器画布对非内置 `type` 可能显示为输入框占位
169
+ - [ ] 保存后的 `prop` 为设计器 id,业务落库字段映射方案已明确
170
+
171
+ ---
172
+
173
+ ## 10. 相关源码位置(本仓库)
174
+
175
+ | 内容 | 路径 |
176
+ |------|------|
177
+ | `componentList` prop 与 `componentWidgetList` | `src/packages/components/framework/framework.vue` |
178
+ | 保存时 `controlType` / `controlConfig` 生成 | `buildFrameworkConfig`(同文件) |
179
+ | 左栏「组件库」 | `src/packages/components/framework/components/WidgetPanel.vue` |
180
+ | 设计区控件映射 | `src/packages/components/framework/components/FormDesignArea.vue` |
181
+ | 运行时解析与事件注入 | `src/packages/components/complex/flmGirdForm/flmGridForm.vue` |
182
+ | 全局注册合并 | `src/packages/index.ts` → `install` |