OrgSelector.vue 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. <template>
  2. <el-dialog
  3. :model-value="visible"
  4. title="选择部门"
  5. :width="600"
  6. :close-on-click-modal="false"
  7. :close-on-press-escape="false"
  8. :destroy-on-close="true"
  9. :append-to-body="true"
  10. @open="onOpen"
  11. @close="onClose"
  12. >
  13. <div class="selector">
  14. <div class="selector-left">
  15. <el-scrollbar>
  16. <el-tree
  17. ref="selectorTreeRef"
  18. :props="treeProps"
  19. node-key="fullPathName"
  20. :show-checkbox="true"
  21. :check-strictly="true"
  22. :default-checked-keys="selectedKeys"
  23. :load="onLazyLoad"
  24. lazy
  25. @check-change="onCheckChange"
  26. class="cacp-pd-s"
  27. >
  28. <template #default="{ data }">
  29. <el-icon class="cacp-mr-s" :size="16">
  30. <Grid />
  31. </el-icon>
  32. <span>{{ data.oguName }}</span>
  33. </template>
  34. </el-tree>
  35. </el-scrollbar>
  36. </div>
  37. <div class="selector-right">
  38. <div class="selector-toolbar">
  39. <span class="count">已选 {{ count }} 个</span><el-link type="primary" @click.prevent="onClear">清空</el-link>
  40. </div>
  41. <div class="selector-list">
  42. <selector-list :dataList="selectedList" @on-delete="onRemove"></selector-list>
  43. </div>
  44. </div>
  45. </div>
  46. <template #footer>
  47. <el-button @click="onClose">取消</el-button>
  48. <el-button type="primary" @click="onSave">确定</el-button>
  49. </template>
  50. </el-dialog>
  51. </template>
  52. <script setup lang="ts">
  53. import { ref, computed } from 'vue'
  54. import type { ElTree } from 'element-plus'
  55. import type TreeNode from 'element-plus/es/components/tree/src/model/node'
  56. import SelectorList from './SelectorList.vue'
  57. import { cloneDeep } from 'lodash-es'
  58. import * as apis from '@/apis/auth'
  59. import type { CacpOrganization } from '@cacp/ui'
  60. import { SuccessResultCode } from '@cacp/ui'
  61. const selectorTreeRef = ref<InstanceType<typeof ElTree>>()
  62. const props = withDefaults(
  63. defineProps<{
  64. visible: boolean
  65. multiple?: boolean
  66. value?: Array<CacpOrganization>
  67. rootPath?: string
  68. }>(),
  69. {
  70. visible: false,
  71. multiple: false,
  72. value: () => [],
  73. rootPath: ''
  74. }
  75. )
  76. const emits = defineEmits<{
  77. (e: 'on-close'): void
  78. (e: 'on-save', value: Array<CacpOrganization>): void
  79. }>()
  80. const treeProps = {
  81. label: 'oguName'
  82. }
  83. const cachedList = ref<Array<CacpOrganization>>([])
  84. const selectedKeys = ref<Array<string>>([])
  85. const selectedList = computed<Array<CacpOrganization>>(() => {
  86. return cachedList.value.filter((o) => selectedKeys.value.includes(o.fullPathName))
  87. })
  88. const count = computed<number>(() => {
  89. return selectedKeys.value.length
  90. })
  91. function onOpen(): void {
  92. selectedKeys.value = props.value ? props.value.map((o) => o.fullPathName) : []
  93. cachedList.value = props.value ? cloneDeep(props.value) : []
  94. }
  95. function onClose(): void {
  96. emits('on-close')
  97. }
  98. function onSave(): void {
  99. emits('on-save', selectedList.value)
  100. }
  101. async function onLazyLoad(
  102. node: TreeNode,
  103. resolve: (data: Array<CacpOrganization>) => void,
  104. reject: () => void
  105. ): Promise<void> {
  106. if (node.level === 0) {
  107. let root: CacpOrganization
  108. if (props.rootPath) {
  109. const result = await apis.getOrganizationByPath(props.rootPath)
  110. if (result.code === SuccessResultCode && result.data) {
  111. root = result.data
  112. resolve([root])
  113. } else {
  114. if (reject) {
  115. reject()
  116. }
  117. }
  118. } else {
  119. const result = await apis.getRootOrganization()
  120. if (result.code === SuccessResultCode && result.data) {
  121. root = result.data
  122. resolve([root])
  123. } else {
  124. if (reject) {
  125. reject()
  126. }
  127. }
  128. }
  129. } else {
  130. const parent = node.data
  131. const result = await apis.getOrganizationListByParent(parent.fullPathName, true)
  132. if (result.code === SuccessResultCode) {
  133. const children = result.data ?? []
  134. resolve(children)
  135. } else {
  136. if (reject) {
  137. reject()
  138. }
  139. }
  140. }
  141. }
  142. function onCheckChange(data: CacpOrganization, checked: boolean) {
  143. if (checked) {
  144. handleCachedList([data])
  145. if (!props.multiple) {
  146. selectedKeys.value.splice(0, 1, data.fullPathName)
  147. } else {
  148. if (!selectedKeys.value.includes(data.fullPathName)) {
  149. selectedKeys.value.push(data.fullPathName)
  150. }
  151. }
  152. } else {
  153. const idx = selectedKeys.value.indexOf(data.fullPathName)
  154. if (idx > -1) {
  155. selectedKeys.value.splice(idx, 1)
  156. }
  157. }
  158. handleCheckStatus()
  159. }
  160. function onRemove(fullPathName: string): void {
  161. const idx = selectedKeys.value.indexOf(fullPathName)
  162. if (idx > -1) {
  163. selectedKeys.value.splice(idx, 1)
  164. }
  165. handleCheckStatus()
  166. }
  167. function onClear(): void {
  168. selectedKeys.value = []
  169. handleCheckStatus()
  170. }
  171. function handleCachedList(list: Array<CacpOrganization>): void {
  172. for (const org of list) {
  173. const idx = cachedList.value.findIndex((o) => o.fullPathName === org.fullPathName)
  174. if (idx > -1) {
  175. cachedList.value.splice(idx, 1)
  176. }
  177. cachedList.value.push(org)
  178. }
  179. }
  180. function handleCheckStatus(): void {
  181. if (selectorTreeRef.value) {
  182. selectorTreeRef.value.setCheckedKeys(selectedKeys.value, false)
  183. }
  184. }
  185. </script>
  186. <style lang="less" scoped>
  187. .selector {
  188. display: flex;
  189. align-items: flex-start;
  190. border: 1px solid var(--el-border-color);
  191. border-radius: var(--el-border-radius-base);
  192. min-height: 260px;
  193. max-height: 400px;
  194. align-items: stretch;
  195. &-left {
  196. flex: 1;
  197. border-right: 1px solid var(--el-border-color);
  198. }
  199. &-right {
  200. flex: 1;
  201. overflow: hidden;
  202. display: flex;
  203. flex-direction: column;
  204. }
  205. &-list {
  206. flex: 1;
  207. overflow: hidden;
  208. }
  209. &-toolbar {
  210. line-height: 32px;
  211. height: 32px;
  212. padding: 0 var(--cacp-padding-space-s);
  213. font-size: var(--font-size-s);
  214. display: flex;
  215. align-items: center;
  216. border-bottom: 1px solid var(--el-border-color);
  217. justify-content: space-between;
  218. }
  219. }
  220. </style>