InspectorSelect.vue 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  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 type="primary" @click="handleSubmit">确定</el-button>
  51. </div>
  52. </div>
  53. </div>
  54. </template>
  55. <script lang="ts" setup>
  56. import { onMounted, ref, watch, computed } from 'vue';
  57. import empty from 'assets/images/empty@1X.png';
  58. import type { TreeNode } from '@/views/disaster/types';
  59. import type { PersonGroupItem } from '@/types/person-group/type';
  60. import { ElMessage, type ElTree } from 'element-plus';
  61. import { useUserInfoHook } from '@/views/disaster/hooks/userInfo';
  62. import { updateTaskInspector } from '@/api/disaster-precaution';
  63. const { getUserFirstLevelTreeList, treeData, id: userId } = useUserInfoHook();
  64. const props = defineProps<{
  65. customUserList: PersonGroupItem[];
  66. currentTaskId: number | undefined;
  67. }>();
  68. interface Tree {
  69. [key: string]: any;
  70. }
  71. const queryContent = ref('');
  72. const treeRef = ref<InstanceType<typeof ElTree>>();
  73. const selectedPersonList = ref<PersonGroupItem[]>([]);
  74. const filterNode = (
  75. value: string,
  76. data: Tree,
  77. node: {
  78. parent: { label: string; parent: any; level: number };
  79. label: string;
  80. level: number;
  81. },
  82. ) => {
  83. if (!value) return true;
  84. let parentNode = node.parent,
  85. labels = [node.label],
  86. level = 1;
  87. while (level < node.level) {
  88. labels = [...labels, parentNode.label];
  89. parentNode = parentNode.parent;
  90. level++;
  91. }
  92. return labels.some((label) => label.indexOf(value) !== -1);
  93. };
  94. // 需要禁用的用户ID列表
  95. const disabledIds = computed(() => {
  96. const selectedIds = selectedPersonList.value.filter((item) => item.checked).map((item) => item.id);
  97. const ids = Array.from(new Set([userId, ...selectedIds]));
  98. return ids;
  99. });
  100. const removeSelectedPerson = (id: number) => {
  101. const index = selectedPersonList.value.findIndex((item) => item.id === id);
  102. if (index !== -1) {
  103. selectedPersonList.value.splice(index, 1);
  104. }
  105. };
  106. const handleCheckChange = (node: TreeNode, checked: boolean) => {
  107. if (checked) {
  108. const selectedPerson = {
  109. id: node.id,
  110. realname: node.label.split('(')[0],
  111. staffNo: node.staffNo || '',
  112. checked: false,
  113. };
  114. selectedPersonList.value.push(selectedPerson);
  115. } else {
  116. removeSelectedPerson(node.id);
  117. }
  118. };
  119. const handleTagClose = (id) => {
  120. const index = selectedPersonList.value.findIndex((item) => item.id === id);
  121. if (index !== -1) {
  122. selectedPersonList.value.splice(index, 1);
  123. treeRef.value!.setChecked(id, false, true);
  124. }
  125. };
  126. const emit = defineEmits(['cancel']);
  127. const handleSubmit = async () => {
  128. if (!props.currentTaskId) return;
  129. const ids = selectedPersonList.value.map((item) => item.id);
  130. await updateTaskInspector({
  131. id: props.currentTaskId,
  132. inspectorIdList: ids,
  133. });
  134. ElMessage.success('添加检查人员成功');
  135. emit('cancel');
  136. };
  137. onMounted(async () => {
  138. await getUserFirstLevelTreeList();
  139. });
  140. watch(queryContent, (val) => {
  141. treeRef.value!.filter(val);
  142. });
  143. watch(
  144. () => props.customUserList,
  145. (newVal) => {
  146. selectedPersonList.value = newVal;
  147. },
  148. {
  149. immediate: true,
  150. },
  151. );
  152. </script>
  153. <style lang="scss" scoped>
  154. .inspector-select {
  155. display: flex;
  156. width: 100%;
  157. height: 600px;
  158. .left {
  159. display: flex;
  160. flex-direction: column;
  161. gap: 10px;
  162. width: 50%;
  163. height: 100%;
  164. padding: 0 5px;
  165. border-right: 1px solid rgba($text-color, 0.1);
  166. }
  167. .filter-result {
  168. flex: 1;
  169. overflow-y: auto;
  170. .el-tree {
  171. width: 100%;
  172. height: 100%;
  173. }
  174. .empty,
  175. img {
  176. width: 100%;
  177. }
  178. .empty {
  179. @include flex-center;
  180. flex-direction: column;
  181. height: 100%;
  182. gap: 5px;
  183. }
  184. }
  185. .select-type {
  186. width: 75px;
  187. }
  188. .right {
  189. display: flex;
  190. flex-direction: column;
  191. flex: 1;
  192. height: 100%;
  193. position: relative;
  194. margin-left: 16px;
  195. gap: 20px;
  196. .head {
  197. display: flex;
  198. align-items: center;
  199. font-weight: 400;
  200. font-size: 16px;
  201. color: rgba($text-color, 0.88);
  202. line-height: 22px;
  203. margin: 6px 0 6px;
  204. }
  205. .selected {
  206. display: flex;
  207. flex-wrap: wrap;
  208. gap: 8px;
  209. overflow-y: auto;
  210. max-height: calc(100% - 120px);
  211. }
  212. .footer {
  213. display: flex;
  214. gap: 6px;
  215. position: absolute;
  216. right: 24px;
  217. bottom: 20px;
  218. }
  219. }
  220. }
  221. </style>