# ProForm 表单组件

基于 Element UI Form 的二次封装，参考 [Vben Admin Form](https://doc.vvbin.cn/components/form.html)，支持 Schema 驱动、useForm、render/slot、动态显示等能力。

## 组件构成

- **ProForm** - 主表单组件
- **ProFormItem** - 表单项（按 schema 自动渲染）
- **FormActions** - 提交/重置/展开按钮组（可单独使用）

## 基础用法

通过 `schemas` 配置表单项，支持 `input`、`select`、`api-select`、`tree-select`、`date-picker`、`switch` 等组件类型。

```vue
<template>
  <ProForm
    :schemas="schemas"
    :initial-values="{ name: '' }"
    :base-col-props="{ span: 8 }"
    @submit="handleSubmit"
  />
</template>

<script setup lang="ts">
import { ProForm } from 'element-component-pro'
import type { ProFormSchema } from 'element-component-pro'

const schemas: ProFormSchema[] = [
  { field: 'name', label: '姓名', component: 'input', required: true, colProps: { span: 8 } },
  {
    field: 'status',
    label: '状态',
    component: 'select',
    componentProps: {
      options: [
        { label: '启用', value: 1 },
        { label: '禁用', value: 0 },
      ],
    },
    colProps: { span: 8 },
  },
]

const handleSubmit = (values: Record<string, unknown>) => {
  console.log(values)
}
</script>
```

## useForm 方式

通过 `useForm` 传入 formProps，支持编程式调用表单方法。**参数 props 内的值可以是 computed 或 ref 类型**。

```vue
<template>
  <ProForm @register="register" @submit="handleSubmit" />
</template>

<script setup lang="ts">
import { ProForm, useForm } from 'element-component-pro'

const [register, formActions] = useForm({
  schemas: [...],
  labelWidth: '120px',
  actionColOptions: { span: 24 },
})

// 响应式 props
const formProps = ref({ schemas: [...], disabled: false })
const [register2] = useForm(formProps)

const handleSubmit = (values: Record<string, unknown>) => {
  console.log(values)
}
</script>
```

## 自定义 component

`schema.component` 除内置类型外，支持**自定义组件**，有两种用法：

### 1. 组件名 + components 映射

在 ProForm 上传入 `components`，schema 中 `component` 填组件名字符串，会从 `components` 中解析；若未找到则当作全局注册的组件名。

```vue
<template>
  <ProForm :schemas="schemas" :components="customComponents" @submit="handleSubmit" />
</template>

<script setup lang="ts">
import { ProForm } from 'element-component-pro'
import type { ProFormSchema } from 'element-component-pro'
import MyRate from './MyRate.vue'

const customComponents = { MyRate }

const schemas: ProFormSchema[] = [
  { field: 'name', label: '姓名', component: 'input' },
  { field: 'score', label: '评分', component: 'MyRate', componentProps: { max: 5 } },
]

const handleSubmit = (values: Record<string, unknown>) => {
  console.log(values)
}
</script>
```

### 2. 内联组件（直接传组件定义）

`schema.component` 可直接传入 Vue 组件选项对象或构造函数，无需事先注册到 `components`。

```ts
import Rate from './Rate.vue'

const schemas: ProFormSchema[] = [
  { field: 'score', label: '评分', component: Rate, componentProps: { max: 5 } },
]
```

**约定**：自定义组件需支持 `value`（或 `modelValue`）prop 与 `input` 事件，以与表单双向绑定；也可通过 `componentProps` 覆盖 `value` / `@input`。

## Props

| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| schemas | `ProFormSchema[]` | - | 表单项配置 |
| modelValue | `Record<string, unknown>` | - | 受控表单值，支持 `v-model` 双向绑定 |
| initialValues | `Record<string, unknown>` | - | 初始值 |
| labelWidth | `string` | `'120px'` | 标签宽度 |
| labelPosition | `'left' \| 'right' \| 'top'` | `'right'` | 标签位置 |
| colon | `boolean` | `true` | 是否在 label 后显示冒号 |
| gutter | `number` | `24` | 栅格间距 |
| size | `'medium' \| 'small' \| 'large'` | `'medium'` | 尺寸 |
| disabled | `boolean` | `false` | 是否禁用 |
| baseColProps | `ColEx` | - | 全局栅格配置 |
| baseRowStyle | `Record<string, string \| number>` | - | 栅格行样式 |
| autoSetPlaceholder | `boolean` | `true` | 是否自动设置 placeholder |
| showSubmitButton | `boolean` | `true` | 是否显示提交按钮 |
| showResetButton | `boolean` | `true` | 是否显示重置按钮 |
| submitButtonText | `string` | `'提交'` | 提交按钮文字 |
| resetButtonText | `string` | `'重置'` | 重置按钮文字 |
| submitButtonIcon | `string` | `'el-icon-search'` | 提交按钮图标 |
| resetButtonIcon | `string` | `'el-icon-refresh-left'` | 重置按钮图标 |
| showActionButtonGroup | `boolean` | `true` | 是否显示操作按钮组 |
| actionColOptions | `ColEx` | 响应式配置 | 操作按钮区域栅格，支持 xs/sm/md/lg/xl |
| showAdvancedButton | `boolean` | `false` | 是否显示展开/收起按钮 |
| autoAdvancedLine | `number` | `3` | 超过指定行数默认折叠 |
| alwaysShowLines | `number` | `1` | 折叠时显示的行数 |
| submitFunc | `() => Promise<void>` | - | 自定义提交逻辑 |
| resetFunc | `() => Promise<void>` | - | 自定义重置逻辑 |
| submitOnReset | `boolean` | `false` | 重置时是否提交 |
| fieldMapToTime | `FieldMapToTime[]` | - | 时间范围字段映射 |
| formListeners | `FormListeners` | - | 透传给 el-form 的事件监听器 |
| components | `Record<string, unknown>` | - | 自定义组件映射（组件名 -> 组件），供 schema.component 使用 |

## ProFormSchema

| 属性 | 类型 | 说明 |
|------|------|------|
| field | `string` | 字段名 |
| label | `string` | 标签 |
| labelWidth | `string` | 当前表单项标签宽度，配置后可覆盖 ProForm 的 `labelWidth` |
| colon | `boolean` | 当前表单项是否显示 label 冒号，优先级高于 ProForm 的 `colon` |
| component | `string \| object \| Function` | 组件类型：input、select、**api-select**、**tree-select**、date-picker、date-range、input-number、**formatted-number**、switch、cascader、checkbox、radio；或自定义组件名（ProForm components/全局注册）；或内联组件（Vue 组件选项/构造函数） |
| componentProps | `object \| function` | 组件属性，支持对象或函数，详见 [componentProps 详解](#componentprops-详解) |
| placeholder | `string` | 占位符 |
| defaultValue | `unknown` | 默认值 |
| required | `boolean` | 是否必填 |
| rules | `Array` | 校验规则 |
| colProps | `ColEx` | 栅格配置，支持 span、xs、sm、md、lg、xl 等响应式断点 |
| hidden | `boolean` | 是否隐藏 |
| render | `(params) => VNode \| string` | 自定义渲染 |
| slot | `string` | 具名插槽名称 |
| show | `boolean \| (params) => boolean` | 动态显示（CSS 控制） |
| ifShow | `boolean \| (params) => boolean` | 动态显示（不渲染 DOM） |
| dynamicDisabled | `boolean \| (params) => boolean` | 动态禁用 |
| dynamicRules | `Array \| (params) => Array` | 动态校验规则 |
| helpMessage | `string \| string[]` | 标签右侧温馨提示 |
| helpComponentProps | `object` | 温馨提示组件 props |
| tooltip | `boolean \| string \| object \| (params) => boolean \| string \| object` | 表单项 value tooltip，`true` 时默认展示当前值 |

### ColEx 栅格配置

`colProps`、`baseColProps`、`actionColOptions` 均支持响应式断点，与 Element UI 一致：

| 断点 | 视口宽度 |
|------|----------|
| xs | <768px |
| sm | ≥768px |
| md | ≥992px |
| lg | ≥1200px |
| xl | ≥1920px |

```ts
// 响应式栅格示例
baseColProps: { xs: 24, sm: 12, md: 8, lg: 6 }
```

## useForm 方法

| 方法 | 类型 | 说明 |
|------|------|------|
| getFieldsValue | `() => Record<string, unknown>` | 获取表单值 |
| setFieldsValue | `(values) => Promise<void>` | 设置表单字段值 |
| resetFields | `() => Promise<void>` | 重置表单 |
| validate | `(nameList?) => Promise<Record<string, unknown> \| { errors }>` | 校验表单；成功返回表单值对象，失败返回 `{ errors }` |
| validateFields | `(nameList?) => Promise<unknown>` | 校验指定表单项 |
| submit | `() => Promise<void>` | 提交表单 |
| scrollToField | `(name, options?) => Promise<void>` | 滚动到对应字段 |
| clearValidate | `(name?) => void` | 清空校验 |
| updateSchema | `(data) => Promise<void>` | 更新 schema |
| appendSchemaByField | `(schema, prefixField?, first?) => Promise<void>` | 插入 schema |
| removeSchemaByField | `(field) => Promise<void>` | 删除 schema |
| setProps | `(props) => Promise<void>` | 设置表单 Props |
| getComponentInstance | `(field) => ComponentPublicInstance \| null` | 获取指定字段的组件实例 |
| getFieldOptions | `(field, raw?) => Array<{ label: string; value: unknown }> \| unknown[]` | 获取 `api-select` 字段的 options；`raw = true` 时返回接口原始列表数据 |
| isFieldLoading | `(field) => boolean` | 获取 `api-select` 字段的加载状态 |

## Slots

| 名称 | 说明 |
|------|------|
| formHeader | 表单顶部区域 |
| formFooter | 表单底部区域 |
| submitBefore | 提交按钮前 |
| resetBefore | 重置按钮前 |
| advanceBefore | 展开/收起按钮前 |
| advanceAfter | 展开/收起按钮后 |
| actions | 操作按钮区域（在展开/收起按钮后） |
| [schema.field] / [schema.slot] | 自定义表单项内容 |

操作按钮顺序：**提交 → 重置 → 展开/收起 → 自定义操作**。表单项与操作按钮在同一行，操作按钮右对齐。

**展开/收起**：当 `showAdvancedButton` 且字段数超过 `alwaysShowLines` 行可容纳数量时显示。`hasMoreFields` 会根据当前视口宽度和 `colProps` 的响应式断点（xs/sm/md/lg/xl）动态计算。

## 示例

### v-model / modelValue

`ProForm` 支持通过 `v-model` 进行受控绑定。传入 `modelValue` 后，表单项变更、`setFieldsValue()`、`resetFields()` 都会触发 `update:modelValue`。

`initialValues` 只用于补齐缺失字段，不会覆盖 `modelValue` 中已经存在的值。

```vue
<template>
  <ProForm
    v-model="formData"
    :schemas="schemas"
    :initial-values="{ status: 0, remark: '默认备注' }"
    @submit="handleSubmit"
  />
</template>

<script setup lang="ts">
import { ref } from 'vue'
import type { ProFormSchema } from 'element-component-pro'

const formData = ref({
  name: '张三',
})

const schemas: ProFormSchema[] = [
  { field: 'name', label: '姓名', component: 'input' },
  { field: 'status', label: '状态', component: 'select', defaultValue: 1, componentProps: {
    options: [
      { label: '启用', value: 1 },
      { label: '禁用', value: 0 },
    ],
  } },
  { field: 'remark', label: '备注', component: 'input' },
]

const handleSubmit = (values: Record<string, unknown>) => {
  console.log(values)
}
</script>
```

上例初始化后的值为：`name` 使用 `modelValue` 中的 `'张三'`，`status` 补 `defaultValue: 1`，`remark` 补 `initialValues.remark`。

调用 `resetFields()` 或点击重置按钮时，也会按同样规则重建值：优先保留当前外部显式传入的字段，其余字段按 `defaultValue`、`initialValues` 补齐，并触发 `update:modelValue`。

### useForm + v-model

`useForm` 可以和 `v-model` 一起使用。此时 `setFieldsValue()`、`resetFields()` 等编程式方法同样会触发 `update:modelValue`，父组件拿到的仍然是统一的受控数据源。

```vue
<template>
  <ProForm v-model="formData" @register="register" />
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { useForm } from 'element-component-pro'

const formData = ref({ name: '张三' })

const [register, formActions] = useForm({
  schemas,
  modelValue: formData,
  initialValues: { remark: '默认备注' },
})

const fillValues = () => {
  formActions.setFieldsValue({ name: '李四' })
}

const resetValues = () => {
  formActions.resetFields()
}
</script>
```

### labelWidth

表单级 `labelWidth` 用于统一控制标签宽度；当某个字段需要更宽或更窄的标签区域时，可在该项 schema 上单独配置 `labelWidth`，其优先级高于 `ProForm` 的 `labelWidth`。

```vue
<template>
  <ProForm :schemas="schemas" label-width="120px" />
</template>

<script setup lang="ts">
import type { ProFormSchema } from 'element-component-pro'

const schemas: ProFormSchema[] = [
  {
    field: 'name',
    label: '姓名',
    component: 'input',
  },
  {
    field: 'identityCard',
    label: '身份证号码',
    component: 'input',
    labelWidth: '160px',
  },
]
</script>
```

### colon

`ProForm` 默认会在 label 后显示冒号，可通过表单级 `colon` 统一关闭，也可以在单个 schema 上单独覆盖。

```vue
<template>
  <ProForm :schemas="schemas" :colon="false" />
</template>

<script setup lang="ts">
const schemas = [
  {
    field: 'name',
    label: '姓名',
    component: 'input',
  },
  {
    field: 'status',
    label: '状态',
    component: 'select',
    colon: true,
  },
]
</script>
```

规则如下：

- `ProForm` 默认 `colon: true`
- `schema.colon` 优先级高于 `ProForm.colon`
- 关闭后只影响 label 文案展示，不影响校验、helpMessage 和 tooltip

### helpMessage

```ts
{
  field: 'name',
  label: '姓名',
  helpMessage: '请输入真实姓名',
  // 或
  helpMessage: ['提示1', '提示2'],
}
```

### tooltip

`tooltip` 用于给表单项 value 增加悬浮提示，适合输入内容较长、需要补充说明或希望直接回显当前值的场景。

支持以下几种写法：

| 写法 | 说明 |
|------|------|
| `true` | 启用 tooltip，并默认使用当前字段值作为内容 |
| `string` | 使用固定文案作为 tooltip 内容 |
| `object` | 透传给 `el-tooltip` 的 props；未传 `content` 时默认使用当前字段值 |
| `(params) => ...` | 动态返回上面任一类型，适合根据其他字段联动 |

`params` 与 `render`、`show` 等回调保持一致，包含以下字段：

- `schema`：当前字段 schema
- `field`：当前字段名
- `model` / `values`：当前表单值

默认行为：

- 默认 `placement` 为 `top`
- 默认 `effect` 为 `dark`
- 当 tooltip 内容为空字符串、`null`、`undefined` 或空数组时，会自动禁用 tooltip
- 当传入数组值（如多选、级联）且未显式指定 `content` 时，会自动用逗号拼接后展示

```ts
{
  field: 'name',
  label: '姓名',
  component: 'input',
  tooltip: true, // 默认展示当前字段值
}

{
  field: 'status',
  label: '状态',
  component: 'select',
  tooltip: '请选择账号状态',
}

{
  field: 'remark',
  label: '备注',
  component: 'input',
  tooltip: ({ values }) => values.status === 0 ? '禁用状态下仅支持查看' : false,
}

{
  field: 'address',
  label: '地址',
  component: 'input',
  tooltip: {
    placement: 'top-start',
    effect: 'dark',
    content: '请填写详细地址',
  },
}
```

当传入对象且未显式指定 `content` 时，会默认使用当前字段值作为 tooltip 内容。

建议：

- 输入框、文本域、级联、多选这类“内容可能被遮挡或较长”的字段优先使用 `tooltip: true`
- 下拉、开关等语义明确的控件，更适合使用固定文案或函数式 tooltip 做补充说明
- 如果需要完全自定义展示内容，优先使用对象写法并显式传入 `content`

### tooltip 常见问题

#### 1. `tooltip: true` 显示的是什么？

默认显示当前字段的实际值：

- 普通输入值会转成字符串展示
- 多选、级联这类数组值会按逗号拼接展示
- 空值、`null`、`undefined`、空数组时不会显示 tooltip

#### 2. 对 `select` / `radio` / `checkbox` 会显示 label 还是 value？

当前默认显示的是字段真实值，而不是选项的 `label`。

如果你希望展示更友好的文案，建议使用函数或对象形式自行指定 `content`：

```ts
{
  field: 'status',
  label: '状态',
  component: 'select',
  componentProps: {
    options: [
      { label: '启用', value: 1 },
      { label: '禁用', value: 0 },
    ],
  },
  tooltip: ({ values }) => values.status === 1 ? '当前为启用状态' : '当前为禁用状态',
}
```

#### 3. 我想自定义 tooltip 的位置、主题、延迟怎么办？

直接使用对象形式，透传 `el-tooltip` 的 props：

```ts
{
  field: 'remark',
  label: '备注',
  component: 'input',
  tooltip: {
    placement: 'top-start',
    effect: 'light',
    openDelay: 300,
    content: '请填写详细说明',
  },
}
```

#### 4. 哪些场景更推荐开启 tooltip？

推荐优先用于以下字段：

- 长文本输入
- 多选结果较多
- 级联选择层级较深
- 需要补充业务解释的控件

如果字段本身语义很直观、值也很短，可以不必开启，避免交互噪音。

#### 5. tooltip 能不能根据其他字段动态变化？

可以，直接使用函数形式即可。该函数会拿到当前表单值，适合做联动说明：

```ts
{
  field: 'email',
  label: '邮箱',
  component: 'input',
  tooltip: ({ values }) => values.status === 0 ? '禁用状态下邮箱仅做展示' : false,
}
```

### slot 自定义

```vue
<ProForm :schemas="schemas" @submit="handleSubmit">
  <template #customField="{ model, field }">
    <el-input v-model="model[field]" />
  </template>
</ProForm>

// schemas
{ field: 'customField', label: '自定义', slot: 'customField' }
```

### ifShow / dynamicDisabled

```ts
{
  field: 'remark',
  label: '备注',
  ifShow: ({ values }) => !!values.status,  // 仅当 status 有值时显示
},
{
  field: 'email',
  label: '邮箱',
  dynamicDisabled: ({ values }) => values.status === 0,  // status 为 0 时禁用
}
```

**注意**：当 `ifShow` 为 `false` 时，该字段不会出现在提交数据和 `getFieldsValue()` 的返回值中。

## componentProps 详解

`componentProps` 用于配置底层 Element UI 组件的属性，支持**对象**和**函数**两种形式，会透传给 `el-input`、`el-select`、`el-date-picker` 等组件。

### 对象形式

直接传入对象，适用于静态配置：

```ts
{
  field: 'status',
  label: '状态',
  component: 'select',
  componentProps: {
    options: [
      { label: '启用', value: 1 },
      { label: '禁用', value: 0 },
    ],
    clearable: true,
    filterable: true,
  },
}
```

### api-select（接口下拉）

选项由接口动态拉取，适合字典、远程数据。在 schema 中设置 `component: 'api-select'`，并在 `componentProps` 中传入 `api`（及可选的 `labelField`、`valueField`）：

```ts
{
  field: 'status',
  label: '状态',
  component: 'api-select',
  componentProps: {
    api: () => api.getStatusList(),  // 返回 Promise<{ label, value }[]> 或 Promise<{ list: [] }>
    labelField: 'label',             // 可选，默认 'label'
    valueField: 'value',             // 可选，默认 'value'
    clearable: true,
    filterable: true,
  },
}
```

`api` 可声明为 `(params?: Record<string, unknown>) => Promise<...>`，会收到当前 `params`；传入 **params** 后，当 `params` 变化时会自动重新拉取 options（适合依赖其他表单项的联动下拉）。**lazy: true** 时改为懒加载：仅在展开下拉时请求，不在挂载时请求。**filterable: true** 时开启下拉内关键字搜索（在已加载的选项中做前端过滤，与 el-select 一致）。

`api` 返回值约定：直接为 `Array<{ label, value }>`，或为 `{ list: [] }` / `{ data: [] }`，列表项可为任意结构，通过 `labelField` / `valueField` 指定字段名。

### api-select 动态获取 Options

`api-select` 组件支持通过 `formActionType` 动态获取内部加载的 options 和 loading 状态，便于实现联动逻辑或展示辅助信息。

新增的 `FormActionType` 方法：

| 方法 | 说明 | 返回值 |
|------|------|--------|
| `getFieldOptions(field, raw?)` | 获取指定 `api-select` 字段的 options；`raw = true` 时返回接口原始列表数据 | `Array<{ label: string; value: unknown }> \| unknown[]` |
| `isFieldLoading(field)` | 获取指定字段的加载状态 | `boolean` |
| `getComponentInstance(field)` | 获取组件实例（含 `options`、`loading`、`fetchOptions`） | `ComponentPublicInstance \| null` |

**示例：联动下拉**

```vue
<template>
  <ProForm ref="formRef" :schemas="schemas" @submit="handleSubmit" />
  <el-button @click="getCityLabel">获取选中城市的名称</el-button>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import type { ProFormSchema } from 'element-component-pro'

const formRef = ref()

const fetchProvinces = () => Promise.resolve([
  { label: '广东', value: 'gd' },
  { label: '浙江', value: 'zj' },
])

const fetchCities = (params) => Promise.resolve([
  { label: '广州', value: 'gz' },
  { label: '深圳', value: 'sz' },
])

const schemas: ProFormSchema[] = [
  {
    field: 'province',
    label: '省份',
    component: 'api-select',
    componentProps: {
      api: fetchProvinces,
      onChange: () => {
        // 省份变化时，清空城市并触发重新加载
        formRef.value.setFieldsValue({ city: null })
        formRef.value.updateSchema([{
          field: 'city',
          componentProps: { params: { provinceId: formRef.value.getFieldsValue().province } }
        }])
      }
    }
  },
  {
    field: 'city',
    label: '城市',
    component: 'api-select',
    componentProps: {
      api: fetchCities,
      params: { provinceId: '' }
    }
  }
]

const getCityLabel = () => {
  const options = formRef.value.getFieldOptions('city')
  const rawOptions = formRef.value.getFieldOptions('city', true)
  const currentValue = formRef.value.getFieldsValue().city
  const label = options.find(o => o.value === currentValue)?.label
  console.log('当前选中城市:', label)
  console.log('城市原始数据:', rawOptions)
}
</script>
```

**示例：在 componentProps 回调中获取 Options**

```ts
{
  field: 'category',
  label: '子类目',
  component: 'api-select',
  componentProps: ({ formActionType }) => ({
    api: fetchCategoryList,
    params: { type: formActionType.getFieldsValue().type },
    onChange: (value) => {
      // 在回调中也能获取到当前 options 和原始数据
      const options = formActionType.getFieldOptions('category')
      const rawOptions = formActionType.getFieldOptions('category', true)
      console.log('子类目 Options:', options)
      console.log('子类目原始数据:', rawOptions)
    }
  }),
}
```

### tree-select（树选择）

树形数据选择，支持 **直接传入 treeData** 或 **api + params** 远程拉取；**treeData 优先**，仅当未传 treeData（或为空数组）时才请求 api。

- **treeData**：树形数组，直接传入时使用本地数据，不请求 api；可与 `labelField` / `valueField` / `childrenField` 配合使用。
- **api**：`(params?) => Promise<树数据>`，在未提供 treeData 时拉取树数据；返回值为树形数组，每项含 `label`、`value`、`children`（字段名可通过 `labelField` / `valueField` / `childrenField` 配置）。
- **params**：请求参数，变化时重新拉取（仅在使用 api 时生效）。
- **filterable**：为 true 时下拉内显示搜索框，按关键字过滤树节点（前端过滤）。
- **lazy**：为 true 时在展开下拉时再请求数据（仅在使用 api 时生效）。

仅用 treeData 示例：

```ts
{
  field: 'deptId',
  label: '部门',
  component: 'tree-select',
  componentProps: {
    treeData: [
      { label: '总部', value: 'hq', children: [
        { label: '技术部', value: 'hq-tech' },
        { label: '产品部', value: 'hq-product' },
      ]},
      { label: '华东区', value: 'east', children: [
        { label: '上海分公司', value: 'east-sh' },
      ]},
    ],
    labelField: 'label',
    valueField: 'value',
    childrenField: 'children',
    filterable: true,
    placeholder: '请选择部门',
    clearable: true,
  },
}
```

使用 api 示例：

```ts
{
  field: 'deptId',
  label: '部门',
  component: 'tree-select',
  componentProps: {
    api: () => api.getDeptTree(),
    params: { type: 1 },
    labelField: 'name',
    valueField: 'id',
    childrenField: 'children',
    filterable: true,
    placeholder: '请选择部门',
    clearable: true,
  },
}
```

### 函数形式

传入函数可获取表单上下文，适用于动态配置或联动逻辑。参数类型：

| 参数 | 类型 | 说明 |
|------|------|------|
| schema | `ProFormSchema` | 当前表单项的 schema |
| values | `Record<string, unknown>` | 表单数据（与 model 相同引用） |
| model | `Record<string, unknown>` | 表单数据，可直接修改实现联动 |
| field | `string` | 当前字段名 |
| formActionType | `FormActionType` | 表单操作 API |

```ts
{
  field: 'name',
  component: 'input',
  componentProps: ({ values, model, formActionType }) => ({
    // 根据其他字段动态配置
    disabled: values.status === 0,
    // 或使用 formActionType 编程式操作
    onChange: (val: string) => {
      formActionType.setFieldsValue({ otherField: val + '_suffix' })
    },
  }),
}
```

### 事件监听

以 `on` 开头的属性会被识别为事件监听器，通过 `v-on` 绑定到组件。支持 `onChange`、`onInput`、`onBlur` 等，**不区分大小写**（`onchange` 与 `onChange` 均可）。

```ts
{
  field: 'remark',
  component: 'input',
  componentProps: ({ formActionType }) => ({
    onChange: (value: string) => {
      formActionType.setFieldsValue({ phone: value })
    },
    onInput: (value: string) => {
      console.log('输入中', value)
    },
  }),
}
```

### 各组件常用 props

| 组件 | 常用 props | 说明 |
|------|------------|------|
| input | `maxlength`, `clearable`, `showPassword`, `prefixIcon`, `suffixIcon` | 参考 [el-input](https://element.eleme.cn/#/zh-CN/component/input) |
| select | `options` | `{ label, value }[]`，必填 |
| api-select | `api`, `params`, `lazy`, `labelField`, `valueField`, `filterable` | `api` 可接收 `params`；`params` 变化时重新拉取；`lazy: true` 为懒加载；`filterable: true` 开启下拉内关键字搜索（前端过滤已加载选项），见上文 |
| tree-select | `treeData`, `api`, `params`, `lazy`, `labelField`, `valueField`, `childrenField`, `filterable` | 树选择；**treeData 优先**，未传时用 api/params 拉取；filterable 搜索树节点，见上文 |
| input-number | `min`, `max`, `step`, `precision`, `controls` | 参考 [el-input-number](https://element.eleme.cn/#/zh-CN/component/input-number) |
| formatted-number | 在 **`componentProps`** 中传入：`integerDigits`、`decimalPlaces`、`rounding`、`inputLimit`，以及 `clearable`、`placeholder` 等同 el-input | 仅数字输入；失焦时按整数位数上限与小数位舍入后格式化为千分位；`integerDigits` 默认 6，`decimalPlaces` 默认 6，`rounding` 为 `floor` \| `ceil` \| `round`（默认 `round`）；`inputLimit` 默认 `true`，为 `true` 时输入过程中限制整数位不超过 `integerDigits`、小数位不超过 `decimalPlaces`；表单值为**固定小数位的字符串**（如 `12.340000`，以保留配置的小数位；`number` 无法保留尾随 0），清空为 `undefined` |
| date-picker | `value-format`, `type`, `format` | 参考 [el-date-picker](https://element.eleme.cn/#/zh-CN/component/date-picker) |
| switch | `activeText`, `inactiveText`, `activeValue`, `inactiveValue` | 参考 [el-switch](https://element.eleme.cn/#/zh-CN/component/switch) |
| cascader | `options`, `props` | 参考 [el-cascader](https://element.eleme.cn/#/zh-CN/component/cascader) |
| checkbox / radio | `options` | `{ label, value }[]`，必填 |

### fieldMapToTime

将日期范围映射为两个字段：

```ts
useForm({
  schemas: [
    { field: 'dateRange', component: 'date-range', label: '日期范围' },
  ],
  fieldMapToTime: [
    ['dateRange', ['startTime', 'endTime']],
  ],
})
// getFieldsValue() => { startTime, endTime }
```

### formListeners

透传事件监听器到 el-form：

```vue
<ProForm
  :schemas="schemas"
  :form-listeners="{
    validate: (valid) => console.log('validate', valid),
    'validate-field': (prop, valid, msg) => {},
  }"
  @submit="handleSubmit"
/>
```

## FormActions 组件

提交/重置/展开按钮组已抽离为独立组件，可单独使用。提交、重置按钮默认带图标。

| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| submitButtonText | `string` | `'提交'` | 提交按钮文字 |
| resetButtonText | `string` | `'重置'` | 重置按钮文字 |
| submitButtonIcon | `string` | `'el-icon-search'` | 提交按钮图标 |
| resetButtonIcon | `string` | `'el-icon-refresh-left'` | 重置按钮图标 |
| submitLoading | `boolean` | `false` | 提交中 loading 状态 |

```vue
<template>
  <FormActions
    :submit-loading="loading"
    submit-button-text="查询"
    reset-button-text="重置"
    submit-button-icon="el-icon-search"
    reset-button-icon="el-icon-refresh-left"
    @submit="handleSubmit"
    @reset="handleReset"
  />
</template>

<script setup lang="ts">
import { FormActions } from 'element-component-pro'
</script>
```
