Files
crm_uiapp/src/pages-system/dept/form/components/dept-picker.vue
2026-04-14 15:06:26 +08:00

186 lines
4.7 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<wd-col-picker
v-model="selectedValue"
:label="label"
label-width="180rpx"
:columns="deptColumns"
value-key="id"
label-key="name"
:column-change="handleColumnChange"
:display-format="displayFormat"
@confirm="handleConfirm"
/>
</template>
<script lang="ts" setup>
import type { Dept } from '@/api/system/dept'
import { onMounted, ref, watch } from 'vue'
import { getSimpleDeptList } from '@/api/system/dept'
const props = withDefaults(defineProps<{
modelValue?: number
label?: string
showRoot?: boolean // 是否显示顶级部门节点
}>(), {
label: '上级部门',
showRoot: false,
})
const emit = defineEmits<{
(e: 'update:modelValue', value: number | undefined): void
}>()
const deptList = ref<Dept[]>([])
const deptColumns = ref<any[]>([])
const selectedValue = ref<number[]>([])
/** 监听外部值变化,回显选中值 */
watch(
() => props.modelValue,
(val) => {
// 0 或 undefined 都视为顶级部门(如果允许显示顶级)
if (val && val !== 0 && deptList.value.length > 0) {
const path = findDeptPath(val)
selectedValue.value = path
// 构建列数据以支持回显
buildColumnsForPath(path)
} else {
if (props.showRoot) {
// 顶级部门或未选择,重置
selectedValue.value = [0]
} else {
selectedValue.value = []
}
// 重新构建第一列,确保正确
if (deptList.value.length > 0) {
initFirstColumn()
}
}
},
{ immediate: true },
)
/** 初始化第一列 */
function initFirstColumn() {
const topDepts = deptList.value.filter(item => item.parentId === 0)
if (props.showRoot) {
deptColumns.value = [
[
{ id: 0, name: '顶级部门' },
...topDepts,
],
]
} else {
deptColumns.value = [topDepts]
}
}
/** 构建带"选择当前"选项的子列表 */
function buildChildrenWithCurrent(parentId: number) {
const children = deptList.value.filter(item => item.parentId === parentId)
// 添加"选择当前"选项,使用父节点 ID 的负数作为标识
return [
{ id: -parentId, name: '✓ 选择当前' },
...children,
]
}
/** 加载部门列表 */
async function loadDeptList() {
deptList.value = await getSimpleDeptList()
// 初始化第一列
initFirstColumn()
// 如果有初始值,回显
if (props.modelValue && props.modelValue !== 0) {
const path = findDeptPath(props.modelValue)
selectedValue.value = path
buildColumnsForPath(path)
}
}
/** 查找部门路径 */
function findDeptPath(targetId: number): number[] {
const path: number[] = []
const findPath = (parentId: number, id: number): boolean => {
const items = deptList.value.filter(d => d.parentId === parentId)
for (const item of items) {
if (item.id === id) {
path.push(item.id)
return true
}
if (findPath(item.id, id)) {
path.unshift(item.id)
return true
}
}
return false
}
findPath(0, targetId)
return path
}
/** 根据路径构建列数据 */
function buildColumnsForPath(path: number[]) {
if (path.length === 0) {
return
}
// 第一列已经有了,从第二列开始构建
const columns = [deptColumns.value[0]]
for (let i = 0; i < path.length - 1; i++) {
const parentId = path[i]
const children = deptList.value.filter(item => item.parentId === parentId)
if (children.length > 0) {
columns.push(children)
}
}
deptColumns.value = columns
}
/** 列变化 */
function handleColumnChange({ selectedItem, resolve, finish }: any) {
// 选择顶级部门或"选择当前",结束
if (selectedItem.id === 0 || selectedItem.id < 0) {
finish()
return
}
const children = deptList.value.filter(item => item.parentId === selectedItem.id)
if (children.length > 0) {
resolve(buildChildrenWithCurrent(selectedItem.id))
} else {
finish()
}
}
/** 格式化显示 */
function displayFormat(selectedItems: any[]) {
// 过滤掉"选择当前"选项
return selectedItems
.filter(item => item.id >= 0)
.map(item => item.name)
.join('/')
}
/** 确认选择 */
function handleConfirm({ value }: { value: number[] }) {
if (value && value.length > 0) {
const lastValue = value[value.length - 1]
// 如果选择的是"选择当前"(负数 ID取其绝对值作为实际选中的部门 ID
if (lastValue < 0) {
emit('update:modelValue', Math.abs(lastValue))
} else {
emit('update:modelValue', lastValue)
}
} else {
// 如果允许 root默认顶级 0否则 undefined
emit('update:modelValue', props.showRoot ? 0 : undefined)
}
}
/** 初始化 */
onMounted(() => {
loadDeptList()
})
</script>