userSelection.vue 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. <template>
  2. <a-drawer
  3. v-model:visible="props.visible"
  4. title="选择可见成员"
  5. placement="right"
  6. width="50%"
  7. :footer="null"
  8. @close="handleCancel"
  9. >
  10. <!-- 左侧:树状结构成员列表 -->
  11. <div class="left-panel">
  12. <a-input-search
  13. v-model:value="searchValue"
  14. placeholder="输入部门或成员名称"
  15. style="margin-bottom: 16px"
  16. @search="onSearch"
  17. />
  18. <a-tree
  19. v-if="treeData.length > 0"
  20. :tree-data="filteredTreeData"
  21. :field-names="{ key: 'id', title: 'name', children: 'children' }"
  22. :checked-keys="checkedKeys"
  23. :expanded-keys="expandedKeys"
  24. :auto-expand-parent="autoExpandParent"
  25. checkable
  26. show-icon
  27. @check="onCheck"
  28. @expand="onExpand"
  29. >
  30. <template #title="{ name, avatar }">
  31. <span style="display: inline-flex; align-items: center">
  32. <a-avatar :src="avatar" size="small" style="margin-right: 8px" />
  33. {{ name }}
  34. </span>
  35. </template>
  36. <template #switcherIcon="{ expanded }">
  37. <caret-down-outlined v-if="expanded" />
  38. <caret-right-outlined v-else />
  39. </template>
  40. </a-tree>
  41. </div>
  42. <!-- 右侧:已选择成员列表 -->
  43. <div class="right-panel">
  44. <div class="header">
  45. <span>已选 {{ selectedUsers.length }} / 30</span>
  46. <a-button type="link" @click="clearSelection">清空</a-button>
  47. </div>
  48. <a-list item-layout="horizontal" :data-source="selectedUsers">
  49. <template #renderItem="{ item }">
  50. <a-list-item>
  51. <a-list-item-meta>
  52. <template #avatar>
  53. <a-avatar :src="item.avatar" />
  54. </template>
  55. <template #title>
  56. <a>{{ item.name }}</a>
  57. </template>
  58. <template #description>
  59. <span>{{ item.department }}</span>
  60. </template>
  61. </a-list-item-meta>
  62. <template #actions>
  63. <a @click="removeUser(item)">删除</a>
  64. </template>
  65. </a-list-item>
  66. </template>
  67. </a-list>
  68. </div>
  69. <!-- 底部按钮 -->
  70. <template #footer>
  71. <a-space>
  72. <a-button @click="handleCancel">取消</a-button>
  73. <a-button type="primary" @click="handleOk">确定</a-button>
  74. </a-space>
  75. </template>
  76. </a-drawer>
  77. </template>
  78. <script setup>
  79. import { ref, reactive, computed, onMounted } from 'vue'
  80. import resourceAuditApi from '@/api/resourceAudit.js'
  81. import { Modal, Input, Tree, List, Avatar, Button } from 'ant-design-vue'
  82. const emit = defineEmits(['close', 'confirm'])
  83. // const visible = ref(true)
  84. const props = defineProps({
  85. visible: {
  86. type: Boolean,
  87. default: false
  88. },
  89. userRelateIds: {
  90. type: Array,
  91. default: () => {}
  92. }
  93. })
  94. const searchValue = ref('')
  95. const treeData = ref([
  96. {
  97. id: '1',
  98. name: '组织1',
  99. children: [
  100. {
  101. id: '1-1',
  102. name: '张小刚',
  103. avatar: 'https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png',
  104. isLeaf: true
  105. },
  106. {
  107. id: '1-2',
  108. name: '李小红',
  109. avatar: 'https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png',
  110. isLeaf: true
  111. }
  112. ]
  113. },
  114. {
  115. id: '2',
  116. name: '组织2',
  117. children: [
  118. {
  119. id: '2-1',
  120. name: '研发部',
  121. children: [
  122. {
  123. id: '2-1-1',
  124. name: '王小明',
  125. avatar: 'https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png',
  126. isLeaf: true
  127. }
  128. ]
  129. }
  130. ]
  131. }
  132. ])
  133. watch(
  134. () => props.userRelateIds,
  135. (newVal) => {
  136. if (newVal) {
  137. console.log(newVal, 'props.userRelateIds')
  138. checkedKeys.value = newVal
  139. // selectedUsers.value = flatTree(treeData.value)
  140. // .filter((node) => newVal.includes(node.id))
  141. // .map((node) => ({ id: node.id, name: node.name }))
  142. }
  143. },
  144. { deep: true }
  145. )
  146. const selectedKeys = ref([])
  147. const selectedUsers = ref([])
  148. const checkedKeys = ref([])
  149. const expandedKeys = ref([]) // 默认展开第一层
  150. const autoExpandParent = ref(true)
  151. const filteredTreeData = computed(() => {
  152. const filterFn = (node) => {
  153. // 保留匹配节点及其所有祖先节点
  154. if (node.name.includes(searchValue.value)) return true
  155. if (node.children) {
  156. const hasMatchingChild = node.children.some(filterFn)
  157. if (hasMatchingChild) return true
  158. }
  159. return false
  160. }
  161. return treeData.value.filter(filterFn)
  162. })
  163. // 替换原来的onSelect方法
  164. const onCheck = (checkedKeysValue, { checked, node, checkedNodes }) => {
  165. // 过滤掉非叶子节点
  166. const leafNodes = checkedNodes.filter((node) => node.isLeaf&& node.infoType=="user")
  167. checkedKeys.value = leafNodes.map((node) => node.id)
  168. selectedUsers.value = leafNodes.map((node) => ({
  169. id: node.id,
  170. name: node.name
  171. // 可选: 保留部门信息
  172. // department: findDepartmentName(node.id, treeData.value)
  173. }))
  174. }
  175. // 查找部门名称的辅助函数
  176. const findDepartmentName = (id, nodes) => {
  177. for (const node of nodes) {
  178. if (node.children) {
  179. const found = node.children.find((child) => child.id === id)
  180. if (found) return node.name
  181. const result = findDepartmentName(id, node.children)
  182. if (result) return result
  183. }
  184. }
  185. return ''
  186. }
  187. const onExpand = (keys) => {
  188. // console.log(keys, 'onExpand')
  189. expandedKeys.value = keys
  190. autoExpandParent.value = false
  191. }
  192. const onSearch = (value) => {
  193. searchValue.value = value
  194. }
  195. const augmentNode = (node) => {
  196. if (node.children) {
  197. node.isLeaf = false // 有children的节点标记为非叶子节点
  198. node.children.forEach((child) => augmentNode(child))
  199. } else {
  200. node.isLeaf = true // 无children的节点标记为叶子节点
  201. }
  202. }
  203. const getOrgUserTreeRespectively = () => {
  204. resourceAuditApi
  205. .orgUserTreeSelector()
  206. .then((res) => {
  207. if (res?.data) {
  208. console.log(res.data, 'getOrgUserTreeRespectively')
  209. res.data.forEach((root) => augmentNode(root))
  210. // if (treeData.value.length > 0 && treeData.value[0]?.id) {
  211. // expandedKeys.value = [treeData.value[0].id]
  212. // }
  213. treeData.value = res.data
  214. }
  215. })
  216. .catch((err) => {
  217. console.log(err)
  218. })
  219. }
  220. const clearSelection = () => {
  221. selectedKeys.value = []
  222. selectedUsers.value = []
  223. checkedKeys.value = []
  224. }
  225. const removeUser = (user) => {
  226. const index = selectedUsers.value.findIndex((u) => u.id === user.id)
  227. if (index !== -1) {
  228. selectedUsers.value.splice(index, 1)
  229. selectedKeys.value = selectedUsers.value.map((u) => u.id)
  230. checkedKeys.value = selectedUsers.value.map((u) => u.id)
  231. }
  232. // selectedUsers.value = selectedUsers.value.filter((u) => u.id !== user.id)
  233. // checkedKeys.value = selectedUsers.value.map((u) => u.id)
  234. }
  235. const showModal = () => {
  236. visible.value = true
  237. }
  238. const handleOk = () => {
  239. console.log('Selected Users:', selectedUsers.value, checkedKeys.value)
  240. emit('confirm', checkedKeys.value)
  241. }
  242. const handleCancel = () => {
  243. emit('close')
  244. }
  245. onMounted(() => {
  246. getOrgUserTreeRespectively()
  247. // if (props.userRelateIds) {
  248. // checkedKeys.value = props.userRelateIds // 直接赋值给树组件的checkedKeys
  249. // }
  250. })
  251. </script>
  252. <style scoped>
  253. .left-panel,
  254. .right-panel {
  255. display: inline-block;
  256. vertical-align: top;
  257. width: 48%;
  258. height: calc(100vh - 200px);
  259. overflow-y: auto;
  260. padding: 0 10px;
  261. }
  262. .right-panel {
  263. border-left: 1px solid #f0f0f0;
  264. }
  265. .header {
  266. display: flex;
  267. justify-content: space-between;
  268. margin-bottom: 16px;
  269. }
  270. .ant-tree-switcher {
  271. width: 24px;
  272. height: 24px;
  273. line-height: 24px;
  274. }
  275. .ant-tree-switcher-icon {
  276. font-size: 12px;
  277. transition: transform 0.3s;
  278. }
  279. .ant-tree-switcher_close .ant-tree-switcher-icon {
  280. transform: rotate(-90deg);
  281. }
  282. </style>