{"_id":"@chengkejie/form-page","_rev":"3-0beea698ea7a4f44c8a8932f8da0a139","name":"@chengkejie/form-page","dist-tags":{"latest":"1.1.0"},"versions":{"1.0.1":{"name":"@chengkejie/form-page","version":"1.0.1","keywords":["react","form","table","pagination","fusion","alifd"],"license":"MIT","_id":"@chengkejie/form-page@1.0.1","maintainers":[{"name":"chengkejie","email":"1051264090@qq.com"}],"dist":{"shasum":"fa5b928c30f9d6b2e8cfcc08abaf8b2c4dae00f5","tarball":"https://registry.npmjs.org/@chengkejie/form-page/-/form-page-1.0.1.tgz","fileCount":78,"integrity":"sha512-tsIUCsaPzc7R6JKlw2vPDGnkM5+AWtdDvPvW8I8bbi+EmPnir8tycOFNUOyXdgL+fwYhkfBgDOE6l2V9MviPPg==","signatures":[{"sig":"MEUCIQCyjCzU49qGHSO5bgASHFnn+BucrAqcfskd+5TborPJ0gIgAjFc6rmObvhriwVg3J3S2edEYqHC9B2glI9rscMvANM=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":234255},"main":"dist/cjs/index.js","types":"dist/esm/index.d.ts","module":"dist/esm/index.js","exports":{".":{"types":"./dist/esm/index.d.ts","import":"./dist/esm/index.js","require":"./dist/cjs/index.js"},"./dist/*.css":"./dist/esm/*.css"},"gitHead":"61d9eedf822c3259ad84628953c405102bb02079","scripts":{"dev":"father dev","lint":"eslint src --ext .ts,.tsx","build":"father build","prepublishOnly":"npm run build"},"_npmUser":{"name":"chengkejie","email":"1051264090@qq.com"},"repository":{"url":"https://gitlab.linkcld.com/aits/form-page-lib.git","type":"git"},"_npmVersion":"10.2.3","description":"A configurable Form + Table + Pagination component for React with Fusion Design","directories":{},"sideEffects":["*.scss","*.css"],"_nodeVersion":"18.19.0","dependencies":{"ahooks":"^3.0.0"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"devDependencies":{"sass":"^1.60.0","react":"^18.2.0","father":"^4.6.0","moment":"^2.28.0","react-dom":"^18.2.0","typescript":"^4.8.0","@alifd/next":"^1.26.8","@types/react":"^18.0.0","@types/react-dom":"^18.0.0"},"peerDependencies":{"react":">=16.9.0 <19.0.0","moment":">=2.24.0","react-dom":">=16.9.0 <19.0.0","@alifd/next":">=1.26.0"},"_npmOperationalInternal":{"tmp":"tmp/form-page_1.0.1_1786343329302_0.2685031771490469","host":"s3://npm-registry-packages-npm-production"}},"1.0.2":{"name":"@chengkejie/form-page","version":"1.0.2","keywords":["react","form","table","pagination","fusion","alifd"],"license":"MIT","_id":"@chengkejie/form-page@1.0.2","maintainers":[{"name":"chengkejie","email":"1051264090@qq.com"}],"dist":{"shasum":"0883ba09f02ea05fa1061e30494fe4660ad90440","tarball":"https://registry.npmjs.org/@chengkejie/form-page/-/form-page-1.0.2.tgz","fileCount":78,"integrity":"sha512-u+niOk5bBQu80CClgLdVyRLLbsUss1hRCAdzOchbhtSqJZ734MFkiBQgWrfhowG7e3izIwBawCiwcWA/NU1itw==","signatures":[{"sig":"MEYCIQCIglflE4L2+fHwql3mQcEbwY33e8gSlPUKfUPRgmsXIwIhAOxQ2tfy6h6FGhusHYkdNZpDL1P70r0Il1qtDLQat65+","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":242153},"main":"dist/cjs/index.js","types":"dist/esm/index.d.ts","module":"dist/esm/index.js","exports":{".":{"types":"./dist/esm/index.d.ts","import":"./dist/esm/index.js","require":"./dist/cjs/index.js"},"./dist/*.css":"./dist/esm/*.css"},"gitHead":"2bd1d8ed8db2699c49464bb5aa19c84ac728c2e6","scripts":{"dev":"father dev","lint":"eslint src --ext .ts,.tsx","build":"father build","prepublishOnly":"npm run build"},"_npmUser":{"name":"chengkejie","email":"1051264090@qq.com"},"repository":{"url":"https://gitlab.linkcld.com/aits/form-page-lib.git","type":"git"},"_npmVersion":"10.2.3","description":"A configurable Form + Table + Pagination component for React with Fusion Design","directories":{},"sideEffects":["*.scss","*.css"],"_nodeVersion":"18.19.0","dependencies":{"ahooks":"^3.0.0"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"devDependencies":{"sass":"^1.60.0","react":"^18.2.0","father":"^4.6.0","moment":"^2.28.0","react-dom":"^18.2.0","typescript":"^4.8.0","@alifd/next":"^1.26.8","@types/react":"^18.0.0","@types/react-dom":"^18.0.0"},"peerDependencies":{"react":">=16.9.0 <19.0.0","moment":">=2.24.0","react-dom":">=16.9.0 <19.0.0","@alifd/next":">=1.26.0"},"_npmOperationalInternal":{"tmp":"tmp/form-page_1.0.2_1786689616517_0.449470610940512","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@chengkejie/form-page","version":"1.1.0","description":"A configurable Form + Table + Pagination component for React with Fusion Design","main":"dist/cjs/index.js","module":"dist/esm/index.js","types":"dist/esm/index.d.ts","exports":{".":{"import":"./dist/esm/index.js","require":"./dist/cjs/index.js","types":"./dist/esm/index.d.ts"},"./dist/*.css":"./dist/esm/*.css"},"sideEffects":["*.scss","*.css"],"scripts":{"build":"father build","dev":"father dev","prepublishOnly":"npm run build","lint":"eslint src --ext .ts,.tsx"},"peerDependencies":{"react":">=16.9.0 <19.0.0","react-dom":">=16.9.0 <19.0.0","@alifd/next":">=1.26.0","moment":">=2.24.0"},"dependencies":{"ahooks":"^3.0.0"},"devDependencies":{"father":"^4.6.0","react":"^18.2.0","react-dom":"^18.2.0","@alifd/next":"^1.26.8","moment":"^2.28.0","@types/react":"^18.0.0","@types/react-dom":"^18.0.0","typescript":"^4.8.0","sass":"^1.60.0"},"publishConfig":{"registry":"https://registry.npmjs.org/","access":"public"},"license":"MIT","repository":{"type":"git","url":"https://gitlab.linkcld.com/aits/form-page-lib.git"},"keywords":["react","form","table","pagination","fusion","alifd"],"_id":"@chengkejie/form-page@1.1.0","gitHead":"2947806fd5a19c53e123caf14d819405dd0cd7d4","_nodeVersion":"18.19.0","_npmVersion":"10.2.3","dist":{"integrity":"sha512-ViscaNjtHhDzDCGboi3eSi2N+gh/sOjk+OcSeM/94VU+HDmgGxzAd+H3oH5Pe6e9NP07dXQgqEeAkydWfKtQjw==","shasum":"4c5d2c6737e5276a2bdef60b27bfffe5aa7c8278","tarball":"https://registry.npmjs.org/@chengkejie/form-page/-/form-page-1.1.0.tgz","fileCount":78,"unpackedSize":257861,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCvIHrIvJ6FtikBLwD35s6bey7P+JiVsXiI/LbvL5HJpwIgB60t/5zyZ2Oag0YN/WnB+pvdLNSCHnW1fUZ4X4IzbAk="}]},"_npmUser":{"name":"chengkejie","email":"1051264090@qq.com"},"directories":{},"maintainers":[{"name":"chengkejie","email":"1051264090@qq.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/form-page_1.1.0_1788513133228_0.8126790633634986"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-10T06:28:49.167Z","modified":"2026-09-04T09:12:13.496Z","1.0.1":"2026-08-10T06:28:49.458Z","1.0.2":"2026-08-14T06:40:16.678Z","1.1.0":"2026-09-04T09:12:13.355Z"},"license":"MIT","keywords":["react","form","table","pagination","fusion","alifd"],"repository":{"type":"git","url":"https://gitlab.linkcld.com/aits/form-page-lib.git"},"description":"A configurable Form + Table + Pagination component for React with Fusion Design","maintainers":[{"name":"chengkejie","email":"1051264090@qq.com"}],"readme":"# @chengkejie/form-page\r\n\r\n可配置的 React **搜索表单 + 表格 + 分页** 一体化组件，基于 Fusion Design (`@alifd/next`) 和 `ahooks`。\r\n\r\n> 本文档为完整 API 参考，供开发者和 AI 编码助手在调用本组件时查阅。所有 prop 名、类型、默认值均与源码 [`src/types.ts`](src/types.ts) / [`src/FormPage.tsx`](src/FormPage.tsx) 一致。\r\n\r\n---\r\n\r\n## 目录\r\n\r\n- [安装](#安装)\r\n- [快速开始](#快速开始)\r\n- [FormPage Props](#formpage-props)\r\n- [tableProps 表格配置](#tableprops-表格配置)\r\n- [columns 列配置](#columns-列配置)\r\n- [conditionProps 搜索条件配置](#conditionprops-搜索条件配置)\r\n- [buttonProps 按钮配置](#buttonprops-按钮配置)\r\n- [Ref 方法（FormPageRef）](#ref-方法formpageref)\r\n- [FormPageProvider 外部服务注入](#formpageprovider-外部服务注入)\r\n- [子组件（独立导出）](#子组件独立导出)\r\n- [完整示例](#完整示例)\r\n- [内部行为说明](#内部行为说明)\r\n- [开发 / 发布](#开发--发布)\r\n\r\n---\r\n\r\n## 安装\r\n\r\n```bash\r\nnpm i @chengkejie/form-page\r\n```\r\n\r\n并在入口处引入样式：\r\n\r\n```ts\r\nimport '@chengkejie/form-page/dist/esm/styles/index.module.scss';\r\n```\r\n\r\n---\r\n\r\n## 快速开始\r\n\r\n```tsx\r\nimport { FormPage, FormPageProvider, FormPageRef } from '@chengkejie/form-page';\r\nimport '@chengkejie/form-page/dist/esm/styles/index.module.scss';\r\n\r\n// 1) 查询接口：接收 (表单数据, 分页参数, 是否按钮触发)，返回 { data, total }\r\nasync function fetchList(data: any, params: any) {\r\n  const res = await request('/api/list', { data: { ...data, ...params } });\r\n  return { data: res.list, total: res.total };\r\n}\r\n\r\nfunction App() {\r\n  const ref = useRef<FormPageRef>(null);\r\n\r\n  return (\r\n    // 2) 用 Provider 注入列设置持久化 / 公司选择等外部服务（可选）\r\n    <FormPageProvider\r\n      config={{\r\n        fetchTableKeys: async (tableId) => [],\r\n        onSaveTableKeys: async (params) => {},\r\n      }}\r\n    >\r\n      {/* 3) 配置条件、列、按钮 */}\r\n      <FormPage\r\n        ref={ref}\r\n        searchFn={fetchList}\r\n        conditionProps={[\r\n          { type: 'input', name: 'keyword', label: '关键词', col: 6 },\r\n          { type: 'select', name: 'status', label: '状态', col: 6, options: [{ label: '启用', value: 1 }] },\r\n        ]}\r\n        tableProps={{\r\n          tableId: 'demo-table',\r\n          primaryKey: 'id',\r\n          columns: [\r\n            { title: '名称', dataIndex: 'name', width: 200, ellipsis: true },\r\n            { title: '状态', dataIndex: 'status', width: 120, ellipsis: true, lock: 'left' },\r\n          ],\r\n        }}\r\n        buttonProps={{ buttons: ['submit', 'reset', 'export', 'setting'] }}\r\n      />\r\n    </FormPageProvider>\r\n  );\r\n}\r\n```\r\n\r\n---\r\n\r\n## FormPage Props\r\n\r\n| Prop | 类型 | 默认值 | 说明 |\r\n|------|------|--------|------|\r\n| `searchFn` | `(data, params, fromButton) => Promise<{ data: any[]; total: number }>` | `() => {}` | **查询接口**。`data`=表单字段值，`params`=分页参数，`fromButton`=是否由按钮触发（区分搜索/重置与翻页静默刷新） |\r\n| `exportFn` | `(...args) => any` | `() => {}` | 导出接口，复用同一表单字段，点击「导出」按钮时触发 |\r\n| `rowSelectionFn` | `((keys, records) => void) \\| null` | `null` | 行选择回调；传 `null` 不启用行选择 |\r\n| `tableProps` | [`TableConfigProps`](#tableprops-表格配置) | `{}` | 表格配置（含 `tableId` / `columns` / `primaryKey` 等） |\r\n| `conditionProps` | [`ConditionItem[]`](#conditionprops-搜索条件配置) | `[]` | 搜索条件配置数组，每项的 `type` 决定渲染哪种控件 |\r\n| `buttonProps` | [`ButtonConfigProps`](#buttonprops-按钮配置) | `{}` | 操作按钮区配置 |\r\n| `extra` | `ReactNode` | — | 渲染在搜索条件下方、表格上方的额外自定义区域 |\r\n| `showPagination` | `boolean` | `true` | 是否显示分页器 |\r\n| `autoSearch` | `boolean` | `true` | 挂载后是否自动发起首次查询 |\r\n| `onReset` | `() => void` | `() => {}` | 重置按钮触发的回调（在内部清空逻辑之外额外执行） |\r\n| `onFieldChange` | `(name: string, value: any) => void` | `() => {}` | 任意表单字段值变化回调 |\r\n| `pageSizeList` | `number[]` | `[10, 20, 50, 100]` | 分页条数可选项 |\r\n| `alignLabelWidth` | `boolean` | `true` | 标签宽度自动对齐：同一竖列（24 栅格列起点相同）的标签统一取组内最长字数宽度并右对齐。详见 [标签宽度对齐](#标签宽度对齐alignlabelwidth)。传 `false` 退回默认 `75px` 最小宽度 |\r\n\r\n> `searchFn` 返回值需符合 `ahooks` `useFusionTable` 约定：`{ data: 表格数据数组, total: 总条数 }`。\r\n\r\n---\r\n\r\n## tableProps 表格配置\r\n\r\n`tableProps` 除了 `columns` 外，其余字段会透传给 Fusion 的 `Table.StickyLock`。\r\n\r\n| 字段 | 类型 | 默认值 | 说明 |\r\n|------|------|--------|------|\r\n| `tableId` | `string` | `''` | 表格唯一标识，用作列设置（显示/隐藏、列宽）的持久化 key |\r\n| `columns` | `any[]` | — | 列配置，见 [columns 列配置](#columns-列配置) |\r\n| `primaryKey` | `string` | `'key'` | 行数据的唯一字段名（行选择 / key 复用） |\r\n| `isTree` | `boolean` | `false` | 是否树形表格 |\r\n| `isZebra` | `boolean` | `false` | 是否斑马纹 |\r\n| `hasBorder` | `boolean` | `true` | 是否显示边框 |\r\n| `defaultPageSize` | `number` | `10` | 默认每页条数 |\r\n| `[key: string]` | `any` | — | 其它 Fusion `Table` props 透传（如 `sort`、`fixedHeader` 等） |\r\n\r\n---\r\n\r\n## columns 列配置\r\n\r\n每列 = Fusion `Table.Column` 的全部 props **+ 库扩展属性**。库会自动给每列加上 `asyncResizable: true`（异步可拖拽调宽），无需手动设置。\r\n\r\n### Fusion 常用 props\r\n\r\n| 字段 | 类型 | 说明 |\r\n|------|------|------|\r\n| `dataIndex` | `string` | 对应的数据字段名（支持 `a.b` 取值） |\r\n| `title` | `ReactNode` | 表头文本 |\r\n| `width` | `number \\| string` | 列宽。**锁列（`lock`）时必须为数值** |\r\n| `lock` | `'left' \\| 'right' \\| true` | 锁列 |\r\n| `cell` | `ReactNode \\| ((value, index, record) => ReactNode)` | 自定义单元格渲染 |\r\n| `sortable` | `boolean` | 是否可排序 |\r\n| `align` | `'left' \\| 'center' \\| 'right'` | 单元格对齐 |\r\n\r\n### 库扩展属性 ⭐\r\n\r\n| 字段 | 类型 | 说明 |\r\n|------|------|------|\r\n| `ellipsis` | `true` | **单元格省略号 + hover 气泡**。库自动把单元格内容包进 `Balloon`，触发器宽度取自本列 `width`（`width - 32`，32 为左右内边距）。锁列也不会断层。若同时配置了 `cell`，则包裹 `cell` 的输出。 |\r\n| `balloon` | `ReactNode` | 表头标题旁追加的提示节点（如一个带说明的问号气泡） |\r\n| `children` | `column[]` | 多级表头（渲染为 `Table.ColumnGroup`），递归支持 |\r\n\r\n### 列配置示例\r\n\r\n```tsx\r\ncolumns={[\r\n  // 1) 基础列 + 省略号气泡（自动用 width=200）\r\n  { title: '名称', dataIndex: 'name', width: 200, ellipsis: true },\r\n\r\n  // 2) 锁列 + 省略号气泡（width 必填，库内部已用 width 处理，不会断层）\r\n  { title: '状态', dataIndex: 'status', width: 120, ellipsis: true, lock: 'left' },\r\n\r\n  // 3) 省略号气泡 + 自定义 cell：气泡内展示 cell 的返回值\r\n  {\r\n    title: '金额', dataIndex: 'amount', width: 140, ellipsis: true,\r\n    cell: (v) => `¥${v?.toLocaleString()}`,\r\n  },\r\n\r\n  // 4) 自定义 cell（不使用省略号气泡）\r\n  { title: '操作', dataIndex: 'op', cell: (v, i, record) => <a onClick={() => edit(record)}>编辑</a> },\r\n\r\n  // 5) 表头带提示气泡\r\n  { title: '转化率', dataIndex: 'rate', balloon: <Balloon trigger={<QuestionMark />}>转化率 = 成功数 / 总数</Balloon> },\r\n\r\n  // 6) 多级表头\r\n  {\r\n    title: '地址', children: [\r\n      { title: '省', dataIndex: 'province', width: 100 },\r\n      { title: '市', dataIndex: 'city', width: 100 },\r\n    ],\r\n  },\r\n]}\r\n```\r\n\r\n> 注：`ellipsis` 旧用法 `cell: renderText(width)` 已由库内置；现在只需声明 `ellipsis: true`。\r\n\r\n---\r\n\r\n## conditionProps 搜索条件配置\r\n\r\n每个条件项由公共布局属性 + 各类型特有字段组成。条件项按 24 栅格在一行内自动换行。\r\n\r\n### 公共属性（BaseConditionItem）\r\n\r\n| 字段 | 类型 | 必填 | 说明 |\r\n|------|------|------|------|\r\n| `col` | `number` | 是 | 占据的栅格列数（24 栅格制，如 `6`、`8`、`12`） |\r\n| `label` | `string` | 否 | 表单项标签文本 |\r\n| `labelWidth` | `number` | 否 | 标签宽度，按「字数」计：传几渲染成几 em（`1em ≈ 1` 个全角字宽）。仅 `label` 为字符串时生效，可突破默认 `75px` 最小宽度；配置后该项不参与自动对齐分组 |\r\n| `textAlign` | `'left' \\| 'center' \\| 'right'` | 否 | 对齐方式，不传则按行内位置自动计算（行首 left / 中间 center / 行尾 right） |\r\n| `wrapperCol` | `object` | 否 | `Form.Item` 的 `wrapperCol` |\r\n| `labelCol` | `object` | 否 | `Form.Item` 的 `labelCol` |\r\n\r\n### 标签宽度对齐（alignLabelWidth）\r\n\r\n`<FormPage alignLabelWidth>`（默认 `true`）开启自动对齐。规则：\r\n\r\n1. 组件模拟 24 栅格逐项摆放（放不下整项换行），得到每个条件项的**列起点偏移**（0/6/12/18…）；\r\n2. **列起点相同的项归为同一竖列**（注意不是按 `col` 值分组——`col: 12` 的项从行首开始，起点为 0，与 `col: 6` 的行首项同组）；\r\n3. 组内取「未单独配 `labelWidth` 的字符串标签」的最长字数（`stringWidth`：全角=1、半角两字=1，`+0.5` 缓冲，向上取整）作为该竖列统一宽度；\r\n4. 标签在统一宽度内**右对齐**——短标签左侧留白、右缘与组内最长标签对齐，使同一竖列的输入框起点连成一条竖线。\r\n\r\n```tsx\r\n// col 6 ×4 + col 12 + col 6 + col 12 + col 6 的竖列分组：\r\n// 起点 0  → 名称、日期、创建时间(col12)、增强日期(col12)   同组右缘对齐\r\n// 起点 6  → 状态、月份\r\n// 起点 12 → 类型(树)、公司、金额区间、自定义\r\n// 起点 18 → 地区(级联)、远程搜索\r\n<FormPage alignLabelWidth {...} />   // 传 false 退回默认 75px 最小宽度\r\n```\r\n\r\n> 单独配置 `labelWidth` 的项优先用自己的宽度，且不计入组内最长值统计。\r\n> 字数估算函数 `stringWidth(s)` 已从包内导出，可用于业务侧自行推导展示。\r\n\r\n### 各类型（`type`）\r\n\r\n| `type` | 渲染控件 | 特有字段 | `customProps` 类型 |\r\n|--------|----------|----------|---------------------|\r\n| `input` | `Input` | `name`, `options?` | `InputProps` |\r\n| `select` | `Select` | `name`, `options?` | `SelectProps` |\r\n| `treeSelect` | `TreeSelect` | `name`, `options?` | `TreeSelectProps` |\r\n| `cascaderSelect` | `CascaderSelect` | `name`, `options?` | `CascaderSelectProps` |\r\n| `datePicker` | `DatePicker` | `name`, `options?` | `DatePickerProps` |\r\n| `monthPicker` | `DatePicker.MonthPicker` | `name`, `options?` | `MonthPickerProps` |\r\n| `rangePicker` | 两个 `DatePicker` | `name: [start, end]`, `options: [.,.]`, `customProps?: [.,.]` | `DatePickerProps[]` |\r\n| `section` | 两个 `Input`（区间） | `name: [a, b]`, `options: [.,.]`, `unit?: string`, `customProps?: [.,.]` | `InputProps[]` |\r\n| `companySelect` | `Select`（公司选择） | `name`, `options?`, `hasDefault?` | `SelectProps` |\r\n| `extraDatePicker` | `DatePicker`/`MonthPicker`/`TimePicker` + 快捷按钮 | `name: string[]`, `options: []`, `extra: {...}`, `customProps?: []` | `DatePickerProps[]` |\r\n| `selectSearch` | `Select`（远程搜索） | `name`, `options?`, **`request: (keyword) => Promise<any[]>`** | `SelectProps` |\r\n| `customNode` | 完全自定义 | **`node: (field) => ReactNode`** | — |\r\n| `custom`（兜底） | 任意 | `type: string`, **`custom: ReactNode`** | — |\r\n\r\n> `name` / `options` / `customProps` 通过 `field.init(name, options, customProps)` 绑定到 Fusion `Field`，`options` 即 Field 的初始化选项（如 `{ rules, initValue }`），`customProps` 直接透传给底层控件。\r\n\r\n### 类型常量\r\n\r\n可用 `FormPageData.Type` 引用类型字符串常量（注意 `cascaderSelect` 为小写）：\r\n\r\n```ts\r\nFormPageData.Type.Select         // 'select'\r\nFormPageData.Type.Input          // 'input'\r\nFormPageData.Type.TreeSelect     // 'treeSelect'\r\nFormPageData.Type.cascaderSelect // 'cascaderSelect'\r\nFormPageData.Type.DatePicker     // 'datePicker'\r\nFormPageData.Type.MonthPicker    // 'monthPicker'\r\nFormPageData.Type.RangePicker    // 'rangePicker'\r\nFormPageData.Type.Section        // 'section'\r\nFormPageData.Type.CompanySelect  // 'companySelect'\r\nFormPageData.Type.ExtraDatePicker// 'extraDatePicker'\r\nFormPageData.Type.SelectSearch   // 'selectSearch'\r\nFormPageData.Type.CustomNode     // 'customNode'\r\n```\r\n\r\n### `extraDatePicker` 的 `extra` 结构\r\n\r\n```ts\r\nextra: {\r\n  name: string;            // 存储当前选中按钮索引的字段名\r\n  defaultBtn: number;      // 默认选中的按钮索引\r\n  buttons: Array<{\r\n    name: string;          // 按钮显示文本\r\n    date: any[];           // 该按钮对应的日期值，与外层 name 一一对应\r\n    datePickerType?: 'datePicker' | 'monthPicker' | 'timePicker';\r\n    props?: DatePickerProps | MonthPickerProps | TimePickerProps;\r\n  }>;\r\n}\r\n```\r\n\r\n### `companySelect` / `selectSearch` 依赖说明\r\n\r\n- `companySelect` 依赖 [`FormPageProvider`](#formpageprovider-外部服务注入) 注入的 `onSearchCompany` / `getCompanyInfo`；`hasDefault: true` 且当前用户非管理员时锁定为当前公司。\r\n- `selectSearch` 通过自身的 `request(keyword)` 获取选项，自带防抖，无需 Provider。\r\n\r\n### 条件配置示例\r\n\r\n```tsx\r\nconditionProps={[\r\n  { type: 'input',  name: 'keyword', label: '关键词', col: 6 },\r\n  { type: 'select', name: 'status',  label: '状态', col: 6,\r\n    options: { dataSource: [{ label: '启用', value: 1 }, { label: '停用', value: 0 }] } },\r\n\r\n  // 区间：两个 Input，字段名 minAge / maxAge\r\n  { type: 'section', name: ['minAge', 'maxAge'], label: '年龄', col: 8, unit: '岁',\r\n    options: [{}, {}] },\r\n\r\n  // 远程搜索\r\n  { type: 'selectSearch', name: 'userId', label: '用户', col: 6,\r\n    request: async (kw) => {\r\n      const list = await searchUser(kw);\r\n      return list.map(u => ({ label: u.name, value: u.id }));\r\n    } },\r\n\r\n  // 完全自定义\r\n  { type: 'customNode', label: '高级', col: 12,\r\n    node: (field) => <MyComplexControl value={field.getValue('x')} onChange={(v) => field.setValue('x', v)} /> },\r\n]}\r\n```\r\n\r\n---\r\n\r\n## buttonProps 按钮配置\r\n\r\n```ts\r\ninterface ButtonConfigProps {\r\n  /** @deprecated 不再生效；按钮列自动占据本行剩余宽度以紧贴最右侧 */\r\n  col?: number;\r\n  buttons: Array<string | ReactNode | { name: string; style?: 'primary' | 'ghost' }>;\r\n  /** 按钮整体风格，默认 'primary'；可被单按钮配置覆盖 */\r\n  buttonStyle?: 'primary' | 'ghost';\r\n  /** 按钮级覆盖：key 为内置按钮名，优先级最高 */\r\n  buttonStyles?: Partial<Record<'submit' | 'reset' | 'export' | 'setting', { style?: 'primary' | 'ghost' }>>;\r\n}\r\n```\r\n\r\n`buttons` 中的字符串映射到内置按钮，**未匹配的项直接作为 ReactNode 渲染**（可放自定义 JSX）：\r\n\r\n| 字符串 | 按钮 | 行为 |\r\n|--------|------|------|\r\n| `'submit'` | 搜索 | 标记为按钮触发并提交查询 |\r\n| `'reset'` | 重置 | 清空表单 / 排序 / 选中行，触发 `onReset` |\r\n| `'export'` | 导出 | 调用 `exportFn` |\r\n| `'setting'` | 设置 | 打开列显示/隐藏设置弹窗（依赖 `tableId`） |\r\n\r\n### 按钮风格（三级粒度）\r\n\r\n风格优先级：**`buttonStyles[name]` > `buttons` 内联对象 > `buttonStyle` > 默认 `primary`**\r\n\r\n| 风格 | 视觉 | 说明 |\r\n|------|------|------|\r\n| `'primary'`（默认） | 搜索 = 主题色实心主按钮；重置/导出/设置 = 普通次按钮 | 突出主操作 |\r\n| `'ghost'` | 全部 = 白底 + 主题橙（`#ff7e07`）描边幽灵按钮 | 权重一致，见下图风格 |\r\n\r\n```tsx\r\n// ① 整体风格：全部幽灵描边\r\nbuttonProps={{ buttons: ['submit', 'reset', 'export'], buttonStyle: 'ghost' }}\r\n\r\n// ② 内联覆盖：整体默认，仅重置单独指定为幽灵\r\nbuttonProps={{ buttons: ['submit', { name: 'reset', style: 'ghost' }, 'export'] }}\r\n\r\n// ③ 按钮级覆盖：整体幽灵，但搜索保持实心主按钮\r\nbuttonProps={{\r\n  buttons: ['submit', 'reset', 'export'],\r\n  buttonStyle: 'ghost',\r\n  buttonStyles: { submit: { style: 'primary' } },\r\n}}\r\n\r\n// ④ 混排自定义 JSX（自定义按钮不受风格控制）\r\nbuttonProps={{\r\n  buttons: ['submit', 'reset', <Button key=\"custom\" onClick={...}>自定义</Button>, 'setting'],\r\n}}\r\n```\r\n\r\n> 内联对象 `{ name, style }` 的 `name` 必须是内置按钮名（`'submit' | 'reset' | 'export' | 'setting'`），否则按未知按钮渲染兜底。\r\n\r\n---\r\n\r\n## Ref 方法（FormPageRef）\r\n\r\n通过 `ref` 获取命令式句柄：\r\n\r\n```tsx\r\nconst ref = useRef<FormPageRef>(null);\r\n<FormPage ref={ref} ... />\r\n```\r\n\r\n| 方法 | 签名 | 说明 |\r\n|------|------|------|\r\n| `submit` | `() => void` | 手动提交查询（不标记为按钮触发） |\r\n| `reset` | `() => void` | 重置表单、排序、选中行 |\r\n| `refresh` | `() => void` | 静默刷新当前页（如外部数据变更后调用） |\r\n| `resetWidth` | `() => void` | 重置所有列宽为初始值 |\r\n| `selectedRowKeysChange` | `(keys: any[], records?: any[]) => void` | 程序化修改选中行（需开启行选择） |\r\n| `field` | Fusion `Field` 实例 | 直接读写表单值：`field.getValue(name)` / `field.setValue(name, v)` / `field.validate(...)` 等 |\r\n\r\n```tsx\r\nref.current?.submit();\r\nref.current?.reset();\r\nref.current?.refresh();\r\nref.current?.resetWidth();\r\nref.current?.field.getValue('keyword');\r\nref.current?.selectedRowKeysChange([1, 2]);\r\n```\r\n\r\n---\r\n\r\n## FormPageProvider 外部服务注入\r\n\r\n可选：用 `<FormPageProvider config={...}>` 包裹，注入列设置持久化与公司选择等外部服务。\r\n\r\n```ts\r\nimport { FormPageProvider, FormPageConfig } from '@chengkejie/form-page';\r\n\r\nconst config: FormPageConfig = {\r\n  // 列设置（显示/隐藏）持久化\r\n  fetchTableKeys:  async (tableId) => [...],                       // 读取已隐藏的列\r\n  onSaveTableKeys: async ({ tableId, value, name, tableName }) => {...}, // 保存隐藏列\r\n  getTableName:    () => '当前表格名',                              // 设置弹窗用\r\n\r\n  // 公司选择\r\n  onSearchCompany: async (keyword) => [{ name, id }],             // 按关键字搜公司\r\n  getCompanyInfo:  () => ({ id, name, isManage }),                // 当前公司/是否管理员\r\n};\r\n```\r\n\r\n| 字段 | 签名 | 用于 |\r\n|------|------|------|\r\n| `fetchTableKeys` | `(tableId) => Promise<Array<{ tableId, value }>>` | 列显示/隐藏持久化读取 |\r\n| `onSaveTableKeys` | `(params: { tableId, value, name, tableName? }) => Promise<any>` | 列显示/隐藏持久化保存 |\r\n| `getTableName` | `() => string` | `SettingDialog` 显示表格名 |\r\n| `onSearchCompany` | `(keyword) => Promise<Array<{ name, id }>>` | `companySelect` 远程搜索 |\r\n| `getCompanyInfo` | `() => { id, name, isManage }` | `companySelect` 默认公司 / 锁定判断 |\r\n\r\n> 不使用「设置」弹窗或「公司选择」时，可省略对应字段，甚至完全不用 Provider。\r\n\r\n---\r\n\r\n## 子组件（独立导出）\r\n\r\n以下组件均从包入口导出（见 [src/index.tsx](src/index.tsx)），可在 condition 体系之外单独使用。\r\n\r\n### SettingDialog — 列定制弹窗\r\n\r\n```ts\r\ninterface SettingDialogProps {\r\n  onChange: (id: string) => void;   // 保存成功后回调，入参为 tableId\r\n  id: string;                       // 表格 ID（tableId）\r\n  dataSource: any[];                // 列配置数组；弹窗用 title→label、dataIndex→value 生成勾选项\r\n  container?: HTMLElement | null;   // 弹窗挂载容器\r\n  hiddenKeys: string[];             // 当前已隐藏列的 dataIndex 列表\r\n  onSave: (params: { value; tableId; name; tableName? }) => Promise<any>; // 保存回调\r\n}\r\n// ref 暴露：{ show: () => void }  // 打开弹窗并初始化勾选\r\n```\r\n\r\n- 渲染「定制列」`Dialog`，用 `Checkbox.Group` 列出所有列（由 `dataSource` 的 `title`/`dataIndex` 派生），勾选 = 显示。\r\n- 点「确定」时把**未勾选**的列拼成 `value`（dataIndex 逗号拼接）和 `name`（title 逗号拼接），连同 `tableId`、`tableName`（取自 Provider 的 `getTableName`）调 `onSave`；成功后调 `onChange(id)`。\r\n\r\n```tsx\r\nimport { SettingDialog } from '@chengkejie/form-page';\r\nimport { useRef } from 'react';\r\n\r\nconst ref = useRef<any>(null);\r\n<SettingDialog\r\n  ref={ref}\r\n  id=\"my-table\"\r\n  dataSource={columns}            // 传完整列配置\r\n  hiddenKeys={hiddenDataIndexes}  // 已隐藏列的 dataIndex\r\n  container={containerEl}\r\n  onSave={async (params) => { await api.saveTableKeys(params); }}\r\n  onChange={(id) => reloadHidden(id)}\r\n/>;\r\nref.current?.show();              // 打开弹窗\r\n```\r\n\r\n### 条件渲染组件（CompanySelect / ExtraDatePicker / SelectSearch / Section / CustomNode）\r\n\r\n这些是 [`conditionProps`](#conditionprops-搜索条件配置) 各类型对应的渲染组件，签名为 `{ field, data }`，其中 `data` 就是对应类型的条件条目。一般通过 condition 配置使用；单独使用时需自行传入 Fusion `field` 实例与条件条目：\r\n\r\n| 组件 | `data` 类型 | 说明 |\r\n|------|------------|------|\r\n| `CompanySelect` | `CompanySelectCondition` | 公司搜索 Select，依赖 Provider 的 `onSearchCompany`/`getCompanyInfo` |\r\n| `ExtraDatePicker` | `ExtraDatePickerCondition` | 带快捷按钮的 日期/月/时间 选择器 |\r\n| `SelectSearch` | `SelectSearchCondition` | 远程搜索 Select，依赖 `data.request(keyword)` |\r\n| `Section` | `SectionCondition` | 双 Input 区间，支持 `data.unit` |\r\n| `CustomNode` | `CustomNodeCondition` | 渲染 `data.node(field)` |\r\n\r\n```tsx\r\nimport { Section } from '@chengkejie/form-page';\r\nimport { Field } from '@alifd/next';\r\n\r\nconst field = Field.useField();\r\n<Section\r\n  field={field}\r\n  data={{ type: 'section', name: ['min', 'max'], options: [{}, {}], col: 6, unit: '元' }}\r\n/>\r\n```\r\n\r\n---\r\n\r\n## 完整示例\r\n\r\n```tsx\r\nimport { useRef } from 'react';\r\nimport { FormPage, FormPageProvider, FormPageRef, FormPageData } from '@chengkejie/form-page';\r\nimport '@chengkejie/form-page/dist/esm/styles/index.module.scss';\r\n\r\nconst fetchList = async (data: any, params: any) => {\r\n  const res = await fetch(`/api/users?q=${data.keyword}&page=${params.current}&size=${params.pageSize}`).then(r => r.json());\r\n  return { data: res.list, total: res.total };\r\n};\r\n\r\nexport default function Page() {\r\n  const ref = useRef<FormPageRef>(null);\r\n\r\n  return (\r\n    <FormPageProvider\r\n      config={{\r\n        fetchTableKeys: async () => [],\r\n        onSaveTableKeys: async () => {},\r\n      }}\r\n    >\r\n      <FormPage\r\n        ref={ref}\r\n        searchFn={fetchList}\r\n        autoSearch\r\n        rowSelectionFn={(keys) => console.log('selected', keys)}\r\n        conditionProps={[\r\n          { type: FormPageData.Type.Input,  name: 'keyword', label: '关键词', col: 6 },\r\n          { type: FormPageData.Type.Select, name: 'status',  label: '状态', col: 6,\r\n            options: { dataSource: [{ label: '启用', value: 1 }, { label: '停用', value: 0 }] } },\r\n        ]}\r\n        tableProps={{\r\n          tableId: 'user-table',\r\n          primaryKey: 'id',\r\n          defaultPageSize: 20,\r\n          columns: [\r\n            { title: 'ID',   dataIndex: 'id',   width: 80,  lock: true },\r\n            { title: '姓名', dataIndex: 'name', width: 160, ellipsis: true },\r\n            { title: '邮箱', dataIndex: 'email', width: 220, ellipsis: true },\r\n            { title: '状态', dataIndex: 'statusText', width: 100, align: 'center' },\r\n            { title: '操作', dataIndex: 'op', cell: (v, i, r) => <a onClick={() => edit(r)}>编辑</a> },\r\n          ],\r\n        }}\r\n        buttonProps={{ buttons: ['submit', 'reset', 'export', 'setting'] }}\r\n        onReset={() => console.log('reset')}\r\n      />\r\n    </FormPageProvider>\r\n  );\r\n}\r\n```\r\n\r\n---\r\n\r\n## 内部行为说明\r\n\r\n以下为组件内部固定、非 prop 可配的行为，调用方知晓即可：\r\n\r\n- **调试钩子 `window.field`**：组件挂载后会把当前 Fusion `Field` 实例挂到 `window.field`（[FormPage.tsx:191](src/FormPage.tsx#L191)），方便在浏览器控制台读取/调试表单值。**生产环境也会挂载**，介意可忽略该全局变量。\r\n- **表格形态**：使用 `Table.StickyLock`（锁列吸附）+ `fixedHeader`；`maxBodyHeight` 按表格容器实测高度自动计算（减去表头高度，默认 44px）。非用户可配。\r\n- **排序**：单列排序。内部维护 `sort` state，列上加 `sortable: true` 即参与排序；点击其它列会替换（而非叠加）当前排序。\r\n- **分页器样式**：固定 `pageSizeSelector=\"dropdown\"`、`useFloatLayout`、显示「共 X 条」。可由 `pageSizeList` 改选项、`showPagination={false}` 关闭。\r\n- **列宽拖拽**：所有列自动设为 `asyncResizable`（可拖拽调宽），拖拽宽度保留在组件 state（300ms 防抖）；`ref.resetWidth()` 重置为初始值。\r\n- **列显示/隐藏**：`tableId` 对应的列中，`dataIndex` 命中 `tbKeys`（隐藏列表）即不渲染；持久化依赖 Provider 的 `fetchTableKeys`（读取）/`onSaveTableKeys`（保存）。\r\n- **按钮列宽度**：操作按钮列自动占据本行剩余栅格宽度以紧贴最右侧；剩余空间按「模拟逐项摆放后的末行实际占用」计算（非总和取模，避免 flex 提前换行导致按钮列错位）；条件正好填满整行（24 栅格）时按钮独占一行。\r\n- **条件对齐**：条件项 `textAlign` 未显式设置时，按行内位置自动取 行首 `left` / 中间 `center` / 行尾 `right`。\r\n- **条件间距**：条件行使用 24 栅格 + `gutter=16`（列内 padding，不影响百分比宽度与换行）。\r\n\r\n---\r\n\r\n## 开发 / 发布\r\n\r\n```bash\r\n# 安装依赖\r\nnpm i\r\n\r\n# 开发模式（监听文件变化）\r\nnpm run dev\r\n\r\n# 构建（输出 dist/esm、dist/cjs）\r\nnpm run build\r\n\r\n# 代码检查\r\nnpm run lint\r\n```\r\n\r\n### 发布（公共 npm）\r\n\r\n1. npm 账号 `chengkejie`（包名 scope 即 `@chengkejie`）。\r\n2. 本机认证：`npm login`，或 `npm config set //registry.npmjs.org/:_authToken <token>`。\r\n   > ⚠️ 账号开了 2FA（Windows 通行密钥，不是 TOTP）。命令行发版会要 OTP 而通行密钥给不出 —— 需先把 npmjs.com 的 2FA 调成 **\"仅授权 / authorization only\"**，或用 Classic **Automation** token（绕过 2FA）。\r\n\r\n```bash\r\n# 1. 改 package.json 的 version\r\n# 2. 从 release 分支发布（dist 已构建；release 无 src，需跳过 prepublishOnly=father build）\r\nnpm publish --ignore-scripts\r\n```\r\n\r\n> 新包发布后有几分钟 CDN 读延迟，`npm view` 可能短暂 404，以 `npm publish` 输出 `+ @chengkejie/form-page@x.y.z` 为准。\r\n\r\n## 依赖\r\n\r\n| 依赖 | 版本 |\r\n|------|------|\r\n| React | >=16.9.0 <19.0.0 |\r\n| React DOM | >=16.9.0 <19.0.0 |\r\n| @alifd/next | >=1.26.0 |\r\n| moment | >=2.24.0 |\r\n| ahooks | ^3.0.0 |\r\n\r\n## License\r\n\r\nMIT\r\n","readmeFilename":"README.md"}