index.vue 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. <script lang="ts" setup>
  2. import { defaultProps, handleTree } from '@/utils/tree'
  3. import { ElTree } from 'element-plus'
  4. import * as DeptApi from '@/api/system/dept'
  5. import { Search } from '@element-plus/icons-vue'
  6. const props = defineProps({
  7. deptId: {
  8. type: Number,
  9. required: true
  10. },
  11. modelValue: {
  12. type: Number,
  13. default: undefined
  14. },
  15. topId: {
  16. type: Number,
  17. required: true
  18. },
  19. title: {
  20. type: String,
  21. default: '部门'
  22. },
  23. initSelect: {
  24. type: Boolean,
  25. default: true
  26. },
  27. showTitle: {
  28. type: Boolean,
  29. default: true
  30. }
  31. })
  32. const emits = defineEmits(['update:modelValue', 'node-click'])
  33. const deptName = ref('')
  34. const deptList = ref<Tree[]>([])
  35. const treeRef = ref<InstanceType<typeof ElTree>>()
  36. const expandedKeys = ref<number[]>([])
  37. const sortTreeBySort = (treeNodes: Tree[]) => {
  38. if (!treeNodes || !Array.isArray(treeNodes)) return treeNodes
  39. const sortedNodes = [...treeNodes].sort((a, b) => {
  40. const sortA = a.sort != null ? a.sort : 999999
  41. const sortB = b.sort != null ? b.sort : 999999
  42. return sortA - sortB
  43. })
  44. sortedNodes.forEach((node) => {
  45. if (node.children && Array.isArray(node.children)) {
  46. node.children = sortTreeBySort(node.children)
  47. }
  48. })
  49. return sortedNodes
  50. }
  51. const loadTree = async () => {
  52. try {
  53. let id = props.deptId
  54. // 1. 校验 ID 范围逻辑 (保持原有逻辑:确保 deptId 在 topId 范围内)
  55. if (id !== props.topId) {
  56. const depts = await DeptApi.specifiedSimpleDepts(props.topId)
  57. const self = depts.find((item) => item.id === props.deptId)
  58. if (depts.length && !self) {
  59. id = props.topId
  60. }
  61. }
  62. // 2. 获取最终 ID 对应的部门列表
  63. const res = await DeptApi.specifiedSimpleDepts(id)
  64. // 3. 处理 modelValue 的赋值逻辑 (关键修改点)
  65. if (props.initSelect) {
  66. // 检查传入的 modelValue 是否存在于当前加载的树数据中
  67. const isModelValueValid = props.modelValue && res.some((item) => item.id === props.modelValue)
  68. if (!isModelValueValid) {
  69. emits('update:modelValue', id)
  70. }
  71. }
  72. // 4. 生成树结构
  73. deptList.value = sortTreeBySort(handleTree(res))
  74. // 5. 界面交互:高亮并展开
  75. nextTick(() => {
  76. // 优先使用 props.modelValue (如果刚才触发了 update,父组件可能还没传回来,所以这里取 props.modelValue 或者 id)
  77. // 但为了稳妥,我们再次检查逻辑
  78. const targetKey = props.modelValue ? props.modelValue : props.initSelect ? id : null
  79. if (targetKey && treeRef.value) {
  80. treeRef.value.setCurrentKey(targetKey)
  81. // 确保该节点被展开
  82. if (!expandedKeys.value.includes(targetKey)) {
  83. expandedKeys.value.push(targetKey)
  84. }
  85. } else if (deptList.value.length > 0) {
  86. // 如果没有选中项,默认展开第一级
  87. expandedKeys.value = deptList.value.map((item) => item.id)
  88. }
  89. })
  90. } catch (error) {
  91. console.error('加载部门树失败:', error)
  92. }
  93. }
  94. const handleNodeClick = (data: Tree) => {
  95. emits('update:modelValue', data.id)
  96. emits('node-click', data)
  97. }
  98. const filterNode = (value: string, data: Tree) => {
  99. if (!value) return true
  100. return data.name.includes(value)
  101. }
  102. watch(deptName, (val) => {
  103. treeRef.value?.filter(val)
  104. })
  105. watch(
  106. () => props.deptId,
  107. (newVal, oldVal) => {
  108. if (newVal !== oldVal) {
  109. loadTree()
  110. }
  111. }
  112. )
  113. watch(
  114. () => props.modelValue,
  115. (newVal) => {
  116. if (newVal && treeRef.value) {
  117. treeRef.value.setCurrentKey(newVal)
  118. if (!expandedKeys.value.includes(newVal)) {
  119. expandedKeys.value.push(newVal)
  120. }
  121. }
  122. },
  123. { immediate: true }
  124. )
  125. onMounted(() => {
  126. loadTree()
  127. })
  128. </script>
  129. <template>
  130. <div class="gap-4 flex flex-col h-full">
  131. <h1 v-if="showTitle" class="text-lg font-medium">{{ props.title }}</h1>
  132. <el-input
  133. v-model="deptName"
  134. size="default"
  135. placeholder="请输入部门名称"
  136. clearable
  137. :prefix-icon="Search"
  138. />
  139. <div class="flex-1 relative">
  140. <el-auto-resizer class="absolute">
  141. <template #default="{ height }">
  142. <el-scrollbar :style="{ height: `${height}px` }">
  143. <el-tree
  144. ref="treeRef"
  145. :data="deptList"
  146. :props="defaultProps"
  147. :expand-on-click-node="false"
  148. :filter-node-method="filterNode"
  149. node-key="id"
  150. highlight-current
  151. :default-expanded-keys="expandedKeys"
  152. @node-click="handleNodeClick"
  153. />
  154. </el-scrollbar>
  155. </template>
  156. </el-auto-resizer>
  157. </div>
  158. </div>
  159. </template>