User.vue 5.8 KB

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