diff --git a/packages/core/client/src/block-provider/TableFieldProvider.tsx b/packages/core/client/src/block-provider/TableFieldProvider.tsx index af8726783..42d98445d 100644 --- a/packages/core/client/src/block-provider/TableFieldProvider.tsx +++ b/packages/core/client/src/block-provider/TableFieldProvider.tsx @@ -11,6 +11,7 @@ export const TableFieldContext = createContext({}); const InternalTableFieldProvider = (props) => { const { params = {}, showIndex, dragSort, fieldName } = props; const field = useField(); + const fieldSchema = useFieldSchema(); const { resource, service } = useBlockRequestContext(); const formBlockCtx = useFormBlockContext(); @@ -28,6 +29,7 @@ const InternalTableFieldProvider = (props) => { { showIndex: ctx.showIndex, dragSort: ctx.dragSort, pagination: false, + required: ctx?.fieldSchema?.parent?.required, rowKey: (record: any) => { return field.value?.indexOf?.(record); }, diff --git a/packages/core/client/src/schema-component/antd/form-item/FormItem.tsx b/packages/core/client/src/schema-component/antd/form-item/FormItem.tsx index a34e45f95..56db52a9a 100644 --- a/packages/core/client/src/schema-component/antd/form-item/FormItem.tsx +++ b/packages/core/client/src/schema-component/antd/form-item/FormItem.tsx @@ -189,7 +189,7 @@ FormItem.Designer = (props) => { { const schema = { ['x-uid']: fieldSchema['x-uid'], diff --git a/packages/core/client/src/schema-component/antd/table-v2/Table.tsx b/packages/core/client/src/schema-component/antd/table-v2/Table.tsx index 5901355ce..aa5947fc8 100644 --- a/packages/core/client/src/schema-component/antd/table-v2/Table.tsx +++ b/packages/core/client/src/schema-component/antd/table-v2/Table.tsx @@ -1,15 +1,16 @@ import { MenuOutlined } from '@ant-design/icons'; +import { SortableContext, useSortable } from '@dnd-kit/sortable'; import { css } from '@emotion/css'; -import { ArrayField } from '@formily/core'; +import { ArrayField, Field } from '@formily/core'; import { ISchema, observer, RecursionField, Schema, useField, useFieldSchema } from '@formily/react'; +import { reaction } from '@formily/reactive'; +import { useMemoizedFn } from 'ahooks'; import { Table as AntdTable, TableColumnProps } from 'antd'; import { default as classNames, default as cls } from 'classnames'; -import React, { useCallback, useMemo } from 'react'; +import React, { useCallback, useEffect, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { DndContext } from '../..'; import { RecordIndexProvider, RecordProvider, useSchemaInitializer } from '../../../'; -import { SortableContext, useSortable } from '@dnd-kit/sortable'; -import { useMemoizedFn } from 'ahooks'; const isColumnComponent = (schema: Schema) => { return schema['x-component']?.endsWith('.Column') > -1; @@ -17,7 +18,7 @@ const isColumnComponent = (schema: Schema) => { const isCollectionFieldComponent = (schema: ISchema) => { return schema['x-component'] === 'CollectionField'; -} +}; const useTableColumns = () => { const start = Date.now(); @@ -31,12 +32,11 @@ const useTableColumns = () => { } }, []) .map((s: Schema) => { - const collectionFields = s - .reduceProperties((buf, s) => { - if (isCollectionFieldComponent(s)) { - return buf.concat([s]); - } - }, []); + const collectionFields = s.reduceProperties((buf, s) => { + if (isCollectionFieldComponent(s)) { + return buf.concat([s]); + } + }, []); const dataIndex = collectionFields?.length > 0 ? collectionFields[0].name : s.name; return { @@ -68,34 +68,41 @@ const useTableColumns = () => { }); }; - const topActiveClass = css` & > td { border-top: 2px solid rgba(241, 139, 98, 0.6) !important; } -` +`; const bottomActiveClass = css` & > td { border-bottom: 2px solid rgba(241, 139, 98, 0.6) !important; } -` +`; const SortableRow = (props) => { - const id = props['data-row-key']?.toString() + const id = props['data-row-key']?.toString(); const { setNodeRef, isOver, active, over } = useSortable({ - id - }) + id, + }); - const className = (active?.data.current?.sortable.index ?? -1) > (over?.data.current?.sortable.index ?? -1) ? topActiveClass : bottomActiveClass - - return -} + const className = + (active?.data.current?.sortable.index ?? -1) > (over?.data.current?.sortable.index ?? -1) + ? topActiveClass + : bottomActiveClass; + return ( + + ); +}; const SortHandle = (props) => { const { listeners } = useSortable({ - id: props.id - }) + id: props.id, + }); return ; }; @@ -124,23 +131,52 @@ const usePaginationProps = (pagination1, pagination2) => { }; }; +const useValidator = (validator: (value: any) => string) => { + const field = useField(); + useEffect(() => { + const dispose = reaction( + () => field.value, + (value) => { + const message = validator(value); + field.setFeedback({ + type: 'error', + code: 'ValidateError', + messages: message ? [message] : [], + }); + }, + ); + return () => { + dispose(); + }; + }, []); +}; + export const Table: any = observer((props: any) => { const field = useField(); - const columns = useTableColumns(); - const { pagination: pagination1, useProps, onChange, ...others1 } = props; - const { pagination: pagination2, ...others2 } = useProps?.() || {}; - const { + const columns = useTableColumns(); + const { pagination: pagination1, useProps, onChange, ...others1 } = props; + const { pagination: pagination2, ...others2 } = useProps?.() || {}; + const { dragSort = false, showIndex = true, onRowSelectionChange, onChange: onTableChange, rowSelection, rowKey, + required, ...others } = { ...others1, ...others2 } as any; - const onRowDragEnd = useMemoizedFn(others.onRowDragEnd || (() => {})) + const onRowDragEnd = useMemoizedFn(others.onRowDragEnd || (() => {})); const paginationProps = usePaginationProps(pagination1, pagination2); - + const requiredValidator = field.required || required; + useEffect(() => { + field.setValidator((value) => { + if (requiredValidator) { + return Array.isArray(value) && value.length > 0 ? null : 'The field value is required'; + } + return; + }); + }, [requiredValidator]); const components = useMemo(() => { return { header: { @@ -172,25 +208,27 @@ export const Table: any = observer((props: any) => { body: { wrapper: (props) => { return ( - { - if (!e.active || !e.over) { - console.warn('move cancel') - return - } + { + if (!e.active || !e.over) { + console.warn('move cancel'); + return; + } - const fromIndex = e.active?.data.current?.sortable?.index - const toIndex = e.over?.data.current?.sortable?.index - const from = field.value[fromIndex]; - const to = field.value[toIndex]; - field.move(fromIndex, toIndex); - onRowDragEnd({ fromIndex, toIndex, from, to }); - }}> + const fromIndex = e.active?.data.current?.sortable?.index; + const toIndex = e.over?.data.current?.sortable?.index; + const from = field.value[fromIndex]; + const to = field.value[toIndex]; + field.move(fromIndex, toIndex); + onRowDragEnd({ fromIndex, toIndex, from, to }); + }} + > ); }, row: (props) => { - return + return ; }, cell: (props) => ( { ), }, }; - }, [field, onRowDragEnd, dragSort]) + }, [field, onRowDragEnd, dragSort]); const defaultRowKey = (record: any) => { return field.value?.indexOf?.(record); @@ -219,34 +257,34 @@ export const Table: any = observer((props: any) => { if (typeof rowKey === 'string') { return record[rowKey]?.toString(); } else { - return (rowKey ?? defaultRowKey)(record)?.toString() + return (rowKey ?? defaultRowKey)(record)?.toString(); } - } + }; const restProps = { rowSelection: rowSelection ? { - type: 'checkbox', - selectedRowKeys: field?.data?.selectedRowKeys || [], - onChange(selectedRowKeys: any[], selectedRows: any[]) { - field.data = field.data || {}; - field.data.selectedRowKeys = selectedRowKeys; - onRowSelectionChange?.(selectedRowKeys, selectedRows); - }, - renderCell: (checked, record, index, originNode) => { - if (!dragSort && !showIndex) { - return originNode; - } - const current = props?.pagination?.current; - const pageSize = props?.pagination?.pageSize || 20; - if (current) { - index = index + (current - 1) * pageSize; - } - return ( -
{ + if (!dragSort && !showIndex) { + return originNode; + } + const current = props?.pagination?.current; + const pageSize = props?.pagination?.pageSize || 20; + if (current) { + index = index + (current - 1) * pageSize; + } + return ( +
{ } } `, - )} - > -
+
- {dragSort && } - {showIndex && } -
-
+ {dragSort && } + {showIndex && } +
+
{ display: none; } `, - )} - > - {originNode} + )} + > + {originNode} +
-
- ); - }, - ...rowSelection, - } + ); + }, + ...rowSelection, + } : undefined, }; - const SortableWrapper = useCallback(({ children }) => { - return dragSort ? React.createElement(SortableContext, { - items: field.value.map(getRowKey), - children: children, - }) : React.createElement(React.Fragment, { - children - }) - }, [field, dragSort]) + const SortableWrapper = useCallback( + ({ children }) => { + return dragSort + ? React.createElement(SortableContext, { + items: field.value.map(getRowKey), + children: children, + }) + : React.createElement(React.Fragment, { + children, + }); + }, + [field, dragSort], + ); return (
{ dataSource={field?.value?.slice?.()} /> + {field.errors.length > 0 && ( +
+ {field.errors.map((error) => { + return error.messages.map((message) =>
{message}
); + })} +
+ )}
); }); diff --git a/packages/core/client/src/schema-component/antd/table-v2/TableField.tsx b/packages/core/client/src/schema-component/antd/table-v2/TableField.tsx index 01d6ec169..b34069fe6 100644 --- a/packages/core/client/src/schema-component/antd/table-v2/TableField.tsx +++ b/packages/core/client/src/schema-component/antd/table-v2/TableField.tsx @@ -1,3 +1,4 @@ +import { Field } from '@formily/core'; import { observer, useField, useFieldSchema, useForm } from '@formily/react'; import React, { useEffect } from 'react'; import { useFormBlockContext } from '../../../block-provider'; @@ -8,7 +9,7 @@ import { ActionBar } from '../action'; export const TableField: any = observer((props) => { const fieldSchema = useFieldSchema(); const { getField } = useCollection(); - const field = useField(); + const field = useField(); const collectionField = getField(fieldSchema.name); const compile = useCompile(); const ctx = useFormBlockContext(); @@ -21,6 +22,9 @@ export const TableField: any = observer((props) => { ctx.field.added.add(fieldSchema.name); } }, []); + useEffect(() => { + field.decoratorProps.asterisk = fieldSchema.required; + }, [fieldSchema.required]); return
{props.children}
; });