feat(@zhst/biz): 树组件支持tag面板、优化filter传参、优化option传参、废弃之前的定制化方案

This commit is contained in:
NICE CODE BY DEV 2024-05-23 15:14:17 +08:00
parent e90dcce641
commit da3c1714c5
18 changed files with 606 additions and 248 deletions

View File

@ -1,5 +1,11 @@
# @zhst/biz # @zhst/biz
## 0.24.0
### Minor Changes
- 树组件支持 tag 面板、优化 filter 传参、优化 option 传参、废弃之前的定制化方案
## 0.23.0 ## 0.23.0
### Minor Changes ### Minor Changes

View File

@ -1,6 +1,6 @@
{ {
"name": "@zhst/biz", "name": "@zhst/biz",
"version": "0.23.0", "version": "0.24.0",
"description": "业务库", "description": "业务库",
"keywords": [ "keywords": [
"business", "business",

View File

@ -1,4 +1,4 @@
import React, { FC, useContext } from 'react'; import React, { FC, ReactNode, useContext } from 'react';
import { Tabs, TabsProps } from 'antd' import { Tabs, TabsProps } from 'antd'
import { ConfigProvider } from '@zhst/meta'; import { ConfigProvider } from '@zhst/meta';
import BoxPanel from './components/boxPanel'; import BoxPanel from './components/boxPanel';
@ -9,31 +9,18 @@ export interface BoxSelectTreeProps extends BoxPanelProps {
onTabChange?: (e: any) => void onTabChange?: (e: any) => void
tabsProps?: TabsProps tabsProps?: TabsProps
prefixCls?: string; prefixCls?: string;
footer?: ReactNode
} }
const { ConfigContext } = ConfigProvider const { ConfigContext } = ConfigProvider
const BoxSelectTree: FC<BoxSelectTreeProps> = (props) => { const BoxSelectTree: FC<BoxSelectTreeProps> = (props) => {
const { const {
data, prefixCls: customizePrefixCls,
boxDataSource = [],
onTabChange,
onSearch,
onItemCheck,
onItemSelect,
onBoxBatchDelete,
onBoxDelete,
onCreateSubmit,
onClockClick,
onImport,
onCreate,
tabsProps, tabsProps,
searchInputProps, onTabChange,
treeProps, footer,
customImport, ...rest
showOptions,
extraBtns,
prefixCls: customizePrefixCls
} = props } = props
const { getPrefixCls } = useContext(ConfigContext); const { getPrefixCls } = useContext(ConfigContext);
const componentName = getPrefixCls('biz-box-select-tree', customizePrefixCls); const componentName = getPrefixCls('biz-box-select-tree', customizePrefixCls);
@ -61,23 +48,9 @@ const BoxSelectTree: FC<BoxSelectTreeProps> = (props) => {
{...tabsProps} {...tabsProps}
/> />
<BoxPanel <BoxPanel
searchInputProps={searchInputProps} {...rest}
boxDataSource={boxDataSource}
treeProps={treeProps}
data={data}
onCreate={onCreate}
onCreateSubmit={onCreateSubmit}
onBoxBatchDelete={onBoxBatchDelete}
onBoxDelete={onBoxDelete}
onSearch={onSearch}
onItemCheck={onItemCheck}
onItemSelect={onItemSelect}
showOptions={showOptions}
customImport={customImport}
extraBtns={extraBtns}
onClockClick={onClockClick}
onImport={onImport}
/> />
{footer}
</div> </div>
); );
}; };

View File

@ -1,9 +1,11 @@
.zhst-biz-box-select-tree-panel { .zhst-biz-box-select-tree-panel {
&-search { &-search {
display: flex; display: flex;
align-items: center;
padding: 0 12px; padding: 0 12px;
&-input { &-input {
flex: 1;
margin-right: 4px; margin-right: 4px;
} }
@ -18,6 +20,7 @@
justify-content: space-between; justify-content: space-between;
&-common { &-common {
flex: 1;
padding: 4px 8px; padding: 4px 8px;
} }
@ -30,6 +33,60 @@
} }
} }
&-tags {
position: relative;
height: 100%;
margin-top: 6px;
padding: 12px;
font-size: 0;
border-top: 1px solid #09f;
border-bottom: 1px solid #09f;
&-tag {
margin-right: 6px;
font-weight: bold;
font-size: 14px;
transition:.3s ease all;
&_common{
margin-bottom: 6px;
display: inline-block;
margin-right: 6px;
padding: 6px;
font-size: 12px;
cursor: pointer;
background-color: #f6f6f6;
transition: .3s ease all;
border-radius: 3px;
box-sizing: content-box;
&:hover {
color: #09f;
background-color: rgba(0, 0, 0, 6%);
}
}
&_checked {
color: #09f;
background-color: #edf8ff;
}
&_fz12 {
font-size: 12px;
}
&_option {
display: inline-block;
}
&_absolute {
position: absolute;
top: 12px;
right: 0;
}
}
}
&-tree { &-tree {
padding: 6px 0; padding: 6px 0;
} }

View File

@ -1,73 +1,160 @@
import React, { FC, useState, useRef, useContext } from 'react'; import React, { FC, useState, useContext, ReactNode } from 'react';
import{ Button, Divider, Input, Space, TreeDataNode } from 'antd' import{ Button, Divider, Dropdown, Input, TreeDataNode } from 'antd'
import { ModalForm, ModalFormProps, ProFormInstance, ProFormText } from '@ant-design/pro-components' import { ModalFormProps } from '@ant-design/pro-components'
import { ConfigProvider } from '@zhst/meta'; import { ButtonProps, ConfigProvider, Tooltip } from '@zhst/meta';
import { IconFont } from '@zhst/icon';
import { ClockCircleOutlined, CloseCircleOutlined, DiffOutlined, FolderAddOutlined, ImportOutlined, SwitcherOutlined } from '@ant-design/icons' import { ClockCircleOutlined, CloseCircleOutlined, DiffOutlined, FolderAddOutlined, ImportOutlined, SwitcherOutlined } from '@ant-design/icons'
import type { TreeProps, InputProps } from 'antd'; import type { TreeProps, InputProps, DropDownProps } from 'antd';
import classNames from 'classnames';
import type { BoxTreeProps } from '../../../tree'; import type { BoxTreeProps } from '../../../tree';
import TreeTransferModal from '../../../treeTransferModal'
import BoxTree from '../../../tree'; import BoxTree from '../../../tree';
import './index.less' import './index.less'
import classNames from 'classnames';
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 { export interface BoxPanelProps {
searchInputProps?: InputProps searchInputProps?: InputProps
showOptions?: boolean showOptions?: boolean
treeProps?: Partial<BoxTreeProps> treeProps?: Partial<BoxTreeProps>
data: TreeDataNode[] data: TreeDataNode[]
boxDataSource: TreeDataNode[]
handleImport?: () => void
onSearch?: (e: any) => void onSearch?: (e: any) => void
onItemCheck?: TreeProps['onCheck'] onItemCheck?: TreeProps['onCheck']
onItemSelect?: TreeProps['onSelect'] onItemSelect?: TreeProps['onSelect']
/**
* @deprecated 0.23.0
*/
onBoxBatchDelete?: (data?: any) => void onBoxBatchDelete?: (data?: any) => void
/**
* @deprecated 0.23.0
*/
onBoxDelete?: (data?: any) => void onBoxDelete?: (data?: any) => void
/**
* @deprecated 0.23.0
*/
onCreateSubmit?: ModalFormProps['onFinish'] onCreateSubmit?: ModalFormProps['onFinish']
onClockClick?: () => void /**
* @deprecated 0.23.0
*/
onClockClick?: () => void //
/**
* @deprecated 0.23.0
*/
onImport?: () => void onImport?: () => void
/**
* @deprecated 0.23.0
*/
onBatch?: () => void onBatch?: () => void
/**
* @deprecated 0.23.0
*/
onCreate?: () => void onCreate?: () => void
customImport?: any customImport?: ReactNode | string // 自定义搜索栏边上的过滤图标
extraBtns?: any extraBtns?: ReactNode | string // 搜索栏下面的插槽
prefixCls?: 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 { ConfigContext } = ConfigProvider
const BoxPanel: FC<BoxPanelProps> = (props) => { const BoxPanel: FC<BoxPanelProps> = (props) => {
const [isTreeCheckable, setIsTreeCheckable] = useState(false)
const { const {
searchInputProps, searchInputProps,
showOptions = true, showOptions = true,
extraBtns, extraBtns,
noFilter,
data = [], data = [],
onSearch, onSearch,
treeProps, treeProps,
onItemCheck, onItemCheck,
onItemSelect, onItemSelect,
onCreateSubmit,
onBoxBatchDelete, onBoxBatchDelete,
onBoxDelete, onBoxDelete,
onClockClick, onClockClick,
onImport, onImport,
onBatch, onBatch,
onCreate, onCreate,
boxDataSource, 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, prefixCls: customizePrefixCls,
customImport customImport: customFilter
} = props } = props
const { getPrefixCls } = useContext(ConfigContext); const { getPrefixCls } = useContext(ConfigContext);
const componentName = getPrefixCls('biz-box-select-tree-panel', customizePrefixCls); const componentName = getPrefixCls('biz-box-select-tree-panel', customizePrefixCls);
const [isTreeCheckable, setIsTreeCheckable] = useState(false)
const [targetItems, setTargetItems] = useState<TreeDataNode[]>([]);
const [boxChoiceOpen, setBoxChoiceOpen] = useState(false)
const [checkedKeys, setCheckedKeys] = useState<string[]>([]);
const createFormRef = useRef<
ProFormInstance<{
name: string;
boxList?: any[];
}>
>()
/** /**
* *
@ -77,151 +164,154 @@ const BoxPanel: FC<BoxPanelProps> = (props) => {
setIsTreeCheckable(pre => !pre) setIsTreeCheckable(pre => !pre)
} }
const onTreeCheck: TreeProps['onCheck'] = (keys: any, info) => { /**
let _targetItems: TreeDataNode[] = [] * filter
setCheckedKeys(keys) * @param _list
info.checkedNodes.forEach(o => { * @returns
o.isLeaf && _targetItems.push(o) */
}) const initFilter = (_list?: BoxPanelProps['filterList']) => {
setTargetItems(_targetItems) 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 key * @param _list
* @param param1 * @returns
*/ */
const onItemDelete = (key: any, { keys }: any) => { const initOptions = (_list?: BoxPanelProps['optionList']) => {
setCheckedKeys(pre => { return _list?.map((item, idx) => (
const newKeys = pre.filter(_key => !keys.includes(_key)) <>
return newKeys <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)
}
}) })
setTargetItems(pre => pre.filter(o => o.key !== key))
}
// 盒子点击确定
const onBoxChoiceOk = async (data: any) => {
createFormRef.current?.setFieldValue('boxList', data)
createFormRef.current?.setFieldValue('boxName', 123)
console.log(createFormRef.current?.getFieldValue('boxList'))
setBoxChoiceOpen(false)
}
// 盒子选择重置
const onBoxChoiceReset = () => {
setCheckedKeys([])
setTargetItems([])
} }
return ( return (
<div className={componentName}> <div className={componentName}>
{/* 盒子选择弹框 */} {/* 搜索栏 */}
<TreeTransferModal {showSearchBar && (
open={boxChoiceOpen} <div className={classNames(componentName + '-search')}>
onCancel={() => setBoxChoiceOpen(false)} <Input
onRadioChange={(e) => console.log('radio', e.target.value)} // 顶部 radio 事件 className={classNames(componentName + '-search-input')}
dataSource={boxDataSource} // 数据源 size='middle'
targetItems={targetItems} // 右侧选中项 onChange={(e) => onSearch?.(e)}
checkedKeys={checkedKeys} // 左侧选中 placeholder='请输入盒子名称'
onReset={onBoxChoiceReset} // 重置按钮事件 {...searchInputProps}
onOk={onBoxChoiceOk} // 确定按钮事件 />
onTreeCheck={onTreeCheck} // 树check选中事件 {customFilter || (!noFilter && (
onItemDelete={onItemDelete} // 右侧点击删除事件 <div
/> className={classNames(componentName + '-search-btns')}
<div className={classNames(componentName + '-search')}> >
<Input {/* @ts-ignore */}
className={classNames(componentName + '-search-input')} {initFilter(filterList)}
size='middle' </div>
onChange={(e) => onSearch?.(e)} ))}
placeholder='请输入盒子名称' </div>
{...searchInputProps} )}
/> {/* 默认操作按钮 */}
{customImport || (
<div
className={classNames(componentName + '-search-btns')}
>
<Button type="text" onClick={() => onBatch?.() || handleCheckable()} icon={isTreeCheckable ? <SwitcherOutlined /> : <DiffOutlined />} />
<Button type="text" onClick={() => onClockClick?.()} icon={<ClockCircleOutlined />} />
</div>
)}
</div>
{/* 是否显示操作按钮 */}
{showOptions && ( {showOptions && (
<> <>
<div className={classNames(componentName + '-btns')}> <div className={classNames(componentName + '-btns')}>
<Button className={classNames(componentName + '-btns-common')} type='text' onClick={() => onImport?.()} icon={<ImportOutlined />} ></Button> {initOptions(optionList)}
<Divider className={classNames(componentName + '-btns-divider')} type="vertical" />
{onCreate ?
(
<Button className={classNames(componentName + '-btns-common')} onClick={onCreate} type='text' icon={<FolderAddOutlined />} ></Button>
) : (
<ModalForm<{
name: string
boxList?: any[]
}>
className={classNames(componentName + '-create-modal')}
open={onCreate ? false : undefined}
formRef={createFormRef}
title="新建组"
modalProps={{ destroyOnClose: true }}
layout='horizontal'
labelCol={{ span: 6 }}
wrapperCol={{ span: 18 }}
trigger={<Button type='text' className={classNames(componentName + '-btns-common')} icon={<FolderAddOutlined />} ></Button>}
submitter={{
searchConfig: {
submitText: '确定',
resetText: '取消',
},
}}
onFinish={onCreateSubmit}
>
<ProFormText
rules={[
{
required: true,
max: 20,
},
{
pattern: /^[^\s]*$/g,
message: '禁止输入空格'
}
]}
fieldProps={{ showCount: true }}
width="md"
name="name"
label="盒子组名称"
placeholder="请输入盒子名称"
/>
<ProFormText
width="md"
name="boxList"
label="盒子选择"
fieldProps={{
readOnly: true,
value: `已选择${createFormRef.current?.getFieldValue('boxList')?.length || 0}个盒子`,
suffix: (
<Space>
<a onClick={() => {
createFormRef.current?.setFieldValue('boxList', null)
onBoxChoiceReset()
}} ></a>
<a onClick={() => setBoxChoiceOpen(true)}></a>
</Space>
)
}}
/>
</ModalForm>
)
}
<Divider className={classNames(componentName + '-btns-divider')} type="vertical" />
{/* @ts-ignore */}
<Button className={classNames(componentName + '-btns-common')} danger type='text' icon={<CloseCircleOutlined />} disabled={treeProps?.checkedKeys?.length <= 0} onClick={onBoxBatchDelete} ></Button>
</div> </div>
<Divider style={{ margin: 0 }} /> <Divider style={{ margin: 0 }} />
</> </>
)} )}
{extraBtns} {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 <BoxTree
className={classNames(componentName + '-tree')} className={classNames(componentName + '-tree')}
treeCheckable={isTreeCheckable} treeCheckable={isTreeCheckable}

View File

@ -1,7 +1,7 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { BoxSelectTree } from '@zhst/biz'; import { BoxSelectTree } from '@zhst/biz';
import { treeData, boxDataSource } from './mock' import { treeData, boxDataSource } from './mock'
import { Select, TreeProps, Modal, Checkbox } from 'antd'; import { Select, TreeProps, Modal, Checkbox, Button } from 'antd';
const { Option } = Select const { Option } = Select
@ -81,6 +81,7 @@ const demo = () => {
onItemRenameFinish: async (val, data) => console.log('盒子重命名提交(返回boolean,控制弹框显示\隐藏)', val, data), onItemRenameFinish: async (val, data) => console.log('盒子重命名提交(返回boolean,控制弹框显示\隐藏)', val, data),
checkedKeys, checkedKeys,
}} }}
footer={<Button block ></Button>}
/> />
</div> </div>
); );

