Files
as-partner-frontend/src/components/org-tree-select/index.vue

255 lines
6.5 KiB
Vue
Raw Normal View History

<template>
<div class="org-tree-select-wrap" v-loading="loading">
<el-tree-select
:model-value="modelValue"
:data="treeSelectData"
:props="{ label: 'label', value: 'id', children: 'children' }"
node-key="id"
check-strictly
:placeholder="inputPlaceholder"
:clearable="clearable"
:filterable="filterable"
:disabled="disabled"
:render-after-expand="false"
:default-expand-all="defaultExpandAll"
:style="{ width: width }"
popper-class="org-tree-select-popper"
@update:model-value="onUpdateModelValue"
@focus="onFocus"
/>
</div>
</template>
<script setup lang="ts">
import { ref, computed, watch, onMounted } from 'vue'
import { getOrgAllTree as getOrgTree } from '@/api/org'
import { treeDataTranslate } from '@/utils'
import type { OrgInfo } from '@/api/org'
import { typeLabelMap } from './index.data'
import type { OrgTreeSelectProps } from './index.data'
const props = withDefaults(defineProps<OrgTreeSelectProps>(), {
modelValue: null,
treeData: null,
autoLoad: true,
height: '320px',
width: '100%',
defaultExpandAll: true,
disabled: false,
placeholder: '请选择组织',
clearable: true,
filterable: true,
emptyText: '暂无数据',
hideNodeTypes: () => []
})
const emit = defineEmits<{
'update:modelValue': [value: string | number | null]
change: [payload: { node: OrgInfo | null; path: OrgInfo[]; orgId: string | number | null; channelOrgId: string | number | null; agentOrgId: string | number | null }]
loaded: [data: OrgInfo[]]
}>()
const rawTree = ref<OrgInfo[]>([])
const loading = ref(false)
function normType(type?: string): string {
return (type || '').toUpperCase().trim()
}
function typeLabel(type?: string): string {
const m: Record<string, string> = typeLabelMap
return m[normType(type)] || type || '-'
}
function filterHideTypes(nodes: OrgInfo[], hideSet: Set<string>): OrgInfo[] {
if (!nodes?.length) return []
const out: OrgInfo[] = []
for (const n of nodes) {
const t = normType(n.type)
if (hideSet.has(t)) {
out.push(...filterHideTypes(n.children || [], hideSet))
continue
}
const children = filterHideTypes(n.children || [], hideSet)
out.push({
...n,
children: children.length ? children : undefined
})
}
return out
}
const displayTree = computed(() => {
const hideSet = new Set((props.hideNodeTypes || []).map(normType))
return filterHideTypes(rawTree.value, hideSet)
})
function mapTreeSelectNodes(nodes: OrgInfo[]): any[] {
if (!nodes?.length) return []
return nodes.map((n) => {
const childList = n.children?.length ? mapTreeSelectNodes(n.children) : undefined
return {
...n,
label: `${n.name || '-'}(${typeLabel(n.type)})`,
children: childList
}
})
}
const treeSelectData = computed(() => mapTreeSelectNodes(displayTree.value))
const inputPlaceholder = computed(() => {
if (loading.value) return '加载中…'
if (treeSelectData.value.length === 0) return props.emptyText
return props.placeholder
})
function findPath(nodes: OrgInfo[], targetId: string | number, path: OrgInfo[] = []): OrgInfo[] | null {
if (!nodes?.length) return null
const sid = String(targetId)
for (const n of nodes) {
const next = [...path, n]
if (String(n.id) === sid) return next
const sub = findPath(n.children || [], targetId, next)
if (sub) return sub
}
return null
}
function deriveBindings(path: OrgInfo[]) {
if (!path?.length) {
return { orgId: null as string | number | null, channelOrgId: null as string | number | null, agentOrgId: null as string | number | null }
}
let channelOrgId: string | number | null = null
let agentOrgId: string | number | null = null
for (const n of path) {
const t = normType(n.type)
if (t === 'CHANNEL') channelOrgId = n.id
if (t === 'SERVICE_PROVIDER') agentOrgId = n.id
}
const last = path[path.length - 1]
const orgId = last.id
const lt = normType(last.type)
if (lt === 'CHANNEL') {
channelOrgId = last.id
agentOrgId = null
} else if (lt === 'SERVICE_PROVIDER') {
agentOrgId = last.id
}
return { orgId, channelOrgId, agentOrgId }
}
function onUpdateModelValue(val: string | number | null) {
emit('update:modelValue', val)
if (val == null || val === '') {
emit('change', {
node: null,
path: [],
orgId: null,
channelOrgId: null,
agentOrgId: null
})
return
}
const path = findPath(displayTree.value, val)
const pathArr = path ?? []
const node = pathArr.length ? pathArr[pathArr.length - 1] : null
if (node) {
emit('change', {
node,
path: pathArr,
...deriveBindings(pathArr)
})
} else {
emit('change', {
node: null,
path: [],
orgId: val,
channelOrgId: null,
agentOrgId: null
})
}
}
function parseTreeResponse(data: any): OrgInfo[] {
let list: any = []
if (Array.isArray(data)) {
list = data
} else if (data && typeof data === 'object') {
const d = data.data
if (Array.isArray(d)) list = d
else if (d && typeof d === 'object') list = d.tree || d.list || []
else list = data.tree || data.list || []
}
if (!Array.isArray(list)) list = []
if (list.length > 0 && list[0].children !== undefined) {
return list as OrgInfo[]
}
return treeDataTranslate(list, 'id', 'parentId') as OrgInfo[]
}
function loadTree(): Promise<void> {
if (props.treeData != null && Array.isArray(props.treeData)) {
rawTree.value = props.treeData
emit('loaded', rawTree.value)
return Promise.resolve()
}
if (!props.autoLoad) {
rawTree.value = []
return Promise.resolve()
}
loading.value = true
return getOrgTree()
.then(({ data }) => {
rawTree.value = Array.isArray(parseTreeResponse(data)) ? parseTreeResponse(data) : []
emit('loaded', rawTree.value)
})
.catch(() => {
rawTree.value = []
emit('loaded', [])
})
.finally(() => {
loading.value = false
})
}
function onFocus() {
if (props.autoLoad && props.treeData == null && rawTree.value.length === 0) {
loadTree()
}
}
watch(
() => props.treeData,
(v) => {
if (v != null && Array.isArray(v)) {
rawTree.value = v
}
},
{ deep: true }
)
function refresh(): Promise<void> {
rawTree.value = []
return loadTree()
}
defineExpose({
refresh,
loadTree,
getDisplayTree: () => rawTree.value
})
onMounted(() => {
if (props.autoLoad) {
loadTree()
}
})
</script>
<style scoped lang="scss">
.org-tree-select-wrap {
width: 100%;
}
</style>