import React, { Component, ReactNode } from 'react'; import moment from 'moment'; import { Checkbox, Icon } from 'antd'; import { is, List } from 'immutable'; import * as Tools from 'jad-tool'; import { cloneDeepWith } from 'lodash'; import { initOptions } from './utils'; type PropsKeyValue = { propPValue?: string, propValue: string, propName: string, } type OptionType = { value: string | number, name?: string, fullName?: (string)[], leaf: number, // 从 0 开始 parent?: OptionType, children?: OptionType[], checked: boolean, indeterminate: boolean, // 我们这一版只考虑父子节点有关联的,即 indeterminate 为 fasle 的,且此时 showCheckedStrategy 最好设置为 SHOW_ALL [propName: string]: any, }; type State = { options: OptionType[], selectedMap: OptionType[], // 所有选中的选项,用于抛出给上层 clickedShowMap: { [stateName: string]: string | number }, // 点击了那些层级 }; type PanelProps = { options: OptionType[], value?: (string | number)[], // 一般纯面板 MultiCascaderPanel 传这个 selectedMap?: OptionType[], // 一般 MultiCascaderSelect 可传这个 disabled?: boolean, keyValue?: PropsKeyValue, onChange?: (selectedMap: OptionType[]) => void, showCheckedStrategy?: 'SHOW_PARENT' | 'SHOW_CHILD' | 'SHOW_ALL', // 默认 SHOW_PARENT [propName: string]: any, } export class MultiCascaderPanel extends Component{ state: State; keyValue = { propPValue: 'pValue', propValue: 'value', propName: 'name', } constructor(props: PanelProps) { super(props); this.state = { options: props.options, selectedMap: [], clickedShowMap: {}, } if (props['keyValue']) this.keyValue = { propPValue: props['keyValue']['propPValue'] || this.keyValue.propPValue, propValue: props['keyValue']['propValue'] || this.keyValue.propValue, propName: props['keyValue']['propName'] || this.keyValue.propName, } } componentDidMount() { const { options, value, onChange } = this.props; if (!Tools.isEmptyArray(options) && value) { this.initOptions(value, options) } } /** * 这些场景需要有效触发 UNSAFE_componentWillReceiveProps 更新 * 1. options * 2. value: * 3. 或者 selectedMap: */ UNSAFE_componentWillReceiveProps(nextProps) { const { options: nextPropsOptions, value, selectedMap } = nextProps; if (!is(List(nextPropsOptions), List(this.props.options)) || !is(List(value), List(this.props.value))) { this.initOptions(value, nextPropsOptions) } if (!is(List(nextPropsOptions), List(this.props.options)) || !is(List(selectedMap), List(this.props.selectedMap))) { this.setState({ selectedMap: selectedMap, options: nextPropsOptions }) } } initOptions = (initValue: (string | number)[], initValueOptions: OptionType[]) => { const { selectedMap, options, optionsFlat, selectedValue } = initOptions.bind(this)(initValue, initValueOptions); const { onChange } = this.props; this.setState({ selectedMap, options, optionsFlat, selectedValue, }) onChange && onChange(selectedValue) } // 从低到高 onCheckLow2High = (checked: boolean, record: OptionType) => { record['checked'] = checked; if (!record['parent']) { return } else { let parentChildren = record['parent']['children']; let flag = false; if (parentChildren.every(item => item.checked)) { flag = true; } if (!flag && parentChildren.some(item => item.checked || item.indeterminate)) { record['parent']['indeterminate'] = true; } else { record['parent']['indeterminate'] = false; } record['parent']['checked'] = flag; this.onCheckLow2High(flag, record['parent']) }; } // 从高到低 onCheckHigh2Low = (checked: boolean, record: OptionType) => { record['checked'] = checked; record['indeterminate'] = false; // 从高到低,indeterminate其实可直接设置为 checked 的值,不需要遍历children 的 checked if (record['children'] && record['children'].length) { record['children'].forEach(child => { this.onCheckHigh2Low(checked, child) }) } } // 找到根结点 findRoot = (record: OptionType) => { const { propValue } = this.keyValue; function walk(r) { if (r && r['parent'] && r['parent'][propValue]) { return walk(r['parent']) } else if (r && !r['parent']) { return r } } return walk(record) } /** * @param options 最新操作过后的 options * 1. getSelectedMap 获取选中的项,此处可以根据 showCheckedStrategy 定制回填方式 * 2. 取值: * 2.1 SHOW_ALL:所有选中的节点都展示 * 2.2 SHOW_PARENT:当父节点的子节点都选中时,展示父节点(父节点:cheked:true ) —— 此为默认 * 2.3 SHOW_CHILD:展示最底层节点 */ getSelectedMap = (options: OptionType[]) => { const { showCheckedStrategy } = this.props; return options.reduce((prev, current) => { if (showCheckedStrategy === 'SHOW_CHILD') { if (current.checked && !current.children) { return prev.concat(current) } else if ((current.checked || current.indeterminate) && current.children) { return prev.concat(this.getSelectedMap(current.children)) } } else if (showCheckedStrategy === 'SHOW_ALL') { if (current.checked) { if (current.children) return prev.concat(current).concat(this.getSelectedMap(current.children)) else return prev.concat(current) } else if (current.indeterminate) { //后代部分选中 return prev.concat(this.getSelectedMap(current.children)) } } else { if (current.checked) { return prev.concat(current) } else if (current.indeterminate && current.children) { //后代部分选中 return prev.concat(this.getSelectedMap(current.children)) } } return prev }, []) } // 获取 selectedMap 和 替换 原来的 options中新操作过的 rootRecord onCollect = (record: OptionType, options: OptionType[]) => { const { propValue } = this.keyValue; const rootRecord = this.findRoot(record); options.find((item, index) => { if (item[propValue] === rootRecord[propValue]) { options[index] = rootRecord; // 注意:这里修改了 options return true }; }) const selectedMap = this.getSelectedMap(options); return { selectedMap: selectedMap, options: options } } // 选中 onCheck = (checked: boolean, record: OptionType, e?) => { window.event ? window.event.cancelBubble = true : e.stopPropagation(); this.onShow(record); let { options } = this.state; let { onChange } = this.props; let newRecord = record; let newOptions = options; this.onCheckLow2High(checked, newRecord); this.onCheckHigh2Low(checked, newRecord); const collect = this.onCollect(newRecord, newOptions); this.setState({ selectedMap: collect.selectedMap, options: collect.options }, () => { const { selectedMap } = this.state; onChange && onChange(selectedMap) }) } // 打开子面板 onShow = (record: OptionType) => { if(!record) return console.error('MultiCascaderPanel onShow receive OptionType is undefined') const { propValue } = this.keyValue; const newClickedShowMap = {}; function walkParent(r: OptionType) { newClickedShowMap[r['leaf']] = r[propValue] if (r && r['parent'] && r['parent'][propValue]) { walkParent(r['parent']) } } walkParent(record) this.setState({ clickedShowMap: { ...newClickedShowMap, [record['leaf'] + 1]: undefined } }) } renderColumns = () => { const { options, clickedShowMap } = this.state; const { disabled } = this.props; const { propValue, propName } = this.keyValue; const columns = []; function mapColumn(options, clickedShowMap) { if (!options || !options.length) return const column = () columns.unshift(column); } mapColumn.bind(this)(options, clickedShowMap); return columns; } render() { const { onMouseDown } = this.props; return (
{this.renderColumns()}
); } }