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

View File

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

View File

@ -2,14 +2,14 @@
* Created by jiangzhixiong on 2024/04/28 * 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 { ConfigProvider, EMPTY_BASE64 } from '@zhst/meta'
import { Flex, Image } from 'antd'; import { Flex, Image } from 'antd';
import './index.less' import './index.less'
const { ConfigContext } = ConfigProvider const { ConfigContext } = ConfigProvider
export interface Idata { export interface IData {
id?: string | number; id?: string | number;
url?: string; url?: string;
sort?: number; sort?: number;
@ -17,24 +17,25 @@ export interface Idata {
subtitle?: string; subtitle?: string;
} }
export interface SearchCardProps extends Idata { export interface CommonCardProps extends IData {
prefixCls?: string; prefixCls?: string;
data?: Idata data?: IData
width?: string; width?: string;
height?: string; height?: string;
onCreateTxt?: string; onCreateTxt?: string;
onCreate?: (data: any) => void; onCreate?: () => void;
onAddTxt?: string; onAddTxt?: string;
onAdd?: (data: any) => void; onAdd?: (data: any) => void;
onRemoveTxt?: string; onRemoveTxt?: string;
onRemove?: (data: any) => void; 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 { const {
prefixCls: customizePrefixCls, prefixCls: customizePrefixCls,
url, url,
@ -43,26 +44,29 @@ const SearchCard = forwardRef<SearchCardRefProps, SearchCardProps>((props, ref)
subtitle, subtitle,
sort, sort,
data, data,
onCreate, actions = [],
onCreateTxt = '创建检索',
onAddTxt = '添加目标',
onRemoveTxt = '移除轨迹',
onAdd,
onRemove,
customOptionRender,
width = '184px', width = '184px',
height = '100%' height = '100%',
onItemClick
} = props } = props
const { getPrefixCls } = useContext(ConfigContext) const { getPrefixCls } = useContext(ConfigContext)
const componentName = getPrefixCls('biz-search-card', customizePrefixCls); const componentName = getPrefixCls('biz-search-card', customizePrefixCls);
const stopBumble = (e: React.MouseEvent<HTMLAnchorElement, MouseEvent>, fn?: ((data: Idata) => void), data?: Idata) => { const optionListRender = (_actions: ReactNode[]) => {
e.stopPropagation() return _actions.map((action, i) => (
fn?.(data!) // 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, () => ({ useImperativeHandle(ref, () => ({
})) }))
return ( return (
@ -72,9 +76,10 @@ const SearchCard = forwardRef<SearchCardRefProps, SearchCardProps>((props, ref)
width, width,
height height
}} }}
onClick={e => handleItemClick(e, data)}
> >
<div className={`${componentName}-main`}> <div className={`${componentName}-main`}>
<i className={`${componentName}-main-num`}>{id || sort}</i> <i className={`${componentName}-main-num`}>{sort || id}</i>
<Image <Image
className={`${componentName}-main-img`} className={`${componentName}-main-img`}
src={url || data?.url} src={url || data?.url}
@ -82,17 +87,9 @@ const SearchCard = forwardRef<SearchCardRefProps, SearchCardProps>((props, ref)
preview={false} preview={false}
fallback={EMPTY_BASE64} fallback={EMPTY_BASE64}
/> />
<Flex align='center' justify='space-between' className={`${componentName}-main-opt`}> <ul className={`${componentName}-main-opt`}>
{customOptionRender || ( {optionListRender(actions)}
<> </ul>
<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>
</div> </div>
<div className={`${componentName}-footer`}> <div className={`${componentName}-footer`}>
<p className={`${componentName}-footer-tit`}>{title || data?.title}</p> <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 { .zhst-biz-search-card {
display: inline-block;
border: 2px solid transparent; border: 2px solid transparent;
transition: .1s ease-in all; transition: .1s ease-in all;
cursor: pointer; cursor: pointer;
overflow: hidden;
&:hover { &:hover {
transition: .5s ease all;
border: 2px solid #09f; border: 2px solid #09f;
.zhst-biz-search-card-main-opt { .zhst-biz-search-card-main-opt {
display: flex; display: flex;
align-items: center;
} }
} }
&-main { &-main {
position: relative; position: relative;
overflow: hidden;
&-num { &-num {
position: absolute; position: absolute;
@ -32,11 +37,17 @@
&-img { &-img {
width: 100%; width: 100%;
height: 240px; overflow: hidden;
&:hover {
transition: .5s ease-in all;
transform: scale(1.05);
}
} }
&-opt { &-opt {
display: none; display: none;
margin: 0;
position: absolute; position: absolute;
padding: 6px 3px; padding: 6px 3px;
left: 0; left: 0;
@ -48,11 +59,28 @@
box-sizing: border-box; box-sizing: border-box;
transition: .2s ease-in all; 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 { a {
color: #fff; color: #fff;
&:hover { &: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 { 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

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

View File

@ -2,24 +2,30 @@
* Created by jiangzhixiong * 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 { ConfigProvider } from '@zhst/meta';
import { Divider, Flex } from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import InfiniteScroll from 'react-infinite-scroll-component'; 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 './index.less'
import { Idata } from './components/SearchCard';
const { ConfigContext } = ConfigProvider 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' type?: 'custom' | 'auto'
prefixCls?: string; prefixCls?: string;
height?: number; height?: number;
itemRender?: (data?: any) => React.ReactNode itemRender?: (data?: IData, index?: number) => React.ReactNode
loading?: boolean; // loading?: boolean; //
data: Idata[]; data: IData[];
targetId?: string; // 滚动列表 ID targetId?: string; // 滚动列表 ID
loadMore?: (data?: any) => any; loadMore?: (data?: any) => any;
params?: { params?: {
@ -28,101 +34,79 @@ export interface InfiniteListProps {
hasMore: boolean; hasMore: boolean;
endMessage?: ReactNode endMessage?: ReactNode
loadingMessage?: ReactNode loadingMessage?: ReactNode
onItemClick?: (data: any) => void; loadingProps?: SpinProps
searchCardProps?: SearchCardProps
} }
export interface InfiniteListRefProps { export interface InfiniteListRefProps {
scrollViewSize?: { width: number; height: number }
listSize?: { width: number; height: number }
} }
const InfiniteList = forwardRef<InfiniteListRefProps, InfiniteListProps>((props, ref) => { const InfiniteList = forwardRef<InfiniteListRefProps, InfiniteListProps>((props, ref) => {
const { const {
prefixCls: customizePrefixCls, prefixCls: customizePrefixCls,
height, height = 600,
loading,
type = 'auto', type = 'auto',
loadingMessage = <p style={{ textAlign: 'center' }}>...</p>, loadingMessage = <p style={{ textAlign: 'center' }}>...</p>,
targetId = 'scrollableDiv', targetId = 'scrollableDiv',
itemRender, itemRender = (data) => <div>{data?.title}</div>,
hasMore, hasMore,
onItemClick,
loadMore, loadMore,
data = [], data = [],
endMessage = <Divider plain>...🤐</Divider>, endMessage = <div style={{ textAlign: 'center' }} >...🤐</div>,
searchCardProps style,
loadingProps,
className
} = props } = props
const { getPrefixCls } = useContext(ConfigContext); const { getPrefixCls } = useContext(ConfigContext);
const componentName = getPrefixCls('biz-infinite-list', customizePrefixCls); const componentName = getPrefixCls('biz-infinite-list', customizePrefixCls);
const listRef = useRef<HTMLDivElement>(null); 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, () => ({ useImperativeHandle(ref, () => ({
scrollViewSize,
listSize
})) }))
return ( return (
<div <Spin spinning={loading} {...loadingProps}>
id={targetId} <div
className={classNames(componentName)} id={targetId}
ref={listRef} className={classNames(componentName, className)}
style={{ ref={listRef}
height, style={{
overflow: 'auto', height,
padding: 12 overflow: 'auto',
}} ...style
> }}
{/* {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}
> >
<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) => ( {data?.map((item, idx) => (
itemRender?.(item) || ( itemRender?.({ ...item, index: idx})
<div
key={idx}
className={classNames(componentName + 'items-item')}
onClick={() => {
onItemClick?.(item)
}}
>
<SearchCard
id={idx + 1}
data={item}
width="184px"
{...searchCardProps}
/>
</div>
)
))} ))}
</Flex> </InfiniteScroll>
</InfiniteScroll> </div>
{/* <div style={{ marginTop: 8 }}> </Spin>
{!noMore && (
<Button onClick={loadMore} disabled={loadingMore}>
{loadingMore ? '加载中...' : '点击加载更多'}
</Button>
)}
{noMore && <span></span>}
</div> */}
</div>
) )
}) })

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 React, { useEffect, useState } from 'react'
import { InfiniteList } from '@zhst/biz' import { InfiniteList, CommonCard } from '@zhst/biz'
import { uniqueId } from '@zhst/func'
export default () => { export default () => {
const [data, setData] = useState([]) 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') fetch('https://randomuser.me/api/?results=10&inc=id,key,name,gender,email,nat,picture&noinfo')
.then((res) => res.json()) .then((res) => res.json())
.then((body) => { .then((body) => {
let res = body.results.map(o => { let res = body.results.map((o, index) => {
return { return {
id: uniqueId(),
sort: index + 1,
title: o.name.first, title: o.name.first,
subtitle: o.name.last, subtitle: o.name.last,
url: o.picture.large url: o.picture.large
@ -36,14 +39,23 @@ export default () => {
<InfiniteList <InfiniteList
loading={loading} loading={loading}
loadMore={loadMoreData} loadMore={loadMoreData}
height={300} height={1200}
hasMore={data.length < 100} hasMore={data.length < 60}
data={data} data={data}
onItemClick={_data => console.log('item点击', _data)} itemRender={(item) => {
searchCardProps={{ return (
onAdd: (_data) => console.log('新增', _data), <CommonCard
onCreate: (_data) => console.log('创建', _data), key={item.id}
onRemove: (_data) => console.log('删除', _data), 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' import { Button, Input, Space } from 'antd'
export default () => { export default () => {
const [data, setData] = useState([]) const [data, setData] = useState<any>([])
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [params, setParams] = useState({}) const [params, setParams] = useState({})
const loadMoreData = (params?: { name: string; age?: number; sex: string; tel: number }) => { const loadMoreData = (params?: any) => {
if (loading) { if (loading) {
return; return;
} }
@ -15,7 +15,7 @@ export default () => {
fetch('https://randomuser.me/api/?results=10&inc=id,key,name,gender,email,nat,picture&noinfo') fetch('https://randomuser.me/api/?results=10&inc=id,key,name,gender,email,nat,picture&noinfo')
.then((res) => res.json()) .then((res) => res.json())
.then((body) => { .then((body) => {
let res = body.results.map(o => { let res = body.results.map((o: { name: { first: any; last: any }; picture: { large: any } }) => {
return { return {
title: o.name.first, title: o.name.first,
subtitle: o.name.last, subtitle: o.name.last,
@ -35,7 +35,7 @@ export default () => {
}, []); }, []);
return ( return (
<Space direction='vertical'> <Space direction='vertical' size={10} style={{ padding: '12px', border: '1px solid #ccc' }}>
<Space> <Space>
<Input placeholder='名称' onChange={(e) => setParams(pre => ({ ...pre, name: e.target.value }))} style={{ width: '120px' }} /> <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' }} /> <Input placeholder='年龄' onChange={(e) => setParams(pre => ({ ...pre, age: e.target.value }))} style={{ width: '120px' }} />
@ -49,9 +49,7 @@ export default () => {
height={300} height={300}
hasMore={data.length < 100} hasMore={data.length < 100}
data={data} data={data}
type="custom"
loadingMessage={<Button onClick={() => loadMoreData(params)}></Button>} loadingMessage={<Button onClick={() => loadMoreData(params)}></Button>}
onItemClick={data => console.log('item点击', data)}
/> />
</Space> </Space>
) )

View File

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

View File

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,12 +1,18 @@
# @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
- Updated dependencies - Updated dependencies
- @zhst/meta@0.22.0 - @zhst/biz@0.23.0
- @zhst/biz@0.22.3
## 0.18.2 ## 0.18.2

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",