import { Divider, Space, Tree } from 'antd'; import type { DataNode, TreeProps } from 'antd/es/tree'; import type { SpaceSize } from 'antd/lib/space'; import Link from 'antd/lib/typography/Link'; import _ from 'lodash'; import moment from 'moment'; import type { CheckInfo } from 'rc-tree/lib/Tree'; import type { Key, ReactNode } from 'react'; import { memo, useCallback, useEffect, useMemo, useState } from 'react'; import { FormattedMessage } from 'umi'; import CustomBatchDatePicker from '../CustomDatePicker/CustomBatchDatePicker'; import CustomBatchDay from '../CustomDatePicker/CustomBatchDay'; import CustomLabel from '../CustomDatePicker/CustomLabel'; import Flex from '../Flex'; import PermissionParentShow from '../PermissionParentShow'; import { formatPostData, validate, validateDay } from '../utils'; import useAppDetail, { useBatchSetExpires, useGetAppId } from '../utils/hooks'; import { useOptionLevel, useRelation } from './hooks'; import './index.less'; import Level from './Level'; import { dealTreeData } from './util'; export type CheckedType = | { checked: Key[]; halfChecked: Key[]; } | Key[]; export interface TreeNode extends DataNode { id: number; appId?: number; /** * @description 名称 */ name?: string; /** * @description 有效期,这里传入的值都是秒为单位。 */ expiredAt?: any; uniqueKey?: string; isAllowApply?: 0 | 1; children?: TreeNode[]; parentNode?: TreeNode; isBind?: boolean; } export interface CustomTreeProps extends TreeProps { /** * @description 树结构 */ treeData: TreeNode[]; /** * @description 整棵树都禁止 */ disabled?: boolean; /** * @description 需要禁用的id列表 */ disabledIds?: string[]; /** * 需要联动固定住的id列表 */ fixedIds?: string[]; /** * @description 是否需要隐藏不允许申请的内容 */ isAllowApply?: boolean; /** * @description 需要区分的类型 */ type?: 'feature' | 'option' | 'city'; /** * @description 是否添加日期,默认不加 */ isHaveDate?: boolean; /** * @description 是否添加天数,默认不加 */ isHaveDay?: boolean; /** * @description 这里是如果需要日期,需要加上老数据和现在value做对比的。 */ isHaveCheckBox?: boolean; /** * @description 运营端配置权限,可配置默认选中。 */ oldData?: TreeNode[]; /** * @description 是否需要显示层级,默认显示层级 */ isLevel?: boolean; /** * 只有叶子节点可选,默认都可选 */ onlyLeafCheckable?: boolean; value?: any; onChange?: (v: any) => void; appId?: number; customRender?: (treeName: React.ReactNode, item: any) => React.ReactNode; checkInterceptor?: (checked: CheckedType, info: CheckInfo) => Promise; isOtherUser?: boolean; /** 扩展工具栏渲染 */ renderToolbar?: () => ReactNode; /** 展开收起左侧渲染 */ renderExpandLeft?: () => ReactNode; /** 工具栏间距 */ toolbarSpaceSize?: SpaceSize | [SpaceSize, SpaceSize]; fixedHeight?: boolean; } const CustomTree = (props: CustomTreeProps) => { const { value, onChange = () => {}, isLevel = true, appId: parentId, customRender, isOtherUser, treeData, type, oldData, isHaveDate = false, isHaveDay = false, disabled = false, disabledIds, isAllowApply = false, isHaveCheckBox = false, checkInterceptor, onlyLeafCheckable = false, renderToolbar, renderExpandLeft, toolbarSpaceSize, fixedHeight = true, fixedIds, ...otherProps } = props; const appId = useGetAppId(); const { appDetail } = useAppDetail(parentId || appId); const { defaultExpirationTime } = appDetail || {}; const [selectIdMap, setSelectIdMap] = useState>({}); const { checkStrictly, setCheckStrictly, getRemain } = useRelation(); const { expandedKeys, setDefaultExpandAll, onExpand, defaultExpandAll } = useOptionLevel({ treeData, }); const { applyAll, applyNew } = useBatchSetExpires({ selectIdMap, onChange, value, oldData, }); const flattenTreeData = useMemo( () => dealTreeData(treeData)?.filter((item) => item.isAllowApply === 0), [treeData], ); const formatTreeDate = useMemo( () => formatPostData(treeData, (item: TreeNode): TreeNode => { const { name, key, uniqueKey, isAllowApply: allowApply, disabled: nodeDisabled, id: nodeId, children, } = item || {}; const id = String(nodeId); const currDisabled = nodeDisabled || disabledIds?.includes(id); const keyText = key || uniqueKey; const title = `${name} ${keyText ? `(${keyText})` : ''}`; return { ...item, title: isAllowApply ? ( Boolean(allowApply) ? ( title ) : ( ) ) : ( title ), key: id, disabled: isAllowApply ? !allowApply || currDisabled : currDisabled, checkable: onlyLeafCheckable ? !Boolean(Array.isArray(children) && children.length) : undefined, }; }), [treeData, isAllowApply, disabledIds, onlyLeafCheckable], ); const defaultRender = useCallback( (item: TreeNode) => { const { id: nodeId, isAllowApply: allowApply, disabled: nodeDisabled, title, isBind, name, appId: nodeAppId, } = item || {}; const id = String(nodeId); const currDisabled = nodeDisabled || disabledIds?.includes(id); const ele = ( <> {selectIdMap[id] ? ( ) : ( title )} ); if (type && type !== 'city') { return ( <> {selectIdMap[id] ? ( ) ) : ( name ) } appId={nodeAppId} data={item} > {title} } isHaveDate={isHaveDate} isHaveDay={isHaveDay} data={value?.data} selectId={nodeId} isHaveCheckBox={isHaveCheckBox} isBind={!isHaveCheckBox && isBind && !isOtherUser} /> ) : ( ) ) : ( name ) } appId={nodeAppId} data={item} > {title} )} ); } return ele; }, [ disabledIds, isAllowApply, isHaveCheckBox, isHaveDate, isHaveDay, isOtherUser, onChange, selectIdMap, type, value?.data, ], ); const renderTreeNode = useCallback( (item: TreeNode) => { const ele = defaultRender(item); if (type && ['feature', 'city', 'option'].includes(type) && _.isFunction(customRender)) { return customRender(ele, item); } return ele; }, [customRender, defaultRender, type], ); useEffect(() => { setSelectIdMap(_.keyBy(value?.data || [], 'id')); }, [value]); const checkHandler = useCallback( async (checked: CheckedType, info: CheckInfo) => { if (_.isFunction(checkInterceptor)) { const intercept = await checkInterceptor(checked, info); if (intercept) return; } // 触发变更事件 const remain = getRemain(checked, info, [ ...(disabledIds || []), ...(fixedIds || []), ...(isAllowApply ? flattenTreeData : [])?.map((item) => String(item.id)), ]); const tempSelectIdMap = {}; remain.forEach((v) => { const key = String(v); if (selectIdMap[key]) { tempSelectIdMap[key] = selectIdMap[key]; } else { tempSelectIdMap[key] = { id: v, expiredAt: isHaveDate ? defaultExpirationTime ? defaultExpirationTime * 1000 + moment().valueOf() : defaultExpirationTime : isHaveDay ? defaultExpirationTime ? defaultExpirationTime / 60 / 60 / 24 : defaultExpirationTime : undefined, }; } }); setSelectIdMap(tempSelectIdMap); if (isHaveDay) { validateDay(isHaveDay, tempSelectIdMap, onChange); } else { validate(isHaveDate, tempSelectIdMap, onChange); } }, [ checkInterceptor, defaultExpirationTime, disabledIds, fixedIds, flattenTreeData, getRemain, isAllowApply, isHaveDate, isHaveDay, onChange, selectIdMap, ], ); const checkedKeys = value?.data ?.filter?.((item: any) => item) .map((item: any) => String(item.id)); return ( <> {Boolean(isLevel) && ( {renderToolbar?.()} {!disabled && !onlyLeafCheckable && ( )} {renderExpandLeft?.()} setDefaultExpandAll(!defaultExpandAll)}> {Boolean(isHaveDate) && (
)} {Boolean(isHaveDay) && (
)}
)} ); }; export default memo(CustomTree);