User.vue 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  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. emit('submit', selectedPersonList.value);
  127. emit('cancel');
  128. };
  129. watch(
  130. () => props.customUserList,
  131. (newVal) => {
  132. selectedPersonList.value = newVal.map((item) => {
  133. return {
  134. id: item.id,
  135. name: `${item.username}-${item.realname}`,
  136. children: [],
  137. };
  138. });
  139. },
  140. {
  141. immediate: true,
  142. },
  143. );
  144. </script>
  145. <style lang="scss" scoped>
  146. .User {
  147. display: flex;
  148. width: 100%;
  149. height: 100%;
  150. .left {
  151. display: flex;
  152. flex-direction: column;
  153. width: 50%;
  154. height: 100%;
  155. padding: 0 5px;
  156. border-right: 1px solid rgba($text-color, 0.1);
  157. }
  158. .filter-title {
  159. @include flex-center;
  160. gap: 10px;
  161. justify-content: space-between;
  162. }
  163. .filter-result {
  164. flex: 1;
  165. overflow-y: auto;
  166. .empty,
  167. img {
  168. width: 100%;
  169. }
  170. .empty {
  171. @include flex-center;
  172. flex-direction: column;
  173. height: 100%;
  174. gap: 5px;
  175. }
  176. }
  177. .select-type {
  178. width: 75px;
  179. }
  180. .right {
  181. display: flex;
  182. flex-direction: column;
  183. flex: 1;
  184. height: 100%;
  185. position: relative;
  186. margin-left: 16px;
  187. gap: 20px;
  188. .head {
  189. display: flex;
  190. align-items: center;
  191. font-weight: 400;
  192. font-size: 16px;
  193. color: rgba($text-color, 0.88);
  194. line-height: 22px;
  195. margin: 6px 0 6px;
  196. }
  197. .selected {
  198. display: flex;
  199. flex-wrap: wrap;
  200. gap: 8px;
  201. overflow-y: auto;
  202. max-height: calc(100% - 120px);
  203. }
  204. .footer {
  205. display: flex;
  206. gap: 6px;
  207. position: absolute;
  208. right: 24px;
  209. bottom: 20px;
  210. }
  211. }
  212. }
  213. </style>