UNPKG

@ithinkdt/naive

Version:

iThinkDT Naive UI

456 lines (422 loc) 18.8 kB
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> ) } }, })