# Forms 模块文档

## 📋 概述

Forms 模块是 FF AI Workspace 平台的动态表单引擎前端实现，基于 JSON Schema 标准，支持动态渲染、验证和提交各类业务表单。

## 🏗️ 模块结构

```
forms/
├── FormRenderer.tsx           # 表单渲染器（主组件）
├── FormFieldRenderer.tsx      # 字段渲染器（子组件）
├── designer/                  # 表单设计器
│   ├── FormDesigner.tsx      # 设计器主组件
│   ├── FieldPalette.tsx      # 字段拖拽面板
│   ├── DesignCanvas.tsx      # 设计画布
│   ├── PropertyPanel.tsx     # 属性配置面板
│   ├── types.ts              # 设计器类型定义
│   └── useDesignerStore.ts   # 设计器状态管理
├── index.ts                   # 统一导出
└── README.md                  # 本文档
```

## 🎯 核心组件

### 1. FormRenderer - 表单渲染器

完整的表单渲染组件，负责整体布局、验证和提交。

#### 基础用法

```tsx
import { FormRenderer } from '@/components/forms';

function MyFormPage() {
  const handleSubmit = async (formData: Record<string, unknown>) => {
    console.log('提交数据:', formData);
    // 调用 API 提交数据
  };

  return (
    <FormRenderer
      schema={jsonSchema}
      uiSchema={uiConfig}
      onSubmit={handleSubmit}
    />
  );
}
```

#### 完整示例

```tsx
import { FormRenderer } from '@/components/forms';
import type { JSONSchema, UISchema, SubmitterInfo } from '@/components/forms';

function ExpenseFormPage() {
  const [formData, setFormData] = useState({});

  const schema: JSONSchema = {
    type: 'object',
    title: '报销申请表',
    description: '请填写报销相关信息',
    required: ['amount', 'category', 'description'],
    properties: {
      amount: {
        type: 'number',
        title: '报销金额',
        minimum: 0,
        maximum: 50000,
      },
      category: {
        type: 'string',
        title: '报销类别',
        enum: ['交通费', '餐饮费', '住宿费', '办公用品', '其他'],
      },
      description: {
        type: 'string',
        title: '费用说明',
        minLength: 10,
        maxLength: 500,
      },
      date: {
        type: 'string',
        title: '费用日期',
        format: 'date',
      },
    },
  };

  const uiSchema: Record<string, UISchema> = {
    description: {
      'ui:widget': 'textarea',
      'ui:placeholder': '请详细说明费用用途',
      'ui:help': '至少填写10个字符',
    },
  };

  const submitter: SubmitterInfo = {
    id: 'user-001',
    name: '张三',
    departments: [
      { id: 'dept-01', name: '技术部' },
      { id: 'dept-02', name: '产品部' },
    ],
  };

  const handleSubmit = async (data: Record<string, unknown>, deptId?: string) => {
    console.log('提交数据:', data);
    console.log('选择部门:', deptId);
    // 调用 API
  };

  const handleSave = async (data: Record<string, unknown>) => {
    console.log('保存草稿:', data);
    // 保存草稿到本地或服务器
  };

  return (
    <div className="max-w-2xl mx-auto p-6">
      <FormRenderer
        schema={schema}
        uiSchema={uiSchema}
        formData={formData}
        onChange={setFormData}
        onSubmit={handleSubmit}
        onSave={handleSave}
        submitter={submitter}
        locale="zh-CN"
      />
    </div>
  );
}
```

#### Props 属性

| 属性 | 类型 | 必填 | 默认值 | 说明 |
|------|------|------|--------|------|
| `schema` | `JSONSchema` | ✅ | - | JSON Schema 表单定义 |
| `uiSchema` | `Record<string, UISchema>` | ❌ | `{}` | UI Schema 配置 |
| `formData` | `Record<string, unknown>` | ❌ | `{}` | 表单数据 |
| `onChange` | `(data) => void` | ❌ | - | 数据变化回调 |
| `onSubmit` | `(data, deptId?) => Promise<void>` | ❌ | - | 提交回调 |
| `onSave` | `(data) => Promise<void>` | ❌ | - | 保存草稿回调 |
| `disabled` | `boolean` | ❌ | `false` | 是否禁用整个表单 |
| `showSaveButton` | `boolean` | ❌ | `true` | 是否显示保存按钮 |
| `showSubmitButton` | `boolean` | ❌ | `true` | 是否显示提交按钮 |
| `submitButtonText` | `string` | ❌ | `'提交'` | 提交按钮文本 |
| `saveButtonText` | `string` | ❌ | `'保存草稿'` | 保存按钮文本 |
| `locale` | `string` | ❌ | `'zh-CN'` | 语言设置 |
| `submitter` | `SubmitterInfo` | ❌ | - | 提交者信息 |
| `showSubmitterInfo` | `boolean` | ❌ | `true` | 是否显示提交者信息 |

