You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
214 lines
8.2 KiB
214 lines
8.2 KiB
import { Button, Input, Modal, ModalProps, Select, SelectProps, Spin, Tag } from 'antd'
|
|
import { memo, ReactNode, useEffect, useRef, useState } from 'react'
|
|
import { Flexbox } from 'react-layout-kit'
|
|
import { useStyle } from './style.ts'
|
|
import { List, ListViewItem } from './List.tsx'
|
|
import { useTranslation } from '@/i18n.ts'
|
|
import DepartmentTree from '@/components/department-tree/DepartmentTree.tsx'
|
|
import { DraggablePanel } from '@/components/draggable-panel'
|
|
import { useAtom, useAtomValue } from 'jotai'
|
|
import { userListAtom, userSearchAtom, } from '@/store/system/user.ts'
|
|
import { IUser } from '@/types'
|
|
import EmptyWrap from '@/components/empty/EmptyWrap.tsx'
|
|
|
|
export interface UserSelectProps extends SelectProps {
|
|
value?: any[]
|
|
onChange?: (value: any[]) => void
|
|
//多选
|
|
multiple?: boolean,
|
|
renderValue?: (value: any[], def: ReactNode) => ReactNode
|
|
|
|
}
|
|
|
|
export interface UserModelProps extends Pick<ModalProps, 'open'> {
|
|
value?: any[]
|
|
onChange?: (value?: any[]) => void
|
|
children?: ReactNode
|
|
//多选
|
|
multiple?: boolean
|
|
|
|
renderValue?: (value: any[], def: ReactNode) => ReactNode
|
|
|
|
}
|
|
|
|
export type UserPickerProps =
|
|
| {
|
|
type?: 'modal';
|
|
/** Props for the modal component */
|
|
} & UserModelProps
|
|
| {
|
|
type: 'select';
|
|
/** Props for the select component */
|
|
} & UserSelectProps
|
|
|
|
const UserSelect = memo((props: UserSelectProps) => {
|
|
|
|
return (
|
|
<Select {...props}>
|
|
|
|
</Select>
|
|
)
|
|
})
|
|
|
|
const UserModel = memo(({ multiple, children, value, onChange, ...props }: UserModelProps) => {
|
|
const { styles, cx } = useStyle()
|
|
const { t } = useTranslation()
|
|
const [ innerValue, setValue ] = useState(() => {
|
|
|
|
if (!multiple && !Array.isArray(value)) {
|
|
return [ value ]
|
|
}
|
|
})
|
|
const [ , setSearch ] = useAtom(userSearchAtom)
|
|
const { data: users, isPending } = useAtomValue(userListAtom)
|
|
const [ open, setOpen ] = useState(false)
|
|
const selectUserRef = useRef<IUser[]>([])
|
|
const [ , update ] = useState({})
|
|
|
|
useEffect(() => {
|
|
if (value === undefined) return
|
|
|
|
setValue(Array.isArray(value) ? value : [ value ])
|
|
}, [ value ])
|
|
|
|
useEffect(() => {
|
|
|
|
selectUserRef.current = []
|
|
innerValue?.forEach(id => {
|
|
const item = users?.rows?.find(user => user.id === id)
|
|
if (item) {
|
|
selectUserRef.current = [ ...selectUserRef.current, item ]
|
|
}
|
|
})
|
|
update({})
|
|
|
|
}, [ innerValue ])
|
|
|
|
useEffect(() => {
|
|
return () => {
|
|
selectUserRef.current = []
|
|
}
|
|
|
|
}, [])
|
|
|
|
const renderTarget = () => {
|
|
return <span onClick={() => setOpen(true)}>
|
|
{children ?? <Button type={'primary'} >{t('component.UserPicker.targetText', '选择成员')}</Button>}
|
|
</span>
|
|
}
|
|
|
|
const renderValue = () => {
|
|
const dom = selectUserRef.current?.map(user => {
|
|
return <Tag key={user.id} color={'blue'} closable onClose={() => {
|
|
const newVal = innerValue?.filter(val => val !== user.id)
|
|
setValue(newVal)
|
|
onChange?.(newVal)
|
|
}}>{user.name}</Tag>
|
|
})
|
|
if (props.renderValue) {
|
|
return props.renderValue(selectUserRef.current, dom)
|
|
}
|
|
return dom
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<div>
|
|
{renderTarget()}
|
|
</div>
|
|
<div className={styles.values}>
|
|
{renderValue()}
|
|
</div>
|
|
|
|
<Modal
|
|
className={styles.modal}
|
|
title={t('component.UserPicker.title', '成员选择')}
|
|
width={800}
|
|
{...props}
|
|
open={open}
|
|
onOk={() => {
|
|
onChange?.(multiple ? innerValue : innerValue?.[0])
|
|
setOpen(false)
|
|
}}
|
|
onCancel={() => {
|
|
setOpen(false)
|
|
}}
|
|
>
|
|
<Flexbox horizontal={true} className={styles.container} gap={8} height={400}>
|
|
<Flexbox flex={5} gap={10}>
|
|
<Input.Search
|
|
allowClear={true}
|
|
onSearch={value => {
|
|
setSearch({
|
|
key: value,
|
|
})
|
|
}}
|
|
placeholder={t('component.UserPicker.placeholder', '输入成员姓名,回车查询')}/>
|
|
<Flexbox flex={1} horizontal={true} className={styles.bordered}>
|
|
<Flexbox flex={1} padding={4}>
|
|
<DepartmentTree
|
|
root={true}
|
|
fieldNames={{
|
|
title: 'name',
|
|
key: 'id'
|
|
}}
|
|
autoExpandParent={true}
|
|
onSelect={(keys) => {
|
|
setSearch({
|
|
dept_id: keys?.[0]
|
|
})
|
|
}}
|
|
/>
|
|
</Flexbox>
|
|
<Flexbox flex={1} className={styles.usersPanel}>
|
|
<Spin spinning={isPending} delay={200}>
|
|
<EmptyWrap isEmpty={!users?.rows || users?.rows.length === 0}>
|
|
<List rowKey={'id'}
|
|
multiple={multiple}
|
|
value={innerValue}
|
|
onChange={(val) => {
|
|
setValue(val)
|
|
}}
|
|
dataSource={users?.rows ?? []}
|
|
/>
|
|
</EmptyWrap>
|
|
</Spin>
|
|
</Flexbox>
|
|
</Flexbox>
|
|
</Flexbox>
|
|
<DraggablePanel expandable={false}
|
|
placement="right"
|
|
maxWidth={300}
|
|
minWidth={280}
|
|
className={cx(styles.bordered, styles.draggablePanel)}
|
|
>
|
|
<Flexbox flex={6 / 2}>
|
|
<div className={styles.selected}>{t('component.UserPicker.selected', '已选({{count}})', { count: selectUserRef.current?.length ?? 0 })}</div>
|
|
<div>
|
|
{
|
|
selectUserRef.current?.map(user => {
|
|
return (<ListViewItem
|
|
onDel={user => {
|
|
setValue(innerValue?.filter(id => id !== user.id))
|
|
}}
|
|
key={user.id}
|
|
user={user!}/>)
|
|
})
|
|
}
|
|
</div>
|
|
</Flexbox>
|
|
</DraggablePanel>
|
|
|
|
</Flexbox>
|
|
</Modal>
|
|
</>
|
|
|
|
)
|
|
})
|
|
|
|
|
|
const UserPicker = memo(({ type = 'modal', ...props }: UserPickerProps) => {
|
|
return type === 'modal' ? <UserModel {...props as UserModelProps} /> : <UserSelect {...props as UserSelectProps} />
|
|
})
|
|
|
|
export default UserPicker
|