npm package: @jack-ui/j-form
Description: Schema-driven React form package built on Ant Design with runtime API, search form, async options, and modal/drawer submit pages.
Latest version: 0.1.3

# @jack-ui/j-form

[![npm version](https://img.shields.io/npm/v/%40jack-ui%2Fj-form?color=0f766e&label=npm)](https://www.npmjs.com/package/@jack-ui/j-form)
[![downloads](https://img.shields.io/npm/dm/%40jack-ui%2Fj-form?color=1677ff&label=downloads)](https://www.npmjs.com/package/@jack-ui/j-form)
[![React](https://img.shields.io/badge/React-19.x-149eca)](https://react.dev/)
[![Ant Design](https://img.shields.io/badge/Ant%20Design-6.x-1677ff)](https://ant.design/)

[English](https://github.com/jackBoVip/jack-ui/blob/master/packages/j-form/README.md)

面向 Ant Design 的 schema 驱动 React 表单系统，统一覆盖：

- 通用业务表单
- 查询 / 搜索区表单
- 异步选项字段
- 弹窗与抽屉提交页

## 为什么是 JForm

很多表单体系最终会拆成几块互相割裂的能力：

- 常规表单一套渲染方式
- 查询表单再包一层
- 命令式控制另起一套 API
- 弹窗 / 抽屉表单再独立实现一次

`@jack-ui/j-form` 的目标是把这些表面能力压在同一套运行时模型上。

## 产品能力面

| 能力面 | 最适合解决的问题 |
| --- | --- |
| `JForm` | 通用 schema 驱动业务表单 |
| `JSearchForm` | 查询区、折叠区、内联操作区、横向 label |
| `createJFormApi()` / `useJForm()` | 运行时编排、schema 变更、校验、重置、订阅 |
| `optionsSource` | 防抖、缓存、远程选项加载 |
| `submit-page` | 多步、默认底栏动作、service 打开的弹窗 / 抽屉提交页 |

## 30 秒开始

### 安装

```bash
pnpm add @jack-ui/j-form antd @ant-design/icons react react-dom
```

### 导入样式

```ts
import '@jack-ui/j-form/styles.css';
import '@jack-ui/j-form/submit-page/styles.css'; // 只有使用 submit-page 时才需要
```

### 渲染一个表单

```tsx
import '@jack-ui/j-form/styles.css';
import { JForm } from '@jack-ui/j-form';

type ProjectValues = {
  name?: string;
  status?: string;
  budget?: number;
};

export function ProjectForm() {
  return (
    <JForm<ProjectValues>
      gridColumns={2}
      layout="vertical"
      onSubmit={async (values) => {
        console.log(values);
      }}
      schema={[
        { name: 'name', label: '项目名称', required: true },
        {
          name: 'status',
          kind: 'select',
          label: '状态',
          options: [
            { label: '草稿', value: 'draft' },
            { label: '已审批', value: 'approved' },
          ],
        },
        { name: 'budget', kind: 'number', label: '预算' },
      ]}
    />
  );
}
```

## 导入入口

核心表单运行时：

```ts
import {
  JForm,
  JSearchForm,
  createJFormApi,
  useJForm,
  useJFormSelector,
} from '@jack-ui/j-form';
```

提交页运行时：

```ts
import {
  JFormSubmitPage,
  useJFormSubmitPage,
} from '@jack-ui/j-form/submit-page';

import {
  JFormSubmitPageProvider,
  useJFormSubmitPageService,
} from '@jack-ui/j-form/submit-page/service';
```

说明：

- `submit-page` 运行时代码通过子路径拆分。
- `submit-page` 的 TypeScript 类型仍然从 `@jack-ui/j-form` 根入口导出。
- 样式不会由 JS 自动注入，必须显式导入。

## 该选哪一层能力

### `JForm`

通用录入表单使用 `JForm`。

```tsx
<JForm
  gridColumns={3}
  schema={[
    { name: 'name', label: '名称', required: true },
    { name: 'owner', label: '负责人' },
    { name: 'summary', kind: 'textarea', label: '说明', span: 24 },
  ]}
/>
```

### `JSearchForm`

查询区场景使用 `JSearchForm`。

它在 `JForm` 基础上额外提供：

- 固定查询态
- 默认横向布局
- 折叠 / 展开能力
- 最后一行末尾的内联操作区
- 隐藏校验字段需要暴露时自动展开

```tsx
<JSearchForm
  collapsedRows={1}
  gridColumns={4}
  schema={[
    { name: 'keyword', label: '关键字' },
    { name: 'status', kind: 'select', label: '状态' },
    { name: 'city', kind: 'select', label: '城市' },
    { name: 'owner', label: '负责人', required: true },
  ]}
/>
```

### `submit-page`

当表单放在弹窗或抽屉里时，使用 `JFormSubmitPage`。

```tsx
import '@jack-ui/j-form/submit-page/styles.css';
import { JFormSubmitPage } from '@jack-ui/j-form/submit-page';

<JFormSubmitPage
  defaultOpen
  mode="drawer"
  drawer={{ width: '60%' }}
  schema={[
    { name: 'name', label: '名称', required: true },
    { name: 'summary', kind: 'textarea', label: '说明' },
  ]}
  title="新建项目"
/>;
```

## 运行时控制

### `useJForm()`

在 React 里，如果你需要稳定的 `formApi`，使用它。

```tsx
import { JForm, useJForm } from '@jack-ui/j-form';

export function ControlledForm() {
  const formApi = useJForm({
    values: {
      status: 'draft',
    },
  });

  return (
    <JForm
      formApi={formApi}
      schema={[{ name: 'status', label: '状态' }]}
    />
  );
}
```

### `createJFormApi()`

如果你需要 React 外部的独立运行时控制器，使用它。

```ts
import { createJFormApi } from '@jack-ui/j-form';

const formApi = createJFormApi({
  schema: [{ name: 'name', label: '名称' }],
  values: { name: '初始值' },
});

formApi.setFieldValue('name', '更新后的值');
console.log(formApi.getValues());
```

### `JFormApi` 重点方法

状态和值：

- `setValues(values)`
- `replaceValues(values)`
- `setFieldValue(name, value)`
- `getValues()`
- `getFieldValue(name)`
- `getSnapshot()`
- `subscribe(listener)`

Schema：

- `setSchema(schema)`
- `getSchema()`
- `updateSchema(name, updater)`
- `appendSchemaByField(name, fields)`
- `removeSchemaByField(name)`
- `removeSchemaByFields(names)`
- `setVisibleFields(names)`

校验与提交：

- `validate(names?)`
- `validateResult(names?)`
- `validateField(name)`
- `validateFieldResult(name)`
- `isFieldValid(name)`
- `clearErrors(names?)`
- `clearValidate(names?)`
- `reset()`
- `resetForm(values?)`
- `submit()`
- `submitForm()`
- `validateAndSubmitForm()`

事件：

- `submit`
- `reset`
- `lazy-options-start`
- `lazy-options-success`
- `lazy-options-error`
- `lazy-options-end`

## Schema 模型

### 常用字段属性

| 属性 | 说明 |
| --- | --- |
| `name` | 字段名，支持点路径，例如 `user.name` |
| `kind` | 内建字段类型 |
| `label` | 表单项标题 |
| `required` | 是否必填 |
| `rules` | Ant Design 校验规则 |
| `defaultValue` | 字段加入时若当前值缺失则应用默认值 |
| `span` | 手动覆盖字段宽度 |
| `width` | 控件显式宽度 |
| `options` | 静态选项 |
| `optionsSource` | 异步选项配置 |
| `dependsOn` | 依赖驱动 hidden / disabled / required / rules / componentProps |
| `component` | 自定义字段组件 |
| `render` | 自定义字段渲染函数 |
| `componentProps` | 透传给控件的 props |
| `itemProps` | 透传给 `Form.Item` 的 props |
| `normalize` | 值标准化钩子 |

### 内建字段类型

- `input`
- `password`
- `textarea`
- `select`
- `date`
- `datetime`
- `daterange`
- `datetimerange`
- `month`
- `year`
- `time`
- `timerange`
- `number`
- `switch`
- `radio`
- `radio-button`
- `checkbox`
- `custom`

### 依赖联动字段

依赖联动可以推导：

- `hidden`
- `disabled`
- `required`
- `rules`
- `componentProps`

```tsx
{
  name: 'reviewer',
  label: '审批人',
  dependsOn: {
    watch: ['status'],
    hidden: ({ values }) => values.status !== 'approved',
    required: ({ values }) => values.status === 'approved',
  },
}
```

### 自定义字段上下文

自定义 `component` / `render` 会拿到：

- `api`
- `field`
- `values`
- `value`
- `setValue(value, { rawData })`
- `options`
- `loading`
- `disabled`
- `onSearch(keyword)`
- `getPopupContainer()`

## 异步选项

### `optionsSource`

支持字段：

- `triggers`
- `debounceMs`
- `cache`
- `cacheMaxEntries`
- `cacheTtlMs`
- `cacheKey`
- `minKeywordLength`
- `preserveOnError`
- `clearBeforeLoad`
- `once`
- `mapOptions`
- `load(context)`

```tsx
{
  name: 'city',
  kind: 'select',
  label: '城市',
  optionsSource: {
    triggers: ['search'],
    debounceMs: 300,
    cache: true,
    minKeywordLength: 2,
    async load({ keyword, signal }) {
      const response = await fetch(`/api/cities?q=${keyword}`, { signal });
      const data = await response.json();
      return {
        options: data.items,
        raw: data,
      };
    },
  },
}
```

### 远程 helper

根入口提供这些 helper：

- `createJFormOptionsSource()`
- `createJFormRemoteOptionsSource()`
- `createJFormRemoteSearchParams()`
- `mapJFormRemoteOptionList()`
- `mapJFormTreeOptions()`
- `resolveJFormOptionsArray()`

```tsx
import { createJFormRemoteOptionsSource } from '@jack-ui/j-form';

const cityOptionsSource = createJFormRemoteOptionsSource({
  cache: true,
  trigger: 'search',
  async request({ keyword, signal }) {
    const response = await fetch(`/api/cities?q=${keyword}`, { signal });
    return response.json();
  },
});
```

## Submit-page 子系统

`submit-page` 是一个统一抽象，可以渲染成弹窗或抽屉。

### 声明式

```tsx
import { JFormSubmitPage } from '@jack-ui/j-form/submit-page';

<JFormSubmitPage
  defaultOpen
  mode="modal"
  schema={[{ name: 'name', label: '名称', required: true }]}
  title="新建项目"
/>;
```

### Hook

```tsx
import { Button } from 'antd';
import { useJFormSubmitPage } from '@jack-ui/j-form/submit-page';

export function HookDemo() {
  const { controller, node } = useJFormSubmitPage({
    schema: [{ name: 'name', label: '名称', required: true }],
    title: 'Hook 提交页',
  });

  return (
    <>
      <Button onClick={() => controller.open()}>打开</Button>
      {node}
    </>
  );
}
```

### Service

```tsx
import { Button } from 'antd';
import {
  JFormSubmitPageProvider,
  useJFormSubmitPageService,
} from '@jack-ui/j-form/submit-page/service';

function Trigger() {
  const service = useJFormSubmitPageService();

  return (
    <Button
      onClick={() => {
        const handle = service.open({
          mode: 'drawer',
          drawer: { width: '60%' },
          schema: [{ name: 'name', label: '名称', required: true }],
          title: 'Service 提交页',
        });

        void handle.closed.then((result) => {
          console.log(result.reason, result.values);
        });
      }}
    >
      打开
    </Button>
  );
}

export function ServiceDemo() {
  return (
    <JFormSubmitPageProvider>
      <Trigger />
    </JFormSubmitPageProvider>
  );
}
```

### Submit-page API 重点

主 props：

- `mode`
- `schema`
- `steps`
- `formApi`
- `open` / `defaultOpen`
- `title`
- `header`
- `footer`
- `showStepHeader`
- `stepTransitionMs`
- `resetOnClose`
- `resetOnSubmit`
- `beforeClose`
- `onCancel`
- `onOpenChange`
- `onStepChange`
- `onSubmitSuccess`
- `modal`
- `drawer`

Footer 行为：

- 默认底栏按钮会自动构建
- `footer.actions` 默认是追加
- 设置 `footer.showDefaultActions = false` 可以关闭默认按钮
- 内建 action 支持 `text: null` 的图标按钮形式

Controller API：

- `open()`
- `close(reason?)`
- `toggle(open?)`
- `setOpen(open, reason?)`
- `getOpen()`
- `getStep()`
- `getTotalSteps()`
- `goToStep(step)`
- `next()`
- `prev()`
- `getFormApi()`

Service handle API：

- `id`
- `controller`
- `formApi`
- `update(partialOptions)`
- `close(reason?)`
- `closed`

## 国际化与主题

### 文案覆盖

```tsx
import { JFormConfigProvider } from '@jack-ui/j-form';

const localeText = {
  submit: '保存',
  reset: '清空',
  query: '查询',
  expand: '展开',
  collapse: '收起',
};

export function App() {
  return (
    <JFormConfigProvider localeText={localeText}>
      {/* app */}
    </JFormConfigProvider>
  );
}
```

### 主题 token

`j-form` 会跟随外层 Ant Design token。需要在自定义组件中读取时，可以使用 `useJFormTokens()`。

## CSS 策略

- `@jack-ui/j-form/styles.css`：`JForm` 和 `JSearchForm` 基础样式
- `@jack-ui/j-form/submit-page/styles.css`：submit-page 样式
- 根运行时入口没有隐式样式副作用

## 相关链接

- npm: [@jack-ui/j-form](https://www.npmjs.com/package/@jack-ui/j-form)
- GitHub 仓库: [jackBoVip/jack-ui](https://github.com/jackBoVip/jack-ui)
- 英文文档: [README.md](https://github.com/jackBoVip/jack-ui/blob/master/packages/j-form/README.md)