### 2. FormFieldRenderer - 字段渲染器

单个字段的渲染组件，通常不需要直接使用（由 FormRenderer 自动调用）。

```tsx
import { FormFieldRenderer } from '@/components/forms';

<FormFieldRenderer
  name="email"
  schema={{ type: 'string', format: 'email', title: '邮箱' }}
  uiSchema={{ 'ui:placeholder': '请输入邮箱' }}
  value={formData.email}
  onChange={handleFieldChange}
  required={true}
  errors={errors}
/>
```

## 📝 JSON Schema 规范

### 支持的字段类型

#### 1. String 字符串

```typescript
{
  type: 'string',
  title: '字段标题',
  description: '字段描述',
  default: '默认值',
  minLength: 5,
  maxLength: 100,
  pattern: '^[A-Za-z0-9]+$',  // 正则验证
  format: 'email',             // email | date | time | date-time
  enum: ['选项1', '选项2'],     // 枚举值（下拉选择）
}
```

**Widget 类型**：
- `text` - 默认文本输入（default）
- `textarea` - 多行文本
- `email` - 邮箱输入
- `password` - 密码输入
- `date` - 日期选择
- `time` - 时间选择
- `datetime` - 日期时间选择

#### 2. Number / Integer 数字

```typescript
{
  type: 'number',  // 或 'integer'
  title: '数量',
  default: 0,
  minimum: 0,
  maximum: 100,
}
```

#### 3. Boolean 布尔值

```typescript
{
  type: 'boolean',
  title: '是否同意',
  default: false,
}
```

**Widget 类型**：
- `checkbox` - 复选框（default）
- `switch` - 开关

#### 4. Object 对象

##### 4.1 分组字段

```typescript
{
  type: 'object',
  'x-type': 'group',
  'x-icon': '📝',
  title: '基本信息',
  properties: {
    name: { type: 'string', title: '姓名' },
    age: { type: 'integer', title: '年龄' },
  },
  required: ['name'],
}
```

##### 4.2 布局容器（多列）

```typescript
{
  type: 'object',
  'x-type': 'container',
  'x-layout': {
    ratios: [1, 2],  // 列宽比例
    gap: 16,         // 列间距（px）
  },
  properties: {
    field1: { type: 'string', title: '字段1', 'x-column': 0 },
    field2: { type: 'string', title: '字段2', 'x-column': 1 },
  },
}
```

##### 4.3 金额字段

```typescript
{
  type: 'object',
  'ui:widget': 'currency',
  title: '金额',
  'ui:options': {
    currencyType: 'CNY',              // 默认货币
    showCurrencySelect: true,          // 是否显示货币选择
    allowedCurrencies: ['CNY', 'USD'], // 可选货币
    precision: 2,                      // 小数位数
  },
}
```

#### 5. Array 数组（子表单）

```typescript
{
  type: 'array',
  title: '费用明细',
  minItems: 1,     // 最少行数
  maxItems: 10,    // 最多行数
  items: {
    type: 'object',
    'x-column-order': ['date', 'category', 'amount'],  // 列顺序
    properties: {
      date: {
        type: 'string',
        format: 'date',
        title: '日期',
        'x-placeholder': '选择日期',
      },
      category: {
        type: 'string',
        title: '类别',
        enum: ['餐饮', '交通', '住宿'],
        enumNames: ['餐饮费', '交通费', '住宿费'],
      },
      amount: {
        type: 'number',
        title: '金额',
        minimum: 0,
      },
    },
    required: ['date', 'amount'],
  },
}
```

### UI Schema 配置

UI Schema 用于控制字段的渲染方式和交互行为：

```typescript
const uiSchema: Record<string, UISchema> = {
  fieldName: {
    'ui:widget': 'textarea',               // Widget 类型
    'ui:placeholder': '请输入内容',        // 占位符
    'ui:help': '这是帮助文本',             // 帮助文本
    'ui:disabled': false,                  // 是否禁用
    'ui:readonly': false,                  // 是否只读
    'ui:options': {                        // 自定义选项
      rows: 6,                             // textarea 行数
      label: '开关文本',                   // switch 标签
    },
  },
  
  // 字段显示顺序
  'ui:order': ['field1', 'field2', 'field3'],
};
```

## 🎨 表单设计器

表单设计器用于可视化设计表单结构。

### 使用方式

```tsx
import { FormDesigner } from '@/components/forms/designer';

function FormDesignerPage() {
  const [schema, setSchema] = useState<JSONSchema>({
    type: 'object',
    properties: {},
  });

  const handleSave = async (schema: JSONSchema) => {
    console.log('保存表单定义:', schema);
    // 调用 API 保存
  };

  return (
    <FormDesigner
      initialSchema={schema}
      onSave={handleSave}
    />
  );
}
```

### 设计器功能

