# FlameFramework 外部组件接入指南

本文说明**外部系统**如何开发（或封装）Vue 组件，并通过 `componentList` 传入 `FlameFramework`（表单设计器），使设计结果在 **`flmDynamicForm` / `flmGridForm` 运行时**中正确渲染。

**运行时如何汇总表单值、为何自定义控件必须 `onChange`/`onInput`：** 见 [framework-dynamic-form-value-retrieval.md](./framework-dynamic-form-value-retrieval.md)。

---

## 1. 整体流程

1. 在宿主应用中**全局注册**组件，**注册名**必须与表单项里的 `controlType` 字符串**完全一致**（例如 `flmSysUserSelect`）。
2. 通过 `app.use(FlamePlus, …, [YourComponent])` 或 `app.component(name, YourComponent)` 完成注册（见第 3 节）。
3. 将预设条目数组传给 `<FlameFramework :component-list="…" />`。
4. 设计器保存后，配置中的 `items[].controlType` 即为上述名称；运行时由 `flmGridForm` 使用 `resolveComponent(controlType)` 解析。

---

## 2. `componentList` 数据结构

`FlameFramework` 接收可选 prop：`componentList`。每一项在设计器左栏「**组件库**」中显示为可拖拽预设，拖入表单后会与内置默认配置合并。

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `key` | `string` | 是 | 列表唯一标识（如 `sys-user-select`），便于外部列表渲染与去重 |
| `label` | `string` | 是 | 左栏显示名称（如「人员单选」） |
| `type` | `string` | 是 | **控件类型名**，写入保存结果的 `controlType`，须与全局注册的组件名一致 |
| `icon` | `string` | 否 | 左栏图标，默认 `🧩` |
| `defaultConfig` | `Record<string, unknown>` | 否 | 与 `getDefaultConfig(type)` 结果**浅合并**，覆盖默认 `controlConfig` |
| `defaultFormItem` | 对象 | 否 | 覆盖默认表单项元数据（如 `label`、`required`、`rules`） |

`defaultFormItem` 常用字段与 `FormItemSettings` 对齐，例如：`label`、`required`、`rules`、`labelWidth` 等。

**注意**：源码中 `FrameworkComponentItem.type` 目前标注为 `BasicControlType`（仅内置基础控件）。传入 **`flmSearchSelect`、自定义 `flmXxx`** 等在运行时通常仍可用，但 TypeScript 可能需要 `as any` 或本地扩展类型声明，直至类型定义放宽。

---

## 3. 在宿主应用中注册组件

### 3.1 使用 Flame-Plus `install` 第三参数合并

`install` 会将自定义组件与内置表合并后统一 `app.component(name, component)`：

```ts
import { createApp } from 'vue'
import FlamePlus from 'flame-plus'
import FlmSysUserSelect from './components/FlmSysUserSelect.vue'

const app = createApp(App)
app.use(FlamePlus, undefined, { FlmSysUserSelect }) // 键名即全局组件名
```

对象形式下，**对象的 key** 为组件名（须与 `componentList[].type`、`controlType` 一致）。

### 3.2 单独注册

```ts
app.component('FlmSysUserSelect', FlmSysUserSelect)
```

未全局注册的 `controlType` 在运行时会解析失败或无法渲染。

---

## 4. 传入 `FlameFramework`

```vue
<template>
  <FlameFramework
    :component-list="systemComponentPresets"
    @save="onSave"
  />
</template>

<script setup lang="ts">
const systemComponentPresets = [
  {
    key: 'sys-user-single',
    label: '人员（弹窗单选）',
    icon: '👤',
    type: 'flmSearchSelect',
    defaultConfig: {
      service_name: 'your-service',
      fk_table_name: 'your_person_table',
      placeholder: '请选择人员'
    },
    defaultFormItem: { label: '人员' }
  },
  {
    key: 'sys-dept-tree',
    label: '部门（下拉）',
    type: 'flmSelect',
    defaultConfig: {
      placeholder: '请选择部门',
      options: [],
      clearable: true
    },
    defaultFormItem: { label: '部门' }
  }
]
</script>
```

`defaultConfig` 的内容即落库后的 `controlConfig` 初始值（设计器内还可继续改）。

---

## 5. 保存结果中的表单项形态