View File

@ -0,0 +1,68 @@
import React, { useState } from 'react';
import { BoxSelectTree } from '@zhst/biz';
import { treeData, boxDataSource } from './mock'
import { FilterOutlined } from '@ant-design/icons';
import { Badge } from 'antd';
const demo = () => {
const [activeKey] = useState('1')
const [checkedKeys] = useState<string[]>([]);
return (
<div style={{ border: '1px solid #ccc', width: '340px', minHeight: '900px' }}>
<BoxSelectTree
data={activeKey === '1' ? treeData : boxDataSource}
boxDataSource={boxDataSource}
showOptions={false}
tabsProps={{
activeKey,
}}
treeProps={{
checkedKeys
}}
filterList={[
{
label: '过滤',
key: 'multi',
icon: <FilterOutlined />,
type: 'dropdown',
showTooltip: false,
dropdownConfig: {
menu: {
// 自定义返回项
_internalRenderMenuItem: (originNode, menuItemProps, stateProps) => {
return (
<div>
{originNode}
</div>
)
},
selectable: true,
items: [
{
label: <p style={{ margin: '0', textAlign: 'center' }} ></p>,
key: 'all',
onClick: () => console.log('多选1')
},
{
icon: <Badge status="success" />,
label: '多选1',
key: 'multi1',
onClick: () => console.log('多选1')
},
{
label: '多选2',
icon: <Badge status='error' />,
key: 'multi2',
},
],
}
}
},
]}
/>
</div>
);
};
export default demo;

