diff --git a/package.json b/package.json index e92faac4e..da8b651aa 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "@types/react-dom": "^17.0.0", "@typescript-eslint/eslint-plugin": "^4.9.1", "@typescript-eslint/parser": "^4.8.2", - "antd": "^4.18.5", + "antd": "^4.18.9", "classnames": "^2.3.1", "concurrently": "^7.0.0", "cross-env": "^5.2.0", diff --git a/packages/client/package.json b/packages/client/package.json index b3f92d793..ad4c6456a 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -24,7 +24,7 @@ "@formily/core": "^2.0.7", "@formily/react": "^2.0.7", "ahooks": "^3.0.5", - "antd": "^4.18.5", + "antd": "^4.18.9", "axios": "^0.24.0", "classnames": "^2.3.1", "file-saver": "^2.0.5", diff --git a/packages/client/src/schema-component/antd/action/Action.Popover.tsx b/packages/client/src/schema-component/antd/action/Action.Popover.tsx new file mode 100644 index 000000000..e69de29bb diff --git a/packages/client/src/schema-component/antd/action/Action.tsx b/packages/client/src/schema-component/antd/action/Action.tsx index 4144b6e2c..c850da680 100644 --- a/packages/client/src/schema-component/antd/action/Action.tsx +++ b/packages/client/src/schema-component/antd/action/Action.tsx @@ -1,8 +1,9 @@ import { css } from '@emotion/css'; -import { observer, useField } from '@formily/react'; -import { Button, Modal } from 'antd'; +import { observer, RecursionField, useField, useFieldSchema } from '@formily/react'; +import { Button, Modal, Popover } from 'antd'; import classnames from 'classnames'; import React, { useState } from 'react'; +import { useActionContext } from '../..'; import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings'; import { SortableItem } from '../../common'; import { useDesigner } from '../../hooks'; @@ -62,43 +63,89 @@ export const actionDesignerCss = css` `; export const Action: ComposedAction = observer((props: any) => { - const { confirm, openMode, containerRefKey, component, useAction = useA, onClick, className, ...others } = props; + const { + popover, + confirm, + openMode, + containerRefKey, + component, + useAction = useA, + onClick, + className, + ...others + } = props; const [visible, setVisible] = useState(false); const Designer = useDesigner(); const field = useField(); const { run } = useAction(); + const fieldSchema = useFieldSchema(); + const renderButton = () => ( + { + e.preventDefault(); + e.stopPropagation(); + const onOk = () => { + onClick?.(e); + setVisible(true); + run(); + }; + if (confirm) { + Modal.confirm({ + ...confirm, + onOk, + }); + } else { + onOk(); + } + }} + component={component || Button} + className={classnames(className, actionDesignerCss)} + > + + {field.title} + + ); return ( - - { - e.preventDefault(); - e.stopPropagation(); - const onOk = () => { - onClick?.(e); - setVisible(true); - run(); - }; - if (confirm) { - Modal.confirm({ - ...confirm, - onOk, - }); - } else { - onOk(); - } - }} - component={component || Button} - className={classnames(className, actionDesignerCss)} - > - - {field.title} - - {props.children} + + {popover && } + {!popover && renderButton()} + {!popover && props.children} ); }); +Action.Popover = observer((props) => { + const { button, visible, setVisible } = useActionContext(); + return ( + { + setVisible(visible); + }} + content={props.children} + > + {button} + + ); +}); + +Action.Popover.Footer = observer((props) => { + return ( +
+ {props.children} +
+ ); +}); + Action.Designer = () => { return ( diff --git a/packages/client/src/schema-component/antd/action/context.tsx b/packages/client/src/schema-component/antd/action/context.tsx index 39ff9818d..82135c4a3 100644 --- a/packages/client/src/schema-component/antd/action/context.tsx +++ b/packages/client/src/schema-component/antd/action/context.tsx @@ -3,6 +3,7 @@ import { createContext } from 'react'; export const ActionContext = createContext({}); export interface ActionContextProps { + button?: any; visible?: boolean; setVisible?: (v: boolean) => void; openMode?: 'drawer' | 'modal' | 'page'; diff --git a/packages/client/src/schema-component/antd/action/demos/demo4.tsx b/packages/client/src/schema-component/antd/action/demos/demo4.tsx new file mode 100644 index 000000000..8080b049d --- /dev/null +++ b/packages/client/src/schema-component/antd/action/demos/demo4.tsx @@ -0,0 +1,53 @@ +import { FormItem, Input } from '@formily/antd'; +import { ISchema, observer, useForm } from '@formily/react'; +import { Action, Form, SchemaComponent, SchemaComponentProvider, useActionContext } from '@nocobase/client'; +import React from 'react'; + +const useCloseAction = () => { + const { setVisible } = useActionContext(); + const form = useForm(); + return { + async run() { + setVisible(false); + form.submit((values) => { + console.log(values); + }); + }, + }; +}; + +const schema: ISchema = { + type: 'object', + properties: { + action1: { + 'x-component': 'Action', + 'x-component-props': { + type: 'primary', + popover: true, + openMode: 'popover', + }, + type: 'void', + title: 'Open', + properties: { + popover: { + type: 'void', + 'x-component': 'Action.Popover', + properties: { + hello: { + type: 'void', + 'x-content': 'Hello', + }, + }, + }, + }, + }, + }, +}; + +export default observer(() => { + return ( + + + + ); +}); diff --git a/packages/client/src/schema-component/antd/action/index.md b/packages/client/src/schema-component/antd/action/index.md index 8cf455bc2..8c1ca5c9a 100644 --- a/packages/client/src/schema-component/antd/action/index.md +++ b/packages/client/src/schema-component/antd/action/index.md @@ -36,3 +36,7 @@ group: ### 不同的打开方式 + +### Action + Action.Popover + + diff --git a/packages/client/src/schema-component/antd/cascader/Cascader.tsx b/packages/client/src/schema-component/antd/cascader/Cascader.tsx index dcdfc971d..26085bd87 100644 --- a/packages/client/src/schema-component/antd/cascader/Cascader.tsx +++ b/packages/client/src/schema-component/antd/cascader/Cascader.tsx @@ -41,6 +41,7 @@ export const Cascader = connect( }); // 兼容值为 object[] 的情况 const toValue = () => { + return ['11']; return toArr(value).map((item) => { if (typeof item === 'object') { return item[fieldNames.value]; diff --git a/packages/client/src/schema-component/antd/filter/DynamicComponent.tsx b/packages/client/src/schema-component/antd/filter/DynamicComponent.tsx new file mode 100644 index 000000000..dba078fac --- /dev/null +++ b/packages/client/src/schema-component/antd/filter/DynamicComponent.tsx @@ -0,0 +1,46 @@ +import { createForm, onFieldValueChange } from '@formily/core'; +import React, { useContext, useMemo } from 'react'; +import { FormProvider, SchemaComponent } from '../../../schema-component'; +import { useComponent } from '../../hooks'; +import { FilterContext } from './context'; + +export const DynamicComponent = (props) => { + const { dynamicComponent } = useContext(FilterContext); + const component = useComponent(dynamicComponent); + const form = useMemo( + () => + createForm({ + values: { + value: props.value, + }, + effects() { + onFieldValueChange('value', (field) => { + props?.onChange?.(field.value); + }); + }, + }), + [JSON.stringify(props.schema), JSON.stringify(props.value)], + ); + const renderSchemaComponent = () => { + return ( + + ); + }; + return ( + + {component + ? React.createElement(component, { + value: props.value, + onChange: props?.onChange, + renderSchemaComponent, + }) + : renderSchemaComponent()} + + ); +}; diff --git a/packages/client/src/schema-component/antd/filter/Filter.Action.tsx b/packages/client/src/schema-component/antd/filter/Filter.Action.tsx deleted file mode 100644 index a5d5db406..000000000 --- a/packages/client/src/schema-component/antd/filter/Filter.Action.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { FilterOutlined } from '@ant-design/icons'; -import { FormButtonGroup, Submit } from '@formily/antd'; -import { createForm } from '@formily/core'; -import { FieldContext, FormContext, observer, useFieldSchema } from '@formily/react'; -import { uid } from '@formily/shared'; -import { Button, Popover } from 'antd'; -import flatten from 'flat'; -import React, { useMemo } from 'react'; -import { useTranslation } from 'react-i18next'; -import { useCollection, useCollectionManager } from '../../../collection-manager'; -import { SchemaComponent } from '../../core'; -import { useDesignable } from '../../hooks'; -import { useCompile } from '../../hooks/useCompile'; -import { useFilterContext } from './hooks'; - -export const FilterAction = observer((props: any) => { - const { t } = useTranslation(); - const compile = useCompile(); - const { DesignableBar } = useDesignable(); - const filedSchema = useFieldSchema(); - const form = useMemo(() => createForm(), []); - const { visible, setVisible } = useFilterContext(); - const obj = flatten(form.values.filter || {}); - const count = Object.values(obj).filter((i) => (Array.isArray(i) ? i.length : i)).length; - const { fields } = useCollection(); - const { getInterface } = useCollectionManager(); - const properties = {}; - fields?.forEach((field) => { - const { operators } = getInterface(field.interface); - properties[uid()] = { - type: 'void', - title: field?.uiSchema?.title ?? field.title, - 'x-component': 'Filter.Column', - 'x-component-props': { - operations: [...operators], - }, - properties: { - [field.name ?? uid()]: { ...field?.uiSchema }, - }, - }; - }); - const schema = { - type: 'void', - properties: { - filter: { - type: 'array', - 'x-component': 'Filter', - properties, - }, - }, - }; - return ( - - - - - - { - const { filter } = form.values; - console.log('Table.Filter====', form.values); - setVisible(false); - }} - > - {t('Submit')} - - - - - - } - > - - - ); -}); diff --git a/packages/client/src/schema-component/antd/filter/Filter.DynamicValue.tsx b/packages/client/src/schema-component/antd/filter/Filter.DynamicValue.tsx deleted file mode 100644 index 09b1e1673..000000000 --- a/packages/client/src/schema-component/antd/filter/Filter.DynamicValue.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import { createForm, onFieldValueChange } from '@formily/core'; -import type { ISchema, Schema } from '@formily/react'; -import { connect } from '@formily/react'; -import deepmerge from 'deepmerge'; -import React, { useMemo } from 'react'; -import { SchemaComponent } from '../../core'; - -interface DynamicValueProps { - value?: any; - onChange?: any; - schema?: Schema; - operation?: any; -} - -export const FilterDynamicValue = connect((props: DynamicValueProps) => { - const { onChange, value, operation } = props; - const fieldName = Object.keys(props?.schema?.properties || {}).shift() ?? 'value'; - const fieldSchema = Object.values(props?.schema?.properties || {}).shift(); - const form = useMemo( - () => - createForm({ - initialValues: { - [fieldName]: value, - }, - effects(form) { - onFieldValueChange(fieldName, (field) => { - onChange(field.value); - }); - }, - }), - [], - ); - const extra: ISchema = deepmerge( - { - required: false, - 'x-read-pretty': false, - name: fieldName, - default: value, - 'x-decorator': 'FormilyFormItem', - 'x-decorator-props': { - asterisk: true, - feedbackLayout: 'none', - }, - 'x-component-props': { - style: { - minWidth: '150px', - }, - }, - }, - operation?.schema || {}, - ); - const schema = { - type: 'object', - properties: { - [fieldName]: deepmerge(fieldSchema, extra, { - arrayMerge: (target, source) => source, - }) as any, - }, - }; - return ; -}); diff --git a/packages/client/src/schema-component/antd/filter/Filter.tsx b/packages/client/src/schema-component/antd/filter/Filter.tsx index fd5a577e5..552fce43a 100644 --- a/packages/client/src/schema-component/antd/filter/Filter.tsx +++ b/packages/client/src/schema-component/antd/filter/Filter.tsx @@ -1,27 +1,33 @@ -import { LoadingOutlined } from '@ant-design/icons'; -import { connect, mapProps, mapReadPretty } from '@formily/react'; +import { ObjectField as ObjectFieldModel } from '@formily/core'; +import { observer, useField } from '@formily/react'; import React from 'react'; -import { FilterAction } from './Filter.Action'; -import { FilterDynamicValue } from './Filter.DynamicValue'; +import { useRequest } from '../../../api-client'; +import { FilterContext } from './context'; import { FilterGroup } from './FilterGroup'; -import './style.less'; +import { SaveDefaultValue } from './SaveDefaultValue'; -export const Filter: any = connect( - (props) => { - return ; - }, - mapProps((props, field) => { - return { - ...props, - suffix: {field?.['loading'] || field?.['validating'] ? : props.suffix}, - }; - }), - mapReadPretty((props) => { - return null; - }), -); +const useDef = (options) => { + const field = useField(); + return useRequest(() => Promise.resolve({ data: field.dataSource }), options); +}; -Filter.DynamicValue = FilterDynamicValue; -Filter.Action = FilterAction; +export const Filter: any = observer((props: any) => { + const { useDataSource = useDef, dynamicComponent } = props; + const field = useField(); + useDataSource({ + onSuccess(data) { + console.log('onSuccess', data?.data); + field.dataSource = data?.data || []; + }, + }); + return ( +
+ + + +
{JSON.stringify(field.value, null, 2)}
+
+ ); +}); -export default Filter; +Filter.SaveDefaultValue = SaveDefaultValue; diff --git a/packages/client/src/schema-component/antd/filter/FilterGroup.tsx b/packages/client/src/schema-component/antd/filter/FilterGroup.tsx index 99f2a7136..b2ded2510 100644 --- a/packages/client/src/schema-component/antd/filter/FilterGroup.tsx +++ b/packages/client/src/schema-component/antd/filter/FilterGroup.tsx @@ -1,75 +1,88 @@ import { CloseCircleOutlined } from '@ant-design/icons'; -import { useForm } from '@formily/react'; -import { isValid } from '@formily/shared'; -import { Select } from 'antd'; -import cls from 'classnames'; -import React from 'react'; -import { Trans } from 'react-i18next'; -import { FilterList } from './FilterList'; +import { ObjectField as ObjectFieldModel } from '@formily/core'; +import { ArrayField, connect, useField } from '@formily/react'; +import { Select, Space } from 'antd'; +import React, { useContext } from 'react'; +import { Trans, useTranslation } from 'react-i18next'; +import { RemoveConditionContext } from './context'; +import { FilterItems } from './FilterItems'; -const toValue = (value) => { - if (!value) { - return { - logical: 'and', - list: [{}], +export const FilterGroup = connect((props) => { + const field = useField(); + const remove = useContext(RemoveConditionContext); + const { t } = useTranslation(); + const keys = Object.keys(field.value || {}); + const logic = keys.includes('$or') ? '$or' : '$and'; + const setLogic = (value) => { + const obj = field.value || {}; + field.value = { + [value]: obj[logic] || [], }; - } - if (value.and) { - return { - logical: 'and', - list: value.and, - }; - } - if (value.or) { - return { - logical: 'and', - list: value.or, - }; - } - return { - logical: 'and', - list: [{}], }; -}; - -export function FilterGroup(props) { - const { bordered = true, onRemove, onChange } = props; - const value = toValue(props.value); - const form = useForm(); - console.log('list', form.values, value); - return ( -
- {onRemove && ( - onRemove()}> - - +
+ {remove && ( + remove()} + /> )} -
+
+ {'Meet '} + {' conditions in the group'}
- { - const values = { - [value.logical]: list.filter((item) => isValid(item) && Object.keys(item).length), - }; - onChange?.(values); - }} - /> +
+ +
+ + { + const value = field.value || {}; + const items = value[logic] || []; + items.push({}); + field.value = { + [logic]: items, + }; + }} + > + {t('Add condition')} + + { + const value = field.value || {}; + const items = value[logic] || []; + items.push({ + $and: [{}], + }); + field.value = { + [logic]: items, + }; + }} + > + {t('Add condition group')} + +
); -} +}); diff --git a/packages/client/src/schema-component/antd/filter/FilterItem.tsx b/packages/client/src/schema-component/antd/filter/FilterItem.tsx index 6fc8465e9..ac4825e3e 100644 --- a/packages/client/src/schema-component/antd/filter/FilterItem.tsx +++ b/packages/client/src/schema-component/antd/filter/FilterItem.tsx @@ -1,247 +1,54 @@ import { CloseCircleOutlined } from '@ant-design/icons'; -import { FormItem as FormilyFormItem, FormLayout, Space as AntdSpace } from '@formily/antd'; -import { createForm, onFieldReact, onFieldValueChange, onFormValuesChange } from '@formily/core'; -import { Field } from '@formily/core/esm/models/Field'; -import { Form } from '@formily/core/esm/models/Form'; -import { - FieldContext, - FormContext, - ISchema, - Schema, - SchemaKey, - SchemaOptionsContext, - useFieldSchema, -} from '@formily/react'; -import { isValid, uid } from '@formily/shared'; -import { get } from 'lodash'; -import React, { useContext, useMemo } from 'react'; -import { SchemaComponent } from '../../core'; - -function useFilterColumns(): Map { - const schema = useFieldSchema(); - const columns = schema.reduceProperties((columns, current) => { - if (current['x-component'] === 'Filter.Column') { - const fieldName = Object.keys(current.properties).shift(); - columns.set(fieldName, current); - return columns; - } - return columns; - }, new Map()); - return columns; -} - -export const FilterItem = (props) => { - const { value, initialValues = {}, onRemove, onChange } = props; - const options = useContext(SchemaOptionsContext); - const columns = useFilterColumns(); - - const toValues = (value) => { - if (!value) { - return {}; - } - if (Object.keys(value).length === 0) { - return {}; - } - const fieldName = Object.keys(value).shift(); - const nested = value[fieldName]; - const column = columns.get(fieldName).toJSON(); - const operations = column?.['x-component-props']?.['operations'] || []; - if (!nested) { - return { - column, - operations, - }; - } - if (Object.keys(nested).length === 0) { - return { - column, - operations, - }; - } - const operationValue = Object.keys(nested).shift(); - console.log('toValues', { operationValue }); - const operation = operations.find((operation) => operation.value === operationValue); - console.log('toValues', { operation }); - if (!operation) { - return { - operations, - column, - }; - } - if (operation.noValue) { - return { - column, - operations, - operation, - }; - } - return { - column, - operation, - operations, - value: nested[operationValue], - }; - }; - - const values = toValues(value); - - console.log('toValues', values, value); - - const Remove = (props) => { - return ( - onRemove && ( - onRemove()}> - - - ) - ); - }; - - const form = useMemo( - () => - createForm({ - initialValues: values, - effects: (form) => { - onFieldValueChange('column', (field: Field, form: Form) => { - const column = (field.value || {}) as ISchema; - const operations = column?.['x-component-props']?.['operations'] || []; - field.query('operation').take((f: Field) => { - f.setDataSource(operations); - f.value = get(operations, [0]); - }); - field.query('value').take((f: Field) => { - f.value = undefined; - f.componentProps.schema = column; - }); - }); - onFieldReact('operation', (field: Field) => { - console.log('operation', field.value); - const operation = field.value || {}; - field.query('value').take((f: Field) => { - f.visible = !operation.noValue; - if (operation.noValue) { - f.value = undefined; - } - f.componentProps.operation = operation; - }); - }); - onFormValuesChange((form) => { - const { column, operation, value } = form?.values || {}; - - if (!operation?.value) { - return; - } - const fieldName = Object.keys(column.properties).shift(); - if (operation?.noValue) { - onChange({ - [fieldName]: { - [operation.value]: true, - }, - }); - } else { - onChange( - isValid(value) - ? { - [fieldName]: { - [operation.value]: value, - }, - } - : {}, - ); - } - console.log('form.values', form.values); - }); - }, - }), - [], - ); - - const columnEnum: any = [...columns.values()].map((column) => column.toJSON()); - const schema: ISchema = { - type: 'void', - properties: { - space: { - type: 'void', - 'x-component': 'AntdSpace', - properties: { - column: { - type: 'object', - name: 'column', - 'x-decorator': 'FormilyFormItem', - 'x-decorator-props': { - asterisk: true, - feedbackLayout: 'none', - }, - 'x-component': 'Select', - 'x-component-props': { - objectValue: true, - style: { - width: 100, - }, - fieldNames: { - label: 'title', - value: 'name', - options: 'options', - }, - options: columnEnum, - }, - enum: columnEnum, - }, - operation: { - type: 'object', - name: 'operation', - 'x-decorator': 'FormilyFormItem', - 'x-decorator-props': { - asterisk: true, - feedbackLayout: 'none', - }, - 'x-component': 'Select', - 'x-component-props': { - objectValue: true, - style: { - width: 100, - }, - fieldNames: { - label: 'label', - value: 'value', - options: 'options', - }, - options: values.operations, - }, - enum: values.operations, - }, - value: { - type: 'object', - name: 'value', - 'x-decorator': 'FormilyFormItem', - 'x-decorator-props': { - asterisk: true, - feedbackLayout: 'none', - }, - 'x-component': 'Filter.DynamicValue', - 'x-component-props': { - schema: values.column, - operation: values.operation, - }, - }, - [uid()]: { - type: 'void', - 'x-component': 'Remove', - }, - }, - }, - }, - }; +import { observer, useField } from '@formily/react'; +import { Cascader, Select, Space } from 'antd'; +import React, { useContext } from 'react'; +import { RemoveConditionContext } from './context'; +import { DynamicComponent } from './DynamicComponent'; +import { useValues } from './useValues'; +export const FilterItem = observer((props: any) => { + const field = useField(); + const remove = useContext(RemoveConditionContext); + const { option, options, dataIndex, operator, setDataIndex, setOperator, value, setValue } = useValues(); return ( - - - - - - - +
+ + { + setDataIndex(value); + }} + /> + { + setSource(value); + onChange(null); + }} + options={[ + { + label: '默认', + value: 'default', + }, + { + label: '节点1', + value: 'node1', + }, + ]} + /> + {source === 'default' ? ( + renderSchemaComponent() + ) : ( +