设计器 `save` 事件抛出配置，其中表单分组下的 `items` 与 `FormConfig['items']` 一致。每个控件对应一项，核心字段包括：

- `prop`：设计器生成的控件 id（字符串），**业务侧若需固定字段名，应在加载配置后做映射或在二次开发设计器中改生成规则**。
- `controlType`：与 `componentList[].type` / 全局组件名一致。
- `controlConfig`：控件配置快照（含 `model-value` 等）。
- `gridX` / `gridY` / `gridW` / `gridH`：栅格布局（默认 24 列）。

运行时由 `flmDynamicForm` → `flmGridForm` 根据 `controlType` 渲染。

---

## 6. 自定义控件与 `flmGridForm` 的契约

`flmGridForm` 对**大部分**控件采用统一方式（非 `flmInput`、`flmSearchSelect`、`flmDatePicker`、`flmImage`、`FlmEditor` 等特殊分支）：

- 渲染为：`<YourComponent config={mergedConfig} />`
- `mergedConfig` 在原有 `controlConfig` 基础上会注入：
  - `modelValue`：来自 `controlConfig['model-value']`
  - **`onChange`**：表单写入用（`flmInput` 类型使用 **`onInput`** 而非 `onChange`）

因此，**推荐**自定义控件与内置 `flmSelect` 等保持一致：

1. **Props**：声明 `config: Object`（或具体类型）。
2. **取值**：从 `config['model-value']` 或 `config.modelValue` 读取当前值（以你方 `filterConfig` 习惯为准）。
3. **改值**：用户操作后调用 **`config.onChange?.(newValue)`**（若为输入类且走 `flmInput` 分支则调用 **`config.onInput`**——自定义类型一般不会走 `flmInput` 分支，用 `onChange` 即可）。

若控件需要 `isSearch` 等额外 props，需在 `flmGridForm` 增加分支或复用现有特殊类型模式；**默认动态分支不会传递 `isSearch`**。

### 6.1 与 `flmSearchSelect` 对齐的特殊协议

若 `controlType` 为 `flmSearchSelect`，`flmGridForm` 会单独传 `onUpdate`、合并 `ref` 等，**不能**仅靠通用 `config` 协议实现，需直接使用或封装 `flmSearchSelect`。

---

## 7. 表单设计器画布（设计态）限制

设计区内预览使用 `FormDesignArea` 内置的 `controlComponents` 映射：**仅内置基础控件有真实预览**。未在映射表中的 `type` 会**回退为 `flmInput` 占位**，不影响已保存 JSON 在 **`flmGridForm` 运行态**的解析，但设计时视觉与内置控件不一致。

可选改进方向（需改本库）：

- 为 `flmSearchSelect` 等补充映射；或
- 为 `FormDesignArea` 增加可选 prop，合并外部传入的 `type → 组件` 表。

---

## 8. 与 `flame-types` / `ControlTypes`

消费端若使用 TypeScript 维护表单配置，内置枚举见包内 `ControlTypes`。**自定义 `controlType` 不会出现在枚举中**，可：

- 使用 `string` 扩展类型；或
- 在业务侧声明：`type ControlType = ControlTypes | 'FlmSysUserSelect' | …`。

---

## 9. 自检清单

- [ ] 全局注册名与 `componentList[].type`、`items[].controlType` 一致  
- [ ] 自定义组件实现 `config` + `onChange`（或对齐 `flmSearchSelect` 等特殊协议）  
- [ ] 宿主已安装 `flame-request`、`element-plus` 等依赖（若组件内部使用）  
- [ ] 接受设计器画布对非内置 `type` 可能显示为输入框占位  
- [ ] 保存后的 `prop` 为设计器 id，业务落库字段映射方案已明确  

---

## 10. 相关源码位置（本仓库）

| 内容 | 路径 |
|------|------|
| `componentList` prop 与 `componentWidgetList` | `src/packages/components/framework/framework.vue` |
| 保存时 `controlType` / `controlConfig` 生成 | `buildFrameworkConfig`（同文件） |
| 左栏「组件库」 | `src/packages/components/framework/components/WidgetPanel.vue` |
| 设计区控件映射 | `src/packages/components/framework/components/FormDesignArea.vue` |
| 运行时解析与事件注入 | `src/packages/components/complex/flmGirdForm/flmGridForm.vue` |
| 全局注册合并 | `src/packages/index.ts` → `install` |
