diff --git a/packages/next/docs/components/LogicDiagram.md b/packages/next/docs/components/LogicDiagram.md new file mode 100644 index 00000000000..77da2c2e2da --- /dev/null +++ b/packages/next/docs/components/LogicDiagram.md @@ -0,0 +1,127 @@ +# LogicDiagram + +## Markup Schema 案例 + +```jsx +import React from 'react' +import { + Switch, + Input, + Select, + LogicDiagram, + FormItem, + FormButtonGroup, + Submit, +} from '@formily/next' +import { createForm, onFieldChange } from '@formily/core' +import { FormProvider, createSchemaField } from '@formily/react' + +const SchemaField = createSchemaField({ + components: { + Switch, + Input, + Select, + LogicDiagram, + FormItem, + }, +}) + +const form = createForm({ + values: { + editable: true, + logic: { + relation: 'OR', + rule: [ + { field: 'field1', operator: 'EQ', value: '123' }, + { + relation: 'AND', + rule: [ + { field: 'field2', operator: 'NEQ', value: '456' }, + { field: 'field2', operator: 'NEQ', value: '654' }, + ], + }, + { field: 'field3', operator: 'EQ', value: '789' }, + ], + }, + }, + effects: () => { + onFieldChange('editable', ['value'], (field) => { + field.query('logic').take((target) => { + target.editable = !!field.value + }) + }) + }, +}) + +export default () => ( + + + + + + + + + + + + + + + + + + + 提交 + + +) +``` diff --git a/packages/next/docs/components/index.md b/packages/next/docs/components/index.md index f1f669c8424..e772842ad79 100644 --- a/packages/next/docs/components/index.md +++ b/packages/next/docs/components/index.md @@ -40,6 +40,7 @@ - FormDialog - FormDrawer - Editable + - LogicDiagram - 阅读态组件 - PreviewText - 主题定制能力 diff --git a/packages/next/package.json b/packages/next/package.json index c63c9be2ae6..5271789d380 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -24,22 +24,23 @@ "build:docs": "dumi build" }, "peerDependencies": { + "@alifd/next": "^1.19.0", "@types/react": ">=16.8.0 || >=17.0.0", "@types/react-dom": ">=16.8.0 || >=17.0.0", - "@alifd/next": "^1.19.0", "mobx": "^6.0.3", "react": ">=16.8.0 || >=17.0.0", "react-dom": ">=16.8.0" }, "devDependencies": { - "dumi": "^1.1.0-rc.8", - "@umijs/plugin-sass": "^1.1.1" + "@umijs/plugin-sass": "^1.1.1", + "dumi": "^1.1.0-rc.8" }, "dependencies": { "@ant-design/icons": "^4.0.0", - "@formily/json-schema": "^2.0.0-beta.3", "@formily/core": "^2.0.0-beta.3", + "@formily/json-schema": "^2.0.0-beta.3", "@formily/react": "^2.0.0-beta.3", + "@formily/react-logic-diagram": "^2.0.0-beta.3", "@formily/shared": "^2.0.0-beta.3", "classnames": "^2.2.6", "react-sortable-hoc": "^1.11.0", diff --git a/packages/next/src/index.tsx b/packages/next/src/index.tsx index 515064bccdb..de133b58e5a 100644 --- a/packages/next/src/index.tsx +++ b/packages/next/src/index.tsx @@ -29,4 +29,5 @@ export * from './upload' export * from './preview-text' export * from './submit' export * from './reset' -export * from './editable' \ No newline at end of file +export * from './editable' +export * from './logic-diagram' diff --git a/packages/next/src/logic-diagram/index.tsx b/packages/next/src/logic-diagram/index.tsx new file mode 100644 index 00000000000..f0f65f841e3 --- /dev/null +++ b/packages/next/src/logic-diagram/index.tsx @@ -0,0 +1,377 @@ +import React, { createContext, Fragment, useContext } from 'react' +import { + LogicDiagram as ReactLogicDiagram, + ILogicDiagramProps, + RenderNodeFN, + NodeTypes, +} from '@formily/react-logic-diagram' +import { + observer, + RecursionField, + Schema, + useField, + useFieldSchema, +} from '@formily/react' +import { FormPath } from '@formily/core' +import { usePrefixCls } from '../__builtins__' +import { Balloon, Button, Icon } from '@alifd/next' +import { ButtonProps } from '@alifd/next/lib/button' +import { TooltipProps } from '@alifd/next/lib/balloon' +import { IconProps } from '@alifd/next/types/icon' +import cls from 'classnames' +import { toJS } from 'mobx' + +interface ObservableDiagramSource { + relationSchema?: Schema + ruleSchema?: Schema + addRuleSchema?: Schema + addRuleGroupSchema?: Schema + removeRuleSchema?: Schema +} + +type ComposedLogicDiagram = React.FC & { + AddRule?: React.FC< + ButtonProps & { + tooltipProps?: TooltipProps + iconProps?: IconProps + defaultValue?: any + } + > + AddRuleGroup?: React.FC< + ButtonProps & { + tooltipProps?: TooltipProps + iconProps?: IconProps + defaultValue?: any + } + > + RemoveRule?: React.FC + Relation?: React.FC + Rule?: React.FC +} + +interface IContext { + field: Formily.Core.Models.ObjectField + schema: Schema + childrenKey: string +} + +interface IItemProps { + nodePath: string +} + +const LogicDiagramContext = createContext(null) +const ItemContext = createContext(null) + +const useLogicDiagram = () => { + return useContext(LogicDiagramContext) +} + +const useNodePath = () => { + return useContext(ItemContext)?.nodePath +} + +const isRelationComponent = (schema: Schema) => { + return schema['x-component'] === 'LogicDiagram.Relation' +} + +const isRuleComponent = (schema: Schema) => { + return schema['x-component'] === 'LogicDiagram.Rule' +} + +const isAddRuleComponent = (schema: Schema) => { + return schema['x-component'] === 'LogicDiagram.AddRule' +} + +const isAddRuleGroupComponent = (schema: Schema) => { + return schema['x-component'] === 'LogicDiagram.AddRuleGroup' +} + +const isRemoveRuleComponent = (schema: Schema) => { + return schema['x-component'] === 'LogicDiagram.RemoveRule' +} + +const useLogicDiagramSource = () => { + const schema = useFieldSchema() + const source: ObservableDiagramSource = {} + return schema.reduceProperties((buf, schema) => { + if (isRelationComponent(schema)) { + return { ...buf, relationSchema: schema } + } else if (isRuleComponent(schema)) { + return { ...buf, ruleSchema: schema } + } else if (isAddRuleComponent(schema)) { + return { ...buf, addRuleSchema: schema } + } else if (isAddRuleGroupComponent(schema)) { + return { ...buf, addRuleGroupSchema: schema } + } else if (isRemoveRuleComponent(schema)) { + return { ...buf, removeRuleSchema: schema } + } + return buf + }, source) +} + +const ACTION_NODE = Symbol('ACTION') + +const patchActionNodes = (nodes: any[] = [], childrenKey: string): any[] => { + return [ + ...nodes.map((node) => { + if (!node[childrenKey] || node[childrenKey].length === 0) { + return { ...node } + } + return { + ...node, + [childrenKey]: patchActionNodes(node[childrenKey], childrenKey), + } + }), + { + [ACTION_NODE]: true, + }, + ] +} + +const useDiagramData = (childrenKey: string) => { + const field = useField() + let value = field.value || { [childrenKey]: [{}] } + if (field.editable) { + value = { + ...value, + [childrenKey]: patchActionNodes(value[childrenKey], childrenKey), + } + } + return value +} + +export const LogicDiagram: ComposedLogicDiagram = observer((props) => { + const field = useField() + const schema = useFieldSchema() + const prefixCls = usePrefixCls('formily-logic-diagram') + const { + relationSchema, + ruleSchema, + addRuleSchema, + addRuleGroupSchema, + removeRuleSchema, + } = useLogicDiagramSource() + const childrenKey = ruleSchema.name as string + const data = useDiagramData(childrenKey) + + const renderNode: RenderNodeFN = (nodePath, type, data) => { + switch (type) { + case NodeTypes.LEAF: + if (data[ACTION_NODE]) { + return ( +
+ + + + + + +
+ ) + } else { + return ( +
+ + + + +
+ ) + } + case NodeTypes.NON_LEAF: + return ( +
+ + + +
+ ) + default: + return null + } + } + + return ( +
+ + + +
+ ) +}) + +LogicDiagram.Relation = () => { + return +} + +LogicDiagram.Rule = () => { + return +} + +LogicDiagram.AddRule = ({ + tooltipProps, + iconProps, + defaultValue, + ...buttonProps +}) => { + const self = useField() + const logicDiagram = useLogicDiagram() + const nodePath = useNodePath() + const prefixCls = usePrefixCls('formily-logic-diagram') + return ( + { + logicDiagram?.field?.addProperty(nodePath, defaultValue || {}) + }} + > + + + } + > + {self.title || tooltipProps?.children || '添加条件'} + + ) +} + +LogicDiagram.AddRuleGroup = ({ + tooltipProps, + iconProps, + defaultValue, + ...buttonProps +}) => { + const self = useField() + const logicDiagram = useLogicDiagram() + const nodePath = useNodePath() + const prefixCls = usePrefixCls('formily-logic-diagram') + return ( + { + logicDiagram?.field?.addProperty( + nodePath, + defaultValue || { [logicDiagram.childrenKey]: [{}] } + ) + }} + > + + + } + > + {self.title || tooltipProps?.children || '添加条件组'} + + ) +} + +LogicDiagram.RemoveRule = ({ iconProps, ...buttonProps }) => { + const logicDiagram = useLogicDiagram() + const nodePath = useNodePath() + const prefixCls = usePrefixCls('formily-logic-diagram') + + return ( +
+ +
+ ) +} + +export default LogicDiagram diff --git a/packages/next/src/logic-diagram/main.scss b/packages/next/src/logic-diagram/main.scss new file mode 100644 index 00000000000..1645d984acd --- /dev/null +++ b/packages/next/src/logic-diagram/main.scss @@ -0,0 +1,61 @@ +@import '~@alifd/next/lib/core/index-noreset.scss'; + +$logic-diagram-prefix-cls: $css-prefix + 'formily-logic-diagram'; + +.#{$logic-diagram-prefix-cls} { + .#{$logic-diagram-prefix-cls}-relation { + height: 100%; + display: flex; + align-items: center; + + .#{$css-prefix}form-item { + margin-bottom: 0; + } + } + + .#{$logic-diagram-prefix-cls}-rule { + height: 100%; + display: flex; + align-items: center; + + .#{$logic-diagram-prefix-cls}-remove-action { + visibility: hidden; + padding-left: 8px; + padding-right: 8px; + } + + .#{$css-prefix}form-item { + margin-bottom: 0; + + .#{$css-prefix}form-item-control { + position: relative; + + .#{$css-prefix}form-item-help { + margin-top: 0; + position: absolute; + bottom: -18px; + } + } + } + } + + .#{$logic-diagram-prefix-cls}-action { + height: 100%; + display: flex; + align-items: center; + .#{$logic-diagram-prefix-cls}-add-rule-btn-icon, + .#{$logic-diagram-prefix-cls}-add-rule-group-btn-icon { + color: $color-brand1-6; + } + } + + &:not(.#{$logic-diagram-prefix-cls}-disabled) { + .#{$logic-diagram-prefix-cls}-rule:hover { + background-color: $color-fill1-2; + + .#{$logic-diagram-prefix-cls}-remove-action { + visibility: visible; + } + } + } +} diff --git a/packages/next/src/logic-diagram/style.ts b/packages/next/src/logic-diagram/style.ts new file mode 100644 index 00000000000..516660f1fea --- /dev/null +++ b/packages/next/src/logic-diagram/style.ts @@ -0,0 +1,4 @@ +import '@alifd/next/lib/button/style' +import '@alifd/next/lib/balloon/style' +import '@alifd/next/lib/icon/style' +import './main.scss' diff --git a/packages/next/src/style.ts b/packages/next/src/style.ts index 4df57532983..7da99beaab4 100644 --- a/packages/next/src/style.ts +++ b/packages/next/src/style.ts @@ -28,3 +28,4 @@ import './form-grid/style' import './form-step/style' import './form-tab/style' import './upload/style' +import './logic-diagram/style'