From 85da4ecccefaa9aaac1fc141907f0959e8b95856 Mon Sep 17 00:00:00 2001 From: charlzyx Date: Tue, 23 Jan 2024 16:40:45 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20rm=20pro-editable,?= =?UTF-8?q?=20re=20shadow-form,=20pro-array-table?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bun.lockb | Bin 446945 -> 447302 bytes docs/components/demos/ProArrayTable.tsx | 2 +- ...hArray.tsx => ProArrayTableWithShadow.tsx} | 14 +- docs/components/demos/ProEditable.tsx | 98 ---- .../components/demos/ProEditableWithArray.tsx | 237 -------- docs/components/demos/QueryList.tsx | 25 +- docs/components/demos/ShadowForm.tsx | 108 ---- docs/components/demos/ShadowFormAll.tsx | 203 +++++++ docs/components/demos/Suggestion.tsx | 2 +- docs/components/pro/pro-array-table.mdx | 140 +++-- docs/components/pro/pro-editable.mdx | 31 - docs/components/pro/query-list.mdx | 2 +- docs/components/pro/shadow-form.mdx | 90 ++- docs/install.mdx | 2 +- package.json | 3 +- src/__builtins__/checkers.ts | 2 + src/__builtins__/index.ts | 1 + src/dict/helper.ts | 3 +- src/index.ts | 1 - src/pro-array-table/features/pro-settings.tsx | 12 +- src/pro-array-table/hooks.tsx | 13 +- src/pro-array-table/index.ts | 2 +- src/pro-array-table/mixin.base.tsx | 290 +++++++++ .../{mixin.tsx => mixin.pro.tsx} | 84 +-- src/pro-array-table/pro.tsx | 551 ++++++++++-------- src/pro-array-table/types.tsx | 71 ++- src/pro-editable/Drawer.tsx | 58 -- src/pro-editable/Modal.tsx | 50 -- src/pro-editable/Popconfirm.tsx | 56 -- src/pro-editable/hooks.tsx | 85 --- src/pro-editable/index.tsx | 9 - src/query-form/index.tsx | 22 +- src/query-table/index.tsx | 12 +- src/shadow-form/index.tsx | 8 +- src/shadow-form/modal.tsx | 122 ++++ .../{shadow-popconfirm.tsx => popconfirm.tsx} | 25 +- src/shadow-form/shadow-form.tsx | 101 ++-- src/shared/index.ts | 1 + src/shared/withLayoutGrid.tsx | 25 + ui/antd-v5/adaptor.ts | 3 +- ui/antd-v5/index.ts | 2 + ui/antd/adaptor.ts | 2 +- ui/antd/index.ts | 2 + ui/arco/adaptor.ts | 3 +- ui/arco/index.ts | 5 +- 45 files changed, 1350 insertions(+), 1228 deletions(-) rename docs/components/demos/{ShadowFormWithArray.tsx => ProArrayTableWithShadow.tsx} (94%) delete mode 100644 docs/components/demos/ProEditable.tsx delete mode 100644 docs/components/demos/ProEditableWithArray.tsx delete mode 100644 docs/components/demos/ShadowForm.tsx create mode 100644 docs/components/demos/ShadowFormAll.tsx delete mode 100644 docs/components/pro/pro-editable.mdx create mode 100644 src/__builtins__/checkers.ts create mode 100644 src/pro-array-table/mixin.base.tsx rename src/pro-array-table/{mixin.tsx => mixin.pro.tsx} (74%) delete mode 100644 src/pro-editable/Drawer.tsx delete mode 100644 src/pro-editable/Modal.tsx delete mode 100644 src/pro-editable/Popconfirm.tsx delete mode 100644 src/pro-editable/hooks.tsx delete mode 100644 src/pro-editable/index.tsx create mode 100644 src/shadow-form/modal.tsx rename src/shadow-form/{shadow-popconfirm.tsx => popconfirm.tsx} (75%) create mode 100644 src/shared/withLayoutGrid.tsx diff --git a/bun.lockb b/bun.lockb index 73204a1d0cde3e09e91c2009ae45ceb7c380a439..ac2bba82c77807793c9b03faa1781dc25ba17eb6 100755 GIT binary patch delta 67654 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 delta 67367 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 M`{%zEKT-Vu0Go{g#Q*>R diff --git a/docs/components/demos/ProArrayTable.tsx b/docs/components/demos/ProArrayTable.tsx index cd22def..ef08687 100644 --- a/docs/components/demos/ProArrayTable.tsx +++ b/docs/components/demos/ProArrayTable.tsx @@ -14,7 +14,7 @@ import { import { FormProvider, ISchema, createSchemaField } from "@formily/react"; import { Button, ConfigProvider, Divider, Space } from "antd"; import "antd/dist/antd.css"; -// import "antd/dist/antd.css"; + import zhCN from "antd/lib/locale/zh_CN"; import moment from "moment"; import "moment/locale/zh-cn"; diff --git a/docs/components/demos/ShadowFormWithArray.tsx b/docs/components/demos/ProArrayTableWithShadow.tsx similarity index 94% rename from docs/components/demos/ShadowFormWithArray.tsx rename to docs/components/demos/ProArrayTableWithShadow.tsx index 656999b..97eb22c 100644 --- a/docs/components/demos/ShadowFormWithArray.tsx +++ b/docs/components/demos/ProArrayTableWithShadow.tsx @@ -1,17 +1,15 @@ -import { faker } from "@faker-js/faker"; import { Editable, FormGrid, FormItem, FormLayout, Input } from "@formily/antd"; import { createForm } from "@formily/core"; import { FormProvider, ISchema, createSchemaField } from "@formily/react"; import { Button, ConfigProvider } from "antd"; import "antd/dist/antd.css"; -// import "antd/dist/antd.css"; + import zhCN from "antd/lib/locale/zh_CN"; import moment from "moment"; import "moment/locale/zh-cn"; moment.locale("zh-cn"); import { ProArrayTable, ShadowForm } from "@pro.formily/antd"; -import { useProArrayTableContext } from "src/pro-array-table/mixin"; const form = createForm({ initialValues: { @@ -81,11 +79,12 @@ const schema: ISchema = { "x-component": "ProArrayTable", items: row.items, properties: { + // ↓ 不填写 act 属性的话, 就读这个 modal 字段 modal: { type: "void", - "x-decorator": "ShadowForm", - "x-decorator-props": { - act: "modal", + "x-component": "ProArrayTable.ShadowModal", + "x-component-props": { + // act: "modal", // 这里不填写的话, 就读取上面 schema: { type: "void", properties: { @@ -113,7 +112,6 @@ const schema: ISchema = { }, }, }, - "x-component": "ProArrayTable.ShadowModal", }, add: { type: "void", @@ -121,7 +119,7 @@ const schema: ISchema = { "x-component-props": { onOk: ( data: any, - ctx: ReturnType, + ctx: ReturnType, ) => { // 如果添加数据后将超过当前页,则自动切换到下一页 const total = ctx?.array.field?.value.length || 0; diff --git a/docs/components/demos/ProEditable.tsx b/docs/components/demos/ProEditable.tsx deleted file mode 100644 index 36eda56..0000000 --- a/docs/components/demos/ProEditable.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import { Editable, FormGrid, FormItem, FormLayout, Input } from "@formily/antd"; -import { createForm } from "@formily/core"; -import { FormProvider, ISchema, createSchemaField } from "@formily/react"; -import { ConfigProvider } from "antd"; -import "antd/dist/antd.css"; -// import "antd/dist/antd.css"; -// import zhCN from "antd/lib/locale/zh_CN"; -import zhCN from "antd/lib/locale/zh_CN"; - -import moment from "moment"; -import "moment/locale/zh-cn"; -moment.locale("zh-cn"); - -import { - ProEditable, - QueryForm, - QueryList, - QueryTable, -} from "@pro.formily/antd"; -import { useMemo } from "react"; - -const form = createForm({}); - -const editable = ( - comp: "ProEditable" | "ProEditable.Modal" | "ProEditable.Drawer", -) => ({ - type: "void", - title: comp, - "x-component": comp, - "x-component-props": { - title: `标题${comp}`, - onConfirm: "{{onConfirm}}", - }, - properties: { - domain: { - title: "域名", - "x-decorator": "FormItem", - type: "string", - "x-component": "Input", - }, - desc: { - title: "描述", - "x-decorator": "FormItem", - type: "string", - "x-component": "Input.TextArea", - "x-component-props": { - rows: 4, - }, - }, - }, -}); - -const schema: ISchema = { - type: "object", - properties: { - obj: { - type: "object", - properties: { - ...editable("ProEditable").properties, - _editable1: editable("ProEditable"), - _editable2: editable("ProEditable.Modal"), - _editable3: editable("ProEditable.Drawer"), - }, - }, - }, -}; -const SchemaField = createSchemaField({ - components: { - FormItem, - FormGrid, - Editable, - Input, - FormLayout, - ProEditable: ProEditable, - QueryList, - QueryForm, - QueryTable, - }, -}); -export default () => { - const onConfirm = (data: any) => { - console.log("🚀 ~ onConfirm ~ data:", data); - }; - - const scope = useMemo(() => { - return { - onConfirm, - }; - }, []); - - return ( - - - - - - ); -}; diff --git a/docs/components/demos/ProEditableWithArray.tsx b/docs/components/demos/ProEditableWithArray.tsx deleted file mode 100644 index fa7d88d..0000000 --- a/docs/components/demos/ProEditableWithArray.tsx +++ /dev/null @@ -1,237 +0,0 @@ -import { faker } from "@faker-js/faker"; -import { Editable, FormGrid, FormItem, FormLayout, Input } from "@formily/antd"; -import { createForm } from "@formily/core"; -import { FormProvider, ISchema, createSchemaField } from "@formily/react"; -import { ConfigProvider } from "antd"; -import "antd/dist/antd.css"; -// import "antd/dist/antd.css"; -import zhCN from "antd/lib/locale/zh_CN"; -import moment from "moment"; -import "moment/locale/zh-cn"; -moment.locale("zh-cn"); - -import { - ProEditable, - QueryForm, - QueryList, - QueryTable, - useQueryListRef, -} from "@pro.formily/antd"; -import { useMemo } from "react"; - -const log = (label: string, x: any) => { - console.log("LABEL:", label); - try { - console.group(JSON.parse(JSON.stringify(x))); - } catch (error) { - console.log("stringify error, origin: ", x); - } - console.groupEnd(); -}; - -export const service = ({ - params, - pagination, - sorter, - filters, - extra, -}: any) => { - log("search sevice args ", { params, pagination, sorter, filters, extra }); - - const { - start = moment().toDate(), - end = moment().add(1, "year").toDate(), - classify, - status, - domain, - } = params || {}; - const { current, pageSize } = pagination || {}; - - return Promise.resolve().then(() => { - return new Promise((resolve) => { - setTimeout(() => { - const total = 45; - const list = Array.from({ - length: current * pageSize > total ? total % pageSize : pageSize, - }).map((_, idx) => { - return { - id: current * pageSize + idx, - status: status ?? +faker.string.numeric(1) % 5, - domain: `${ - domain ? `${domain}.` : "" - }${faker.internet.domainName()}`, - subdomains: Array.from( - new Set( - Array.from({ - length: Math.floor(Math.random() * 3), - }).map(() => faker.internet.domainName()), - ), - ).map((item) => { - return { - owner: faker.company.name(), - domain: item, - }; - }), - classify: - classify ?? - Array.from( - new Set( - Array.from({ length: Math.floor(Math.random() * 3) }).map( - () => +faker.string.numeric(1) % 5, - ), - ), - ), - date: moment(faker.date.between({ from: start, to: end })).format( - "YYYY-MM-DD", - ), - img: faker.image.avatar(), - desc: faker.lorem.paragraph(), - }; - }); - log("search response ", { list, total }); - - resolve({ - list, - total, - }); - }, 456); - }); - }); -}; - -const form = createForm({}); - -const editable = ( - comp: "ProEditable" | "ProEditable.Modal" | "ProEditable.Drawer", -) => ({ - type: "void", - title: comp, - "x-component": comp, - "x-component-props": { - title: `标题${comp}`, - onConfirm: "{{onConfirm}}", - }, - properties: { - id: { - title: "ID", - type: "string", - "x-decorator": "FormItem", - "x-component": "Input", - "x-read-pretty": true, - }, - domain: { - title: "域名", - "x-decorator": "FormItem", - type: "string", - "x-component": "Input", - }, - desc: { - title: "描述", - "x-decorator": "FormItem", - type: "string", - "x-component": "Input.TextArea", - "x-component-props": { - rows: 4, - }, - }, - }, -}); -const row: ISchema = { - items: { - type: "object", - properties: { - _id: { - type: "void", - "x-component": "QueryTable.Column", - "x-component-props": { width: 60, title: "ID", align: "center" }, - properties: { - id: { - type: "string", - "x-component": "Input", - "x-read-pretty": true, - }, - }, - }, - _domain: { - type: "void", - "x-component": "QueryTable.Column", - "x-component-props": { width: 200, title: "DOMAIN", align: "center" }, - properties: { - domain: { - type: "string", - "x-read-pretty": true, - "x-component": "Input", - }, - }, - }, - _editable: { - type: "void", - "x-component": "QueryTable.Column", - "x-component-props": { title: "编辑", align: "center" }, - properties: { - popconfirm: editable("ProEditable"), - modal: editable("ProEditable.Modal"), - drawer: editable("ProEditable.Drawer"), - }, - }, - }, - }, -}; - -const schema: ISchema = { - type: "object", - properties: { - querylist: { - type: "void", - "x-component": "QueryList", - "x-component-props": { - queryRef: "{{queryRef}}", - service: "{{service}}", - }, - properties: { - list: { - type: "array", - "x-component": "QueryTable", - items: row.items, - }, - }, - }, - }, -}; -const SchemaField = createSchemaField({ - components: { - FormItem, - FormGrid, - Editable, - Input, - FormLayout, - ProEditable: ProEditable, - QueryList, - QueryForm, - QueryTable, - }, -}); -export default () => { - const queryRef = useQueryListRef(); - const onConfirm = (data: any) => { - console.log("🚀 ~ onConfirm ~ data:", data); - console.log("🚀 ~ onConfirm ~ queryRef:", queryRef); - queryRef?.current?.run?.(); - }; - - const scope = useMemo(() => { - return { - service, - queryRef, - onConfirm, - }; - }, []); - - return ( - - - - - - ); -}; diff --git a/docs/components/demos/QueryList.tsx b/docs/components/demos/QueryList.tsx index ffbfdfc..07148ca 100644 --- a/docs/components/demos/QueryList.tsx +++ b/docs/components/demos/QueryList.tsx @@ -1,26 +1,29 @@ -import { ConfigProvider, Divider } from "antd"; +import { ConfigProvider } from "antd"; import "antd/dist/antd.css"; -// import "antd/dist/antd.css"; -import zhCN from "antd/lib/locale/zh_CN"; + import { faker } from "@faker-js/faker"; -import { createForm } from "@formily/core"; -import { FormProvider, ISchema, createSchemaField } from "@formily/react"; import { DatePicker, Editable, - FormButtonGroup, FormGrid, FormItem, FormLayout, Input, Select, - Submit, } from "@formily/antd"; +import { createForm } from "@formily/core"; +import { FormProvider, ISchema, createSchemaField } from "@formily/react"; +import zhCN from "antd/lib/locale/zh_CN"; import moment from "moment"; import "moment/locale/zh-cn"; moment.locale("zh-cn"); -import { QueryForm, QueryList, QueryTable } from "@pro.formily/antd"; +import { + ProArrayTable, + QueryForm, + QueryList, + QueryTable, +} from "@pro.formily/antd"; const log = (label: string, x: any) => { console.log("LABEL:", label); @@ -172,7 +175,7 @@ const row: ISchema = { properties: { _id: { type: "void", - "x-component": "QueryTable.Column", + "x-component": "ProArrayTable.Column", "x-component-props": { width: 60, title: "ID", align: "center" }, properties: { id: { @@ -184,7 +187,7 @@ const row: ISchema = { }, _status: { type: "void", - "x-component": "QueryTable.Column", + "x-component": "ProArrayTable.Column", "x-component-props": { width: 60, title: "STATUS", align: "center" }, properties: { id: { @@ -196,7 +199,7 @@ const row: ISchema = { }, _domain: { type: "void", - "x-component": "QueryTable.Column", + "x-component": "ProArrayTable.Column", "x-component-props": { title: "DOMAIN", align: "center" }, properties: { domain: { diff --git a/docs/components/demos/ShadowForm.tsx b/docs/components/demos/ShadowForm.tsx deleted file mode 100644 index 2ba9ec0..0000000 --- a/docs/components/demos/ShadowForm.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import { faker } from "@faker-js/faker"; -import { Editable, FormGrid, FormItem, FormLayout, Input } from "@formily/antd"; -import { createForm } from "@formily/core"; -import { FormProvider, ISchema, createSchemaField } from "@formily/react"; -import { Button, ConfigProvider } from "antd"; -import "antd/dist/antd.css"; -// import "antd/dist/antd.css"; -import zhCN from "antd/lib/locale/zh_CN"; -import moment from "moment"; -import "moment/locale/zh-cn"; -moment.locale("zh-cn"); - -import { ShadowForm, ShadowPopconfirm } from "@pro.formily/antd"; - -const form = createForm({ - initialValues: { - info: { id: 1, domain: "www.baidu.com", desc: "ping" }, - }, -}); - -const schema: ISchema = { - type: "object", - properties: { - info: { - type: "object", - "x-read-pretty": "true", - "x-component": "FormItem", - properties: { - id: { - title: "ID", - type: "string", - "x-decorator": "FormItem", - "x-component": "Input", - }, - domain: { - title: "域名", - "x-decorator": "FormItem", - type: "string", - "x-component": "Input", - }, - desc: { - title: "描述", - "x-decorator": "FormItem", - type: "string", - "x-component": "Input.TextArea", - "x-component-props": { - rows: 4, - }, - }, - _shadow: { - type: "void", - title: "点我编辑", - "x-decorator": "ShadowForm", - "x-component": "ShadowPopconfirm", - "x-decorator-props": { - schema: { - type: "void", - properties: { - id: { - title: "ID", - type: "string", - "x-decorator": "FormItem", - "x-component": "Input", - }, - domain: { - title: "域名", - "x-decorator": "FormItem", - type: "string", - "x-component": "Input", - }, - desc: { - title: "描述", - "x-decorator": "FormItem", - type: "string", - "x-component": "Input.TextArea", - "x-component-props": { - rows: 4, - }, - }, - }, - }, - }, - }, - }, - }, - }, -}; -const SchemaField = createSchemaField({ - components: { - FormItem, - FormGrid, - Input, - Button, - ShadowForm, - ShadowPopconfirm, - }, - scope: {}, -}); - -export default () => { - return ( - - - - - - ); -}; diff --git a/docs/components/demos/ShadowFormAll.tsx b/docs/components/demos/ShadowFormAll.tsx new file mode 100644 index 0000000..61a3d83 --- /dev/null +++ b/docs/components/demos/ShadowFormAll.tsx @@ -0,0 +1,203 @@ +import { + FormButtonGroup, + FormGrid, + FormItem, + FormLayout, + Input, + Submit, +} from "@formily/antd"; +import { createForm } from "@formily/core"; +import { + FormProvider, + ISchema, + createSchemaField, + observer, + useField, +} from "@formily/react"; +import { Button, ConfigProvider } from "antd"; +import "antd/dist/antd.css"; +import zhCN from "antd/lib/locale/zh_CN"; +import moment from "moment"; +import "moment/locale/zh-cn"; +moment.locale("zh-cn"); + +import { toJS } from "@formily/reactive"; +import { + ShadowForm, + ShadowFormWrappedProps, + ShadowModal, + ShadowPopconfirm, + withLayoutGrid, +} from "@pro.formily/antd"; +import { useEffect } from "react"; + +const form = createForm({ + initialValues: { + info: { domain: "www.baidu.com", desc: "ping" }, + }, +}); + +const shadowSchema = { + type: "void", + properties: withLayoutGrid({ + domain: { + title: "域名", + "x-decorator": "FormItem", + type: "string", + "x-component": "Input", + }, + desc: { + title: "描述", + "x-decorator": "FormItem", + type: "string", + "x-component": "Input.TextArea", + }, + }), +}; + +const schema: ISchema = { + type: "object", + "x-component": "FormLayout", + "x-component-props": { + labelCol: 6, + wrapperCol: 10, + layout: "vertical", + }, + properties: { + info: { + type: "object", + "x-read-pretty": true, + "x-component": "FormItem", + properties: { + layout: { + type: "void", + properties: { + domain: { + title: "域名", + "x-decorator": "FormItem", + type: "string", + "x-component": "Input", + }, + desc: { + title: "描述", + "x-decorator": "FormItem", + type: "string", + "x-component": "Input.TextArea", + "x-component-props": { + rows: 4, + }, + }, + }, + }, + _modal: { + type: "void", + title: "内置的Modal", + "x-component": "ShadowModal", + "x-decorator": "ShadowForm", + "x-decorator-props": { + schema: shadowSchema, + }, + }, + _popconfirm: { + type: "void", + title: "内置的Popconfirm", + "x-component": "ShadowPopconfirm", + "x-decorator": "ShadowForm", + "x-decorator-props": { + schema: shadowSchema, + }, + }, + _shadow2: { + type: "void", + title: "MyDecoratorIsShadowForm", + "x-component": "MyDecoratorIsShadowForm", + "x-decorator": "ShadowForm", + "x-decorator-props": { + schema: shadowSchema, + }, + }, + _shadow3: { + type: "void", + title: "MyChildisShadowForm", + "x-component": "ShadowForm", + "x-component-props": { + schema: shadowSchema, + }, + "x-decorator": "MyChildisShadowForm", + }, + }, + }, + }, +}; + +const MyDecoratorIsShadowForm: React.FC = ({ + form, + SchemaField, + schema, +}) => { + const field = useField(); + useEffect(() => { + form.setValues(toJS(field.record)); + }, []); + return ( +
+

ShadowForm 作为 x-decorator

+ + + + 查看 + + +
+ ); +}; + +const MyChildisShadowForm: React.FC = observer((props) => { + const field = useField(); + const form = field.data?._form as ReturnType; + + useEffect(() => { + // 等子组件挂载, 所以不如上面那种方便对不对 + setTimeout(() => { + const form = field.data?._form as ReturnType; + form?.setValues(toJS(field.record)); + }); + }, []); + return ( +
+

ShadowForm 作为 x-component

+ {props.children} + +
+ ); +}); +const SchemaField = createSchemaField({ + components: { + FormItem, + FormGrid, + FormLayout, + Input, + Button, + ShadowForm, + ShadowModal, + ShadowPopconfirm, + MyDecoratorIsShadowForm, + MyChildisShadowForm, + }, +}); + +export default () => { + return ( + + + + + + ); +}; diff --git a/docs/components/demos/Suggestion.tsx b/docs/components/demos/Suggestion.tsx index 7415a73..4bf589d 100644 --- a/docs/components/demos/Suggestion.tsx +++ b/docs/components/demos/Suggestion.tsx @@ -1,7 +1,7 @@ +import { FormGrid, FormItem, FormLayout } from "@formily/antd"; import { createForm } from "@formily/core"; import { FormProvider, createSchemaField } from "@formily/react"; import { Suggestion } from "@pro.formily/antd"; -import { FormGrid, FormItem, FormLayout } from "@formily/antd"; import jsonp from "fetch-jsonp"; import qs from "qs"; import React, { useMemo } from "react"; diff --git a/docs/components/pro/pro-array-table.mdx b/docs/components/pro/pro-array-table.mdx index a26a44a..718a9fa 100644 --- a/docs/components/pro/pro-array-table.mdx +++ b/docs/components/pro/pro-array-table.mdx @@ -6,30 +6,65 @@ - ✅ 列配置: 排序与拖动调整列宽 - ✅ 可编辑弹窗, 使用事件委托优化 - ✅ 常用功能配置简化与增强 (pagination/rowSelection/expandable) -- ✅ `expandable.defaultExpandedAllRows` 现在支持惰性加载啦! -- ✅ `expandable.expandedRowKeys` / `rowSelection.selectedRowKeys` 现在会根据页码调整进行智能重置! +- ✅ expandable.defaultExpandedAllRows 现在支持惰性加载啦! +- ✅ onSortEnd 支持 `(from, end, arrayField) => Promise` 回调! +- ✅ expandable.expandedRowKeys / rowSelection.selectedRowKeys 现在会根据页码调整进行智能重置! +## 设计思路 + ProArrayTable 的目的就是简化常用表格操作, 增强 Schema 能力, 同时, 对异步操作友好; + +ArrayTable 本身是对本地数据的一个编辑方案, 缺少一些异步的支持, 比如 onSort/onMoveUp/onMoveDown/onRemove 等操作都没有拦截, +缺少类似 [immber Patches](https://immerjs.github.io/immer/zh-CN/patches) 处理, 不好回退, 这在处理远程数据的时候就非常难受了; +同时, 一些额外的功能, 列选择/列展开无法使用 Schema 语法, 限制了JSON Schema 表现力. + +**弹出层编辑方案设计** + +在过往使用 formily 的过程中, 表格中的弹窗编辑一个是个痛点, 基本上都是在使用 FormDialog 做的处理, 除了再 ShadowForm 中提到的 FormDialog/FormDrwaer 自身的问题, +在实际使用中, 一直把 Formdialog 渲染在 eolumn 的render 中, 这导致了大量的 Modal 被反复创建销毁。 + +为什么不用事件委托? + +1. 需要在表单外部处理委托代码(但其实没有深入尝试, 欢迎讨论) +2. 另一个主要原因是 formdialog 中的表单实例生命周期难以理解(现在想起来可能是因为 initialvalues 迷惑行为加上理解问题导致) + +但总之, 我想到了更好的办法。 + +还是事件委托, 但 builtin, 使用一个内置的 DelegateContex 来管理表单中的 click 事件, 添加 data-属性作为可代理标志, 并添加行信息来区分来源。 + +单看事件委托, 属实平平无奇, 但与 ShadowForm 结合着用才能显现出巨大的抛瓦 ## 属性说明 |属性|说明|类型|默认值| |----|---|---|---| -|`resizeable`|可拖拽列宽功能开关, 在 `Columns` 也可以单独设置|`boolean`|`false`| -|`settings`|动态列排序功能开关| `boolean` | `true`| -|`slice`|分页性能优化开关, 默认开启| `boolean`| `true`| -|`rowSelection`|可选列选择功能, 配置简化, 提供功能钩子, 默认关闭| `true\| TableProps['rowSelection']`|--| -|`pagination`|分页功能, 配置简化, 提供功能钩子, 默认开启, `false` 显式关闭| `false\| TableProps['pagination']`|`{hideOnSingle:true}`| -|`expandable`|行展开功能, 配置简化, 提供功能钩子配置, 默认关闭| `TableProps['expandable']`|--| -|`RowExpand`|`Schame`形式的 行展开|--|--| -|`Toolbar`|`Schame`自定义表头部分内容, 自定义组件名称必须包含 `Toolbar`|--|--| -|`Footer`|`Schame`自定义表头部分内容, 自定义组件名称必须包含 `Footer` |--|--| +|resizeable|可拖拽列宽功能开关, 在 Columns 也可以单独设置|boolean|false| +|onSortEnd| 拖动排序回调, reject 则拒绝执行排序 `(from: number, to: number, arrayField: ArrayField) => void |Promise\`|--| +|settings|动态列排序功能开关| boolean |true| +|slice|分页性能优化开关, 默认开启| boolean| true| +|rowSelection|可选列选择功能, 配置简化, 提供功能钩子, 默认关闭|true\| TableProps['rowSelection']|--| +|pagination|分页功能, 配置简化, 提供功能钩子, 默认开启, false 显式关闭| false\| TableProps['pagination']|`{hideOnSingle:true}`| +|expandable|行展开功能, 配置简化, 提供功能钩子配置, 默认关闭| TableProps['expandable']|--| +|RowExpand|Schame 形式的 行展开|--|--| +|Toolbar|Schame 形式的自定义表头部分内容, 自定义组件名称必须包含 Toolbar|--|--| +|Footer|Schame 形式的自定义表头部分内容, 自定义组件名称必须包含 Footer |--|--| -## 代码案例 +## 代码案例: 基本使用 +## 代码案例:可编辑弹窗 + + + +### ArrayBase 仍然适用的API + +- ArrayBase.useArray +- ArrayBase.useIndex +- ArrayBase.useRecord + + ### ProArrayTable.useTablePagination 获取分页信息 ```tsx | pure @@ -62,8 +97,22 @@ export interface ITableSelectionContext { } ``` +### ProArrayTable.useProArrayTableContext 获取数组所有上下文信息 + +```tsx | pure +export const useProArrayTableContext = () => { + const array = ArrayBase.useArray(); + const pagination = useContext(TablePaginationContext); + const rowSelection = useContext(TableRowSelectionContext); + const expanedable = useContext(TableExpandableContext); + return { array, pagination, rowSelection, expanedable }; +}; +``` + ### ProArrayTable.DelegateAction 代理动作触发器 +代理动作触发器, 给子组件打标记, Table Wrapper 的 click 事件绑定进行分发处理, 通过 `act` 与 ShadowForm 弹窗系列关联 + ```tsx | pure { /** 动作标志, 与 ShadowModal 对应 */ @@ -80,35 +129,31 @@ export interface ITableSelectionContext { } ``` -### ShadowModal/ShadowModal 通用属性 +### ShadowModal/ShadowDrawer 通用属性 + +鉴于这套组件在 ProArrayTable 中使用频率颇高, 融合了 IShadowFormOptions ,弹窗 属性组合成一个组件, 并在回调种注入当前数组的 Context + ```tsx | pure { - /** 动作标志, 与 ShadowModal 对应 */ - act: string; - /** 子表单Schema */ - schema: ISchema, - /** createForm 创建的表单实例, 有自定义 effects 等可以通过自定义一个 form 这个传入 */ - form?: ReturnType, - /** createSchemaField 函数配置 */ - schemaFieldOptions?: Parameters[0], + /** 动作标志, 与 ShadowModal 对应, 不填写的话,取当前 schema 的 name */ + act?: string; /** 取消事件 */ onCancel?: ( - ctx: ReturnType, + ctx: ReturnType, ) => void | Promise; /** 子表单提交事件, 可以做点什么 */ onOk?: ( data: any, - ctx: ReturnType, + ctx: ReturnType, ) => void | Promise; -} +} & IShadowFormOptions ``` -### ProArrayTable.ShadowModal 表格共享弹窗, 结合 ShadowForm/DelegateAction 使用 +### ProArrayTable.ShadowModal 表格共享弹窗, 结合 ShadowForm#DelegateAction 使用 除了 `onOk,onCancel` antd/Modal 其他属性 -### ProArrayTable.ShadowDrawer 表格共享抽屉, 结合 ShaodwForm/DelegateAction 使用 - -除了 `onOk,onCancel` antd/Drawer 其他属性 +### ProArrayTable.ShadowDrawer 表格共享抽屉, 结合 ShaodwForm#DelegateAction 使用 +> Welcome PR. ### ProArrayTable.ProAddition 弹窗/抽屉形式的可编辑内容新增组件 @@ -129,9 +174,12 @@ export interface ITableSelectionContext { } ``` -### ProArrayTable.Column 列配置组件 +### ProArrayTable.Column 表格列 + +> 追加了可拖拽列宽配置开关, 优先级 > Table 上的 resizeable 属性 + +其他与 `@formily/antd` 中的 `ArrayTable.Column` 表现一致 -追加了可拖拽列宽配置开关, 优先级 > Table 上的 resizeable 属性 ```tsx | pure { @@ -141,7 +189,33 @@ export interface ITableSelectionContext { } ``` -### ProArrayTable.Addition 自增组件 - -与 `@formily/antd` 中的 `ArrayTable.Addition` 表现一致 +## ArrayBase 基础事件添加 Promise 支持, 影响组件列表如下 +```tsx | pure +export interface IProArrayTableBaseMixins { + onSortEnd?: ( + form: number, + to: number, + arrayField: ArrayField, + ) => void | Promise; + onAdd?: ( + toIndex: number, + value: any, + arrayField: ArrayField, + ) => void | Promise; + onCopy?: ( + distIndex: number, + value: any, + arrayField: ArrayField, + ) => void | Promise; + onRemove?: (index: number, arrayField: ArrayField) => void | Promise; + onMoveUp?: (index: number, arrayField: ArrayField) => void | Promise; + onMoveDown?: (index: number, arrayField: ArrayField) => void | Promise; +} +``` +- ProArrayTable.SortHandle +- ProArrayTable.Remove +- ProArrayTable.Copy +- ProArrayTable.MoveDown +- ProArrayTable.MoveUp +- ProArrayTable.Index diff --git a/docs/components/pro/pro-editable.mdx b/docs/components/pro/pro-editable.mdx deleted file mode 100644 index cefdb6e..0000000 --- a/docs/components/pro/pro-editable.mdx +++ /dev/null @@ -1,31 +0,0 @@ -# 💡Pro Editable (废弃, 请使用 ShadowForm) - -> ProEditable 是为了解决常见的 `Popconfirm/Modal/Drawer` 弹窗编辑子表单模板代码的问题 - -### 为什么不是 FormDialog/FormDrawer ? - -~~没有人比我更懂formily 👐~~ -这两个都是方法调用, 不能用 json 描述出来所以 `FormDialog/FormDrawer` 适合用于更灵活的场景; -此外, `FormDialog/FormDrawer` 内部是使用了 [document.body.appendChild](https://github.com/alibaba/formily/blob/formily_next/packages/antd/src/form-drawer/index.tsx#L122) 凭空创建了一个新的 -dom 节点来承载这个表单, 脱离了 root 节点, 所以需要用 Portals 打补丁来解决, -但使用中会仍会遇到需要单独配置 ConfigProvider 等其他一些奇奇怪怪的问题.~~就很烦 🪃~~ - -## 设计思路 - -ProEditable 灵感来自 `Editable` 既然单独字段能编辑, 那么, 子对象字段是不是也应该能够编辑 ? -这种场景不要太多: 比如表格行内容编辑; 此时, 这篇[标准化CRUD作用域变量规范 -](https://github.com/alibaba/formily/discussions/3207) 直指 formily 模型抽象核心的内容就尤为重要, 这里面提到的 `record` 模型是如此的重要; -以至于在 `2.2.19` 版本中, `record` 被作为 `Field` 模型的核心属性, 直接追加到了 `BaseField` 上. -关联文档: [RecordScope](https://react.formilyjs.org/zh-CN/api/components/record-scope) - - -在这个基础之上, 那 ProEditable 就顺利成章的消费 `field.record` 的数据, 在不影响当前表单响应模型的基础上; -使用 ProEditable 所在 `field` 上的属性与 `form` 上共享的组件与属性, 创建一个子表单, 供给弹窗消费; - -### 代码案例: 在普通 Object 对象跟随使用 - - -更常见的 - -### 代码案例: 在列表中使用 - diff --git a/docs/components/pro/query-list.mdx b/docs/components/pro/query-list.mdx index 4e64428..8fc5a18 100644 --- a/docs/components/pro/query-list.mdx +++ b/docs/components/pro/query-list.mdx @@ -54,6 +54,6 @@ type QueryFormProps = React.PropsWithChildren<{ ## QueryTable 查询表格 QueryTable是基于 ProArrayTable 的封装, 只是处理与 QueryList 相关联的分页与查询逻辑; -所以几个功能钩子 `usePagination/useRowSelection/useExpandable` 是可以直接用的; 属性也完全一致; +所以几个功能钩子 `ProArrayTable.xxx` 是可以直接用的; 属性也完全一致; diff --git a/docs/components/pro/shadow-form.mdx b/docs/components/pro/shadow-form.mdx index ab866b9..242a969 100644 --- a/docs/components/pro/shadow-form.mdx +++ b/docs/components/pro/shadow-form.mdx @@ -1,16 +1,92 @@ # 👻 ShaowForm 影子表单 -> 脱离于当前 Form 响应式上下文的子表单 +什么是影子表单?影子表单是脱离于当前 Form 响应式上下文的子表单 -这很有用, 解决表格中的弹窗编辑与增加, 以及对象弹窗编辑的 Pro!Editable +> 想攻击我?先试着和影子玩🥊吧 -### 代码案例: Confirm - +### 有什么用? +当你想对部分对象字段进行修改的时候,但又不想对当前表单响应式模型产生影响,可以考虑使用影子表单。如果只是针对单个字段修改的话,你可能在找Editable -### 代码案例: 在列表中使用 - +一个常见的例子:表格里面的行数据编辑,或者大数据对象的部分属性编辑,常见的几种方案 + +1. 使用路径系统将当前行所有数据转换为 editable +2. 使用 Editable 组件对可编辑组件进行包裹 +3. 使用 FormDialog/FormDrawer 进行弹窗编辑 + +各有优劣,方案1,2 只能处理编辑内容与展示内容一致的情况,比如根据行id获取更多详情来编辑就处理不了。内容变更范围不好析出,假如变更内容是需要异步保存到服务器,请求出错本地变更数据回滚就会很麻烦,特别是 initialValues 可能并不像你想的那样工作。我的issues +方案3实际体验相对较差,体现在 + +a. 这两个是api调用,不能使用 schema 描述出来 + +b. 创建的弹窗脱离了当前 root 树, ConfigProvider 之类的全局contex 需要到单独再配置一份,很麻烦。 + +### 根据我练习时长两年半的 CRUD 经验, 我期望: + +1. 能使用 Schema (没戳!我就是 schema 语法铁粉)描述 +2. 不污染当前数据与响应模型,不然怎么叫 shadow +3. 用起来不要太麻烦,既然是子表单,共享一下根表单的一些配置属性也理所当然吧 + +**得益于 formily 优秀的 api 设计, 设计思路如下** + +- 使用 SchemaFieldOptionsContext 获取根节点 createSchemaField 传入的注册组件与 scope, 省去重新 createSchema 的麻烦 +- 使用 createForm 创建一个新的影子 form, 与根表单没有任何关系, 当然也需要提供可配置项, 可以传入自定义的带 effects 等配置的form实例代替默认配置 +- 组件自身在根表单中是一个void节点,不产生任何数据污染 +- 数据初始值获取方式 promise, 子表单实例 form.setvalues ([为什么不是initValues?](https://github.com/alibaba/formily/pull/4053)),field.record 非常适合作为默认值[链接一下 formily curd规范 +- 相比 Editable, 编辑之后 form reset/submit 确认状态交还给用户处理,去代替原始值or发起保存/更新请求。 +- 影子表单的 schema 配置 +- x-component 还是 x-decorator ? 我全都要! + +> Tip: 为什么 field.record 非常适合? +> 这篇[标准化CRUD作用域变量规范](https://github.com/alibaba/formily/discussions/3207) 直指 formily 模型抽象核心的内容就尤为重要, 这里面提到的 record 是如此的重要; +以至于在 2.2.19 版本中, record 被作为 Field 模型的核心属性, 直接追加到了 BaseField 上. + +> Tip: 为什么增加 schema 属性配置, 直接使用 properties 不行吗 +> 可以是可以, 但外层 form 的 RecursionField (@formily/react#RecursionField) 在渲染的时候, 会进行构建, 生成 children 扔给组件处理, 虽然可以扔掉不用, 但多余的创建也是不必要的浪费 + +### 代码案例 + + +### ShadowForm + +```tsx | pure +export interface IShadowFormOptions { + /** 子表单Schema */ + schema: ISchema; + /** createSchemaField 函数配置, components 和 scope */ + schemaFieldOptions?: Parameters[0]; + /** createForm 创建的表单实例, 有自定义 effects 等可以通过自定义一个 form 这个传入 */ + form?: ReturnType; +} + +export interface ShadowFormWrappedProps { + form: ReturnType; + SchemaField: ReturnType; + schema: ISchema; +} + +``` + +### ShadowModal +> 注意! 这跟 ProArrayTable.ShadowModal 并不一样 + +```tsx | pure +export const ShadowModal: React.FC< + ShadowFormWrappedProps & + Omit, "onOk"> & { + initLoader?: React.MutableRefObject<() => object | Promise>; + children?: React.ReactElement; + onOk?: (data: any) => void | Promise; + } +> +``` + +### ShadowDrawer + +> Welcome PR. ### useShadowForm -### ShadowFormProvider +```tsx | pure +type useShadowForm = (opts: IShadowFormOptions) => ShadowFormWrappedProps +``` diff --git a/docs/install.mdx b/docs/install.mdx index 2cb0ae4..5a0489a 100644 --- a/docs/install.mdx +++ b/docs/install.mdx @@ -3,7 +3,7 @@ ProFormily 是基于 @formily/antd 而开发的模板组件, 提供了更高级别的抽象与配置简化功能增强, 开箱即用. 可以显著地提升制作 CRUD 页面的效率, 希望能让你少加班. -> 同时, 得益于 API 的相似性, 重用的大部分逻辑代码, 实现了 `@formily/antd` / `@formily/antd-v5` / `arco.formily` 三个基础库的适配. +> 同时, 得益于 API 的相似性, 重用的大部分逻辑代码, 实现了 @formily/antd / @formily/antd-v5 / arco.formily 三个基础库的适配. ## 安装 diff --git a/package.json b/package.json index dfbe6cf..8b734bf 100644 --- a/package.json +++ b/package.json @@ -49,11 +49,12 @@ "devDependencies": { "@biomejs/biome": "^1.5.0", "@faker-js/faker": "^8.3.1", - "@rsbuild/plugin-react": "latest", "@rsbuild/core": "latest", + "@rsbuild/plugin-react": "latest", "@rspress/plugin-preview": "latest", "@types/bun": "^1.0.2", "@types/node": "^16.18.70", + "@types/qs": "^6.9.11", "@types/react": "^18.2.47", "@types/react-dom": "^18.2.18", "@types/react-resizable": "^3.0.7", diff --git a/src/__builtins__/checkers.ts b/src/__builtins__/checkers.ts new file mode 100644 index 0000000..218b3e1 --- /dev/null +++ b/src/__builtins__/checkers.ts @@ -0,0 +1,2 @@ +export const isReactElement = (obj: any): obj is React.ReactElement => + obj?.$$typeof && obj?._owner; diff --git a/src/__builtins__/index.ts b/src/__builtins__/index.ts index 6d8f1b2..e567b0f 100644 --- a/src/__builtins__/index.ts +++ b/src/__builtins__/index.ts @@ -1,2 +1,3 @@ export * from "./utils"; export * from "./pickomit"; +export * from "./checkers"; diff --git a/src/dict/helper.ts b/src/dict/helper.ts index a0f7beb..a652a4e 100644 --- a/src/dict/helper.ts +++ b/src/dict/helper.ts @@ -73,7 +73,7 @@ export const registerDictLoader = ( return loaders[name]; }; -export const dictEffects = (form: Form) => { +export const dictEffects = (form: Form, hijack?: boolean) => { onFieldMount("*", (field) => { const maybe = field.data?.dict; if (!maybe) return; @@ -118,6 +118,7 @@ export const dictEffects = (form: Form) => { }); onFieldReact("*", (field) => { + if (!hijack) return; const maybe = field.data?.dict; if (!maybe) return; diff --git a/src/index.ts b/src/index.ts index 2520bed..6c6f13d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,7 +6,6 @@ export * from "./dict"; export * from "./image-view"; export * from "./long-text"; export * from "./suggestion"; -export * from "./pro-editable"; export * from "./shared"; export * from "./linkage"; export * from "./shadow-form"; diff --git a/src/pro-array-table/features/pro-settings.tsx b/src/pro-array-table/features/pro-settings.tsx index b004a58..35b3fed 100644 --- a/src/pro-array-table/features/pro-settings.tsx +++ b/src/pro-array-table/features/pro-settings.tsx @@ -35,7 +35,7 @@ const schema: ISchema = { properties: { columns: { type: "array", - "x-component": "ArrayTablePro", + "x-component": "ProArrayTable", "x-component-props": { bordered: false, settings: false, @@ -48,21 +48,21 @@ const schema: ISchema = { properties: { _sort: { type: "void", - "x-component": "ArrayTablePro.Column", + "x-component": "ProArrayTable.Column", "x-component-props": { width: 40, }, properties: { sort: { type: "void", - "x-component": "ArrayTablePro.SortHandle", + "x-component": "ProArrayTable.SortHandle", }, }, }, // TODO: pin left | right _show: { type: "void", - "x-component": "ArrayTablePro.Column", + "x-component": "ProArrayTable.Column", "x-component-props": { width: 40, }, @@ -75,7 +75,7 @@ const schema: ISchema = { }, _title: { type: "void", - "x-component": "ArrayTablePro.Column", + "x-component": "ProArrayTable.Column", "x-component-props": { width: 60, }, @@ -104,7 +104,7 @@ export const ProSettings: React.FC<{ Input, NumberPicker, Radio, - ArrayTablePro: ProArrayTable, + ProArrayTable, PreviewText, Checkbox, }, diff --git a/src/pro-array-table/hooks.tsx b/src/pro-array-table/hooks.tsx index f3d17c5..2da75c9 100644 --- a/src/pro-array-table/hooks.tsx +++ b/src/pro-array-table/hooks.tsx @@ -2,9 +2,12 @@ import { ArrayField, isArrayField } from "@formily/core"; import { Schema } from "@formily/json-schema"; import { RecursionField, useField, useFieldSchema } from "@formily/react"; import { isArr } from "@formily/shared"; -import { useState } from "react"; +import { useContext, useState } from "react"; import type { ResizeCallbackData } from "react-resizable"; import { ArrayBase } from "../adaptor/adaptor"; +import { TableExpandableContext } from "./features/expandable"; +import { TablePaginationContext } from "./features/pagination"; +import { TableRowSelectionContext } from "./features/row-selection"; import { isAdditionComponent, isColumnComponent, @@ -194,6 +197,14 @@ export const useArrayTableSources = () => { return sources; }; +export const useProArrayTableContext = () => { + const array = ArrayBase.useArray(); + const pagination = useContext(TablePaginationContext); + const rowSelection = useContext(TableRowSelectionContext); + const expanedable = useContext(TableExpandableContext); + return { array, pagination, rowSelection, expanedable }; +}; + export const useExpandRender = (index: number) => { const schema = useFieldSchema(); const array = ArrayBase.useArray(); diff --git a/src/pro-array-table/index.ts b/src/pro-array-table/index.ts index c4f73b4..43a87c6 100644 --- a/src/pro-array-table/index.ts +++ b/src/pro-array-table/index.ts @@ -1,4 +1,4 @@ export { ProArrayTable } from "./pro"; -export { RowSelectionPro } from "./mixin"; +export { RowSelectionPro } from "./mixin.pro"; export type { ProArrayTableProps } from "./types"; export { ArrayTableDelegateContext } from "./features/delegate"; diff --git a/src/pro-array-table/mixin.base.tsx b/src/pro-array-table/mixin.base.tsx new file mode 100644 index 0000000..1486c85 --- /dev/null +++ b/src/pro-array-table/mixin.base.tsx @@ -0,0 +1,290 @@ +import { ISchema, ReactFC, useField } from "@formily/react"; +import { clone, isUndef, isValid } from "@formily/shared"; +import cls from "classnames"; +import React, { Fragment, useContext } from "react"; +import { + BUTTON_TYPE, + Button, + CopyOutlined, + DeleteOutlined, + DownOutlined, + PlusOutlined, + Popconfirm, + UpOutlined, +} from "../adaptor"; +import { ArrayBase, IArrayBaseContext, usePrefixCls } from "../adaptor/adaptor"; +import { TablePaginationContext } from "./features/pagination"; +import type { ColumnProps, IProArrayTableBaseMixins } from "./types"; + +export const Column: ReactFC> = () => { + return ; +}; +export const RowExpand: ReactFC> = () => { + return ; +}; +const useArray = ArrayBase.useArray; +const useIndex = ArrayBase.useIndex; + +const getSchemaDefaultValue = (schema: ISchema) => { + if (schema?.type === "array") return []; + if (schema?.type === "object") return {}; + if (schema?.type === "void") { + for (const key in schema.properties) { + const value = getSchemaDefaultValue( + (schema.properties as any)[key], + ) as any; + if (isValid(value)) return value; + } + } +}; + +const getDefaultValue = (defaultValue: any, schema: ISchema) => { + if (isValid(defaultValue)) return clone(defaultValue); + if (Array.isArray(schema?.items)) + return getSchemaDefaultValue(schema?.items[0]); + return getSchemaDefaultValue(schema?.items as any); +}; + +type ProArrayBaseContext = Omit & { + props: IProArrayTableBaseMixins & { disabled?: boolean }; +}; + +export const Addition = ( + props: React.ComponentProps, +) => { + const self = useField(); + const array = useArray() as ProArrayBaseContext; + const page = useContext(TablePaginationContext); + + const prefixCls = usePrefixCls("formily-array-base"); + if (!array) return null; + if ( + array.field?.pattern !== "editable" && + array.field?.pattern !== "disabled" + ) + return null; + return ( + + ); +}; +export const Copy = React.forwardRef( + (props: React.ComponentProps, ref: any) => { + const self = useField(); + const array = useArray() as ProArrayBaseContext; + + const index = useIndex(props.index); + const prefixCls = usePrefixCls("formily-array-base"); + if (!array) return null; + if (array.field?.pattern !== "editable") return null; + return ( + + ); + }, +); + +export const Remove = React.forwardRef( + ( + props: React.ComponentProps & { + confirm?: true | React.ComponentProps; + }, + ref: any, + ) => { + const index = useIndex(props.index); + const self = useField(); + const array = useArray() as ProArrayBaseContext; + + const prefixCls = usePrefixCls("formily-array-base"); + if (!array) return null; + if (array.field?.pattern !== "editable") return null; + const onClick = (e: any) => { + if (self?.disabled) return; + e.stopPropagation(); + if (props.onClick) { + props.onClick(e); + if (e.defaultPrevented) return; + } + Promise.resolve(() => { + return array.props?.onRemove?.(index, array.field); + }).then(() => { + array.field?.remove?.(index); + }); + }; + const Wrapper = props.confirm ? Popconfirm : React.Fragment; + const confirmProps = + props.confirm && props.confirm !== true + ? props.confirm + : { + onConfirm: onClick, + title: "确定要删除此项吗?", + okText: "我确定", + cancel: "我在想想", + }; + return ( + + + + ); + }, +); + +export const MoveDown = React.forwardRef( + (props: React.ComponentProps, ref: any) => { + const index = useIndex(props.index); + const self = useField(); + const array = useArray() as ProArrayBaseContext; + + const prefixCls = usePrefixCls("formily-array-base"); + if (!array) return null; + if (array.field?.pattern !== "editable") return null; + return ( + + ); + }, +); + +export const MoveUp = React.forwardRef( + (props: React.ComponentProps, ref: any) => { + const index = useIndex(props.index); + const self = useField(); + const array = useArray() as ProArrayBaseContext; + + const prefixCls = usePrefixCls("formily-array-base"); + if (!array) return null; + if (array.field?.pattern !== "editable") return null; + return ( + + ); + }, +); diff --git a/src/pro-array-table/mixin.tsx b/src/pro-array-table/mixin.pro.tsx similarity index 74% rename from src/pro-array-table/mixin.tsx rename to src/pro-array-table/mixin.pro.tsx index 4786320..7067fb6 100644 --- a/src/pro-array-table/mixin.tsx +++ b/src/pro-array-table/mixin.pro.tsx @@ -1,23 +1,10 @@ -import { FormProvider, ReactFC, observer, useField } from "@formily/react"; +import { FormProvider, useField } from "@formily/react"; import { toJS } from "@formily/reactive"; -import React, { Fragment, useContext, useEffect, useRef } from "react"; +import React, { useContext, useEffect, useRef } from "react"; import { omit, pick } from "../__builtins__"; -import { - Alert, - BUTTON_TYPE, - Button, - Divider, - Drawer, - Modal, - Space, -} from "../adaptor"; -import { ArrayBase, ArrayBaseMixins } from "../adaptor/adaptor"; -import { - IShadowFormOptions, - ShadowFormContext, - ShadowFormProvider, - useShadowForm, -} from "../shadow-form/shadow-form"; +import { Alert, BUTTON_TYPE, Button, Divider, Modal, Space } from "../adaptor"; +import { ArrayBase } from "../adaptor/adaptor"; +import { IShadowFormOptions, useShadowForm } from "../shadow-form/shadow-form"; import { useRecord } from "../shared"; import { ArrayTableDelegateContext, @@ -25,41 +12,8 @@ import { DATE_DELEGATE_INDEX_KEY, IArrayTableDelegateContext, } from "./features/delegate"; -import { TableExpandableContext } from "./features/expandable"; -import { TablePaginationContext } from "./features/pagination"; import { TableRowSelectionContext } from "./features/row-selection"; -import type { ColumnProps } from "./types"; - -export const Column: ReactFC> = () => { - return ; -}; -export const RowExpand: ReactFC> = () => { - return ; -}; - -export const Addition: ArrayBaseMixins["Addition"] = observer((props) => { - const array = ArrayBase.useArray(); - const page = useContext(TablePaginationContext); - return ( - { - // 如果添加数据后将超过当前页,则自动切换到下一页 - if (!page) return; - const total = array?.field?.value.length || 0; - if (total >= page!.current! * page.pageSize!) { - page.setPagination((memo) => { - return { ...memo, current: memo.current + 1 }; - }); - } - props.onClick?.(e); - }} - /> - ); -}); +import { useProArrayTableContext } from "./hooks"; const justifyContentList: Required["justifyContent"][] = [ "space-around", @@ -176,15 +130,8 @@ export const RowSelectionPro = (props: { ) : null; }; -export const useProArrayTableContext = () => { - const array = ArrayBase.useArray(); - const pagination = useContext(TablePaginationContext); - const rowSelection = useContext(TableRowSelectionContext); - const expanedable = useContext(TableExpandableContext); - return { array, pagination, rowSelection, expanedable }; -}; - export interface CommonShadowPopup extends IShadowFormOptions { + act?: string; onCancel?: ( ctx: ReturnType, ) => void | Promise; @@ -193,13 +140,16 @@ export interface CommonShadowPopup extends IShadowFormOptions { ctx: ReturnType, ) => void | Promise; } -export const ShadowModal: React.FC< + +export const ArrayTableShowModal: React.FC< Omit, "children" | "onCancel" | "onOk"> & CommonShadowPopup > = (props) => { - const { SchemaField, form, act, schema } = useShadowForm( - pick(props, "act", "schema", "schemaFieldOptions", "form"), + const { SchemaField, form, schema } = useShadowForm( + pick(props, "schema", "schemaFieldOptions", "form"), ); + const act = props.act ?? schema.name; + const field = useField(); const delegate = useContext(ArrayTableDelegateContext); const visible = delegate.act === act && delegate.index > -1; const pending = useRef(false); @@ -207,7 +157,6 @@ export const ShadowModal: React.FC< useEffect(() => { if (visible) { - // 需要等等的 trigger 里面 initLoader.current set 的 nextick 吗? Promise.resolve(delegate.initLoader?.current?.()).then((init) => { form.setInitialValues(toJS(init || {})); }); @@ -220,7 +169,7 @@ export const ShadowModal: React.FC< return new Promise((resolve) => { // 优化关闭展示 setTimeout(() => { - return resolve(form.reset("*", { forceClear: true, validate: true })); + return resolve(form.reset()); }, 200); }).finally(() => { pending.current = false; @@ -231,6 +180,7 @@ export const ShadowModal: React.FC< { if (pending.current) return; return form @@ -310,8 +260,8 @@ export const ProAddition = ({ >) => { return ( - - + + ); }; diff --git a/src/pro-array-table/pro.tsx b/src/pro-array-table/pro.tsx index 99e8b35..b99eb1a 100644 --- a/src/pro-array-table/pro.tsx +++ b/src/pro-array-table/pro.tsx @@ -1,4 +1,4 @@ -import { ArrayField } from "@formily/core"; +import { ArrayField, JSXComponent } from "@formily/core"; import { ReactFC, RecursionField, @@ -6,8 +6,7 @@ import { useField, useFieldSchema, } from "@formily/react"; -import React, { useEffect, useRef } from "react"; -import { useContext } from "react"; +import React, { useContext, useEffect, useRef } from "react"; import { Pagination, Space, @@ -41,289 +40,313 @@ import { useArrayTableColumns, useArrayTableSources, useFooter, + useProArrayTableContext, useShadowComponents, useToolbar, } from "./hooks"; import { Addition, Column, + Copy, + MoveDown, + MoveUp, + Remove, + RowExpand, +} from "./mixin.base"; +import { + ArrayTableShowModal, DelegateAction, Flex, ProAddition, - RowExpand, RowSelectionPro, - ShadowModal, -} from "./mixin"; +} from "./mixin.pro"; import { IChangeData, ProArrayTableProps } from "./types"; -const ArrayTableProInside: ReactFC = observer((props) => { - const wrapperRef = useRef(null); - const field = useField(); - const schema = useFieldSchema(); - const prefixCls = usePrefixCls("formily-array-table-pro"); +const ArrayTableProInside: ReactFC = observer( + ({ onAdd, onRemove, onCopy, onMoveDown, onMoveUp, onSortEnd, ...props }) => { + const wrapperRef = useRef(null); + const field = useField(); + const schema = useFieldSchema(); + const prefixCls = usePrefixCls("formily-array-table-pro"); - const [wrapSSR, hasId] = useStyle(prefixCls); - const delegateCtx = useDelegate(); - /** - * 优化笔记: - * 本来以为这个 slice 没什么用,直到我膝盖中了一箭 - * 联动: useArrayTableSources -> useColumns -> render -> indexOf - */ - const dataSource = Array.isArray(field.value) ? field.value.slice() : []; - const sources = useArrayTableSources(); + const [wrapSSR, hasId] = useStyle(prefixCls); + const delegateCtx = useDelegate(); + /** + * 优化笔记: + * 本来以为这个 slice 没什么用,直到我膝盖中了一箭 + * 联动: useArrayTableSources -> useColumns -> render -> indexOf + */ + const dataSource = Array.isArray(field.value) ? field.value.slice() : []; + const sources = useArrayTableSources(); - const [columns, proColumns] = useArrayTableColumns( - field, - sources, - dataSource, - ); + const [columns, proColumns] = useArrayTableColumns( + field, + sources, + dataSource, + ); - /** 因为 pagination 被我们重写了, 所以需要很啰嗦的处理一下 */ - const changeData = useRef({ - pagination: {}, - filters: {}, - sorter: {}, - extra: {} as any, - }); + /** 因为 pagination 被我们重写了, 所以需要很啰嗦的处理一下 */ + const changeData = useRef({ + pagination: {}, + filters: {}, + sorter: {}, + extra: {} as any, + }); - const handlePageChange = ( - current: number, - pageSize: number, - other: typeof props.pagination, - ) => { - if (!props.onTableChange) return; - changeData.current.pagination = { - ...other, - current, - pageSize, + const handlePageChange = ( + current: number, + pageSize: number, + other: typeof props.pagination, + ) => { + if (!props.onTableChange) return; + changeData.current.pagination = { + ...other, + current, + pageSize, + }; + props.onTableChange( + changeData.current.pagination, + changeData.current.filters, + changeData.current.sorter, + changeData.current.extra, + ); }; - props.onTableChange( - changeData.current.pagination, - changeData.current.filters, - changeData.current.sorter, - changeData.current.extra, - ); - }; - const [pageCtx, page] = usePagination( - props.pagination, - dataSource.length, - handlePageChange, - ); + const [pageCtx, page] = usePagination( + props.pagination, + dataSource.length, + handlePageChange, + ); - const startIndex = pageCtx ? (pageCtx.current! - 1) * pageCtx.pageSize! : 0; - const endIndex = pageCtx ? startIndex + pageCtx.pageSize! : 0; + const startIndex = pageCtx ? (pageCtx.current! - 1) * pageCtx.pageSize! : 0; + const endIndex = pageCtx ? startIndex + pageCtx.pageSize! : 0; - const dataSlice = - pageCtx && props.slice !== false - ? dataSource.slice(startIndex, endIndex) - : dataSource; + const dataSlice = + pageCtx && props.slice !== false + ? dataSource.slice(startIndex, endIndex) + : dataSource; - const addition = useAddition(); - const toolbar = useToolbar(); - const footer = useFooter(); - const shaowPops = useShadowComponents(); + const addition = useAddition(); + const toolbar = useToolbar(); + const footer = useFooter(); + const shaowPops = useShadowComponents(); - const rowKey = (record: any) => { - return props.rowKey - ? typeof props.rowKey === "function" - ? props.rowKey(record) - : record?.[props.rowKey as string] - : dataSource.indexOf(record); - }; + const rowKey = (record: any) => { + return props.rowKey + ? typeof props.rowKey === "function" + ? props.rowKey(record) + : record?.[props.rowKey as string] + : dataSource.indexOf(record); + }; - const rowKeyRef = useRef(rowKey); - useEffect(() => { - rowKeyRef.current = rowKey; - }, [rowKey]); + const rowKeyRef = useRef(rowKey); + useEffect(() => { + rowKeyRef.current = rowKey; + }, [rowKey]); - const [expandableCtx, expandable] = useExpandable( - props.expandable, - dataSlice, - rowKeyRef, - pageCtx?.current, - ); + const [expandableCtx, expandable] = useExpandable( + props.expandable, + dataSlice, + rowKeyRef, + pageCtx?.current, + ); - const [rowSelectionCtx, rowSelection] = useRowSelection( - props.rowSelection, - pageCtx?.current, - ); + const [rowSelectionCtx, rowSelection] = useRowSelection( + props.rowSelection, + pageCtx?.current, + ); - let showPage = true; - if (!page) { - showPage = false; - } else if (page.hideOnSinglePage) { - showPage = (page.pageSize || 0) < (page?.total || 0); - } else { - showPage = true; - } + let showPage = true; + if (!page) { + showPage = false; + } else if (page.hideOnSinglePage) { + showPage = (page.pageSize || 0) < (page?.total || 0); + } else { + showPage = true; + } - const pagination = showPage ? ( -
- -
- ) : null; + const pagination = showPage ? ( +
+ +
+ ) : null; - const showHeader = - props.title || - props.rowSelection || - toolbar || - addition || - props.settings !== false; + const showHeader = + props.title || + props.rowSelection || + toolbar || + addition || + props.settings !== false; - const _header = !showHeader ? null : ( - - - {props.title ? ( - typeof props.title === "function" ? ( - props.title(dataSource) - ) : ( - - {props.title} - - ) - ) : null} - {rowSelection?.showPro === "top" ? ( - - ) : null} - - - {toolbar} - {addition} - {!props.extra && props.settings === false ? null : ( - - {props.extra} - {props.settings !== false ? ( - - ) : null} - - )} + const _header = !showHeader ? null : ( + + + {props.title ? ( + typeof props.title === "function" ? ( + props.title(dataSource) + ) : ( + + {props.title} + + ) + ) : null} + {rowSelection?.showPro === "top" ? ( + + ) : null} + + + {toolbar} + {addition} + {!props.extra && props.settings === false ? null : ( + + {props.extra} + {props.settings !== false ? ( + + ) : null} + + )} + - - ); + ); - const showFooter = props.footer || footer || pagination; - const _footer = !showFooter ? null : ( - - - - {footer} - {pagination} + const showFooter = props.footer || footer || pagination; + const _footer = !showFooter ? null : ( + + + + {footer} + {pagination} + - - ); - - const header = useResizeHeader({ - enable: props.resizeable, - }); + ); - const body = useSortableBody(dataSource, (from, to) => field.move(from, to), { - wrapperRef: wrapperRef, - enable: hasSortable(schema), - prefixCls, - start: startIndex, - }); + const header = useResizeHeader({ + enable: props.resizeable, + }); - const handleDelegate = (e: React.SyntheticEvent) => { - let target = e.target as HTMLElement; - while ( - target && - target !== wrapperRef.current && - !isDelegateTarget(target) - ) { - target = target.parentElement as any; - } - if (isDelegateTarget(target)) { - const info = getDelegateInfo(target); - if (!info) return; - delegateCtx.setAct({ index: info.index, act: info.act! }); - } - }; + const body = useSortableBody( + dataSource, + (from, to) => + Promise.resolve(() => { + return onSortEnd?.(from, to, field); + }).then(() => field.move(from, to)), + { + wrapperRef: wrapperRef, + enable: hasSortable(schema), + prefixCls, + start: startIndex, + }, + ); - return wrapSSR( -
- - - - - - {_header} + const handleDelegate = (e: React.SyntheticEvent) => { + let target = e.target as HTMLElement; + while ( + target && + target !== wrapperRef.current && + !isDelegateTarget(target) + ) { + target = target.parentElement as any; + } + if (isDelegateTarget(target)) { + const info = getDelegateInfo(target); + if (!info) return; + delegateCtx.setAct({ index: info.index, act: info.act! }); + } + }; - { - if (!props.onTableChange) return; - changeData.current.filters = filters; - changeData.current.sorter = sorter; - changeData.current.extra = extra; - props.onTableChange( - changeData.current.pagination, - filters, - sorter, - extra, - ); - }} - pagination={false as any} - columns={columns} - dataSource={dataSlice} - components={{ - header: { - ...props.components?.header, - ...header, - }, - body: { - ...props.components?.body, - ...body, - }, - }} - /> - {_footer} - {sources.map((column, key) => { - if (!isColumnComponent(column.schema)) return; - return React.createElement(RecursionField, { - name: column.name, - schema: column.schema, - onlyRenderSelf: true, - key, - }); - })} - {shaowPops} - - - - - - , - ); -}); + return wrapSSR( +
+ + + + + + {_header} +
{ + if (!props.onTableChange) return; + changeData.current.filters = filters; + changeData.current.sorter = sorter; + changeData.current.extra = extra; + props.onTableChange( + changeData.current.pagination, + filters, + sorter, + extra, + ); + }} + pagination={false as any} + columns={columns} + dataSource={dataSlice} + components={{ + header: { + ...props.components?.header, + ...header, + }, + body: { + ...props.components?.body, + ...body, + }, + }} + /> + {_footer} + {sources.map((column, key) => { + if (!isColumnComponent(column.schema)) return; + return React.createElement(RecursionField, { + name: column.name, + schema: column.schema, + onlyRenderSelf: true, + key, + }); + })} + {shaowPops} + + + + + + , + ); + }, +); const useTableExpandable = () => { return useContext(TableExpandableContext); @@ -340,16 +363,40 @@ const useTablePagination = () => { export const ProArrayTable = Object.assign( ArrayBase.mixin(ArrayTableProInside), { + Copy, + MoveUp, + MoveDown, + Remove, Column, Addition, ProAddition, RowExpand, - ShadowModal, + ShadowModal: ArrayTableShowModal, DelegateAction, useTableExpandable, useTableRowSelection, useTablePagination, + useProArrayTableContext, }, ); -ProArrayTable.displayName = "ArrayTablePro"; +export const ProArrayTableMixin = (o: T) => { + return Object.assign(ArrayBase.mixin(o as any), { + Copy, + MoveUp, + MoveDown, + Remove, + Column, + Addition, + ProAddition, + RowExpand, + ShadowModal: ArrayTableShowModal, + DelegateAction, + useTableExpandable, + useTableRowSelection, + useTablePagination, + useProArrayTableContext, + }); +}; + +ProArrayTable.displayName = "ProArrayTable"; diff --git a/src/pro-array-table/types.tsx b/src/pro-array-table/types.tsx index c56d055..e9c83da 100644 --- a/src/pro-array-table/types.tsx +++ b/src/pro-array-table/types.tsx @@ -1,4 +1,5 @@ -import { FieldDisplayTypes, GeneralField } from "@formily/core"; +import { ArrayBase } from "@formily/antd"; +import { ArrayField, FieldDisplayTypes, GeneralField } from "@formily/core"; import { Schema } from "@formily/json-schema"; import type { Table } from "../adaptor"; @@ -26,25 +27,55 @@ export type Mutable = { -readonly [K in keyof T]: T[K]; }; -export type ProArrayTableProps = Omit, "title"> & { - title: string | TableProps["title"]; - footer: string | TableProps["footer"]; - rowSelection?: - | (Exclude & { - showPro?: "top" | "bottom" | false; - }) - | true; - /** 列表配置齿轮, 默认 true */ - settings?: boolean; - /** 在列表配置齿轮左边的位置 */ - extra?: React.ReactNode; - /** 表头列宽是否可拖动, 默认 true, 整体配置, column 配置中可以单独关闭 */ - resizeable?: boolean; - /** 是否开启根据 page 信息 slice 性能优化, 默认开启 */ - slice?: boolean; - /** onChange 与 formily#field 的 onChange 冲突,但很有用, 所以改个名字 */ - onTableChange?: TableProps["onChange"]; -}; +// interface IArrayBaseProps { +// disabled?: boolean; +// onAdd?: (index: number) => void; +// onCopy?: (index: number) => void; +// onRemove?: (index: number) => void; +// onMoveDown?: (index: number) => void; +// onMoveUp?: (index: number) => void; +// } +export interface IProArrayTableBaseMixins { + onSortEnd?: ( + form: number, + to: number, + arrayField: ArrayField, + ) => void | Promise; + onAdd?: ( + toIndex: number, + value: any, + arrayField: ArrayField, + ) => void | Promise; + onCopy?: ( + distIndex: number, + value: any, + arrayField: ArrayField, + ) => void | Promise; + onRemove?: (index: number, arrayField: ArrayField) => void | Promise; + onMoveUp?: (index: number, arrayField: ArrayField) => void | Promise; + onMoveDown?: (index: number, arrayField: ArrayField) => void | Promise; +} + +export type ProArrayTableProps = Omit, "title"> & + IProArrayTableBaseMixins & { + title: string | TableProps["title"]; + footer: string | TableProps["footer"]; + rowSelection?: + | (Exclude & { + showPro?: "top" | "bottom" | false; + }) + | true; + /** 列表配置齿轮, 默认 true */ + settings?: boolean; + /** 在列表配置齿轮左边的位置 */ + extra?: React.ReactNode; + /** 表头列宽是否可拖动, 默认 true, 整体配置, column 配置中可以单独关闭 */ + resizeable?: boolean; + /** 是否开启根据 page 信息 slice 性能优化, 默认开启 */ + slice?: boolean; + /** onChange 与 formily#field 的 onChange 冲突,但很有用, 所以改个名字 */ + onTableChange?: TableProps["onChange"]; + }; export interface ObservableColumnSource { field?: GeneralField; columnProps: ColumnProps & { diff --git a/src/pro-editable/Drawer.tsx b/src/pro-editable/Drawer.tsx deleted file mode 100644 index dafd4c1..0000000 --- a/src/pro-editable/Drawer.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { useField } from "@formily/react"; -import React, { useState } from "react"; -import { BUTTON_TYPE, Button, Drawer } from "../adaptor"; -import { RecordFormProps, useFieldRecordForm } from "./hooks"; - -export const ProDrawer: React.FC< - RecordFormProps & - React.ComponentProps & { - onCancel: () => void | Promise; - onConfirm: (data: any) => void | Promise; - } -> = ({ children, ...fieldProps }) => { - const field = useField(); - const { body, form, FormButtons } = useFieldRecordForm(fieldProps); - const [visible, setVisible] = useState(false); - const props = field.componentProps; - return ( - - setVisible(false)} - footer={ - { - form - .reset() - .then(props.onCancel) - .then(() => { - setVisible(false); - }); - }} - onSubmit={() => { - form - .submit() - .then(props.onConfirm) - .then(() => { - setVisible(false); - }); - }} - /> - } - > - {body} - - - - ); -}; diff --git a/src/pro-editable/Modal.tsx b/src/pro-editable/Modal.tsx deleted file mode 100644 index 8f25e66..0000000 --- a/src/pro-editable/Modal.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { useField } from "@formily/react"; -import React, { useState } from "react"; -import { BUTTON_TYPE, Button, Modal } from "../adaptor"; -import { RecordFormProps, useFieldRecordForm } from "./hooks"; - -export const ProModal: React.FC< - RecordFormProps & - React.ComponentProps & { - onCancel: () => void | Promise; - onConfirm: (data: any) => void | Promise; - } -> = ({ children, ...fieldProps }) => { - const field = useField(); - const { body, form } = useFieldRecordForm(fieldProps); - const [visible, setVisible] = useState(false); - const props = field.componentProps; - - return ( - - { - return form - .reset() - .then(props.onCancel) - .then(() => setVisible(false)); - }} - onOk={() => { - return form - .submit() - .then(props.onConfirm) - .then(() => setVisible(false)); - }} - > - {body} - - - - ); -}; diff --git a/src/pro-editable/Popconfirm.tsx b/src/pro-editable/Popconfirm.tsx deleted file mode 100644 index e71ff3e..0000000 --- a/src/pro-editable/Popconfirm.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { useField } from "@formily/react"; -import React, { useState } from "react"; -import { BUTTON_TYPE, Button, Popconfirm, Typography } from "../adaptor"; -import { RecordFormProps, useFieldRecordForm } from "./hooks"; - -export const ProPopconfirm: React.FC< - RecordFormProps & - React.ComponentProps & { - onCancel: () => void | Promise; - onConfirm: (data: any) => void | Promise; - } -> = ({ children, ...fieldProps }) => { - const field = useField(); - const { body, form } = useFieldRecordForm(fieldProps); - const [visible, setVisible] = useState(false); - const props = field.componentProps; - - return ( - { - return form - .reset() - .then(props.onCancel) - .then(() => setVisible(false)); - }} - onConfirm={() => { - return form - .submit() - .then(props.onConfirm) - .then(() => setVisible(false)); - }} - title={ - - {props.title as any} - {body} - - } - destroyTooltipOnHide - disabled={!field.editable} - > - - - ); -}; diff --git a/src/pro-editable/hooks.tsx b/src/pro-editable/hooks.tsx deleted file mode 100644 index e43420a..0000000 --- a/src/pro-editable/hooks.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { createForm } from "@formily/core"; -import { - FormProvider, - SchemaOptionsContext, - createSchemaField, - useField, - useFieldSchema, -} from "@formily/react"; -import { toJS } from "@formily/reactive"; -import React, { useContext, useMemo } from "react"; -import { Button } from "../adaptor"; -import { FormButtonGroup, FormGrid, FormLayout } from "../adaptor/adaptor"; -import { useRecord } from "../shared/useRecord"; -export const useRecordIsolationForm = () => { - const parentField = useField(); - const schema = useFieldSchema(); - const options = useContext(SchemaOptionsContext); - const record = useRecord(); - - const form = createForm({ - ...parentField.data?.formOptions, - initialValues: toJS(record ?? {}), - }); - - return { schema, form, options }; -}; - -export type RecordFormProps = React.PropsWithChildren<{ - cancelText?: string; - okText?: string; - grid?: React.ComponentProps; - layout?: React.ComponentProps; - buttonProps?: React.ComponentProps; -}>; - -const buttonGroupStyle: React.CSSProperties = { - width: "100%", - display: "flex", - alignItems: "center", - justifyContent: "flex-end", -}; - -export const useFieldRecordForm = (props: RecordFormProps) => { - const { cancelText, okText, grid, layout } = props; - const { form, options, schema } = useRecordIsolationForm(); - - const gridProps = useMemo(() => { - return { - ...grid, - maxColumns: grid?.maxColumns || 1, - }; - }, [grid]); - - const FormButtons: React.FC<{ - onReset: () => void | Promise; - onSubmit: (data: any) => void | Promise; - }> = (props) => ( - - - - - - - ); - - const SchemaField = createSchemaField(options); - - const body = ( - - - - - {props.children} - - - - ); - - return { body, FormButtons, form }; -}; diff --git a/src/pro-editable/index.tsx b/src/pro-editable/index.tsx deleted file mode 100644 index 7ddf8ad..0000000 --- a/src/pro-editable/index.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { ProDrawer } from "./Drawer"; -import { ProModal } from "./Modal"; -import { ProPopconfirm } from "./Popconfirm"; -export * from "./hooks"; - -export const ProEditable = Object.assign(ProPopconfirm, { - Modal: ProModal, - Drawer: ProDrawer, -}); diff --git a/src/query-form/index.tsx b/src/query-form/index.tsx index 19c0552..9342a18 100644 --- a/src/query-form/index.tsx +++ b/src/query-form/index.tsx @@ -23,7 +23,7 @@ const buttonGroupStyle: React.CSSProperties = { }; export const QueryForm = observer((props: QueryFormProps) => { - const { resetText, submitText } = props; + const { resetText, submitText, layout } = props; const field = useField(); const { grid, expanded, toggle } = useGrid(props.grid!); const querylist = useQueryListContext(); @@ -86,14 +86,16 @@ export const QueryForm = observer((props: QueryFormProps) => { }; return props.children ? ( - - {props.children} - - {renderActions()} - - + + + {props.children} + + {renderActions()} + + + ) : null; }); diff --git a/src/query-table/index.tsx b/src/query-table/index.tsx index a122949..120ed85 100644 --- a/src/query-table/index.tsx +++ b/src/query-table/index.tsx @@ -1,19 +1,17 @@ import type { ArrayField } from "@formily/core"; import { observer, useField } from "@formily/react"; import { useEffect } from "react"; -import { ProArrayTableProps } from "src/pro-array-table/types"; import { BUTTON_TYPE, Button, SyncOutlined, TablePaginationConfig, } from "../adaptor"; -import { ArrayBase } from "../adaptor/adaptor"; -import { Addition, Column, RowExpand } from "../pro-array-table/mixin"; +import { ProArrayTableProps } from "../pro-array-table"; import { ProArrayTable } from "../pro-array-table/pro"; import { useQueryListContext } from "../query-list"; -export const QueryTablePro = observer((overprops: ProArrayTableProps) => { +export const QueryTable = observer((overprops: ProArrayTableProps) => { const field = useField(); const querylist = useQueryListContext(); const page = { @@ -69,10 +67,4 @@ export const QueryTablePro = observer((overprops: ProArrayTableProps) => { ); }); -export const QueryTable = Object.assign(ArrayBase.mixin(QueryTablePro), { - Column, - Addition, - RowExpand, -}); - QueryTable.displayName = "QueryTable"; diff --git a/src/shadow-form/index.tsx b/src/shadow-form/index.tsx index bdb36da..d3386d3 100644 --- a/src/shadow-form/index.tsx +++ b/src/shadow-form/index.tsx @@ -1,5 +1,7 @@ export { - ShadowFormProvider as ShadowForm, - useShadowSchemaField, + useShadowForm, + ShadowForm, } from "./shadow-form"; -export { ShadowPopconfirm } from "./shadow-popconfirm"; +export { ShadowPopconfirm } from "./popconfirm"; +export { ShadowModal } from "./modal"; +export type { ShadowFormWrappedProps } from "./shadow-form"; diff --git a/src/shadow-form/modal.tsx b/src/shadow-form/modal.tsx new file mode 100644 index 0000000..45b3e05 --- /dev/null +++ b/src/shadow-form/modal.tsx @@ -0,0 +1,122 @@ +import { VoidField } from "@formily/core"; +import { FormProvider, observer, useField } from "@formily/react"; +import { toJS } from "@formily/reactive"; +import React, { useEffect, useRef, useState } from "react"; +import { BUTTON_TYPE, Button, Modal } from "../adaptor"; +import { ShadowFormWrappedProps } from "./shadow-form"; + +const HideInBush: React.CSSProperties = { + width: 0, + height: 0, +}; + +export const ShadowModal: React.FC< + ShadowFormWrappedProps & + Omit, "onOk"> & { + initLoader?: React.MutableRefObject<() => object | Promise>; + children?: React.ReactElement; + onOk?: (data: any) => void | Promise; + } +> = observer( + ({ form, schema, SchemaField, initLoader, children, ...props }) => { + const field = useField(); + + const [visible, setVisible] = useState(false); + const pending = useRef(false); + const navRef = useRef(null); + + const init = (c = 0) => { + if (!form) { + return new Promise((resolve) => { + setTimeout(() => { + resolve(init(c + 1)); + }, 16); + }); + } + if (c > 1000) { + return Promise.reject(); + } + if (initLoader?.current) { + return Promise.resolve(initLoader.current()).then((init) => { + form?.setValues(toJS(init || {})); + }); + } else { + return Promise.resolve(form?.setValues(toJS(field.record))); + } + }; + + useEffect(() => { + if (navRef.current) { + const sib = navRef.current.nextSibling; + if (!sib) return; + const onClick = () => { + setVisible(true); + }; + sib.addEventListener("click", onClick); + return () => { + sib.removeEventListener("click", onClick); + }; + } + }, []); + + useEffect(() => { + if (visible) { + init(); + } + }, [visible]); + + const reset = () => { + setVisible(false); + pending.current = true; + return new Promise((resolve) => { + // 优化关闭展示 + setTimeout(() => { + return resolve(form?.reset()); + }, 200); + }).finally(() => { + pending.current = false; + }); + }; + + return ( + + { + if (pending.current) return; + return form + ?.reset() + .then(() => props?.onCancel?.(e)) + .then(() => reset()); + }} + onOk={() => { + if (pending.current) return; + return form + ?.submit() + .then((data: any) => props?.onOk?.(data)) + .then(() => reset()); + }} + > + + + + + {children ? ( + + + {children} + + ) : ( + + )} + + ); + }, +); diff --git a/src/shadow-form/shadow-popconfirm.tsx b/src/shadow-form/popconfirm.tsx similarity index 75% rename from src/shadow-form/shadow-popconfirm.tsx rename to src/shadow-form/popconfirm.tsx index 54a4a5c..301693b 100644 --- a/src/shadow-form/shadow-popconfirm.tsx +++ b/src/shadow-form/popconfirm.tsx @@ -1,26 +1,26 @@ import { Field } from "@formily/core"; import { FormProvider, useField } from "@formily/react"; import { toJS } from "@formily/reactive"; -import React, { useContext, useEffect, useRef, useState } from "react"; +import React, { useEffect, useRef, useState } from "react"; import { BUTTON_TYPE, Button, Popconfirm } from "../adaptor"; -import { ShadowFormContext } from "../shadow-form/shadow-form"; +import { ShadowFormWrappedProps } from "./shadow-form"; export const ShadowPopconfirm: React.FC< - React.ComponentProps & { - initLoader?: React.MutableRefObject<() => object | Promise>; - } -> = ({ initLoader, ...props }) => { + ShadowFormWrappedProps & + React.ComponentProps & { + initLoader?: React.MutableRefObject<() => object | Promise>; + } +> = ({ form, SchemaField, schema, initLoader, ...props }) => { const field = useField(); const [visible, setVisible] = useState(false); const pending = useRef(false); - const { SchemaField, form, schema } = useContext(ShadowFormContext); const reset = () => { pending.current = true; return new Promise((resolve) => { // 优化关闭展示 setTimeout(() => { - return resolve(form.reset("*", { forceClear: true, validate: true })); + return resolve(form.reset()); }, 200); }).finally(() => { pending.current = false; @@ -70,8 +70,15 @@ export const ShadowPopconfirm: React.FC< } + icon={null} > - {props.children ? props.children : field.title} + {props.children ? ( + props.children + ) : ( + + )} ); }; diff --git a/src/shadow-form/shadow-form.tsx b/src/shadow-form/shadow-form.tsx index fa0db10..cceda80 100644 --- a/src/shadow-form/shadow-form.tsx +++ b/src/shadow-form/shadow-form.tsx @@ -1,11 +1,15 @@ -import { createForm } from "@formily/core"; +import { GeneralField, VoidField, createForm } from "@formily/core"; import { + FormProvider, ISchema, + SchemaComponentsContext, SchemaOptionsContext, createSchemaField, + useField, useFieldSchema, } from "@formily/react"; -import React, { createContext, useContext } from "react"; +import React, { useContext, useMemo } from "react"; +import { isReactElement } from "../__builtins__"; export const useShadowSchemaField = ( schemaFieldOptions?: Parameters>[0], @@ -24,8 +28,6 @@ export const useShadowSchemaField = ( }; export interface IShadowFormOptions { - /** 子表单对应动作名称 */ - act: string; /** 子表单Schema */ schema: ISchema; /** createSchemaField 函数配置, components 和 scope */ @@ -33,56 +35,61 @@ export interface IShadowFormOptions { /** createForm 创建的表单实例, 有自定义 effects 等可以通过自定义一个 form 这个传入 */ form?: ReturnType; } -export interface IShadowFormContext - extends Required> { - SchemaField: ReturnType; -} -export const ShadowFormContext = createContext({ - act: "", - schema: {}, - form: createForm(), - SchemaField: createSchemaField(), -}); -export const useShadowForm = (options: { - schema: ISchema; - schemaFieldOptions?: Parameters[0]; - act?: string; - form?: ReturnType; -}) => { - const { schema, schemaFieldOptions, act, form } = options; +export const useShadowForm = (options: IShadowFormOptions) => { + const { schema, schemaFieldOptions, form } = options; const SchemaField = useShadowSchemaField(schemaFieldOptions); - const realForm = form ?? createForm({}); - const myField = useFieldSchema(); - const name = myField?.name as string; + const realForm = useMemo(() => form ?? createForm({}), [form]); return { form: realForm, schema, SchemaField, - act: act ?? name, }; }; -export const ShadowFormProvider: React.FC< - React.PropsWithChildren<{ - schema: ISchema; - schemaFieldOptions?: Parameters[0]; - act?: string; - form?: ReturnType; - }> -> = ({ children, ...options }) => { - const { SchemaField, act, form, schema } = useShadowForm(options); - - return ( - - {children} - - ); +const role = (field: GeneralField, self: any) => { + const components = useContext(SchemaComponentsContext); + const decorator = components[(field.decorator as any)?.[0]]; + const component = components[(field.component as any)?.[0]]; + const isDecorator = decorator === self; + const isComponent = component === self; + return isDecorator ? "decorator" : isComponent ? "component" : null; }; + +export interface ShadowFormWrappedProps { + form: ReturnType; + SchemaField: ReturnType; + schema: ISchema; +} + +export const ShadowForm: React.FC> = + ({ children, ...props }) => { + const field = useField(); + const im = role(field, ShadowForm); + const { SchemaField, form, schema } = useShadowForm(props); + + if (im === "component") { + // IOC + field.data = field.data ?? {}; + field.data._form = form; + } + + const child = // at x-component + im === "component" ? ( + + + + ) : // at x-decorator + isReactElement(children) ? ( + React.cloneElement(children, { + ...children.props, + form, + SchemaField, + schema, + }) + ) : ( + children + ); + // biome-ignore lint/complexity/noUselessFragments: + return {child}; + }; diff --git a/src/shared/index.ts b/src/shared/index.ts index 12c0fc8..0f3fd54 100644 --- a/src/shared/index.ts +++ b/src/shared/index.ts @@ -1,2 +1,3 @@ export * from "./useGrid"; export * from "./useRecord"; +export * from "./withLayoutGrid"; diff --git a/src/shared/withLayoutGrid.tsx b/src/shared/withLayoutGrid.tsx new file mode 100644 index 0000000..dc42774 --- /dev/null +++ b/src/shared/withLayoutGrid.tsx @@ -0,0 +1,25 @@ +import { ISchema } from "@formily/react"; +import { FormGrid, FormLayout } from "../adaptor/adaptor"; + +export const withLayoutGrid = ( + properties: T, + layout?: React.ComponentProps, + grid?: React.ComponentProps, +) => { + return { + _layout: { + type: "void", + "x-decorator": "FormLayout", + "x-decorator-props": { + labelCol: 5, + wrapperCol: 19, + ...layout, + } as typeof layout, + "x-component": "FormGrid", + "x-component-props": { + ...grid, + } as typeof grid, + properties: properties, + }, + }; +}; diff --git a/ui/antd-v5/adaptor.ts b/ui/antd-v5/adaptor.ts index 2734f5a..1f14624 100644 --- a/ui/antd-v5/adaptor.ts +++ b/ui/antd-v5/adaptor.ts @@ -12,7 +12,8 @@ export { } from "@formily/antd-v5"; import { ArrayBase as AntdArrayBase } from "@formily/antd-v5"; -export type { ArrayBaseMixins } from "@formily/antd-v5"; +export type { ArrayBaseMixins, IArrayBaseContext } from "@formily/antd-v5"; + // 类型fix export const ArrayBase = AntdArrayBase; export type { ColumnsType, ColumnProps, TableProps } from "antd/es/table"; diff --git a/ui/antd-v5/index.ts b/ui/antd-v5/index.ts index 4b8dac4..f7b621a 100644 --- a/ui/antd-v5/index.ts +++ b/ui/antd-v5/index.ts @@ -6,6 +6,8 @@ export { SettingOutlined, SyncOutlined, ToTopOutlined, + DownOutlined, + UpOutlined, } from "@ant-design/icons"; export { Alert, diff --git a/ui/antd/adaptor.ts b/ui/antd/adaptor.ts index 5e76a63..1f622bd 100644 --- a/ui/antd/adaptor.ts +++ b/ui/antd/adaptor.ts @@ -12,7 +12,7 @@ export { } from "@formily/antd"; import { ArrayBase as AntdArrayBase } from "@formily/antd"; -export type { ArrayBaseMixins } from "@formily/antd"; +export type { ArrayBaseMixins, IArrayBaseContext } from "@formily/antd"; // 类型 Required export const ArrayBase = AntdArrayBase as Required & diff --git a/ui/antd/index.ts b/ui/antd/index.ts index 91a1468..cdbb6e1 100644 --- a/ui/antd/index.ts +++ b/ui/antd/index.ts @@ -6,6 +6,8 @@ export { SettingOutlined, SyncOutlined, ToTopOutlined, + DownOutlined, + UpOutlined, } from "@ant-design/icons"; export { Alert, diff --git a/ui/arco/adaptor.ts b/ui/arco/adaptor.ts index 438cd62..9b5088d 100644 --- a/ui/arco/adaptor.ts +++ b/ui/arco/adaptor.ts @@ -16,7 +16,8 @@ import { TableProps as ArcoTableProps, } from "@arco-design/web-react"; import { ArrayBase as AntdArrayBase } from "arco.formily"; -export type { ArrayBaseMixins } from "arco.formily"; +export type { ArrayBaseMixins, IArrayBaseContext } from "arco.formily"; + // 类型fix export const ArrayBase = AntdArrayBase as Required & typeof AntdArrayBase; diff --git a/ui/arco/index.ts b/ui/arco/index.ts index 0536884..4d09526 100644 --- a/ui/arco/index.ts +++ b/ui/arco/index.ts @@ -20,11 +20,13 @@ import "@arco-design/web-react/es/Typography/style/index"; import { IconCopy, IconDelete, + IconDown, IconPlus, IconSettings, IconShrink, IconSync, IconToTop, + IconUp, } from "@arco-design/web-react/icon"; import "./themes/index"; export const ColumnHeightOutlined = IconShrink; @@ -34,7 +36,8 @@ export const CopyOutlined = IconCopy; export const DeleteOutlined = IconDelete; export const PlusOutlined = IconPlus; export const ToTopOutlined = IconToTop; - +export const DownOutlined = IconDown; +export const UpOutlined = IconUp; export { Badge, Button,