import React, { useEffect } from 'react'; import { connect, mapReadPretty, mapProps, useField, observer, RecursionField, useFieldSchema, Schema, } 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, toArr } from '@formily/shared'; import { useState } from 'react'; import { useDesignable } from '../'; import { createContext } from 'react'; import { useContext } from 'react'; import { get, isEmpty } from 'lodash'; import { Field, isArrayField, isField } from '@formily/core'; import { Action } from '../action'; import { BlockSchemaContext, VisibleContext } from '../../context'; import { SchemaRenderer } from '../../components/schema-renderer'; import { uid } from '@formily/shared'; import { CollectionFieldContext } from '../table'; import { CollectionProvider, useCollectionContext, useResourceRequest, } from '../../constate'; import { Resource } from '../../resource'; import { useRequest } from 'ahooks'; import constate from 'constate'; import './index.less'; import { useCompile } from '../../hooks/useCompile'; export const Select: any = connect( (props) => { const { options = [], ...others } = props; const compile = useCompile(); return ( {options.map((option: any, key: any) => { if (option.children) { return ( {option.children.map((child: any, childKey: any) => ( {compile(child.label)} ))} ); } else { return ( {compile(option.label)} ); } })} ); }, mapProps( { dataSource: 'options', loading: true, }, (props, field) => { return { ...props, suffixIcon: field?.['loading'] || field?.['validating'] ? ( ) : ( props.suffixIcon ), }; }, ), mapReadPretty((props) => { // console.log('mapReadPretty', props.value) // return
; if (!isValid(props.value)) { return
; } const compile = useCompile(); const field = useField(); if (isArrayField(field) && field?.value?.length === 0) { return
; } const dataSource = field.dataSource || []; console.log('field.value', field.value, dataSource); const values = toArr(field.value); const findOptions = (options: any[]) => { let current = []; for (const option of options) { if (values.includes(option.value)) { current.push(option); } if (option.children) { const children = findOptions(option.children); current.push(...children); } } return current; }; const options = findOptions(dataSource); return (
{options.map((option, key) => ( {compile(option.label)} ))}
); }), ); Select.Object = connect( (props) => { const field = useField(); const { value, onChange, fieldNames = { label: 'label', value: 'value', }, ...others } = props; 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], }; } return ( { if (!isValid(selectValue)) { onChange(null); return; } 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 { // 这里不能用 undefined,需要用 null onChange( options.find( (option) => option[fieldNames.value] === selectValue.value, ), ); } }} options={options.map((option) => { return { label: option[fieldNames.label], value: option[fieldNames.value], }; })} /> ); }, mapProps( { dataSource: 'options', loading: true, }, (props, field) => { return { ...props, options: field?.['dataSource'], suffixIcon: field?.['loading'] || field?.['validating'] ? ( ) : ( props.suffixIcon ), }; }, ), mapReadPretty( observer((props: any) => { const { value, fieldNames = { label: 'label', color: 'color' }, ...others } = props; if (!value) { return null; } if (isEmpty(value)) { return null; } const values = toArr(value); return (
{values.map((val) => fieldNames.color ? ( {val[fieldNames.label]} ) : ( {val[fieldNames.label]} ), )}
); }), ), ); const OptionTagContext = createContext(null); const SelectedRowsContext = createContext({}); Select.useOkAction = () => { const { props } = useContext(SelectContext); const { selectedRows } = useContext(SelectedRowsContext); return { async run() { props.onChange(selectedRows); console.log('selectedRows', selectedRows); }, }; }; Select.useRowSelection = () => { const { props } = useContext(SelectContext); return { type: props.multiple ? 'checkbox' : 'radio', }; }; Select.useSelect = () => { const { setSelectedRows } = useContext(SelectedRowsContext); return (keys, rows) => { setSelectedRows && setSelectedRows(rows); console.log('Select.onSelect', keys, rows); }; }; export const useSelectedRowKeys = () => { const { rowKey, selectedRows } = useContext(SelectedRowsContext); const [selectedRowKeys, setSelectedRowKeys] = useState( selectedRows.map((row) => row[rowKey]), ); useEffect(() => { setSelectedRowKeys(selectedRows.map((row) => row[rowKey])); }, [selectedRows]); console.log('useSelectedRowKeys', selectedRows); return { selectedRowKeys, setSelectedRowKeys }; }; Select.useSelectedRowKeys = useSelectedRowKeys; const SelectContext = createContext(null); Select.Drawer = connect( (props) => { const field = useField(); const { onChange, // fieldNames = { // label: 'id', // value: 'id', // }, ...others } = props; let value = props.value; const [visible, setVisible] = useState(false); const { schema } = useDesignable(); const fieldNames = { label: 'id', value: 'id', ...(get(schema['x-component-props'], 'fieldNames') || {}), }; const options = field?.['dataSource'] || props.options || []; if (props.multiple) { Object.assign(others, { mode: 'multiple', }); } let optionValue = undefined; if (props.multiple) { value = toArr(value); } 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 [selectedRows, setSelectedRows] = useState(toArr(field.value)); console.log('fieldNames', { fieldNames, field, value }); console.log('useSelectedRowKeys.toArr', toArr(field.value)); useEffect(() => { setSelectedRows(toArr(field.value)); }, [field.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), ); } // setSelectedRows(toArr(field.value)); }; // const selectedKeys = toArr(optionValue).map((item) => item.value); const collectionField = useContext(CollectionFieldContext); // console.log({ optionValue, value, schema, collectionField }); 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); } }} > { if (s['x-component'] === 'Select.Options.Drawer') { const prop = Object.values(s.properties).shift(); if (prop) { prop['x-component-props']['rowKey'] = collectionField?.targetKey || 'id'; } } return s['x-component'] === 'Select.Options.Drawer'; }} /> ); }, mapProps( { dataSource: 'options', loading: true, }, (props, field) => { return { ...props, options: field?.['dataSource'], suffixIcon: field?.['loading'] || field?.['validating'] ? ( ) : ( props.suffixIcon ), }; }, ), mapReadPretty( observer((props: any) => { const collectionField = useContext(CollectionFieldContext); const field = useField(); const { ...others } = props; const value = field.value || field.initialValue; const { schema } = useDesignable(); const fieldNames = { label: 'id', value: 'id', ...(get(schema['x-component-props'], 'fieldNames') || {}), }; console.log('fieldNames', { fieldNames, field, value }); if (!value) { return null; } const values = toArr(value); const s = schema.reduceProperties((buf, current) => { if (current['x-component'] === 'Select.OptionTag') { return current; } return buf; }, null); return (
{values.map((data, index) => { return ( {s ? ( ) : ( data[fieldNames.label] )} ); })}
); }), ), ); Select.Drawer.useResource = ({ onSuccess }) => { const { collection } = useCollectionContext(); const ctx = useContext(OptionTagContext); const resource = useResourceRequest({ resourceName: collection?.name, resourceKey: ctx.data.id, }); console.log('OptionTagContext', ctx.data.id); const { schema } = useDesignable(); const fieldFields = (schema: Schema) => { const names = []; schema.reduceProperties((buf, current) => { if (current['x-component'] === 'Form.Field') { const fieldName = current['x-component-props']?.['fieldName']; if (fieldName) { buf.push(fieldName); } } else { const fieldNames = fieldFields(current); buf.push(...fieldNames); } return buf; }, names); return names; }; const [visible] = useContext(VisibleContext); const service = useRequest( (params?: any) => { console.log('Table.useResource', params); return resource.get({ ...params, appends: fieldFields(schema) }); }, { formatResult: (result) => result?.data, onSuccess, manual: true, }, ); useEffect(() => { if (visible) { service.run(); } }, [visible]); return { resource, service, initialValues: service.data, ...service }; }; Select.Options = observer((props) => { return <>{props.children}; }); Select.Options.Drawer = Action.Drawer; export function useSelect() { const { onChange } = useContext(SelectContext); return { async run() { onChange && onChange([]); }, }; } export function useOptionTagValues() { const { data } = useContext(OptionTagContext); return data; } Select.OptionTag = observer((props) => { const [visible, setVisible] = useState(false); const { data, fieldNames } = useContext(OptionTagContext); return ( setVisible(true)}> {data[fieldNames.label]} {props.children} ); }); export default Select;