diff --git a/packages/biz/CHANGELOG.md b/packages/biz/CHANGELOG.md index 109ce61..cea2db4 100644 --- a/packages/biz/CHANGELOG.md +++ b/packages/biz/CHANGELOG.md @@ -1,5 +1,11 @@ # @zhst/biz +## 0.24.0 + +### Minor Changes + +- 树组件支持 tag 面板、优化 filter 传参、优化 option 传参、废弃之前的定制化方案 + ## 0.23.0 ### Minor Changes diff --git a/packages/biz/package.json b/packages/biz/package.json index 463d3f2..08240e5 100644 --- a/packages/biz/package.json +++ b/packages/biz/package.json @@ -1,6 +1,6 @@ { "name": "@zhst/biz", - "version": "0.23.0", + "version": "0.24.0", "description": "业务库", "keywords": [ "business", diff --git a/packages/biz/src/boxSelectTree/boxSelectTree.tsx b/packages/biz/src/boxSelectTree/boxSelectTree.tsx index b8b7484..e460842 100644 --- a/packages/biz/src/boxSelectTree/boxSelectTree.tsx +++ b/packages/biz/src/boxSelectTree/boxSelectTree.tsx @@ -1,4 +1,4 @@ -import React, { FC, useContext } from 'react'; +import React, { FC, ReactNode, useContext } from 'react'; import { Tabs, TabsProps } from 'antd' import { ConfigProvider } from '@zhst/meta'; import BoxPanel from './components/boxPanel'; @@ -9,31 +9,18 @@ export interface BoxSelectTreeProps extends BoxPanelProps { onTabChange?: (e: any) => void tabsProps?: TabsProps prefixCls?: string; + footer?: ReactNode } const { ConfigContext } = ConfigProvider const BoxSelectTree: FC = (props) => { const { - data, - boxDataSource = [], - onTabChange, - onSearch, - onItemCheck, - onItemSelect, - onBoxBatchDelete, - onBoxDelete, - onCreateSubmit, - onClockClick, - onImport, - onCreate, + prefixCls: customizePrefixCls, tabsProps, - searchInputProps, - treeProps, - customImport, - showOptions, - extraBtns, - prefixCls: customizePrefixCls + onTabChange, + footer, + ...rest } = props const { getPrefixCls } = useContext(ConfigContext); const componentName = getPrefixCls('biz-box-select-tree', customizePrefixCls); @@ -61,23 +48,9 @@ const BoxSelectTree: FC = (props) => { {...tabsProps} /> + {footer} ); }; diff --git a/packages/biz/src/boxSelectTree/components/boxPanel/constants.ts b/packages/biz/src/boxSelectTree/components/boxPanel/constants.ts new file mode 100644 index 0000000..e69de29 diff --git a/packages/biz/src/boxSelectTree/components/boxPanel/index.less b/packages/biz/src/boxSelectTree/components/boxPanel/index.less index 0567c7f..73c02e3 100644 --- a/packages/biz/src/boxSelectTree/components/boxPanel/index.less +++ b/packages/biz/src/boxSelectTree/components/boxPanel/index.less @@ -1,9 +1,11 @@ .zhst-biz-box-select-tree-panel { &-search { display: flex; + align-items: center; padding: 0 12px; &-input { + flex: 1; margin-right: 4px; } @@ -18,6 +20,7 @@ justify-content: space-between; &-common { + flex: 1; 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 { padding: 6px 0; } diff --git a/packages/biz/src/boxSelectTree/components/boxPanel/index.tsx b/packages/biz/src/boxSelectTree/components/boxPanel/index.tsx index 2168c40..299b2b1 100644 --- a/packages/biz/src/boxSelectTree/components/boxPanel/index.tsx +++ b/packages/biz/src/boxSelectTree/components/boxPanel/index.tsx @@ -1,73 +1,160 @@ -import React, { FC, useState, useRef, useContext } from 'react'; -import{ Button, Divider, Input, Space, TreeDataNode } from 'antd' -import { ModalForm, ModalFormProps, ProFormInstance, ProFormText } from '@ant-design/pro-components' -import { ConfigProvider } from '@zhst/meta'; +import React, { FC, useState, useContext, ReactNode } from 'react'; +import{ Button, Divider, Dropdown, Input, TreeDataNode } from 'antd' +import { ModalFormProps } from '@ant-design/pro-components' +import { ButtonProps, ConfigProvider, Tooltip } from '@zhst/meta'; +import { IconFont } from '@zhst/icon'; 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 TreeTransferModal from '../../../treeTransferModal' import BoxTree from '../../../tree'; 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 { searchInputProps?: InputProps showOptions?: boolean treeProps?: Partial data: TreeDataNode[] - boxDataSource: TreeDataNode[] - handleImport?: () => void onSearch?: (e: any) => void onItemCheck?: TreeProps['onCheck'] onItemSelect?: TreeProps['onSelect'] + /** + * @deprecated 将于下个版本 0.23.0 以后弃用 + */ onBoxBatchDelete?: (data?: any) => void + /** + * @deprecated 将于下个版本 0.23.0 以后弃用 + */ onBoxDelete?: (data?: any) => void + /** + * @deprecated 将于下个版本 0.23.0 以后弃用 + */ onCreateSubmit?: ModalFormProps['onFinish'] - onClockClick?: () => void + /** + * @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?: any - extraBtns?: any - prefixCls?: string; + 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 = (props) => { + const [isTreeCheckable, setIsTreeCheckable] = useState(false) const { searchInputProps, showOptions = true, extraBtns, + noFilter, data = [], onSearch, treeProps, onItemCheck, onItemSelect, - onCreateSubmit, onBoxBatchDelete, onBoxDelete, onClockClick, onImport, onBatch, onCreate, - boxDataSource, + showSearchBar = true, + optionList = [ + { + label: '导入盒子', + key: 'import', + icon: , + onClick: () => onImport?.() + }, + { + label: '新建组', + key: 'add', + icon: , + onClick: () => onCreate?.() + }, + { + label: '删除', + key: 'del', + icon: , + onClick: () => onBoxBatchDelete?.(), + props: { + danger: true + } + } + ], + filterList = [ + { + label: '多选', + key: 'multi', + icon: isTreeCheckable ? : , + onClick: () => onBatch?.() || handleCheckable() + }, + { + label: '时钟', + key: 'clock', + icon: , + onClick: () => onClockClick?.() + } + ], + showTagPanel, + tagList, + tagExpandAll, + onTagExpand, + checkedTags = [], + onTagCheck, + onResetTags, + tagFootRender, prefixCls: customizePrefixCls, - customImport + customImport: customFilter } = props const { getPrefixCls } = useContext(ConfigContext); const componentName = getPrefixCls('biz-box-select-tree-panel', customizePrefixCls); - const [isTreeCheckable, setIsTreeCheckable] = useState(false) - const [targetItems, setTargetItems] = useState([]); - const [boxChoiceOpen, setBoxChoiceOpen] = useState(false) - const [checkedKeys, setCheckedKeys] = useState([]); - const createFormRef = useRef< - ProFormInstance<{ - name: string; - boxList?: any[]; - }> - >() /** * 修改选择状态 @@ -77,151 +164,154 @@ const BoxPanel: FC = (props) => { setIsTreeCheckable(pre => !pre) } - const onTreeCheck: TreeProps['onCheck'] = (keys: any, info) => { - let _targetItems: TreeDataNode[] = [] - setCheckedKeys(keys) - info.checkedNodes.forEach(o => { - o.isLeaf && _targetItems.push(o) - }) - setTargetItems(_targetItems) + /** + * 初始化拓展 filter + * @param _list + * @returns + */ + const initFilter = (_list?: BoxPanelProps['filterList']) => { + const WithDropdown = (dom: ReactNode, _config?: DropDownProps) => { + return ( + + {dom} + + ) + } + + return _list?.map(item => ( + + {item.type === 'dropdown' ? ( + WithDropdown( + + + {idx !== _list.length - 1 && } + + )) + } + + /** + * 初始化标签面板 + * @param _tagList + * @param sort 是否分类 + * @returns + */ + const initTagPanel = (_tagList: BoxPanelProps['tagList'], sort?: boolean) => { + // 正常标签渲染 + const commonTag = (_tagProps: ITag) => ( + onTagCheck?.(_tagProps.value, _tagProps)} + >{_tagProps.label} + ) + // 包装父级标签 + const _withFather = (tag: ITag) => ( +
+ {tag.label} + {tag.children?.map?.(_tag => commonTag(_tag))} +
+ ) + + 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 (
- {/* 盒子选择弹框 */} - setBoxChoiceOpen(false)} - onRadioChange={(e) => console.log('radio', e.target.value)} // 顶部 radio 事件 - dataSource={boxDataSource} // 数据源 - targetItems={targetItems} // 右侧选中项 - checkedKeys={checkedKeys} // 左侧选中 - onReset={onBoxChoiceReset} // 重置按钮事件 - onOk={onBoxChoiceOk} // 确定按钮事件 - onTreeCheck={onTreeCheck} // 树check选中事件 - onItemDelete={onItemDelete} // 右侧点击删除事件 - /> -
- onSearch?.(e)} - placeholder='请输入盒子名称' - {...searchInputProps} - /> - {customImport || ( -
-
- )} -
- {/* 是否显示操作按钮 */} + {/* 搜索栏 */} + {showSearchBar && ( +
+ onSearch?.(e)} + placeholder='请输入盒子名称' + {...searchInputProps} + /> + {customFilter || (!noFilter && ( +
+ {/* @ts-ignore */} + {initFilter(filterList)} +
+ ))} +
+ )} + {/* 默认操作按钮 */} {showOptions && ( <>
- - - {onCreate ? - ( - - ) : ( - - className={classNames(componentName + '-create-modal')} - open={onCreate ? false : undefined} - formRef={createFormRef} - title="新建组" - modalProps={{ destroyOnClose: true }} - layout='horizontal' - labelCol={{ span: 6 }} - wrapperCol={{ span: 18 }} - trigger={} - submitter={{ - searchConfig: { - submitText: '确定', - resetText: '取消', - }, - }} - onFinish={onCreateSubmit} - > - - - { - createFormRef.current?.setFieldValue('boxList', null) - onBoxChoiceReset() - }} >恢复默认 - setBoxChoiceOpen(true)}>范围选择 - - ) - }} - /> - - ) - } - - {/* @ts-ignore */} - + {initOptions(optionList)}
)} {extraBtns} + {showTagPanel && ( +
+ 标签: + {initTagPanel(tagList, tagExpandAll)} +
+ {tagExpandAll && ( + 重置 + )} + {tagExpandAll ? '收起' : '更多'} +
+ {tagFootRender} +
+ )} { onItemRenameFinish: async (val, data) => console.log('盒子重命名提交(返回boolean,控制弹框显示\隐藏)', val, data), checkedKeys, }} + footer={} />
); diff --git a/packages/biz/src/boxSelectTree/demo/customFilter.tsx b/packages/biz/src/boxSelectTree/demo/customFilter.tsx new file mode 100644 index 0000000..73b9c76 --- /dev/null +++ b/packages/biz/src/boxSelectTree/demo/customFilter.tsx @@ -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([]); + + return ( +
+ , + type: 'dropdown', + showTooltip: false, + dropdownConfig: { + menu: { + // 自定义返回项 + _internalRenderMenuItem: (originNode, menuItemProps, stateProps) => { + return ( +
+ {originNode} +
+ ) + }, + selectable: true, + items: [ + { + label:

全部

, + key: 'all', + onClick: () => console.log('多选1') + }, + { + icon: , + label: '多选1', + key: 'multi1', + onClick: () => console.log('多选1') + }, + { + label: '多选2', + icon: , + key: 'multi2', + }, + ], + } + } + }, + ]} + /> +
+ ); +}; + +export default demo; diff --git a/packages/biz/src/boxSelectTree/demo/customOptions.tsx b/packages/biz/src/boxSelectTree/demo/customOptions.tsx new file mode 100644 index 0000000..85197ab --- /dev/null +++ b/packages/biz/src/boxSelectTree/demo/customOptions.tsx @@ -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([]); + + return ( +
+ , + } + ]} + /> +
+ ); +}; + +export default demo; diff --git a/packages/biz/src/boxSelectTree/demo/extraBtns.tsx b/packages/biz/src/boxSelectTree/demo/extraBtns.tsx index d889b8b..2c01e2b 100644 --- a/packages/biz/src/boxSelectTree/demo/extraBtns.tsx +++ b/packages/biz/src/boxSelectTree/demo/extraBtns.tsx @@ -2,10 +2,12 @@ import React, { useState } from 'react'; import { BoxSelectTree } from '@zhst/biz'; import { treeData, boxDataSource } from './mock' import { Button } from 'antd'; +import { ClockCircleOutlined, DiffOutlined, SwitcherOutlined } from '@ant-design/icons'; const demo = () => { const [activeKey, setActiveKey] = useState('1') const [checkedKeys, setCheckedKeys] = useState([]); + const [isTreeCheckable, setIsTreeCheckable] = useState(false) return (
@@ -13,10 +15,23 @@ const demo = () => { data={activeKey === '1' ? treeData : boxDataSource} boxDataSource={boxDataSource} showOptions={false} - extraBtns={} + extraBtns={
Hello Lambo
} tabsProps={{ activeKey, }} + filterList={[ + { + label: '多选', + key: 'multi', + icon: isTreeCheckable ? : , + onClick: () => setIsTreeCheckable(pre => !pre) + }, + { + label: '时钟', + key: 'clock', + icon: , + } + ]} treeProps={{ checkedKeys }} diff --git a/packages/biz/src/boxSelectTree/demo/noFilter.tsx b/packages/biz/src/boxSelectTree/demo/noFilter.tsx new file mode 100644 index 0000000..349e87d --- /dev/null +++ b/packages/biz/src/boxSelectTree/demo/noFilter.tsx @@ -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([]); + + return ( +
+ +
+ ); +}; + +export default demo; diff --git a/packages/biz/src/boxSelectTree/demo/noOptions.tsx b/packages/biz/src/boxSelectTree/demo/noOptions.tsx index 304bb73..9b4b0b0 100644 --- a/packages/biz/src/boxSelectTree/demo/noOptions.tsx +++ b/packages/biz/src/boxSelectTree/demo/noOptions.tsx @@ -30,7 +30,7 @@ const demo = () => { tabsProps={{ activeKey, }} - customImport={