fix(biz,material): 修改冲突

This commit is contained in:
NICE CODE BY DEV 2024-05-29 14:49:53 +08:00
commit f9c5dccb73
31 changed files with 857 additions and 400 deletions

View File

@ -1,11 +1,16 @@
# @zhst/biz
## 0.22.3
## 0.24.0
### Patch Changes
### Minor Changes
- Updated dependencies
- @zhst/meta@0.22.0
- 树组件支持 tag 面板、优化 filter 传参、优化 option 传参、废弃之前的定制化方案
## 0.23.0
### Minor Changes
- feat(biz 无限滚动组件): 添加无限滚动组件,屏幕自适应撑开
## 0.22.2

View File

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

View File

@ -2,14 +2,14 @@
* Created by jiangzhixiong on 2024/04/28
*/
import React, { forwardRef, useContext, useImperativeHandle } from 'react'
import React, { forwardRef, MouseEventHandler, ReactNode, useContext, useImperativeHandle } from 'react'
import { ConfigProvider, EMPTY_BASE64 } from '@zhst/meta'
import { Flex, Image } from 'antd';
import './index.less'
const { ConfigContext } = ConfigProvider
export interface Idata {
export interface IData {
id?: string | number;
url?: string;
sort?: number;
@ -17,24 +17,25 @@ export interface Idata {
subtitle?: string;
}
export interface SearchCardProps extends Idata {
export interface CommonCardProps extends IData {
prefixCls?: string;
data?: Idata
data?: IData
width?: string;
height?: string;
onCreateTxt?: string;
onCreate?: (data: any) => void;
onCreate?: () => void;
onAddTxt?: string;
onAdd?: (data: any) => void;
onRemoveTxt?: string;
onRemove?: (data: any) => void;
customOptionRender?: React.ReactNode
actions?: ReactNode[]
onItemClick?: (data: any, e: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
}
export interface SearchCardRefProps {
export interface CommonCardRefProps {
}
const SearchCard = forwardRef<SearchCardRefProps, SearchCardProps>((props, ref) => {
const CommonCard = forwardRef<CommonCardRefProps, CommonCardProps>((props, ref) => {
const {
prefixCls: customizePrefixCls,
url,
@ -43,26 +44,29 @@ const SearchCard = forwardRef<SearchCardRefProps, SearchCardProps>((props, ref)
subtitle,
sort,
data,
onCreate,
onCreateTxt = '创建检索',
onAddTxt = '添加目标',
onRemoveTxt = '移除轨迹',
onAdd,
onRemove,
customOptionRender,
actions = [],
width = '184px',
height = '100%'
height = '100%',
onItemClick
} = props
const { getPrefixCls } = useContext(ConfigContext)
const componentName = getPrefixCls('biz-search-card', customizePrefixCls);
const stopBumble = (e: React.MouseEvent<HTMLAnchorElement, MouseEvent>, fn?: ((data: Idata) => void), data?: Idata) => {
e.stopPropagation()
fn?.(data!)
const optionListRender = (_actions: ReactNode[]) => {
return _actions.map((action, i) => (
// eslint-disable-next-line react/no-array-index-key
<li key={`${componentName}-main-opt-action-${i}`}>
{action}
{i !== _actions.length - 1 && <em className={`${componentName}-main-opt-action-split`} />}
</li>
))
}
const handleItemClick = (e: React.MouseEvent<HTMLDivElement, MouseEvent>, _data: IData | undefined) => {
onItemClick?.(data, e)
}
useImperativeHandle(ref, () => ({
}))
return (
@ -72,9 +76,10 @@ const SearchCard = forwardRef<SearchCardRefProps, SearchCardProps>((props, ref)
width,
height
}}
onClick={e => handleItemClick(e, data)}
>
<div className={`${componentName}-main`}>
<i className={`${componentName}-main-num`}>{id || sort}</i>
<i className={`${componentName}-main-num`}>{sort || id}</i>
<Image
className={`${componentName}-main-img`}
src={url || data?.url}
@ -82,17 +87,9 @@ const SearchCard = forwardRef<SearchCardRefProps, SearchCardProps>((props, ref)
preview={false}
fallback={EMPTY_BASE64}
/>
<Flex align='center' justify='space-between' className={`${componentName}-main-opt`}>
{customOptionRender || (
<>
<a onClick={(e) => stopBumble(e, onCreate, data)}>{onCreateTxt}</a>
|
<a onClick={(e) => stopBumble(e, onAdd, data)}>{onAddTxt}</a>
|
<a onClick={(e) => stopBumble(e, onRemove, data)}>{onRemoveTxt}</a>
</>
)}
</Flex>
<ul className={`${componentName}-main-opt`}>
{optionListRender(actions)}
</ul>
</div>
<div className={`${componentName}-footer`}>
<p className={`${componentName}-footer-tit`}>{title || data?.title}</p>
@ -102,4 +99,4 @@ const SearchCard = forwardRef<SearchCardRefProps, SearchCardProps>((props, ref)
)
})
export default SearchCard
export default CommonCard

View File

@ -1,18 +1,23 @@
.zhst-biz-search-card {
display: inline-block;
border: 2px solid transparent;
transition: .1s ease-in all;
cursor: pointer;
overflow: hidden;
&:hover {
transition: .5s ease all;
border: 2px solid #09f;
.zhst-biz-search-card-main-opt {
display: flex;
align-items: center;
}
}
&-main {
position: relative;
overflow: hidden;
&-num {
position: absolute;
@ -32,11 +37,17 @@
&-img {
width: 100%;
height: 240px;
overflow: hidden;
&:hover {
transition: .5s ease-in all;
transform: scale(1.05);
}
}
&-opt {
display: none;
margin: 0;
position: absolute;
padding: 6px 3px;
left: 0;
@ -48,11 +59,28 @@
box-sizing: border-box;
transition: .2s ease-in all;
li {
position: relative;
list-style: none;
flex: 1;
text-align: center;
}
&-action-split {
position: absolute;
top: 50%;
right: 0;
width: 1px;
height: 80%;
transform: translateY(-50%);
background-color: #fff;
}
a {
color: #fff;
&:hover {
opacity: 0.9;
opacity: 0.88;
}
}
}

View File

@ -0,0 +1,5 @@
import CommonCard from './CommonCard'
export type { CommonCardProps, CommonCardRefProps } from './CommonCard'
export default CommonCard

View File

@ -0,0 +1,42 @@
import React, { useEffect, useState } from 'react'
import { CommonCard } from '@zhst/biz'
import { uniqueId } from '@zhst/func'
export default () => {
const [data, setData] = useState([])
useEffect(() => {
fetch('https://randomuser.me/api/?results=10&inc=id,key,name,gender,email,nat,picture&noinfo')
.then((res) => res.json())
.then((body) => {
let res = body.results.map((o, index) => {
return {
id: uniqueId(),
sort: index + 1,
title: o.name.first,
subtitle: o.name.last,
url: o.picture.large
}
})
setData(res);
})
}, [])
return (
<div>
{data?.map(item => (
<CommonCard
key={item.id}
sort={item.sort}
data={item}
width="184px"
actions={[
<a></a>,
<a></a>,
<a></a>
]}
/>
))}
</div>
)
}

View File

@ -0,0 +1,31 @@
---
category: Components
title: CustomCard 定制化卡片
toc: content
group:
title: 数据展示
---
定制化卡片
## 代码演示
<code src="./demo/commonCard.tsx">基本卡片</code>
## API
### CommonCardProps
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| data | 数据源 | IData[] | [] | - |
| prefixCls | 数据源 | string | [] | - |
| width | 宽度 | string | [] | - |
| height | 高度 | string | [] | - |
| onCreateTxt | 创建方法文字 | string | [] | - |
| onCreate | 创建方法 | () => void | [] | - |
| onAddTxt | 数据源 | string | [] | - |
| onAdd | 数据源 | () => void | [] | - |
| onRemoveTxt | 数据源 | string | [] | - |
| onRemove | 数据源 | () => void | [] | - |
| customOptionRender | 数据源 | React.ReactNode | - | - |

View File

@ -0,0 +1,6 @@
/**
* Created by jiangzhixiong on 2024/04/28
*/
export { default as CommonCard } from './components/commonCard'
export type { CommonCardProps, CommonCardRefProps } from './components/commonCard'

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 { 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<BoxSelectTreeProps> = (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<BoxSelectTreeProps> = (props) => {
{...tabsProps}
/>
<BoxPanel
searchInputProps={searchInputProps}
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}
{...rest}
/>
{footer}
</div>
);
};

View File

@ -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;
}

View File

@ -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<BoxTreeProps>
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<BoxPanelProps> = (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: <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
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<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)
}
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 (
<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>
))
}
/**
*
* @param key
* @param param1
* filter
* @param _list
* @returns
*/
const onItemDelete = (key: any, { keys }: any) => {
setCheckedKeys(pre => {
const newKeys = pre.filter(_key => !keys.includes(_key))
return newKeys
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)
}
})
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 (
<div className={componentName}>
{/* 盒子选择弹框 */}
<TreeTransferModal
open={boxChoiceOpen}
onCancel={() => 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} // 右侧点击删除事件
/>
<div className={classNames(componentName + '-search')}>
<Input
className={classNames(componentName + '-search-input')}
size='middle'
onChange={(e) => onSearch?.(e)}
placeholder='请输入盒子名称'
{...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>
{/* 是否显示操作按钮 */}
{/* 搜索栏 */}
{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')}>
<Button className={classNames(componentName + '-btns-common')} type='text' onClick={() => onImport?.()} icon={<ImportOutlined />} ></Button>
<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>
{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')}
treeCheckable={isTreeCheckable}

View File

@ -1,7 +1,7 @@
import React, { useState } from 'react';
import { BoxSelectTree } from '@zhst/biz';
import { treeData, boxDataSource } from './mock'
import { Select, TreeProps, Modal, Checkbox } from 'antd';
import { Select, TreeProps, Modal, Checkbox, Button } from 'antd';
const { Option } = Select
@ -81,6 +81,7 @@ const demo = () => {
onItemRenameFinish: async (val, data) => console.log('盒子重命名提交(返回boolean,控制弹框显示\隐藏)', val, data),
checkedKeys,
}}
footer={<Button block ></Button>}
/>
</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 { 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<string[]>([]);
const [isTreeCheckable, setIsTreeCheckable] = useState(false)
return (
<div style={{ border: '1px solid #ccc', width: '340px', minHeight: '900px' }}>
@ -13,10 +15,23 @@ const demo = () => {
data={activeKey === '1' ? treeData : boxDataSource}
boxDataSource={boxDataSource}
showOptions={false}
extraBtns={<Button type="dashed" style={{ color: 'green' }}></Button>}
extraBtns={<div><Button type="dashed" style={{ color: 'red' }}></Button> Hello Lambo</div>}
tabsProps={{
activeKey,
}}
filterList={[
{
label: '多选',
key: 'multi',
icon: isTreeCheckable ? <SwitcherOutlined /> : <DiffOutlined />,
onClick: () => setIsTreeCheckable(pre => !pre)
},
{
label: '时钟',
key: 'clock',
icon: <ClockCircleOutlined />,
}
]}
treeProps={{
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={{
activeKey,
}}
customImport={<Button type="text" icon={<FilterOutlined />} />}
customImport={<div style={{ color: 'red' }}></div>}
searchInputProps={{
addonBefore: (
<Select
@ -43,7 +43,6 @@ const demo = () => {
>
{[
{ value: '1', label: '盒子' },
{ value: '2', label: '盒子组' }
].map(item => (
<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 盒子树
toc: content
demo:
cols: 2
cols: 3
group:
title: 进阶组件
---
@ -15,6 +15,10 @@ group:
<code src="./demo/basic.tsx">基本用法</code>
<code src="./demo/extraBtns.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>
## API
@ -22,17 +26,35 @@ group:
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| 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 | - | - |
| onItemSelect | 树当前选中(单选) | function: (e) => void | - | - |
| onItemCheck | 树选择(支持多选) | function: (e) => void | - | - |
| tabsProps | Tabs组件的Props | antd的Tabs组件 | - | - |
| searchInputProps | 搜索框的Props | antd的Input组件 | - | - |
| onTabChange | tab切换监听 | function: (e) => void | - | - |
| onBoxDelete | 盒子删除事件 | function: (e) => void | - | - |
| onBoxBatchDelete | 盒子批量删除事件 | function: (e) => void | - | - |
| onCreateSubmit | 新建提交事件 | function: (e) => void | - | - |
| onImport | 监听导入盒子点击事件 | function: () => void | - | - |
| onClockClick | 监听时钟点击事件 | function: () => void | - | - |
| onCreate | 监听创建点击事件 | function: () => void | 如果不传默认用自带的创建事件 | - |
| showOptions | 展示其它功能按钮 | boolean | true | - |
| onBoxDelete | 盒子删除事件 | function: (e) => void | - | 0.23.0 以后弃用 |
| onBatch | 多选 | function: (e) => void | - | 0.23.0 以后弃用 |
| onBoxBatchDelete | 盒子批量删除事件 | function: (e) => void | - | 0.23.0 以后弃用 |
| onCreateSubmit | 新建提交事件 | function: (e) => void | - | 0.23.0 以后弃用 |
| onImport | 监听导入盒子点击事件 | function: () => void | - | 0.23.0 以后弃用 |
| onClockClick | 监听时钟点击事件 | function: () => void | - | 0.23.0 以后弃用 |
| onCreate | 监听创建点击事件 | function: () => void | 如果不传默认用自带的创建事件 | 0.23.0 以后弃用 |

View File

@ -9,6 +9,11 @@ export type { TreeTransferProps } from './treeTransfer'
export { default as TreeTransferModal } from './treeTransferModal'
export type { TreeTransferModalProps } from './treeTransferModal'
export { default as WarningRecordCard } from './WarningRecordCard'
export { CommonCard } from './CustomCard'
export type {
CommonCardProps,
CommonCardRefProps
} from './CustomCard'
export type { IRecord, WarningRecordCardProps } from './WarningRecordCard'
export { default as OdModal } from './odModal'
export type { ODModalProps } from './odModal'

View File

@ -2,24 +2,30 @@
* Created by jiangzhixiong
*/
import React, { forwardRef, ReactNode, useContext, useImperativeHandle, useRef } from 'react'
import React, { forwardRef, ReactNode, useContext, useEffect, useImperativeHandle, useRef } from 'react'
import { ConfigProvider } from '@zhst/meta';
import { Divider, Flex } from 'antd';
import classNames from 'classnames';
import InfiniteScroll from 'react-infinite-scroll-component';
import { SearchCard, SearchCardProps } from './components';
import { Spin, SpinProps } from 'antd';
import { useSize } from '@zhst/hooks';
import './index.less'
import { Idata } from './components/SearchCard';
const { ConfigContext } = ConfigProvider
export interface InfiniteListProps {
export interface IData {
key: string;
title: string;
index?: number;
[k: string]: any;
}
export interface InfiniteListProps extends React.HtmlHTMLAttributes<HTMLDivElement> {
type?: 'custom' | 'auto'
prefixCls?: string;
height?: number;
itemRender?: (data?: any) => React.ReactNode
itemRender?: (data?: IData, index?: number) => React.ReactNode
loading?: boolean; //
data: Idata[];
data: IData[];
targetId?: string; // 滚动列表 ID
loadMore?: (data?: any) => any;
params?: {
@ -28,101 +34,79 @@ export interface InfiniteListProps {
hasMore: boolean;
endMessage?: ReactNode
loadingMessage?: ReactNode
onItemClick?: (data: any) => void;
searchCardProps?: SearchCardProps
loadingProps?: SpinProps
}
export interface InfiniteListRefProps {
scrollViewSize?: { width: number; height: number }
listSize?: { width: number; height: number }
}
const InfiniteList = forwardRef<InfiniteListRefProps, InfiniteListProps>((props, ref) => {
const {
prefixCls: customizePrefixCls,
height,
height = 600,
loading,
type = 'auto',
loadingMessage = <p style={{ textAlign: 'center' }}>...</p>,
targetId = 'scrollableDiv',
itemRender,
itemRender = (data) => <div>{data?.title}</div>,
hasMore,
onItemClick,
loadMore,
data = [],
endMessage = <Divider plain>...🤐</Divider>,
searchCardProps
endMessage = <div style={{ textAlign: 'center' }} >...🤐</div>,
style,
loadingProps,
className
} = props
const { getPrefixCls } = useContext(ConfigContext);
const componentName = getPrefixCls('biz-infinite-list', customizePrefixCls);
const listRef = useRef<HTMLDivElement>(null);
const scrollRef = useRef<HTMLDivElement>(null);
const scrollViewSize = useSize(listRef.current) || { width: 0, height: 0 }; // 无限滚动视窗大小
// @ts-ignore
const listSize = useSize(scrollRef.current?._infScroll) || { width: 0, height: 0 } // 无限滚动列表大小
useEffect(() => {
// 当数据不够一屏时继续加载
if (listSize.height < scrollViewSize.height) {
loadMore?.()
}
}, [listSize.height])
useImperativeHandle(ref, () => ({
scrollViewSize,
listSize
}))
return (
<div
id={targetId}
className={classNames(componentName)}
ref={listRef}
style={{
height,
overflow: 'auto',
padding: 12
}}
>
{/* {loading ? (
<p>...</p>
) : (
<Flex wrap='wrap' gap="small" className={classNames(componentName + 'items')}>
{data?.list?.map((item) => (
itemRender?.(item) || (
<div className={classNames(componentName + 'items-item')}>
<SearchCard data={item} />
</div>
)
))}
</Flex>
)} */}
<InfiniteScroll
dataLength={data.length}
next={type === 'auto' ? loadMore! : () => {}}
hasMore={hasMore}
loader={loadingMessage}
endMessage={endMessage}
scrollableTarget={targetId}
<Spin spinning={loading} {...loadingProps}>
<div
id={targetId}
className={classNames(componentName, className)}
ref={listRef}
style={{
height,
overflow: 'auto',
...style
}}
>
<Flex wrap='wrap' gap="small" className={classNames(componentName + 'items')}>
<InfiniteScroll
// @ts-ignore
ref={scrollRef}
dataLength={data.length}
next={type === 'auto' ? loadMore! : () => {}}
hasMore={hasMore}
loader={loadingMessage}
endMessage={endMessage}
scrollableTarget={targetId}
>
{data?.map((item, idx) => (
itemRender?.(item) || (
<div
key={idx}
className={classNames(componentName + 'items-item')}
onClick={() => {
onItemClick?.(item)
}}
>
<SearchCard
id={idx + 1}
data={item}
width="184px"
{...searchCardProps}
/>
</div>
)
itemRender?.({ ...item, index: idx})
))}
</Flex>
</InfiniteScroll>
{/* <div style={{ marginTop: 8 }}>
{!noMore && (
<Button onClick={loadMore} disabled={loadingMore}>
{loadingMore ? '加载中...' : '点击加载更多'}
</Button>
)}
{noMore && <span></span>}
</div> */}
</div>
</InfiniteScroll>
</div>
</Spin>
)
})

View File

@ -1,6 +0,0 @@
/**
* Created by jiangzhixiong on 2024/04/28
*/
export { default as SearchCard } from './SearchCard'
export type { SearchCardProps, SearchCardRefProps } from './SearchCard'

View File

@ -1,5 +1,6 @@
import React, { useEffect, useState } from 'react'
import { InfiniteList } from '@zhst/biz'
import { InfiniteList, CommonCard } from '@zhst/biz'
import { uniqueId } from '@zhst/func'
export default () => {
const [data, setData] = useState([])
@ -13,8 +14,10 @@ export default () => {
fetch('https://randomuser.me/api/?results=10&inc=id,key,name,gender,email,nat,picture&noinfo')
.then((res) => res.json())
.then((body) => {
let res = body.results.map(o => {
let res = body.results.map((o, index) => {
return {
id: uniqueId(),
sort: index + 1,
title: o.name.first,
subtitle: o.name.last,
url: o.picture.large
@ -36,14 +39,23 @@ export default () => {
<InfiniteList
loading={loading}
loadMore={loadMoreData}
height={300}
hasMore={data.length < 100}
height={1200}
hasMore={data.length < 60}
data={data}
onItemClick={_data => console.log('item点击', _data)}
searchCardProps={{
onAdd: (_data) => console.log('新增', _data),
onCreate: (_data) => console.log('创建', _data),
onRemove: (_data) => console.log('删除', _data),
itemRender={(item) => {
return (
<CommonCard
key={item.id}
sort={item.sort}
data={item}
width="184px"
actions={[
<a></a>,
<a></a>,
<a></a>
]}
/>
)
}}
/>
)

View File

@ -3,11 +3,11 @@ import { InfiniteList } from '@zhst/biz'
import { Button, Input, Space } from 'antd'
export default () => {
const [data, setData] = useState([])
const [data, setData] = useState<any>([])
const [loading, setLoading] = useState(false)
const [params, setParams] = useState({})
const loadMoreData = (params?: { name: string; age?: number; sex: string; tel: number }) => {
const loadMoreData = (params?: any) => {
if (loading) {
return;
}
@ -15,7 +15,7 @@ export default () => {
fetch('https://randomuser.me/api/?results=10&inc=id,key,name,gender,email,nat,picture&noinfo')
.then((res) => res.json())
.then((body) => {
let res = body.results.map(o => {
let res = body.results.map((o: { name: { first: any; last: any }; picture: { large: any } }) => {
return {
title: o.name.first,
subtitle: o.name.last,
@ -35,7 +35,7 @@ export default () => {
}, []);
return (
<Space direction='vertical'>
<Space direction='vertical' size={10} style={{ padding: '12px', border: '1px solid #ccc' }}>
<Space>
<Input placeholder='名称' onChange={(e) => setParams(pre => ({ ...pre, name: e.target.value }))} style={{ width: '120px' }} />
<Input placeholder='年龄' onChange={(e) => setParams(pre => ({ ...pre, age: e.target.value }))} style={{ width: '120px' }} />
@ -49,9 +49,7 @@ export default () => {
height={300}
hasMore={data.length < 100}
data={data}
type="custom"
loadingMessage={<Button onClick={() => loadMoreData(params)}></Button>}
onItemClick={data => console.log('item点击', data)}
/>
</Space>
)

View File

@ -18,21 +18,20 @@ group:
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| data | 数据源 | Idata[] | [] | - |
| data | 数据源 | IData[] | [] | - |
| height | 无限滚动列表可视区高度 | number | 600 | - |
| loading | 数据源 | Array[] | [] | - |
| data | 数据源 | Array[] | [] | - |
| data | 数据源 | Array[] | [] | - |
| data | 数据源 | Array[] | [] | - |
| data | 数据源 | Array[] | [] | - |
| data | 数据源 | Array[] | [] | - |
| dataLength | 数据数量 | number | [] | - |
| next | 下一页方法 | function | () => {} | - |
| hasMore | 是否还有更多 | boolean | false | - |
| loadingProps | 参考 antd-spin | spinProps | [] | - |
| itemRender | 自定义渲染项 | (IData) => ReactNode | - | - |
## Idata
## 设计思路
```js
interface Idata {
id?: string | number;
url?: string; // 链接
title?: string; // 标题
subtitle?: string; // 副标题
}
```
无限滚动,同时支持:
1. 自动、主动加载更多
2. 一屏没加载完,继续加载,直到填满屏幕:
- 需要第二次加载的内容是否为空,为空则停止加载
- 通过整体的page-height 和 浏览器可视区域

View File

View File

@ -1,7 +1,7 @@
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 { ModalForm, ProFormText } from '@ant-design/pro-components';
import classNames from 'classnames';
import './index.less'
const componentName = 'zhst-biz-tree'
@ -30,7 +30,7 @@ const boxTree: FC<BoxTreeProps> = (props) => {
showItemOption = true,
treeCheckable = false,
onItemRename,
onItemRenameFinish,
className: customClassName,
customOptions
} = props
const { token } = useToken()
@ -45,6 +45,7 @@ const boxTree: FC<BoxTreeProps> = (props) => {
return (
<Tree
className={classNames(componentName, customClassName)}
checkable={treeCheckable}
blockNode
onSelect={(selectedKeys, info) => {
@ -70,38 +71,11 @@ const boxTree: FC<BoxTreeProps> = (props) => {
<Space className={`${componentName}-item-render_right`} style={{ float:'right' }} >
{customOptions || (
<>
<ModalForm
title="重命名"
width={600}
modalProps={{ destroyOnClose: true }}
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>
<EditOutlined onClick={(e) => {
e.preventDefault();
e.stopPropagation();
onItemRename?.(_nodeData)
}} />
<SettingOutlined onClick={(e) => {
e.preventDefault();
e.stopPropagation();

View File

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

View File

@ -1,12 +1,18 @@
# @zhst/material
## 0.18.4
### Patch Changes
- Updated dependencies
- @zhst/biz@0.24.0
## 0.18.3
### Patch Changes
- Updated dependencies
- @zhst/meta@0.22.0
- @zhst/biz@0.22.3
- @zhst/biz@0.23.0
## 0.18.2

View File

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