333 lines
9.0 KiB
TypeScript
333 lines
9.0 KiB
TypeScript
import React, { FC, useState, useContext, ReactNode } from 'react';
|
||
import { ModalFormProps } from '@ant-design/pro-components'
|
||
import { Input, Dropdown } from 'antd'
|
||
import {
|
||
ButtonProps,
|
||
ConfigProvider,
|
||
Tooltip,
|
||
Button,
|
||
Divider,
|
||
DataNode as TreeDataNode,
|
||
Tree as BoxTree,
|
||
TreeProps as BoxTreeProps,
|
||
TreeProps,
|
||
InputProps,
|
||
DropDownProps
|
||
} from '@zhst/meta';
|
||
import { IconFont } from '@zhst/icon';
|
||
import { ClockCircleOutlined, CloseCircleOutlined, DiffOutlined, FolderAddOutlined, ImportOutlined, SwitcherOutlined } from '@ant-design/icons'
|
||
import classNames from 'classnames';
|
||
import './index.less'
|
||
|
||
interface IOption {
|
||
label: string
|
||
key: string
|
||
icon?: string | ReactNode
|
||
type?: ButtonProps['type'] | 'dropdown'
|
||
disabled?: boolean;
|
||
showTooltip?: boolean;
|
||
props?: ButtonProps
|
||
onClick?: () => void
|
||
dropdownConfig?: DropDownProps
|
||
}
|
||
|
||
interface ITag {
|
||
label: string
|
||
value: string
|
||
icon?: ReactNode
|
||
parentNode?: string
|
||
children?: ITag[]
|
||
}
|
||
|
||
export interface BoxPanelProps {
|
||
searchInputProps?: InputProps
|
||
showOptions?: boolean
|
||
treeProps?: Partial<BoxTreeProps>
|
||
data: TreeDataNode[]
|
||
onSearch?: (e: any) => void
|
||
onItemCheck?: TreeProps['onCheck']
|
||
onItemSelect?: TreeProps['onSelect']
|
||
/**
|
||
* @deprecated 将于下个版本 0.23.0 以后弃用
|
||
*/
|
||
onBoxBatchDelete?: (data?: any) => void
|
||
/**
|
||
* @deprecated 将于下个版本 0.23.0 以后弃用
|
||
*/
|
||
onCreateSubmit?: ModalFormProps['onFinish']
|
||
/**
|
||
* @deprecated 将于下个版本 0.23.0 以后弃用
|
||
*/
|
||
onClockClick?: () => void //
|
||
/**
|
||
* @deprecated 将于下个版本 0.23.0 以后弃用
|
||
*/
|
||
onImport?: () => void
|
||
/**
|
||
* @deprecated 将于下个版本 0.23.0 以后弃用
|
||
*/
|
||
onBatch?: () => void
|
||
/**
|
||
* @deprecated 将于下个版本 0.23.0 以后弃用
|
||
*/
|
||
onCreate?: () => void
|
||
customImport?: ReactNode | string // 自定义搜索栏边上的过滤图标
|
||
extraBtns?: ReactNode | string // 搜索栏下面的插槽
|
||
prefixCls?: string
|
||
showSearchBar?: boolean // 是否显示搜索栏
|
||
noFilter?: boolean // 是否显示搜索拓展 icon
|
||
filterList?: IOption[]
|
||
optionList?: IOption[]
|
||
showTagPanel?: boolean // 标签插槽
|
||
tagList?: ITag[] // 标签列表
|
||
tagExpandAll?: boolean // 展开所有
|
||
onTagCheck?: (value: string, tag: ITag) => void;
|
||
checkedTags?: string[]
|
||
onResetTags?: () => void
|
||
onTagExpand?: (e: any) => void
|
||
tagFootRender?: ReactNode
|
||
}
|
||
|
||
const { ConfigContext } = ConfigProvider
|
||
|
||
const BoxPanel: FC<BoxPanelProps> = (props) => {
|
||
const [isTreeCheckable, setIsTreeCheckable] = useState(false)
|
||
const {
|
||
searchInputProps,
|
||
showOptions = true,
|
||
extraBtns,
|
||
noFilter,
|
||
data = [],
|
||
onSearch,
|
||
treeProps,
|
||
onItemCheck,
|
||
onItemSelect,
|
||
onBoxBatchDelete,
|
||
onClockClick,
|
||
onImport,
|
||
onBatch,
|
||
onCreate,
|
||
showSearchBar = true,
|
||
optionList = [
|
||
{
|
||
label: '导入盒子',
|
||
key: 'import',
|
||
icon: <ImportOutlined />,
|
||
onClick: () => onImport?.()
|
||
},
|
||
{
|
||
label: '新建组',
|
||
key: 'add',
|
||
icon: <FolderAddOutlined />,
|
||
onClick: () => onCreate?.()
|
||
},
|
||
{
|
||
label: '删除',
|
||
key: 'del',
|
||
icon: <CloseCircleOutlined />,
|
||
onClick: () => onBoxBatchDelete?.(),
|
||
props: {
|
||
danger: true
|
||
}
|
||
}
|
||
],
|
||
filterList = [
|
||
{
|
||
label: '多选',
|
||
key: 'multi',
|
||
icon: isTreeCheckable ? <SwitcherOutlined /> : <DiffOutlined />,
|
||
onClick: () => onBatch?.() || handleCheckable()
|
||
},
|
||
{
|
||
label: '时钟',
|
||
key: 'clock',
|
||
icon: <ClockCircleOutlined />,
|
||
onClick: () => onClockClick?.()
|
||
}
|
||
],
|
||
showTagPanel,
|
||
tagList,
|
||
tagExpandAll,
|
||
onTagExpand,
|
||
checkedTags = [],
|
||
onTagCheck,
|
||
onResetTags,
|
||
tagFootRender,
|
||
prefixCls: customizePrefixCls,
|
||
customImport: customFilter
|
||
} = props
|
||
|
||
const { getPrefixCls } = useContext(ConfigContext);
|
||
const componentName = getPrefixCls('biz-box-select-tree-panel', customizePrefixCls);
|
||
|
||
/**
|
||
* 修改选择状态
|
||
* @param _data
|
||
*/
|
||
const handleCheckable = () => {
|
||
setIsTreeCheckable(pre => !pre)
|
||
}
|
||
|
||
/**
|
||
* 初始化拓展 filter
|
||
* @param _list
|
||
* @returns
|
||
*/
|
||
const initFilter = (_list?: BoxPanelProps['filterList']) => {
|
||
const WithDropdown = (dom: ReactNode, _config?: DropDownProps) => {
|
||
return (
|
||
<Dropdown placement="bottomLeft" arrow {..._config}>
|
||
{dom}
|
||
</Dropdown>
|
||
)
|
||
}
|
||
|
||
return _list?.map(item => (
|
||
<Tooltip
|
||
title={item.label}
|
||
open={item.showTooltip}
|
||
>
|
||
{item.type === 'dropdown' ? (
|
||
WithDropdown(
|
||
<Button className={classNames(componentName + '-search-btns-btn')} type="text" onClick={item.onClick} icon={item.icon} />,
|
||
item.dropdownConfig
|
||
)
|
||
) : (
|
||
<Button className={classNames(componentName + '-search-btns-btn')} type="text" onClick={item.onClick} icon={item.icon} />
|
||
)}
|
||
</Tooltip>
|
||
))
|
||
}
|
||
|
||
/**
|
||
* 初始化拓展 filter
|
||
* @param _list
|
||
* @returns
|
||
*/
|
||
const initOptions = (_list?: BoxPanelProps['optionList']) => {
|
||
return _list?.map((item, idx) => (
|
||
<>
|
||
<Tooltip
|
||
title={item.label}
|
||
>
|
||
{/* @ts-ignore */}
|
||
<Button className={classNames(componentName + '-btns-common')} type={item.type || 'text'} onClick={item.onClick} icon={item.icon} {...item?.props}>{item.label}</Button>
|
||
</Tooltip>
|
||
{idx !== _list.length - 1 && <Divider className={classNames(componentName + '-btns-divider')} type="vertical" />}
|
||
</>
|
||
))
|
||
}
|
||
|
||
/**
|
||
* 初始化标签面板
|
||
* @param _tagList
|
||
* @param sort 是否分类
|
||
* @returns
|
||
*/
|
||
const initTagPanel = (_tagList: BoxPanelProps['tagList'], sort?: boolean) => {
|
||
// 正常标签渲染
|
||
const commonTag = (_tagProps: ITag) => (
|
||
<span
|
||
className={classNames(
|
||
componentName + '-tags-tag_common',
|
||
{[componentName + '-tags-tag_checked']: checkedTags.includes(_tagProps.value)}
|
||
)}
|
||
key={_tagProps.value}
|
||
onClick={() => onTagCheck?.(_tagProps.value, _tagProps)}
|
||
>{_tagProps.label}</span>
|
||
)
|
||
// 包装父级标签
|
||
const _withFather = (tag: ITag) => (
|
||
<div key={tag.value}>
|
||
<span className={classNames(componentName + '-tags-tag')}>{tag.label}</span>
|
||
{tag.children?.map?.(_tag => commonTag(_tag))}
|
||
</div>
|
||
)
|
||
|
||
return _tagList?.map(tag => {
|
||
if (tag.children?.length && sort) {
|
||
return _withFather(tag)
|
||
} else {
|
||
return commonTag(tag)
|
||
}
|
||
})
|
||
}
|
||
|
||
return (
|
||
<div className={componentName}>
|
||
{/* 搜索栏 */}
|
||
{showSearchBar && (
|
||
<div className={classNames(componentName + '-search')}>
|
||
<Input
|
||
className={classNames(componentName + '-search-input')}
|
||
size='middle'
|
||
onChange={(e) => onSearch?.(e)}
|
||
placeholder='请输入盒子名称'
|
||
{...searchInputProps}
|
||
/>
|
||
{customFilter || (!noFilter && (
|
||
<div
|
||
className={classNames(componentName + '-search-btns')}
|
||
>
|
||
{/* @ts-ignore */}
|
||
{initFilter(filterList)}
|
||
</div>
|
||
))}
|
||
</div>
|
||
)}
|
||
{/* 默认操作按钮 */}
|
||
{showOptions && (
|
||
<>
|
||
<div className={classNames(componentName + '-btns')}>
|
||
{initOptions(optionList)}
|
||
</div>
|
||
<Divider style={{ margin: 0 }} />
|
||
</>
|
||
)}
|
||
{extraBtns}
|
||
{showTagPanel && (
|
||
<div className={classNames(componentName + '-tags')}>
|
||
<span
|
||
className={classNames(componentName + '-tags-tag')}
|
||
style={tagExpandAll ? {
|
||
marginBottom: '12px',
|
||
display: tagExpandAll ? 'block' : 'inline-block'
|
||
} : {}
|
||
}
|
||
>标签:</span>
|
||
{initTagPanel(tagList, tagExpandAll)}
|
||
<div
|
||
className={classNames(
|
||
componentName + '-tags-tag_option',
|
||
{ [componentName + '-tags-tag_absolute']: tagExpandAll },
|
||
)}
|
||
>
|
||
{tagExpandAll && (
|
||
<span
|
||
className={classNames(componentName + '-tags-tag_common', componentName + '-tags-tag_fz12')}
|
||
onClick={onResetTags}
|
||
>重置</span>
|
||
)}
|
||
<span
|
||
className={classNames(componentName + '-tags-tag_common', componentName + '-tags-tag_fz12')}
|
||
onClick={onTagExpand}
|
||
>{tagExpandAll ? '收起' : '更多'}<IconFont icon={tagExpandAll ? 'icon-shangjiantou' : 'icon-xiajiantou'} /></span>
|
||
</div>
|
||
{tagFootRender}
|
||
</div>
|
||
)}
|
||
<BoxTree
|
||
className={classNames(componentName + '-tree')}
|
||
checkable={isTreeCheckable}
|
||
treeData={data}
|
||
blockNode
|
||
onSelect={onItemSelect}
|
||
onCheck={onItemCheck}
|
||
{...treeProps}
|
||
/>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
export default BoxPanel
|