init: 橘视厅酒店版会员系统前端
This commit is contained in:
254
src/components/org-tree-select/index.vue
Executable file
254
src/components/org-tree-select/index.vue
Executable file
@@ -0,0 +1,254 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user