@ithinkdt/naive
Version:
iThinkDT Naive UI
456 lines (422 loc) • 18.8 kB
JSX
import { computed, defineComponent, reactive, shallowRef, ref, watch, nextTick } from 'vue'
import {
NButton,
NSelect,
NTransfer,
NTree,
NList,
NListItem,
NCheckbox,
NEmpty,
NSpin,
NAvatar,
NIcon,
NTag,
NRadio,
NTreeSelect,
} from 'ithinkdt-ui'
import { CORE_CTX, $dialog } from '@ithinkdt/core'
import { flattenTree, walkTree } from '@ithinkdt/common'
import { IDept, IGroup, ILeft, IRight } from './assets'
export const DtUserDept = defineComponent({
name: 'UserDept',
inheritAttrs: false,
props: {
users: { type: Array, default: () => [] },
groups: { type: Array, default: () => [] },
depts: { type: Array, default: () => [] },
value: { type: [Array, String], default: undefined },
placeholder: { type: String, default: undefined },
size: { type: String, default: undefined },
type: { type: String, default: 'user' },
selectType: { type: String, default: 'dropdown' },
max: { type: Number, default: undefined },
multiple: { type: Boolean, default: false },
defaultExpandAll: { type: Boolean, default: false },
filterable: { type: Boolean, default: false },
disabled: { type: Boolean, default: undefined },
getUsersByGroup: { type: Function, default: () => [] },
getUsersByDept: { type: Function, default: () => [] },
},
emits: ['update:value', 'update-value'],
setup(props, { emit, attrs }) {
const valueArray = computed(() => {
if (props.multiple) {
return props.value || []
}
const value = props.value?.trim()
return value ? [value] : []
})
const userMap = new Map()
const users2 = computed(() => {
userMap.clear()
return props.users.map((u) => {
u = reactive(u)
u.label = u.nickname
u.value = u.username
u.type = 'user'
userMap.set(u.username, u)
return u
})
})
const deptArray = shallowRef([])
watch(
() => props.depts,
(depts) => {
walkTree(depts, (d) => {
d.label = d.name
d.value = d.code
d.type = 'dept'
if (!d.children?.length) {
delete d.children
}
})
deptArray.value = flattenTree(depts)
},
{ immediate: true },
)
const userArray = computed(() => {
return [
...props.depts,
...props.groups.map((g) => {
return { label: g.name, value: g.code, type: 'group' }
}),
...users2.value,
]
})
const depth = ref([])
const current = ref()
const loadChildren = async (it) => {
let data
data = await (it.type === 'group' ? props.getUsersByGroup(it.value) : props.getUsersByDept(it.value))
for (const u of users2.value) {
u.disabled = true
}
current.value = [
...(it.type === 'dept' ? it.children ?? [] : []),
...data.map((u) => {
const u2 = userMap.get(u.username)
u2 && (u2.disabled = false)
return {
...u,
label: u.nickname,
value: u.username,
type: 'user',
}
}),
]
}
const valuesMap = new WeakMap()
const renderSourceList = (value, onUpdateValue, pattern) => {
if (props.type === 'dept') {
return (
<NTree
data={props.depts}
keyField="value"
labelField="name"
defaultExpandAll={props.defaultExpandAll}
checkable={props.multiple}
selectable={!props.multiple}
multiple={false}
blockLine
checkOnClick
pattern={pattern}
filter={(pattern, it) => it.label.includes(pattern) || it.value.includes(pattern)}
checkedKeys={props.multiple ? value : undefined}
selectedKeys={props.multiple ? undefined : value}
onUpdateCheckedKeys={(checkedKeys) => {
if (props.max && checkedKeys?.length > props.max) return
onUpdateValue(checkedKeys)
}}
onUpdateSelectedKeys={(selectedKeys) => {
onUpdateValue(selectedKeys)
}}
/>
)
}
let valueSet = value ? valuesMap.get(value) : new Set()
if (value && !valueSet) {
valueSet = new Set(value)
valuesMap.set(value, valueSet)
}
if (current.value === false) {
return <NSpin show />
}
let list = current.value || userArray.value
pattern = pattern?.trim()
if (pattern) {
list = list?.filter((it) => it.label.includes(pattern) || it.value.includes(pattern))
}
const depths = depth.value
return (
<>
<div style="margin: 12px 16px; display: flex; justify-content: space-between; align-items: center">
{depths.length > 0 ? (
<>
<span style="display: flex; align-items: center">
{depths.length >= 2 ? depths.at(-2).label : '全部'}
<span style="padding: 0 4px">
<IRight />
</span>
{depths.at(-1).label}
</span>
<NButton
text
type="warning"
onClick={() => {
depths.pop()
current.value = depths.length > 0 ? false : undefined
if (depths.length > 0) {
loadChildren(depths.at(-1))
} else {
for (const u of users2.value) {
u.disabled = false
}
}
}}
>
<NIcon>
<ILeft />
</NIcon>
返回上一级
</NButton>
</>
) : (
<span>全部</span>
)}
</div>
{list?.length ? (
<NList showDivider={false} style="padding: 0 16px">
{list.map((it) => {
return (
<NListItem key={it.type + '_' + it.value} style="padding: 6px 0">
{it.type === 'user' ? (
props.multiple ? (
<NCheckbox
checked={valueSet.has(it.value) || false}
onUpdateChecked={(checked) => {
if (checked && props.max && value.length >= props.max) return
let values = [...(value || [])]
if (checked) {
values.push(it.value)
} else {
const i = values.indexOf(it.value)
values.splice(i, 1)
}
onUpdateValue(values)
}}
style="margin-left: 3px"
>
{CORE_CTX.renderUsers([it], {
max: 1,
size: 24,
placement: 'right',
})}
</NCheckbox>
) : (
<NRadio
checked={valueSet.has(it.value) || false}
onUpdateChecked={(checked) => {
onUpdateValue(checked ? [it.value] : [])
}}
style="margin-left: 3px"
>
{CORE_CTX.renderUsers([it], {
max: 1,
size: 24,
placement: 'right',
})}
</NRadio>
)
) : (
<div
onClick={() => {
current.value = false
depth.value.push(it)
loadChildren(it)
}}
style="cursor: pointer; display: flex; align-items: center; gap: 8px"
>
{it.type === 'dept' ? (
<NAvatar size={24} style="background-color: red">
<NIcon>
<IDept />
</NIcon>
</NAvatar>
) : (
<NAvatar size={24} style="background-color: green">
<NIcon>
<IGroup />
</NIcon>
</NAvatar>
)}
<span style="flex: 1 1 auto">{it.label}</span>
<NIcon>
<IRight />
</NIcon>
</div>
)}
</NListItem>
)
})}
</NList>
) : (
<NEmpty />
)}
</>
)
}
const renderTargetListEmpty = () => {
return <NEmpty description={props.placeholder} />
}
const renderTransfer = (props2, options, value, onUpdateValue, onUpdateValue2) => {
return (
<NTransfer
{...props2}
key={props.type}
options={options}
renderSourceList={({ onCheck, pattern }) => renderSourceList(value, onCheck, pattern)}
renderTargetList={
props.placeholder?.trim() && !props.value?.length ? renderTargetListEmpty : undefined
}
sourceFilterable={props.filterable}
size={props.size}
disabled={props.disabled || undefined}
value={value}
filter={(pattern, it) => it.label.includes(pattern) || it.value.includes(pattern)}
onUpdate:value={(v) => {
onUpdateValue?.(v)
}}
onUpdateValue={(v) => {
onUpdateValue2?.(v)
}}
/>
)
}
const renderTag = ({ option, handleClose }) => {
return (
<NTag
type={props.type === 'user' ? 'primary' : 'info'}
closable
onMousedown={(e) => e.preventDefault()}
onClose={(e) => {
e.stopPropagation()
handleClose()
}}
>
{option.label}
</NTag>
)
}
const emitValue = (v, k = 'update-value') => {
if (props.multiple) {
emit(k, v)
} else {
emit(k, v?.[0])
}
}
const selectRef = ref()
let opened = false
const showDialog = () => {
if (opened) return
opened = true
const valueRef = shallowRef([...(valueArray.value || [])])
$dialog({
showIcon: false,
title: props.placeholder,
style: {
width: '700px',
},
content: () => {
const options = props.type === 'user' ? users2.value : deptArray.value
return (
<div style={{ height: '550px' }}>
{renderTransfer(
{ style: { height: '100%' } },
options,
valueRef.value,
(v) => (valueRef.value = v),
)}
</div>
)
},
onOk() {
emitValue(valueRef.value, 'update:value')
emitValue(valueRef.value, 'update-value')
},
onAfterLeave() {
nextTick(() => {
selectRef.value?.blur()
opened = false
})
},
})
}
const selectSlots = {
arrow: () => {
return <NIcon>{props.type === 'user' ? <IGroup /> : <IDept />}</NIcon>
},
}
return () => {
const options = props.type === 'user' ? users2.value : deptArray.value
if (props.selectType === 'dropdown' && props.type === 'dept') {
return (
<NTreeSelect
options={options}
keyField="value"
defaultExpandAll={props.defaultExpandAll}
checkable={props.multiple}
multiple={props.multiple}
filter={(pattern, it) => it.label.includes(pattern) || it.value.includes(pattern)}
value={props.value}
onUpdateValue={(v) => {
if (props.multiple && props.max && v?.length > props.max) return
emit('update-value', v)
}}
onUpdate:value={(v) => {
if (props.multiple && props.max && v?.length > props.max) return
emit('update:value', v)
}}
>
{selectSlots}
</NTreeSelect>
)
}
if (props.selectType === 'transfer') {
return renderTransfer(
attrs,
options,
valueArray.value,
(v) => emitValue(v, 'update:value'),
(v) => emitValue(v, 'update-value'),
)
}
const isDropdown = props.selectType === 'dropdown'
return (
<NSelect
{...attrs}
key={props.type}
ref={selectRef}
show={isDropdown ? undefined : false}
filterable={isDropdown ? props.filterable : false}
options={options}
multiple={props.multiple}
size={props.size}
placeholder={props.placeholder}
disabled={props.disabled || undefined}
value={props.value}
renderTag={props.multiple ? renderTag : undefined}
onFocus={isDropdown ? undefined : showDialog}
onUpdateValue={(v) => {
emit('update-value', v)
}}
onUpdate:value={(v) => {
emit('update:value', v)
}}
>
{selectSlots}
</NSelect>
)
}
},
})