255 lines
6.5 KiB
Vue
255 lines
6.5 KiB
Vue
|
|
<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>
|