- ✅ **字段拖拽** - 从左侧面板拖拽字段到画布
- ✅ **属性配置** - 右侧面板配置字段属性
- ✅ **实时预览** - 实时查看表单渲染效果
- ✅ **分组管理** - 支持创建字段分组
- ✅ **布局容器** - 支持多列布局
- ✅ **子表单** - 支持添加明细表
- ✅ **JSON 导入导出** - 支持 JSON 格式导入导出

## 🔄 与后端集成

### API 数据流

```
┌─────────────┐         ┌──────────────┐         ┌─────────────┐
│   前端表单   │ ────▶  │  表单引擎API  │ ────▶  │  审批流程   │
│ FormRenderer │  提交   │ /api/v1/form │  触发   │   Temporal  │
└─────────────┘         └──────────────┘         └─────────────┘
       │                       │
       │                       │
       ▼                       ▼
  JSON Schema            业务数据验证
  UI Schema              存储到数据库
```

### 提交数据格式

```typescript
// 前端提交
{
  formData: {
    field1: 'value1',
    field2: 123,
    subTable: [
      { col1: 'a', col2: 100 },
      { col1: 'b', col2: 200 },
    ],
  },
  submitterDepartmentId: 'dept-001',
}

// 后端接收（已通过 apiClient 处理）
// 详见: frontend/src/lib/api-client.ts
```

### 后端表单定义获取

```typescript
import apiClient from '@/lib/api-client';

// 获取表单定义
const formDefinition = await apiClient.get(`/forms/definitions/${formKey}`);

// 渲染表单
<FormRenderer
  schema={formDefinition.jsonSchema}
  uiSchema={formDefinition.uiSchema}
  onSubmit={handleSubmit}
/>
```

## 🎯 最佳实践

### 1. 表单验证

```typescript
// ✅ 使用 JSON Schema 内置验证
{
  type: 'string',
  minLength: 5,
  maxLength: 50,
  pattern: '^[A-Z]',
  format: 'email',
}

// ⚠️ 避免在 UI 层做复杂验证
// 应该在 schema 中定义验证规则
```

### 2. 默认值处理

```typescript
// ✅ 在 schema 中定义默认值
{
  type: 'string',
  title: '状态',
  default: 'pending',
  enum: ['pending', 'approved', 'rejected'],
}

// ⚠️ 避免在 formData 中硬编码默认值
```

### 3. 子表单最佳实践

```typescript
// ✅ 设置合理的 minItems 和 maxItems
{
  type: 'array',
  minItems: 1,      // 至少1行
  maxItems: 20,     // 最多20行
  items: { ... },
}

// ✅ 使用 x-column-order 控制列顺序
{
  'x-column-order': ['date', 'category', 'amount', 'remark'],
}

// ✅ 为子表单字段添加 placeholder
{
  'x-placeholder': '请输入',
}
```

### 4. 性能优化

```typescript
// ✅ 使用 React.memo 包装大型表单
const MemoizedFormRenderer = React.memo(FormRenderer);

// ✅ 避免在 onChange 中做耗时操作
const handleChange = useMemo(() => debounce((data) => {
  // 处理数据变化
}, 300), []);

// ✅ 按需加载表单设计器
const FormDesigner = dynamic(() => 
  import('@/components/forms/designer'), 
  { ssr: false }
);
```

## 📚 相关文档

- [后端表单引擎文档](../../../../../docs/modules/form-engine/README.md)
- [后端审批引擎文档](../../../../../docs/modules/approval-engine/README.md)
- [后端规范入口](../../../../../docs/standards/05-backend/README.md)
- [前端架构设计](../../../../../docs/standards/02-architecture/frontend-architecture.md)

## 🐛 常见问题

### 1. 表单数据没有更新？

确保传递了 `onChange` 回调并更新了父组件的状态：

```typescript
const [formData, setFormData] = useState({});

<FormRenderer
  formData={formData}
  onChange={setFormData}  // ✅ 必须提供
  {...otherProps}
/>
```

### 2. 验证错误不显示？

检查 `required` 数组和字段约束是否正确设置：

```typescript
{
  type: 'object',
  required: ['email', 'name'],  // ✅ 必填字段列表
  properties: {
    email: {
      type: 'string',
      format: 'email',  // ✅ 格式验证
    },
  },
}
```

### 3. 子表单不显示？

确保 `items.type` 为 `'object'` 并且定义了 `properties`：

```typescript
{
  type: 'array',
  items: {
    type: 'object',  // ✅ 必须是 object
    properties: {    // ✅ 必须有 properties
      field1: { ... },
    },
  },
}
```

## 🔗 相关链接

- [JSON Schema 规范](https://json-schema.org/)
- [React Hook Form](https://react-hook-form.com/)
- [Zod 验证库](https://zod.dev/)

---

**版本**: v1.0  
**最后更新**: 2025-12-21  
**维护者**: FF AI Workspace 前端团队