View File

@ -0,0 +1,33 @@
import React, { useState } from 'react';
import { BoxSelectTree } from '@zhst/biz';
import { treeData, boxDataSource } from './mock'
import { CloseCircleOutlined } from '@ant-design/icons';
const demo = () => {
const [activeKey, setActiveKey] = useState('1')
const [checkedKeys, setCheckedKeys] = useState<string[]>([]);
return (
<div style={{ border: '1px solid #ccc', width: '340px', minHeight: '900px' }}>
<BoxSelectTree
data={activeKey === '1' ? treeData : boxDataSource}
boxDataSource={boxDataSource}
tabsProps={{
activeKey,
}}
treeProps={{
checkedKeys
}}
optionList={[
{
label: '删除',
key: 'del',
icon: <CloseCircleOutlined />,
}
]}
/>
</div>
);
};
export default demo;

View File

@ -2,10 +2,12 @@ import React, { useState } from 'react';
import { BoxSelectTree } from '@zhst/biz'; import { BoxSelectTree } from '@zhst/biz';
import { treeData, boxDataSource } from './mock' import { treeData, boxDataSource } from './mock'
import { Button } from 'antd'; import { Button } from 'antd';
import { ClockCircleOutlined, DiffOutlined, SwitcherOutlined } from '@ant-design/icons';
const demo = () => { const demo = () => {
const [activeKey, setActiveKey] = useState('1') const [activeKey, setActiveKey] = useState('1')
const [checkedKeys, setCheckedKeys] = useState<string[]>([]); const [checkedKeys, setCheckedKeys] = useState<string[]>([]);
const [isTreeCheckable, setIsTreeCheckable] = useState(false)
return ( return (
<div style={{ border: '1px solid #ccc', width: '340px', minHeight: '900px' }}> <div style={{ border: '1px solid #ccc', width: '340px', minHeight: '900px' }}>
@ -13,10 +15,23 @@ const demo = () => {
data={activeKey === '1' ? treeData : boxDataSource} data={activeKey === '1' ? treeData : boxDataSource}
boxDataSource={boxDataSource} boxDataSource={boxDataSource}
showOptions={false} showOptions={false}
extraBtns={<Button type="dashed" style={{ color: 'green' }}></Button>} extraBtns={<div><Button type="dashed" style={{ color: 'red' }}></Button> Hello Lambo</div>}
tabsProps={{ tabsProps={{
activeKey, activeKey,
}} }}
filterList={[
{
label: '多选',
key: 'multi',
icon: isTreeCheckable ? <SwitcherOutlined /> : <DiffOutlined />,
onClick: () => setIsTreeCheckable(pre => !pre)
},
{
label: '时钟',
key: 'clock',
icon: <ClockCircleOutlined />,
}
]}
treeProps={{ treeProps={{
checkedKeys checkedKeys
}} }}

View File

@ -0,0 +1,27 @@
import React, { useState } from 'react';
import { BoxSelectTree } from '@zhst/biz';
import { treeData, boxDataSource } from './mock'
const demo = () => {
const [activeKey, setActiveKey] = useState('1')
const [checkedKeys, setCheckedKeys] = useState<string[]>([]);
return (
<div style={{ border: '1px solid #ccc', width: '340px', minHeight: '900px' }}>
<BoxSelectTree
data={activeKey === '1' ? treeData : boxDataSource}
boxDataSource={boxDataSource}
showOptions={false}
tabsProps={{
activeKey,
}}
treeProps={{
checkedKeys
}}
noFilter
/>
</div>
);
};
export default demo;

View File

@ -30,7 +30,7 @@ const demo = () => {
tabsProps={{ tabsProps={{
activeKey, activeKey,
}} }}
customImport={<Button type="text" icon={<FilterOutlined />} />} customImport={<div style={{ color: 'red' }}></div>}
searchInputProps={{ searchInputProps={{
addonBefore: ( addonBefore: (
<Select <Select
@ -43,7 +43,6 @@ const demo = () => {
> >
{[ {[
{ value: '1', label: '盒子' }, { value: '1', label: '盒子' },
{ value: '2', label: '盒子组' }
].map(item => ( ].map(item => (
<Option value={item.value}>{item.label}</Option> <Option value={item.value}>{item.label}</Option>
))} ))}

View File

@ -0,0 +1,84 @@
import React, { useState } from 'react';
import { BoxSelectTree } from '@zhst/biz';
import { treeData, boxDataSource } from './mock'
import { Button, Switch } from 'antd';
const demo = () => {
const [activeKey] = useState('1')
const [checkedKeys] = useState<string[]>([]);
const [checkedTags, setCheckedTags] = useState<string[]>([]);
const [tagExpandAll, setTagExpandAll] = useState(false);
const [showTagPanel, setShowTagPanel] = useState(true);
return (
<div>
<div style={{ marginBottom: '12px' }}>
<Switch value={showTagPanel} onChange={status => setShowTagPanel(status)} />
</div>
<div style={{ border: '1px solid #ccc', width: '340px', minHeight: '900px' }}>
<BoxSelectTree
data={activeKey === '1' ? treeData : boxDataSource}
boxDataSource={boxDataSource}
showOptions={false}
tabsProps={{
activeKey,
}}
treeProps={{
checkedKeys
}}
showTagPanel={showTagPanel}
tagExpandAll={tagExpandAll}
onTagExpand={() => {
setTagExpandAll(pre => !pre)
setCheckedTags([])
}}
onTagCheck={(value) => setCheckedTags(pre => {
if (pre.includes(value)) {
return pre.filter(item => item !== value)
} else {
return [...pre, value]
}
})}
onResetTags={() => setCheckedTags([])}
checkedTags={checkedTags}
tagFootRender={<Button danger>dom</Button>}
tagList={[
{
label: '标签组1',
value: '1',
children: [
{
label: '标签1-1',
value: '1-1',
},
{
label: '标签1-2',
value: '1-2',
}
]
},
{
label: '标签组2',
value: '2',
children: [
{
label: '标签2-1',
value: '2-1',
},
{
label: '标签2-2',
value: '2-2',
}
]
},
]
}
/>
</div>
</div>
);
};
export default demo;

View File

@ -3,7 +3,7 @@ category: Components
title: BoxSelectTree 盒子树 title: BoxSelectTree 盒子树
toc: content toc: content
demo: demo:
cols: 2 cols: 3
group: group:
title: 进阶组件 title: 进阶组件
--- ---
@ -15,6 +15,10 @@ group:
<code src="./demo/basic.tsx">基本用法</code> <code src="./demo/basic.tsx">基本用法</code>
<code src="./demo/extraBtns.tsx">自定义其它按钮</code> <code src="./demo/extraBtns.tsx">自定义其它按钮</code>
<code src="./demo/noOptions.tsx">不显示其它按钮</code> <code src="./demo/noOptions.tsx">不显示其它按钮</code>
<code src="./demo/customOptions.tsx">自定义配置按钮</code>
<code src="./demo/noFilter.tsx">不显示过滤按钮</code>
<code src="./demo/customFilter.tsx">自定义过滤按钮</code>
<code src="./demo/withTagPanel.tsx">标签看板</code>
<code src="./demo/async.tsx">异步加载数据</code> <code src="./demo/async.tsx">异步加载数据</code>
## API ## API
@ -22,17 +26,35 @@ group:
| 参数 | 说明 | 类型 | 默认值 | 版本 | | 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| data | 数据源 | Array[] | [] | - | | data | 数据源 | Array[] | [] | - |
| tabsProps | Tabs组件的Props | 参考antd的Tabs组件 | - | - |
| searchInputProps | 搜索框的 Props | 参考antd的Input组件 | - | - |
| showOptions | 展示其它功能按钮 | boolean | true | - |
| filterList | 搜索框边上的插槽列表 | boolean | true | - |
| customImport | 自定义搜索栏边上的过滤图标 | ReactNode、string | - | - |
| extraBtns | 搜索栏下面的插槽 | ReactNode、string | - | - |
| prefixCls | class前缀 | string | - | - |
| showSearchBar | 是否显示搜索框 | boolean | true | - |
| noFilter | 是否显示搜索拓展 icon | boolean | false | - |
| filterList | 拓展列表 | IOption[] | [] | - |
| optionList | 操作按钮列表 | IOption[] | [] | - |
| showTagPanel | 是否显示标签看板 | boolean | false | - |
| tagList | 标签列表 | ITag[] | [] | - |
| tagExpandAll | 标签是否展开 | boolean | false | - |
| checkedTags | 选中的标签值 | string[] | [] | - |
| footer | 盒子树底部渲染(需要内容撑开) | ReactNode、string | - | - |
| tagFootRender | 标签看板底部自定义 | ReactNode、string | - | - |
| onResetTags | 标签重置 | () => void | [] | - |
| onTagExpand | 标签展开 | (e: any) => void | [] | - |
| onTabChange | tab切换监听 | function: (e) => void | - | - |
| onTagCheck | 标签选中事件 | (value: string, tag: ITag) => void | false | - |
| onSearch | 搜索监听 | function: (e) => void | - | - | | onSearch | 搜索监听 | function: (e) => void | - | - |
| onItemSelect | 树当前选中(单选) | function: (e) => void | - | - | | onItemSelect | 树当前选中(单选) | function: (e) => void | - | - |
| onItemCheck | 树选择(支持多选) | function: (e) => void | - | - | | onItemCheck | 树选择(支持多选) | function: (e) => void | - | - |
| tabsProps | Tabs组件的Props | antd的Tabs组件 | - | - | | onBoxDelete | 盒子删除事件 | function: (e) => void | - | 0.23.0 以后弃用 |
| searchInputProps | 搜索框的Props | antd的Input组件 | - | - | | onBatch | 多选 | function: (e) => void | - | 0.23.0 以后弃用 |
| onTabChange | tab切换监听 | function: (e) => void | - | - | | onBoxBatchDelete | 盒子批量删除事件 | function: (e) => void | - | 0.23.0 以后弃用 |
| onBoxDelete | 盒子删除事件 | function: (e) => void | - | - | | onCreateSubmit | 新建提交事件 | function: (e) => void | - | 0.23.0 以后弃用 |
| onBoxBatchDelete | 盒子批量删除事件 | function: (e) => void | - | - | | onImport | 监听导入盒子点击事件 | function: () => void | - | 0.23.0 以后弃用 |
| onCreateSubmit | 新建提交事件 | function: (e) => void | - | - | | onClockClick | 监听时钟点击事件 | function: () => void | - | 0.23.0 以后弃用 |
| onImport | 监听导入盒子点击事件 | function: () => void | - | - | | onCreate | 监听创建点击事件 | function: () => void | 如果不传默认用自带的创建事件 | 0.23.0 以后弃用 |
| onClockClick | 监听时钟点击事件 | function: () => void | - | - |
| onCreate | 监听创建点击事件 | function: () => void | 如果不传默认用自带的创建事件 | - |
| showOptions | 展示其它功能按钮 | boolean | true | - |

View File

@ -1,7 +1,7 @@
import React, { FC, useState } from 'react'; import React, { FC, useState } from 'react';
import { Tree, Badge, TreeDataNode, Space, TreeProps, theme } from 'antd'; import { Tree, Badge, TreeDataNode, Space, TreeProps, theme, ConfigProvider } from 'antd';
import { CloseOutlined, EditOutlined, SettingOutlined } from '@ant-design/icons' import { CloseOutlined, EditOutlined, SettingOutlined } from '@ant-design/icons'
import { ModalForm, ProFormText } from '@ant-design/pro-components'; import classNames from 'classnames';
import './index.less' import './index.less'
const componentName = 'zhst-biz-tree' const componentName = 'zhst-biz-tree'
@ -30,7 +30,7 @@ const boxTree: FC<BoxTreeProps> = (props) => {
showItemOption = true, showItemOption = true,
treeCheckable = false, treeCheckable = false,
onItemRename, onItemRename,
onItemRenameFinish, className: customClassName,
customOptions customOptions
} = props } = props
const { token } = useToken() const { token } = useToken()
@ -45,6 +45,7 @@ const boxTree: FC<BoxTreeProps> = (props) => {
return ( return (
<Tree <Tree
className={classNames(componentName, customClassName)}
checkable={treeCheckable} checkable={treeCheckable}
blockNode blockNode
onSelect={(selectedKeys, info) => { onSelect={(selectedKeys, info) => {
@ -70,38 +71,11 @@ const boxTree: FC<BoxTreeProps> = (props) => {
<Space className={`${componentName}-item-render_right`} style={{ float:'right' }} > <Space className={`${componentName}-item-render_right`} style={{ float:'right' }} >
{customOptions || ( {customOptions || (
<> <>
<ModalForm <EditOutlined onClick={(e) => {
title="重命名" e.preventDefault();
width={600} e.stopPropagation();
modalProps={{ destroyOnClose: true }} onItemRename?.(_nodeData)
layout='horizontal' }} />
labelCol={{ span: 6 }}
wrapperCol={{ span: 18 }}
trigger={<EditOutlined onClick={(e) => {
e.preventDefault();
e.stopPropagation();
onItemRename?.(_nodeData)
}} />}
submitter={{
searchConfig: {
submitText: '确定',
resetText: '取消',
},
}}
onFinish={async (value) => onItemRenameFinish?.(value, _nodeData)}
>
<ProFormText
rules={[
{
required: true,
},
]}
width="md"
name="name"
label="盒子名称"
placeholder="请输入盒子名称"
/>
</ModalForm>
<SettingOutlined onClick={(e) => { <SettingOutlined onClick={(e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();

View File

@ -1,9 +1,11 @@
.zhst-biz-tree-item-render { .zhst-biz-tree {
&_right { &-item-render {
display: none; &_right {
} display: none;
}
&:hover &_right { &:hover &_right {
display: inline-flex; display: inline-flex;
}
} }
} }

View File

@ -1,5 +1,12 @@
# @zhst/material # @zhst/material
## 0.18.4
### Patch Changes
- Updated dependencies
- @zhst/biz@0.24.0
## 0.18.3 ## 0.18.3
### Patch Changes ### Patch Changes

View File

@ -1,6 +1,6 @@
{ {
"name": "@zhst/material", "name": "@zhst/material",
"version": "0.18.3", "version": "0.18.4",
"description": "物料库", "description": "物料库",
"keywords": [ "keywords": [
"business", "business",