From 9cc5363f49332a37a223f54fced237377b878acd Mon Sep 17 00:00:00 2001 From: chenos Date: Sat, 10 Jul 2021 16:21:05 +0800 Subject: [PATCH] improve select --- packages/client/src/schemas/form/index.tsx | 5 + packages/client/src/schemas/select/index.md | 314 ++++++++++++++- packages/client/src/schemas/select/index.tsx | 403 ++++++++++++++----- 3 files changed, 616 insertions(+), 106 deletions(-) diff --git a/packages/client/src/schemas/form/index.tsx b/packages/client/src/schemas/form/index.tsx index fea34c198..9fa68ca19 100644 --- a/packages/client/src/schemas/form/index.tsx +++ b/packages/client/src/schemas/form/index.tsx @@ -5,6 +5,7 @@ import { Schema, useFieldSchema, observer, + SchemaExpressionScopeContext, } from '@formily/react'; import { SchemaRenderer, useDesignable } from '../DesignableSchemaField'; import get from 'lodash/get'; @@ -40,6 +41,8 @@ function useDefaultValues() { } export const Form: any = observer((props: any) => { + const scope = useContext(SchemaExpressionScopeContext); + const { useValues = useDefaultValues } = props; const values = useValues(); const form = useMemo(() => { @@ -67,6 +70,8 @@ export const Form: any = observer((props: any) => { return (
{ designableSchema.properties = subSchema.properties; refresh(); diff --git a/packages/client/src/schemas/select/index.md b/packages/client/src/schemas/select/index.md index feba122b7..36cb1782a 100644 --- a/packages/client/src/schemas/select/index.md +++ b/packages/client/src/schemas/select/index.md @@ -11,7 +11,10 @@ group: # Select - 选择器 -## 基本用法 + +## Select + +### 单选 ```tsx import React from 'react'; @@ -70,7 +73,7 @@ export default () => { }; ``` -## 多选 +### 多选 ```tsx import React from 'react'; @@ -134,7 +137,7 @@ export default () => { }; ``` -## 分组 +### 分组 ```tsx import React from 'react'; @@ -200,7 +203,11 @@ export default () => { }; ``` -## 值为对象 +## Select.Object + +选项值为 Object 类型 + +### 单选 ```tsx import React from 'react'; @@ -233,12 +240,14 @@ const schema = { 'x-component': 'Select.Object', 'x-component-props': { placeholder: 'please enter', - labelField: 'title', - valueField: 'id', - labelInValue: true, + // mode: 'tags', + fieldNames: { + label: 'title', + value: 'id', + }, }, 'x-reactions': { - target: 'name2', + target: 'read', fulfill: { state: { value: '{{$self.value}}', @@ -250,16 +259,17 @@ const schema = { interface: 'select', type: 'string', title: `阅读模式`, - required: true, enum: dataSource, 'x-read-pretty': true, 'x-decorator': 'FormItem', 'x-component': 'Select.Object', 'x-component-props': { placeholder: 'please enter', - labelField: 'title', - valueField: 'id', - labelInValue: true, + // mode: 'tags', + fieldNames: { + label: 'title', + value: 'id', + }, }, } } @@ -270,4 +280,282 @@ export default () => { ); }; -``` \ No newline at end of file +``` + +### 多选 + +```tsx +import React from 'react'; +import { SchemaRenderer } from '../'; + +const dataSource = [ + { + id: 1, + title: '标题1', + }, + { + id: 2, + title: '标题2', + }, + { + id: 3, + title: '标题3', + }, +]; + +const schema = { + type: 'object', + properties: { + input: { + interface: 'select', + type: 'string', + title: `编辑模式`, + enum: dataSource, + 'x-decorator': 'FormItem', + 'x-component': 'Select.Object', + 'x-component-props': { + placeholder: 'please enter', + mode: 'tags', + fieldNames: { + label: 'title', + value: 'id', + }, + }, + 'x-reactions': { + target: 'read', + fulfill: { + state: { + value: '{{$self.value}}', + }, + }, + }, + }, + read: { + interface: 'select', + type: 'string', + title: `阅读模式`, + enum: dataSource, + 'x-read-pretty': true, + 'x-decorator': 'FormItem', + 'x-component': 'Select.Object', + 'x-component-props': { + placeholder: 'please enter', + mode: 'tags', + fieldNames: { + label: 'title', + value: 'id', + }, + }, + } + } +}; + +export default () => { + return ( + + ); +}; +``` + +## Select.Drawer + +```tsx +import React from 'react'; +import { SchemaRenderer } from '../'; +import { useSelect } from './'; + +console.log({ useSelect }) + +const dataSource = [ + { + id: 1, + title: '标题1', + }, + { + id: 2, + title: '标题2', + }, + { + id: 3, + title: '标题3', + }, +]; + +function useValues() { + return { + table: dataSource, + } +} + +const schema = { + type: 'object', + properties: { + input: { + interface: 'select', + type: 'string', + title: `编辑模式`, + enum: dataSource, + default: [ + { + id: 1, + title: '标题1', + }, + { + id: 2, + title: '标题2', + }, + ], + 'x-decorator': 'FormItem', + 'x-component': 'Select.Drawer', + 'x-component-props': { + placeholder: 'please enter', + mode: 'tags', + fieldNames: { + label: 'title', + value: 'id', + }, + }, + 'x-reactions': { + target: 'read', + fulfill: { + state: { + value: '{{$self.value}}', + }, + }, + }, + properties: { + options: { + type: 'void', + 'x-component': 'Select.Options', + properties: { + form: { + type: 'void', + 'x-component': 'Form', + 'x-component-props': { + useValues: '{{ useValues }}', + }, + properties: { + table: { + type: 'array', + 'x-component': 'Table', + properties: { + actionbar: { + type: 'void', + 'x-component': 'Table.ActionBar', + 'x-component-props': { + align: 'bottom', + }, + properties: { + action: { + type: 'void', + 'x-component': 'Action', + 'x-component-props': { + useAction: '{{ useSelect }}', + }, + title: '确定', + }, + }, + }, + column1: { + type: 'void', + title: '标题', + 'x-component': 'Table.Column', + 'x-component-props': { + // title: 'z1', + }, + // 'x-designable-bar': 'Table.Column.DesignableBar', + properties: { + title: { + type: 'string', + required: true, + 'x-read-pretty': true, + 'x-decorator-props': { + feedbackLayout: 'popover', + }, + 'x-decorator': 'FormItem', + 'x-component': 'Input', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + read: { + interface: 'select', + type: 'string', + title: `阅读模式`, + enum: dataSource, + default: [ + { + id: 1, + title: '标题1', + }, + { + id: 2, + title: '标题2', + }, + ], + 'x-read-pretty': true, + 'x-decorator': 'FormItem', + 'x-component': 'Select.Drawer', + 'x-component-props': { + placeholder: 'please enter', + mode: 'tags', + fieldNames: { + label: 'title', + value: 'id', + }, + }, + properties: { + option: { + type: 'void', + 'x-component': 'Select.OptionTag', + properties: { + form: { + type: 'void', + 'x-component': 'Form', + properties: { + input: { + type: 'string', + 'x-component': 'Input', + }, + }, + }, + }, + }, + }, + } + } +}; + +export default () => { + return ( + + ); +}; +``` + + diff --git a/packages/client/src/schemas/select/index.tsx b/packages/client/src/schemas/select/index.tsx index 31f28cb0a..f2261c22a 100644 --- a/packages/client/src/schemas/select/index.tsx +++ b/packages/client/src/schemas/select/index.tsx @@ -1,38 +1,50 @@ import React from 'react'; -import { connect, mapReadPretty, mapProps, useField } from '@formily/react'; -import { Select as AntdSelect, Tag } from 'antd'; +import { + connect, + mapReadPretty, + mapProps, + useField, + observer, + RecursionField, + useFieldSchema, +} from '@formily/react'; +import { Drawer, Select as AntdSelect, Tag } from 'antd'; import { PreviewText } from '@formily/antd'; import { LoadingOutlined } from '@ant-design/icons'; import { SelectProps } from 'antd/lib/select'; -import { isArr, isValid } from '@formily/shared'; -import { Field } from '@formily/core/esm/models/Field'; -import { useAction } from '../../state'; -import { Display } from '../display'; -import { useRequest } from 'ahooks'; -import uniq from 'lodash/uniq'; +import { isArr, isValid, toArr } from '@formily/shared'; +import { useState } from 'react'; +import { useDesignable } from '../DesignableSchemaField'; +import { createContext } from 'react'; +import { useContext } from 'react'; +import { useTableContext } from '../table'; -type ComposedSelect = React.FC> & { - Object?: React.FC; -}; - -export const Select: ComposedSelect = connect( +export const Select: any = connect( (props) => { const { options = [], ...others } = props; - return - {options.map((option: any, key: any) => { - if (option.children) { - return ( - - {option.children.map((child: any, childKey: any) => ( - {child.label} - ))} - - ) - } else { - return {option.label} - } - })} - + return ( + + {options.map((option: any, key: any) => { + if (option.children) { + return ( + + {option.children.map((child: any, childKey: any) => ( + + {child.label} + + ))} + + ); + } else { + return ( + + {option.label} + + ); + } + })} + + ); }, mapProps( { @@ -54,9 +66,8 @@ export const Select: ComposedSelect = connect( mapReadPretty((props) => { const field = useField(); const dataSource = field.dataSource || []; - console.log('field.value', field.value, dataSource) - const value = Array.isArray(field.value) ? field.value : [field.value]; - const values = uniq(value); + console.log('field.value', field.value, dataSource); + const values = toArr(field.value); const findOptions = (options: any[]) => { let current = []; for (const option of options) { @@ -64,100 +75,96 @@ export const Select: ComposedSelect = connect( current.push(option); } if (option.children) { - current = current.concat(findOptions(option.children)); + const children = findOptions(option.children); + current.push(...children); } } return current; - } + }; const options = findOptions(dataSource); return (
- {options - .map((option, key) => ( - {option.label} - ))} + {options.map((option, key) => ( + + {option.label} + + ))}
); }), ); -function getFieldName(field: any) { - if (typeof field === 'string') { - return field; - } - if (typeof field === 'object') { - return field.name; - } -} - Select.Object = connect( (props) => { const field = useField(); const { value, onChange, - dataRequest, - labelField: lf, - valueField: vf, + fieldNames = { + label: 'label', + value: 'value', + }, ...others } = props; const options = field?.['dataSource'] || props.options || []; - console.log({options}); - const labelField = getFieldName(lf); - const valueField = getFieldName(vf); - const { data = options, loading } = useRequest(() => dataRequest ? dataRequest : Promise.resolve(options), { - refreshDeps: [options] - }); - const getValues = () => { - if (!isValid(value)) { - return []; - } - return isArr(value) ? value : [value]; - } - const getOptionValue = () => { - const values = getValues().map((item) => { + let optionValue = undefined; + if (isArr(value)) { + optionValue = value.map((val) => { return { - value: item[valueField], - label: item[labelField], + label: val[fieldNames.label], + value: val[fieldNames.value], }; }); - if (props.mode === 'multiple' || props.mode === 'tags') { - return values; - } - return values.length ? values[0] : null; - }; - const getOptions = () => { - return data.map(item => { - return { - value: item[valueField], - label: item[labelField], - }; - }) + } else if (value) { + optionValue = { + label: value[fieldNames.label], + value: value[fieldNames.value], + }; } return ( { - if (!isValid(option)) { + value={optionValue} + onChange={(selectValue: any) => { + if (!selectValue) { onChange(null); } - if (isArr(option)) { - const optionValues = option.map(item => item.value); - const selectValue = data.filter( - (item) => optionValues.includes(item[valueField]), - ); - onChange(selectValue); + if (isArr(selectValue)) { + const selectValues = selectValue.map((s) => s.value); + const values = {}; + if (isArr(value)) { + value.forEach((option) => { + const val = option[fieldNames.value]; + if (selectValues.includes(val)) { + values[val] = option; + } + }); + } + options.forEach((option) => { + const val = option[fieldNames.value]; + if (selectValues.includes(val)) { + values[val] = option; + } + }); + console.log({ selectValue, values }); + onChange(Object.values(values)); } else { - const selectValue = data.find( - (item) => item[valueField] === option.value, + // 这里不能用 undefined,需要用 null + onChange( + options.find( + (option) => option[fieldNames.value] === selectValue.value, + ), ); - onChange(selectValue); } }} - labelInValue - value={getOptionValue()} - options={getOptions()} + options={options.map((option) => { + return { + label: option[fieldNames.label], + value: option[fieldNames.value], + }; + })} /> ); }, @@ -179,7 +186,217 @@ Select.Object = connect( }; }, ), - mapReadPretty(Display.ObjectSelect), + mapReadPretty( + observer((props: any) => { + const { value, fieldNames = { label: 'label' }, ...others } = props; + if (!value) { + return null; + } + const values = toArr(value); + return ( +
+ {values.map((val) => + fieldNames.color ? ( + {val[fieldNames.label]} + ) : ( + {val[fieldNames.label]} + ), + )} +
+ ); + }), + ), ); +const OptionTagContext = createContext(null); + +const SelectContext = createContext({}); + +Select.Drawer = connect( + (props) => { + const field = useField(); + const { + value, + onChange, + fieldNames = { + label: 'label', + value: 'value', + }, + ...others + } = props; + const [visible, setVisible] = useState(false); + const schema = useFieldSchema(); + const options = field?.['dataSource'] || props.options || []; + + let optionValue = undefined; + if (isArr(value)) { + optionValue = value.map((val) => { + return { + label: val[fieldNames.label], + value: val[fieldNames.value], + }; + }); + } else if (value) { + optionValue = { + label: value[fieldNames.label], + value: value[fieldNames.value], + }; + } + + const onFieldChange = (selectValue) => { + if (!isValid(selectValue)) { + onChange(null); + return; + } + if (isArr(selectValue)) { + const values = {}; + if (isArr(value)) { + value.forEach((option) => { + const val = option[fieldNames.value]; + if (selectValue.includes(val)) { + values[val] = option; + } + }); + } + options.forEach((option) => { + const val = option[fieldNames.value]; + if (selectValue.includes(val)) { + values[val] = option; + } + }); + onChange(Object.values(values)); + } else { + // 这里不能用 undefined,需要用 null + onChange( + options.find((option) => option[fieldNames.value] === selectValue), + ); + } + }; + + return ( + <> + { + setVisible(true); + }} + onChange={(selectValue: any) => { + if (!selectValue) { + onChange(null); + return; + } + if (isArr(selectValue)) { + const selectValues = selectValue.map((s) => s.value); + onFieldChange(selectValues); + } else { + onFieldChange(selectValue.value); + } + }} + > + setVisible(false)} + > + + { + return s['x-component'] === 'Select.Options'; + }} + /> + + + + ); + }, + mapProps( + { + dataSource: 'options', + loading: true, + }, + (props, field) => { + return { + ...props, + options: field?.['dataSource'], + suffixIcon: + field?.['loading'] || field?.['validating'] ? ( + + ) : ( + props.suffixIcon + ), + }; + }, + ), + mapReadPretty( + observer((props: any) => { + const field = useField(); + const { fieldNames = { label: 'label' }, ...others } = props; + const value = field.value || field.initialValue; + const schema = useFieldSchema(); + console.log({ field, value }); + if (!value) { + return null; + } + const values = toArr(value); + return ( +
+ {values.map((data, index) => { + return ( + + { + return s['x-component'] === 'Select.OptionTag'; + }} + /> + + ); + })} +
+ ); + }), + ), +); + +Select.Options = observer((props) => { + return <>{props.children}; +}); + +export function useSelect() { + const { onChange } = useContext(SelectContext); + const { data, selectedRowKeys } = useTableContext(); + return { + async run() { + console.log({ data, selectedRowKeys }); + onChange && onChange(selectedRowKeys); + }, + }; +} + +Select.OptionTag = observer((props) => { + const [visible, setVisible] = useState(false); + const { data, fieldNames } = useContext(OptionTagContext); + return ( + <> + setVisible(true)}>{data[fieldNames.label]} + setVisible(false)}> + {props.children} + + + ); +}); + export default Select;