index.vue 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. <template>
  2. <Dialog v-model="dialogVisible" title="人员选择" width="800">
  3. <el-row class="gap2" v-loading="formLoading">
  4. <el-col :span="6">
  5. <ContentWrap class="h-1/1">
  6. <el-tree
  7. ref="treeRef"
  8. :data="deptTree"
  9. :expand-on-click-node="false"
  10. :props="defaultProps"
  11. default-expand-all
  12. highlight-current
  13. node-key="id"
  14. @node-click="handleNodeClick"
  15. />
  16. </ContentWrap>
  17. </el-col>
  18. <el-col :span="17">
  19. <el-transfer
  20. v-model="selectedUserIdList"
  21. :titles="['未选', '已选']"
  22. filterable
  23. filter-placeholder="搜索成员"
  24. :data="transferUserList"
  25. :props="{ label: 'nickname', key: 'id' }"
  26. />
  27. </el-col>
  28. </el-row>
  29. <template #footer>
  30. <el-button
  31. :disabled="formLoading || !selectedUserIdList?.length"
  32. type="primary"
  33. @click="submitForm"
  34. >
  35. 确 定
  36. </el-button>
  37. <el-button @click="dialogVisible = false">取 消</el-button>
  38. </template>
  39. </Dialog>
  40. </template>
  41. <script lang="ts" setup>
  42. import { defaultProps, handleTree } from '@/utils/tree'
  43. import * as DeptApi from '@/api/system/dept'
  44. import * as UserApi from '@/api/system/user'
  45. defineOptions({ name: 'UserSelectForm' })
  46. const emit = defineEmits<{
  47. confirm: [id: any, userList: any[]]
  48. }>()
  49. const { t } = useI18n() // 国际
  50. const message = useMessage() // 消息弹窗
  51. const deptTree = ref<Tree[]>([]) // 部门树形结构化
  52. const deptList = ref<any[]>([]) // 保存扁平化的部门列表数据
  53. const userList = ref<UserApi.UserVO[]>([]) // 所有用户列表
  54. const filteredUserList = ref<UserApi.UserVO[]>([]) // 当前部门过滤后的用户列表
  55. const selectedUserIdList: any = ref([]) // 选中的用户列表
  56. const dialogVisible = ref(false) // 弹窗的是否展示
  57. const formLoading = ref(false) // 表单的加载中
  58. const activityId = ref()
  59. /** 计算属性:合并已选择的用户和当前部门过滤后的用户 */
  60. const transferUserList = computed(() => {
  61. // 1.1 获取所有已选择的用户
  62. const selectedUsers = userList.value.filter((user: any) =>
  63. selectedUserIdList.value.includes(user.id)
  64. )
  65. // 1.2 获取当前部门过滤后的未选择用户
  66. const filteredUnselectedUsers = filteredUserList.value.filter(
  67. (user: any) => !selectedUserIdList.value.includes(user.id)
  68. )
  69. // 2. 合并并去重
  70. return [...selectedUsers, ...filteredUnselectedUsers]
  71. })
  72. /** 打开弹窗 */
  73. const open = async (id: number, selectedList?: any[]) => {
  74. activityId.value = id
  75. resetForm()
  76. // 加载部门、用户列表
  77. const deptData = await DeptApi.getSimpleDeptList()
  78. deptList.value = deptData // 保存扁平结构的部门数据
  79. deptTree.value = handleTree(deptData) // 转换成树形结构
  80. userList.value = await UserApi.getSimpleUserList()
  81. // 初始状态下,过滤列表等于所有用户列表
  82. filteredUserList.value = [...userList.value]
  83. selectedUserIdList.value = selectedList?.map((item: any) => item.id) || []
  84. dialogVisible.value = true
  85. }
  86. /** 获取指定部门及其所有子部门的ID列表 */
  87. const getChildDeptIds = (deptId: number, deptList: any[]): number[] => {
  88. const ids = [deptId]
  89. const children = deptList.filter((dept) => dept.parentId === deptId)
  90. children.forEach((child) => {
  91. ids.push(...getChildDeptIds(child.id, deptList))
  92. })
  93. return ids
  94. }
  95. /** 获取部门过滤后的用户列表 */
  96. const filterUserList = async (deptId?: number) => {
  97. formLoading.value = true
  98. try {
  99. if (!deptId) {
  100. // 如果没有选择部门,显示所有用户
  101. filteredUserList.value = [...userList.value]
  102. return
  103. }
  104. // 直接使用已保存的部门列表数据进行过滤
  105. const deptIds = getChildDeptIds(deptId, deptList.value)
  106. // 过滤出这些部门下的用户
  107. filteredUserList.value = userList.value.filter((user) => deptIds.includes(user.deptId))
  108. } finally {
  109. formLoading.value = false
  110. }
  111. }
  112. /** 提交选择 */
  113. const submitForm = async () => {
  114. try {
  115. message.success(t('common.updateSuccess'))
  116. dialogVisible.value = false
  117. // 从所有用户列表中筛选出已选择的用户
  118. const emitUserList = userList.value.filter((user: any) =>
  119. selectedUserIdList.value.includes(user.id)
  120. )
  121. // 发送操作成功的事件
  122. emit('confirm', activityId.value, emitUserList)
  123. } finally {
  124. }
  125. }
  126. /** 重置表单 */
  127. const resetForm = () => {
  128. deptTree.value = []
  129. deptList.value = []
  130. userList.value = []
  131. filteredUserList.value = []
  132. selectedUserIdList.value = []
  133. }
  134. /** 处理部门被点击 */
  135. const handleNodeClick = (row: { [key: string]: any }) => {
  136. filterUserList(row.id)
  137. }
  138. defineExpose({ open }) // 提供 open 方法,用于打开弹窗
  139. </script>
  140. <style lang="scss" scoped>
  141. :deep() {
  142. .el-transfer {
  143. display: flex;
  144. }
  145. .el-transfer__buttons {
  146. display: flex !important;
  147. flex-direction: column-reverse;
  148. justify-content: center;
  149. gap: 20px;
  150. .el-transfer__button:nth-child(2) {
  151. margin: 0;
  152. }
  153. }
  154. }
  155. </style>