PersonGroupEditDrawer.vue 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. <template>
  2. <el-drawer :title="title" v-model="drawerOpened" @close="close">
  3. <el-form label-width="80px" :model="formData" :rules="formRules" ref="formInstance">
  4. <el-form-item label="分组名称" prop="name">
  5. <el-input
  6. v-model="formData.name"
  7. type="textarea"
  8. :autosize="{ minRows: 1, maxRows: 2 }"
  9. autocomplete="off"
  10. placeholder="请输入15字以内分组名称"
  11. />
  12. </el-form-item>
  13. <el-form-item label="分组描述" prop="description">
  14. <el-input
  15. v-model="formData.description"
  16. type="textarea"
  17. :rows="4"
  18. maxlength="50"
  19. show-word-limit
  20. autocomplete="off"
  21. placeholder="请输入50字以内分组描述"
  22. />
  23. </el-form-item>
  24. <el-form-item label="组内成员" prop="userList" :rules="[{ required: true, message: '组内成员不能为空' }]">
  25. <el-select
  26. placeholder="请添加组内人员"
  27. v-model="formData.userList"
  28. value-key="id"
  29. multiple
  30. @click="dialogOpened = true"
  31. >
  32. <el-option
  33. v-for="user in selectedUser"
  34. :key="user.id"
  35. :label="user.staffNo + '-' + user.realname"
  36. :value="user"
  37. />
  38. </el-select>
  39. <div class="member-count">
  40. <span>共 {{ total }} 人</span>
  41. <span v-if="isEditing" class="import-member-list" @click="batchImportVisible = true">导入成员名单</span>
  42. </div>
  43. </el-form-item>
  44. <el-form-item label="操作人" prop="operator">
  45. <el-input v-model="operater" disabled="true" />
  46. </el-form-item>
  47. </el-form>
  48. <template #footer>
  49. <el-button @click="reset">重置</el-button>
  50. <el-button type="primary" @click="submit">提交</el-button>
  51. </template>
  52. </el-drawer>
  53. <el-dialog
  54. v-model="dialogOpened"
  55. title="添加人员"
  56. align-center
  57. :close-on-click-modal="false"
  58. style="height: 583px"
  59. :width="950"
  60. :destroy-on-close="true"
  61. class="workShopDialog"
  62. >
  63. <PersonGroupFilter
  64. ref="dialogInstance"
  65. :init-selected="selectedUser"
  66. @cancel="handleDialogCancel"
  67. @submit="handleDialogSubmit"
  68. />
  69. </el-dialog>
  70. <BatchImport
  71. :visible="batchImportVisible"
  72. :importApiUrl="importApiUrl"
  73. :templateUrl="templateUrl"
  74. :templateName="'导入名单模版'"
  75. :data="{ userGroupId: formData.id }"
  76. responseType="detailed"
  77. @close="() => (batchImportVisible = false)"
  78. @update="handleUpdate"
  79. style="z-index: 10000"
  80. />
  81. </template>
  82. <script setup lang="ts">
  83. import { reactive, ref, computed, watch } from 'vue';
  84. import {
  85. PersonGroupForm,
  86. AddPersonGroupParams,
  87. EditPersonGroupParams,
  88. PersonGroupItem,
  89. PersonGroupListItem,
  90. } from '@/types/person-group/type';
  91. import { addUserGroup, modifyUserGroup, queryUserGroupDetail } from '@/api/system/person-group';
  92. import { FormRules, FormInstance, ElMessage } from 'element-plus';
  93. import { useUserStore } from '@/store/modules/user';
  94. import PersonGroupFilter from '@/components/PersonSelector/PersonGroupFilter.vue';
  95. import { storeToRefs } from 'pinia';
  96. import { BatchImport } from '@/components/batch-import';
  97. import type { ImportResponseDataPerson, ImportResponseData } from '@/components/batch-import/types';
  98. import { useGlobSetting } from '@/hooks/setting';
  99. import urlJoin from 'url-join';
  100. defineProps<{
  101. title: string;
  102. }>();
  103. const emits = defineEmits<{
  104. (e: 'submitted'): void; // 提交之后触发的事件
  105. }>();
  106. const drawerOpened = ref(false);
  107. const dialogOpened = ref(false);
  108. const selectedUser = ref<PersonGroupItem[]>([]);
  109. // 批量导入
  110. const batchImportVisible = ref(false);
  111. const { urlPrefix } = useGlobSetting();
  112. const importApiUrl = ref(urlJoin(urlPrefix, '/userGroup/importUserGroupMembers'));
  113. const templateUrl = ref('./skyeye-file-upload/sfysecurity/TEMPLATE/import-groupUserList-template.xlsx');
  114. const handleUpdate = (data?: ImportResponseData | ImportResponseDataPerson | string) => {
  115. // 处理批量导入返回的 successMembers
  116. if (data && typeof data === 'object' && 'successMembers' in data) {
  117. const importData = data as ImportResponseDataPerson;
  118. if (importData.successMembers && importData.successMembers.length > 0) {
  119. // 将 successMembers 转换为 PersonGroupItem 格式
  120. const newMembers: PersonGroupItem[] = importData.successMembers.map((member) => ({
  121. checked: true,
  122. id: member.id,
  123. realname: member.realname,
  124. deptId: member.deptId,
  125. deptName: member.deptName,
  126. staffNo: member.staffNo,
  127. jobName: member.jobName,
  128. }));
  129. // 获取现有成员的 id 集合,用于去重
  130. const existingIds = new Set(selectedUser.value.map((user) => user.id));
  131. // 过滤掉已存在的成员,只添加新成员
  132. const membersToAdd = newMembers.filter((member) => !existingIds.has(member.id));
  133. if (membersToAdd.length > 0) {
  134. // 合并到现有列表
  135. selectedUser.value = [...selectedUser.value, ...membersToAdd];
  136. formData.userList = [...formData.userList, ...membersToAdd];
  137. total.value = formData.userList.length;
  138. ElMessage.success(`成功导入 ${membersToAdd.length} 名成员`);
  139. } else {
  140. ElMessage.info('导入的成员已全部存在于当前列表中');
  141. }
  142. }
  143. }
  144. };
  145. // 表单相关
  146. let defaultFormData: PersonGroupForm = {
  147. id: null,
  148. name: '',
  149. description: '',
  150. userList: [],
  151. };
  152. const useUser = useUserStore();
  153. const { info } = storeToRefs(useUser);
  154. const operater = ref(info.value.realname);
  155. const total = ref<number>(0);
  156. const formData = reactive<PersonGroupForm>(defaultFormData);
  157. const formRules: FormRules = {
  158. name: { required: true, trigger: 'blur', message: '请填写分组名称' },
  159. userList: { required: true, trigger: 'blur', message: '请选择组内成员' },
  160. };
  161. const formInstance = ref<FormInstance>();
  162. const isEditing = computed(() => formData.id != null);
  163. /** 重置表单 */
  164. const reset = () => {
  165. formInstance.value?.resetFields();
  166. Object.assign(formData, defaultFormData);
  167. };
  168. /** 提交表单 */
  169. const submit = async () => {
  170. try {
  171. await formInstance.value?.validate();
  172. if (isEditing.value) {
  173. await modifyUserGroup({
  174. name: formData.name,
  175. description: formData.description,
  176. userIdList: formData.userList.map((x) => x.id),
  177. total: formData.userList.length,
  178. userGroupId: formData.id!,
  179. } as EditPersonGroupParams);
  180. } else {
  181. await addUserGroup({
  182. name: formData.name,
  183. description: formData.description,
  184. userIdList: formData.userList.map((x) => x.id),
  185. total: formData.userList.length,
  186. } as AddPersonGroupParams);
  187. }
  188. drawerOpened.value = false;
  189. ElMessage.success('提交成功');
  190. emits('submitted');
  191. } catch (e) {
  192. console.error(e);
  193. }
  194. };
  195. const close = () => {
  196. defaultFormData = {
  197. id: null,
  198. name: '',
  199. description: '',
  200. userList: [],
  201. };
  202. reset();
  203. };
  204. const handleDialogCancel = () => {
  205. dialogOpened.value = false;
  206. };
  207. const handleDialogSubmit = (selectedData: PersonGroupItem[]) => {
  208. selectedUser.value = selectedData;
  209. formData.userList = selectedData;
  210. total.value = formData.userList.length;
  211. dialogOpened.value = false;
  212. };
  213. const open = async (row?: PersonGroupListItem) => {
  214. if (row) {
  215. const res = await queryUserGroupDetail(row.id);
  216. defaultFormData = {
  217. id: res.userGroupId,
  218. name: res.name,
  219. description: res.description,
  220. userList: res.userList.map((x) => {
  221. return {
  222. checked: true,
  223. id: x.id,
  224. realname: x.realname,
  225. deptName: x.deptName,
  226. staffNo: x.staffNo,
  227. jobName: x.jobName,
  228. } as PersonGroupItem;
  229. }),
  230. };
  231. reset();
  232. selectedUser.value = formData.userList;
  233. }
  234. drawerOpened.value = true;
  235. };
  236. watch(
  237. () => formData.userList,
  238. (newUserList) => {
  239. total.value = newUserList.length;
  240. const ids = newUserList.map((user) => user.id);
  241. selectedUser.value = selectedUser.value.filter((user) => ids.includes(user.id));
  242. },
  243. );
  244. defineExpose({
  245. open,
  246. });
  247. </script>
  248. <style scoped lang="scss">
  249. .member-count {
  250. width: 100%;
  251. display: flex;
  252. justify-content: space-between;
  253. align-items: center;
  254. .import-member-list {
  255. color: $primary-color;
  256. cursor: pointer;
  257. }
  258. }
  259. </style>