InspectorSelect.vue 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. <template>
  2. <div class="inspector-select">
  3. <div class="left">
  4. <div class="filter-title">
  5. <el-input v-model="queryContent" placeholder="请输入搜索的内容" clearable />
  6. </div>
  7. <div class="filter-result">
  8. <el-tree
  9. ref="treeRef"
  10. :data="treeData"
  11. node-key="id"
  12. show-checkbox
  13. :props="{
  14. label: 'label',
  15. children: 'children',
  16. disabled: (data) => !data.isUser || disabledIds.includes(data.id),
  17. }"
  18. placeholder="请选择任务检查人"
  19. filterable
  20. check-strictly
  21. default-expand-all
  22. :filter-node-method="filterNode"
  23. @check-change="handleCheckChange"
  24. >
  25. <template #empty>
  26. <div class="empty">
  27. <img :src="empty" alt="" />
  28. <div>暂无数据</div>
  29. </div>
  30. </template>
  31. </el-tree>
  32. </div>
  33. </div>
  34. <div class="right">
  35. <div class="head">
  36. <span>已选择:{{ selectedPersonList.length }}人</span>
  37. </div>
  38. <div class="selected">
  39. <el-tag
  40. v-for="person in selectedPersonList"
  41. :key="person.id"
  42. :closable="!person.checked"
  43. @close="handleTagClose(person.id)"
  44. >
  45. {{ person.realname }}({{ person.staffNo }})
  46. </el-tag>
  47. </div>
  48. <div class="footer">
  49. <el-button @click="emit('cancel')">取消</el-button>
  50. <el-button
  51. type="primary"
  52. @click="
  53. emit(
  54. 'submit',
  55. selectedPersonList.map((item) => item.id),
  56. )
  57. "
  58. >确定
  59. </el-button>
  60. </div>
  61. </div>
  62. </div>
  63. </template>
  64. <script lang="ts" setup>
  65. import { onMounted, ref, watch, computed, nextTick } from 'vue';
  66. import empty from 'assets/images/empty@1X.png';
  67. import type { TreeNode } from '@/views/disaster/types';
  68. import type { PersonGroupItem } from '@/types/person-group/type';
  69. import { ElMessage, type ElTree } from 'element-plus';
  70. import { useUserInfoHook } from '@/views/disaster/hooks/userInfo';
  71. import { updateTaskInspector } from '@/api/disaster-precaution';
  72. const { getUserFirstLevelTreeList, treeData, id: userId } = useUserInfoHook();
  73. const props = defineProps<{
  74. customUserList: PersonGroupItem[];
  75. }>();
  76. interface Tree {
  77. [key: string]: any;
  78. }
  79. const queryContent = ref('');
  80. const treeRef = ref<InstanceType<typeof ElTree>>();
  81. const selectedPersonList = ref<PersonGroupItem[]>([]);
  82. const filterNode = (
  83. value: string,
  84. data: Tree,
  85. node: {
  86. parent: { label: string; parent: any; level: number };
  87. label: string;
  88. level: number;
  89. },
  90. ) => {
  91. if (!value) return true;
  92. let parentNode = node.parent,
  93. labels = [node.label],
  94. level = 1;
  95. while (level < node.level) {
  96. labels = [...labels, parentNode.label];
  97. parentNode = parentNode.parent;
  98. level++;
  99. }
  100. return labels.some((label) => label.indexOf(value) !== -1);
  101. };
  102. // 需要禁用的用户ID列表
  103. const disabledIds = computed(() => {
  104. const selectedIds = selectedPersonList.value.filter((item) => item.checked).map((item) => item.id);
  105. const ids = Array.from(new Set([userId, ...selectedIds]));
  106. return ids;
  107. });
  108. const removeSelectedPerson = (id: number) => {
  109. const index = selectedPersonList.value.findIndex((item) => item.id === id);
  110. if (index !== -1) {
  111. selectedPersonList.value.splice(index, 1);
  112. }
  113. };
  114. const handleCheckChange = (node: TreeNode, checked: boolean) => {
  115. if (checked) {
  116. const selectedPerson = {
  117. id: node.id,
  118. realname: node.label.split('(')[0],
  119. staffNo: node.staffNo || '',
  120. checked: false,
  121. };
  122. selectedPersonList.value.push(selectedPerson);
  123. } else {
  124. removeSelectedPerson(node.id);
  125. }
  126. };
  127. const handleTagClose = (id) => {
  128. const index = selectedPersonList.value.findIndex((item) => item.id === id);
  129. if (index !== -1) {
  130. selectedPersonList.value.splice(index, 1);
  131. treeRef.value!.setChecked(id, false, true);
  132. }
  133. };
  134. const emit = defineEmits(['cancel', 'submit']);
  135. onMounted(async () => {
  136. await getUserFirstLevelTreeList();
  137. });
  138. watch(queryContent, (val) => {
  139. treeRef.value!.filter(val);
  140. });
  141. watch(
  142. () => props.customUserList,
  143. (newVal) => {
  144. selectedPersonList.value = newVal;
  145. const selectedIds = newVal.map((item) => item.id);
  146. nextTick(() => {
  147. if (!treeRef.value) return;
  148. treeRef.value.setCheckedKeys(selectedIds, true);
  149. });
  150. },
  151. {
  152. immediate: true,
  153. },
  154. );
  155. </script>
  156. <style lang="scss" scoped>
  157. .inspector-select {
  158. display: flex;
  159. width: 100%;
  160. height: 600px;
  161. .left {
  162. display: flex;
  163. flex-direction: column;
  164. gap: 10px;
  165. width: 50%;
  166. height: 100%;
  167. padding: 0 5px;
  168. border-right: 1px solid rgba($text-color, 0.1);
  169. }
  170. .filter-result {
  171. flex: 1;
  172. overflow-y: auto;
  173. .el-tree {
  174. width: 100%;
  175. height: 100%;
  176. }
  177. .empty,
  178. img {
  179. width: 100%;
  180. }
  181. .empty {
  182. @include flex-center;
  183. flex-direction: column;
  184. height: 100%;
  185. gap: 5px;
  186. }
  187. }
  188. .select-type {
  189. width: 75px;
  190. }
  191. .right {
  192. display: flex;
  193. flex-direction: column;
  194. flex: 1;
  195. height: 100%;
  196. position: relative;
  197. margin-left: 16px;
  198. gap: 20px;
  199. .head {
  200. display: flex;
  201. align-items: center;
  202. font-weight: 400;
  203. font-size: 16px;
  204. color: rgba($text-color, 0.88);
  205. line-height: 22px;
  206. margin: 6px 0 6px;
  207. }
  208. .selected {
  209. display: flex;
  210. flex-wrap: wrap;
  211. gap: 8px;
  212. overflow-y: auto;
  213. max-height: calc(100% - 120px);
  214. }
  215. .footer {
  216. display: flex;
  217. gap: 6px;
  218. position: absolute;
  219. right: 24px;
  220. bottom: 20px;
  221. }
  222. }
  223. }
  224. </style>