设计出图
This commit is contained in:
		| @ -13,134 +13,212 @@ | ||||
|       <el-form ref="leaveFormRef" :model="form" :disabled="disabledForm" :rules="rules" label-width="120px" class="p-6 space-y-6"> | ||||
|         <!-- 设计负责人 --> | ||||
|         <div class="fonts"> | ||||
|           <el-form-item label="设计负责人" prop="designLeader" class="mb-4"> | ||||
|             <el-select | ||||
|               v-model="form.designLeader" | ||||
|               placeholder="请选择设计负责人" | ||||
|               class="w-full transition-all duration-300 border-gray-300 focus:border-blue-400 focus:ring-1 focus:ring-blue-400" | ||||
|             > | ||||
|               <el-option v-for="item in userList" :key="item.userId" :label="item.nickName" :value="item.userId" /> | ||||
|             </el-select> | ||||
|           </el-form-item> | ||||
|         </div> | ||||
|  | ||||
|         <!-- 设计人员 --> | ||||
|         <div class="border border-gray-200 rounded-lg p-5 transition-all duration-300 hover:shadow-md bg-gray-50"> | ||||
|           <div class="flex justify-between items-center mb-5"> | ||||
|             <h3 class="text-lg font-semibold text-gray-700 flex items-center"><i class="el-icon-user mr-2 text-blue-500"></i>设计人员</h3> | ||||
|             <el-button | ||||
|               type="primary" | ||||
|               size="small" | ||||
|               @click="addPerson('designers')" | ||||
|               class="transition-all duration-300 transform hover:scale-105 bg-blue-500 hover:bg-blue-600" | ||||
|             > | ||||
|               <i class="el-icon-plus mr-1"></i>新增设计人员 | ||||
|             </el-button> | ||||
|           </div> | ||||
|  | ||||
|           <div v-for="(designer, index) in form.designers" :key="index" class="flex items-center mb-4 animate-fadeIn"> | ||||
|             <el-form-item | ||||
|               :prop="`designers.${index}.userId`" | ||||
|               :rules="{ required: true, message: '请选择设计人员', trigger: 'change' }" | ||||
|               class="flex-1 mr-3" | ||||
|             > | ||||
|               <div class="grid grid-cols-1 md:grid-cols-2 gap-4"> | ||||
|                 <!-- 设计人员专业选择(绑定重复校验) --> | ||||
|           <el-row> | ||||
|             <el-col :span="8" | ||||
|               ><el-form-item label="设计负责人" prop="designLeader" class="mb-4"> | ||||
|                 <el-select | ||||
|                   v-model="designer.userMajor" | ||||
|                   placeholder="请选择专业" | ||||
|                   class="transition-all duration-300 border-gray-300" | ||||
|                   :rules="{ required: true, message: '请选择专业', trigger: 'change' }" | ||||
|                   @change="() => checkDuplicate(designer, 'designers', index)" | ||||
|                 > | ||||
|                   <el-option v-for="item in des_user_major" :key="item.value" :label="item.label" :value="item.value" /> | ||||
|                 </el-select> | ||||
|                 <!-- 设计人员选择(绑定重复校验) --> | ||||
|                 <el-select | ||||
|                   v-model="designer.userId" | ||||
|                   placeholder="请选择设计人员" | ||||
|                   class="transition-all duration-300 border-gray-300" | ||||
|                   @change="() => checkDuplicate(designer, 'designers', index)" | ||||
|                   v-model="form.designLeader" | ||||
|                   placeholder="请选择设计负责人" | ||||
|                   class="w-full transition-all duration-300 border-gray-300 focus:border-blue-400 focus:ring-1 focus:ring-blue-400" | ||||
|                 > | ||||
|                   <el-option v-for="item in userList" :key="item.userId" :label="item.nickName" :value="item.userId" /> | ||||
|                 </el-select> | ||||
|               </div> | ||||
|             </el-form-item> | ||||
|             <el-button | ||||
|               type="danger" | ||||
|               size="small" | ||||
|               @click="removePerson('designers', index)" | ||||
|               class="transition-all duration-300 hover:bg-red-600" | ||||
|               :disabled="form.designers.length <= 1" | ||||
|             > | ||||
|               <el-icon :size="16"> | ||||
|                 <Delete /> | ||||
|               </el-icon> | ||||
|             </el-button> | ||||
|           </div> | ||||
|           <div v-if="form.designers.length === 0" class="text-gray-500 text-center py-4 bg-gray-100 rounded-lg border border-dashed border-gray-200"> | ||||
|             暂无设计人员,请点击"新增设计人员"添加 | ||||
|           </div> | ||||
|               </el-form-item> | ||||
|             </el-col> | ||||
|           </el-row> | ||||
|         </div> | ||||
|  | ||||
|         <!-- 校审人员 --> | ||||
|         <!-- 专业人员配置:专业 + 设计人员 + 校审人员 横向排列 --> | ||||
|         <div class="border border-gray-200 rounded-lg p-5 transition-all duration-300 hover:shadow-md bg-gray-50"> | ||||
|           <div class="flex justify-between items-center mb-5"> | ||||
|             <h3 class="text-lg font-semibold text-gray-700 flex items-center"><i class="el-icon-check-circle mr-2 text-green-500"></i>校审人员</h3> | ||||
|             <el-button | ||||
|               type="primary" | ||||
|               size="small" | ||||
|               @click="addPerson('reviewers')" | ||||
|               class="transition-all duration-300 transform hover:scale-105 bg-blue-500 hover:bg-blue-600" | ||||
|             > | ||||
|               <i class="el-icon-plus mr-1"></i>新增校审人员 | ||||
|             </el-button> | ||||
|             <h3 class="text-lg font-semibold text-gray-700 flex items-center"><i class="el-icon-users mr-2 text-blue-500"></i>专业人员配置</h3> | ||||
|             <div class="flex gap-3"> | ||||
|               <!-- 新增专业按钮 --> | ||||
|               <el-button type="primary" size="small" :disabled="disabledForm" @click="addMajor"> | ||||
|                 <i class="el-icon-plus mr-1"></i>新增专业 | ||||
|               </el-button> | ||||
|             </div> | ||||
|           </div> | ||||
|  | ||||
|           <div v-for="(reviewer, index) in form.reviewers" :key="index" class="flex items-center mb-4 animate-fadeIn"> | ||||
|             <el-form-item | ||||
|               :prop="`reviewers.${index}.userId`" | ||||
|               :rules="{ required: true, message: '请选择校审人员', trigger: 'change' }" | ||||
|               class="flex-1 mr-3" | ||||
|             > | ||||
|               <div class="grid grid-cols-1 md:grid-cols-2 gap-4"> | ||||
|                 <!-- 校审人员专业选择(绑定重复校验) --> | ||||
|                 <el-select | ||||
|                   v-model="reviewer.userMajor" | ||||
|                   placeholder="请选择专业" | ||||
|                   class="transition-all duration-300 border-gray-300" | ||||
|           <!-- 表头 --> | ||||
|           <el-row :gutter="20" class="mb-3 font-medium text-gray-700"> | ||||
|             <el-col :span="6" :xs="24" :sm="8">专业</el-col> | ||||
|             <el-col :span="9" :xs="24" :sm="8">设计人员(可多选)</el-col> | ||||
|             <el-col :span="9" :xs="24" :sm="8">校审人员(可多选)</el-col> | ||||
|           </el-row> | ||||
|  | ||||
|           <!-- 分割线 --> | ||||
|           <el-divider class="my-4" /> | ||||
|  | ||||
|           <!-- 专业配置行:专业(左)+ 设计人员(中)+ 校审人员(右) 横向排列 --> | ||||
|           <div | ||||
|             v-for="(majorConfig, configIndex) in combinedConfigs" | ||||
|             :key="configIndex" | ||||
|             style="background: aliceblue; border-radius: 10px" | ||||
|             class="mb-5 animate-fadeIn" | ||||
|           > | ||||
|             <el-row :gutter="20" class="items-top"> | ||||
|               <!-- 左侧:专业选择 --> | ||||
|               <el-col :span="6" :xs="24" :sm="8" class="mb-4 sm:mb-0" style="margin-top: 8px"> | ||||
|                 <el-form-item | ||||
|                   :prop="`designers.${configIndex}.userMajor`" | ||||
|                   :rules="{ required: true, message: '请选择专业', trigger: 'change' }" | ||||
|                   @change="() => checkDuplicate(reviewer, 'reviewers', index)" | ||||
|                   class="mb-0" | ||||
|                   label-width="80px" | ||||
|                   label="专业" | ||||
|                 > | ||||
|                   <el-option v-for="item in des_user_major" :key="item.value" :label="item.label" :value="item.value" /> | ||||
|                 </el-select> | ||||
|                 <!-- 校审人员选择(绑定重复校验) --> | ||||
|                 <el-select | ||||
|                   v-model="reviewer.userId" | ||||
|                   placeholder="请选择校审人员" | ||||
|                   class="transition-all duration-300 border-gray-300" | ||||
|                   @change="() => checkDuplicate(reviewer, 'reviewers', index)" | ||||
|                   <!-- 专业选择下拉框 --> | ||||
|                   <el-select | ||||
|                     v-model="form.designers[configIndex].userMajor" | ||||
|                     placeholder="请选择专业" | ||||
|                     class="w-full transition-all duration-300 border-gray-300" | ||||
|                     @change="(val) => handleMajorChange(val, configIndex)" | ||||
|                   > | ||||
|                     <!-- 临时添加调试显示 --> | ||||
|                     <template v-if="des_user_major && des_user_major.length > 0"> | ||||
|                       <el-option v-for="item in des_user_major" :key="item.value" :label="item.label" :value="item.value" /> | ||||
|                     </template> | ||||
|                     <template v-else> | ||||
|                       <el-option label="无专业数据" value="" disabled /> | ||||
|                     </template> | ||||
|                   </el-select> | ||||
|                 </el-form-item> | ||||
|               </el-col> | ||||
|  | ||||
|               <!-- 中间:设计人员 --> | ||||
|               <el-col :span="9" :xs="24" :sm="8" class="mb-4 sm:mb-0"> | ||||
|                 <div class="pl-0 sm:pl-4 border-l-0 sm:border-l-2 border-blue-200 py-0 sm:py-2"> | ||||
|                   <!-- 设计人员列表 --> | ||||
|                   <div class="space-y-3"> | ||||
|                     <div v-for="(person, personIndex) in majorConfig.designPersons" :key="personIndex" class="flex items-center"> | ||||
|                       <el-form-item | ||||
|                         :prop="`designers.${configIndex}.persons.${personIndex}.userId`" | ||||
|                         :rules="{ required: true, message: '请选择人员', trigger: 'change' }" | ||||
|                         class="flex-1 mr-3 mb-0" | ||||
|                         label="设计人员" | ||||
|                         label-width="80px" | ||||
|                       > | ||||
|                         <el-select | ||||
|                           v-model="person.userId" | ||||
|                           placeholder="请选择设计人员" | ||||
|                           class="w-full transition-all duration-300 border-gray-300" | ||||
|                           @change="() => checkDuplicate(person, 'designers', configIndex, personIndex)" | ||||
|                         > | ||||
|                           <el-option v-for="item in userList" :key="item.userId" :label="item.nickName" :value="item.userId" /> | ||||
|                         </el-select> | ||||
|                       </el-form-item> | ||||
|                       <div> | ||||
|                         <el-button | ||||
|                           type="danger" | ||||
|                           size="small" | ||||
|                           @click="removePerson('designers', configIndex, personIndex)" | ||||
|                           class="transition-all duration-300 hover:bg-red-600" | ||||
|                           :disabled="majorConfig.designPersons.length <= 1 || disabledForm" | ||||
|                         > | ||||
|                           <el-icon :size="16"> | ||||
|                             <Delete /> | ||||
|                           </el-icon> | ||||
|                         </el-button> | ||||
|                         <el-button | ||||
|                           type="success" | ||||
|                           size="small" | ||||
|                           @click="addPerson('designers', configIndex)" | ||||
|                           class="transition-all duration-300 transform hover:scale-105" | ||||
|                           :disabled="!majorConfig.userMajor || disabledForm" | ||||
|                         > | ||||
|                           <el-icon :size="16"> | ||||
|                             <Plus /> | ||||
|                           </el-icon> | ||||
|                         </el-button> | ||||
|                       </div> | ||||
|                     </div> | ||||
|                   </div> | ||||
|  | ||||
|                   <!-- 空状态提示 --> | ||||
|                   <div | ||||
|                     v-if="majorConfig.designPersons.length == 0" | ||||
|                     class="text-gray-500 text-sm py-2 bg-gray-100 rounded-lg border border-dashed border-gray-200 mt-1" | ||||
|                   > | ||||
|                     暂无设计人员,请点击"添加设计人员" | ||||
|                   </div> | ||||
|                 </div> | ||||
|               </el-col> | ||||
|  | ||||
|               <!-- 右侧:校审人员 --> | ||||
|               <el-col :span="9" :xs="24" :sm="8"> | ||||
|                 <div class="pl-0 sm:pl-4 border-l-0 sm:border-l-2 border-green-200 py-0 sm:py-2"> | ||||
|                   <!-- 校审人员列表 --> | ||||
|                   <div class="space-y-3"> | ||||
|                     <div v-for="(person, personIndex) in majorConfig.reviewPersons" :key="personIndex" class="flex items-center"> | ||||
|                       <el-form-item | ||||
|                         :prop="`reviewers.${configIndex}.persons.${personIndex}.userId`" | ||||
|                         :rules="{ required: true, message: '请选择人员', trigger: 'change' }" | ||||
|                         class="flex-1 mr-3 mb-0" | ||||
|                         label="校审人员" | ||||
|                         label-width="80px" | ||||
|                       > | ||||
|                         <el-select | ||||
|                           v-model="person.userId" | ||||
|                           placeholder="请选择校审人员" | ||||
|                           class="w-full transition-all duration-300 border-gray-300" | ||||
|                           @change="() => checkDuplicate(person, 'reviewers', configIndex, personIndex)" | ||||
|                         > | ||||
|                           <el-option v-for="item in userList" :key="item.userId" :label="item.nickName" :value="item.userId" /> | ||||
|                         </el-select> | ||||
|                       </el-form-item> | ||||
|                       <div> | ||||
|                         <el-button | ||||
|                           type="danger" | ||||
|                           size="small" | ||||
|                           @click="removePerson('reviewers', configIndex, personIndex)" | ||||
|                           class="transition-all duration-300 hover:bg-red-600" | ||||
|                           :disabled="majorConfig.reviewPersons.length <= 1 || disabledForm" | ||||
|                         > | ||||
|                           <el-icon :size="16"> | ||||
|                             <Delete /> | ||||
|                           </el-icon> | ||||
|                         </el-button> | ||||
|                         <el-button | ||||
|                           type="success" | ||||
|                           size="small" | ||||
|                           @click="addPerson('reviewers', configIndex)" | ||||
|                           class="transition-all duration-300 transform hover:scale-105" | ||||
|                           :disabled="!majorConfig.userMajor || disabledForm" | ||||
|                         > | ||||
|                           <el-icon :size="16"> | ||||
|                             <Plus /> | ||||
|                           </el-icon> | ||||
|                         </el-button> | ||||
|                       </div> | ||||
|                     </div> | ||||
|                   </div> | ||||
|  | ||||
|                   <!-- 空状态提示 --> | ||||
|                   <div | ||||
|                     v-if="majorConfig.reviewPersons.length == 0" | ||||
|                     class="text-gray-500 text-sm py-2 bg-gray-100 rounded-lg border border-dashed border-gray-200 mt-1" | ||||
|                   > | ||||
|                     暂无校审人员,请点击"添加校审人员" | ||||
|                   </div> | ||||
|                 </div> | ||||
|               </el-col> | ||||
|             </el-row> | ||||
|             <!-- 删除专业配置行 --> | ||||
|             <el-row class="mt-2"> | ||||
|               <el-col :span="24" class="text-right pr-4"> | ||||
|                 <el-button | ||||
|                   type="text" | ||||
|                   class="text-red-500 hover:text-red-700 transition-colors" | ||||
|                   @click="removeMajor(configIndex)" | ||||
|                   :disabled="combinedConfigs.length <= 1 || disabledForm" | ||||
|                 > | ||||
|                   <el-option v-for="item in userList" :key="item.userId" :label="item.nickName" :value="item.userId" /> | ||||
|                 </el-select> | ||||
|               </div> | ||||
|             </el-form-item> | ||||
|             <el-button | ||||
|               type="danger" | ||||
|               size="small" | ||||
|               @click="removePerson('reviewers', index)" | ||||
|               class="transition-all duration-300 hover:bg-red-600" | ||||
|               :disabled="form.reviewers.length <= 1" | ||||
|             > | ||||
|               <el-icon :size="16"> | ||||
|                 <Delete /> | ||||
|               </el-icon> | ||||
|             </el-button> | ||||
|           </div> | ||||
|           <div v-if="form.reviewers.length === 0" class="text-gray-500 text-center py-4 bg-gray-100 rounded-lg border border-dashed border-gray-200"> | ||||
|             暂无校审人员,请点击"新增校审人员"添加 | ||||
|                   <i class="el-icon-delete mr-1"></i>删除专业 | ||||
|                 </el-button> | ||||
|               </el-col> | ||||
|             </el-row> | ||||
|           </div> | ||||
|         </div> | ||||
|  | ||||
|         <!-- 提交按钮区域 --> | ||||
|         <div class="flex justify-center space-x-6 mt-8 pt-6 border-t border-gray-100"> | ||||
|           <el-button | ||||
| @ -149,10 +227,16 @@ | ||||
|             v-hasPermi="['design:user:batch']" | ||||
|             @click="submitForm" | ||||
|             class="px-8 py-2.5 transition-all duration-300 transform hover:scale-105 bg-blue-500 hover:bg-blue-600 text-white font-medium" | ||||
|             :disabled="disabledForm" | ||||
|           > | ||||
|             <i class="el-icon-check mr-2"></i>确认提交 | ||||
|           </el-button> | ||||
|           <el-button size="large" @click="resetForm" class="px-8 py-2.5 transition-all duration-300 border-gray-300 hover:bg-gray-100 font-medium"> | ||||
|           <el-button | ||||
|             size="large" | ||||
|             @click="resetForm" | ||||
|             class="px-8 py-2.5 transition-all duration-300 border-gray-300 hover:bg-gray-100 font-medium" | ||||
|             :disabled="disabledForm" | ||||
|           > | ||||
|             <i class="el-icon-refresh mr-2"></i>重置 | ||||
|           </el-button> | ||||
|         </div> | ||||
| @ -167,7 +251,7 @@ import { getCurrentInstance } from 'vue'; | ||||
| import type { ComponentInternalInstance } from 'vue'; | ||||
| import { useUserStoreHook } from '@/store/modules/user'; | ||||
| import { ElMessage, ElLoading } from 'element-plus'; | ||||
| import { Delete } from '@element-plus/icons-vue'; | ||||
| import { Delete, Plus } from '@element-plus/icons-vue'; // 修复:添加Plus图标导入 | ||||
| import { designUserAdd, designUserList, systemUserList } from '@/api/design/appointment'; | ||||
|  | ||||
| // 获取当前实例 | ||||
| @ -176,17 +260,43 @@ const { proxy } = getCurrentInstance() as ComponentInternalInstance; | ||||
| const userStore = useUserStoreHook(); | ||||
| // 从 store 中获取当前选中的项目 | ||||
| const currentProject = computed(() => userStore.selectedProject); | ||||
| // 专业字典数据 | ||||
| const { des_user_major } = toRefs<any>(proxy?.useDict('des_user_major')); | ||||
| // 专业字典数据 - 增加默认空数组避免undefined | ||||
| const { des_user_major = ref([]) } = toRefs<any>(proxy?.useDict('des_user_major') || {}); | ||||
|  | ||||
| // 表单数据 | ||||
| // 调试:打印专业数据 | ||||
| onMounted(() => { | ||||
|   console.log('专业数据:', des_user_major.value); | ||||
| }); | ||||
|  | ||||
| // 表单数据:保持原有数据结构不变 | ||||
| interface MajorGroup { | ||||
|   userMajor: string | null; // 专业 | ||||
|   persons: Array<{ userId: number | null }>; // 该专业下的多个人员 | ||||
| } | ||||
| const form = reactive({ | ||||
|   projectId: currentProject.value?.id, | ||||
|   designLeader: null, // 设计负责人 | ||||
|   // 设计人员列表(包含用户ID和专业) | ||||
|   designers: [] as Array<{ userId: number | null; userMajor: string | null }>, | ||||
|   // 校审人员列表(包含用户ID和专业) | ||||
|   reviewers: [] as Array<{ userId: number | null; userMajor: string | null }> | ||||
|   designers: [] as MajorGroup[], // 设计人员:按专业分组,每组含多个人员 | ||||
|   reviewers: [] as MajorGroup[] // 校审人员:按专业分组,每组含多个人员 | ||||
| }); | ||||
|  | ||||
| // 组合配置用于视图展示(专业+设计人员+校审人员) | ||||
| const combinedConfigs = computed(() => { | ||||
|   // 确保designers和reviewers数组长度一致 | ||||
|   const maxLength = Math.max(form.designers.length, form.reviewers.length); | ||||
|   while (form.designers.length < maxLength) { | ||||
|     form.designers.push(createEmptyMajorGroup()); | ||||
|   } | ||||
|   while (form.reviewers.length < maxLength) { | ||||
|     form.reviewers.push(createEmptyMajorGroup()); | ||||
|   } | ||||
|  | ||||
|   // 组合数据用于视图展示 | ||||
|   return form.designers.map((designerGroup, index) => ({ | ||||
|     userMajor: designerGroup.userMajor, | ||||
|     designPersons: designerGroup.persons, | ||||
|     reviewPersons: form.reviewers[index].persons | ||||
|   })); | ||||
| }); | ||||
|  | ||||
| // 表单验证规则 | ||||
| @ -222,146 +332,215 @@ const designUser = async () => { | ||||
|   }); | ||||
|   try { | ||||
|     const res = await designUserList({ projectId: currentProject.value?.id }); | ||||
|     if (res.code == 200 && res.rows) { | ||||
|       // 清空现有数据 | ||||
|       form.designLeader = null; | ||||
|       form.designers = []; | ||||
|       form.reviewers = []; | ||||
|       if (res.rows.length > 0) { | ||||
|         disabledForm.value = true; | ||||
|       } | ||||
|       // 处理返回的数据,进行回显 | ||||
|       res.rows.forEach((item: any) => { | ||||
|         if (item.userType == 1) { | ||||
|           form.designLeader = item.userId; | ||||
|         } else if (item.userType == 2) { | ||||
|           form.designers.push({ | ||||
|             userId: item.userId, | ||||
|             userMajor: item.userMajor || null | ||||
|           }); | ||||
|         } else if (item.userType == 3) { | ||||
|           form.reviewers.push({ | ||||
|             userId: item.userId, | ||||
|             userMajor: item.userMajor || null | ||||
|           }); | ||||
|         } | ||||
|       }); | ||||
|       // 补全默认空项 | ||||
|       if (form.designers.length === 0) { | ||||
|         form.designers.push({ userId: null, userMajor: null }); | ||||
|       } | ||||
|       if (form.reviewers.length === 0) { | ||||
|         form.reviewers.push({ userId: null, userMajor: null }); | ||||
|       } | ||||
|     } else { | ||||
|       // 添加默认空项 | ||||
|       form.designers.push({ userId: null, userMajor: null }); | ||||
|       form.reviewers.push({ userId: null, userMajor: null }); | ||||
|     // 清空现有数据 | ||||
|     form.designLeader = null; | ||||
|     form.designers = []; | ||||
|     form.reviewers = []; | ||||
|  | ||||
|     if (res.code == 200 && res.rows && res.rows.length > 0) { | ||||
|       disabledForm.value = true; | ||||
|       // 1. 分类整理数据(按用户类型) | ||||
|       const designLeader = res.rows.find((item) => item.userType == 1); | ||||
|       const designerItems = res.rows.filter((item) => item.userType == 2); | ||||
|       const reviewerItems = res.rows.filter((item) => item.userType == 3); | ||||
|  | ||||
|       // 2. 回显设计负责人 | ||||
|       if (designLeader) form.designLeader = designLeader.userId; | ||||
|  | ||||
|       // 3. 回显设计人员(按专业分组) | ||||
|       form.designers = groupPersonByMajor(designerItems); | ||||
|       // 4. 回显校审人员(按专业分组) | ||||
|       form.reviewers = groupPersonByMajor(reviewerItems); | ||||
|     } | ||||
|  | ||||
|     // 补全默认空项(至少1个专业分组,每组至少1个人员) | ||||
|     if (form.designers.length == 0) form.designers.push(createEmptyMajorGroup()); | ||||
|     if (form.reviewers.length == 0) form.reviewers.push(createEmptyMajorGroup()); | ||||
|   } catch (error) { | ||||
|     ElMessage.error('获取配置数据失败'); | ||||
|     form.designers.push({ userId: null, userMajor: null }); | ||||
|     form.reviewers.push({ userId: null, userMajor: null }); | ||||
|     // 异常时初始化默认空项 | ||||
|     form.designers = [createEmptyMajorGroup()]; | ||||
|     form.reviewers = [createEmptyMajorGroup()]; | ||||
|   } finally { | ||||
|     loading.close(); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| /** 添加人员 */ | ||||
| const addPerson = (type: 'designers' | 'reviewers') => { | ||||
|   form[type].push({ userId: null, userMajor: null }); | ||||
|   // 滚动到最后一个新增元素 | ||||
| /** 辅助函数:创建空的专业分组(含1个空人员) */ | ||||
| const createEmptyMajorGroup = (): MajorGroup => ({ | ||||
|   userMajor: null, | ||||
|   persons: [{ userId: null }] | ||||
| }); | ||||
|  | ||||
| /** 辅助函数:按专业分组整理人员数据(用于回显) */ | ||||
| const groupPersonByMajor = (items: any[]): MajorGroup[] => { | ||||
|   const groupMap: Record<string, MajorGroup> = {}; | ||||
|   items.forEach((item) => { | ||||
|     const major = item.userMajor || '未分类'; | ||||
|     // 不存在该专业分组则创建 | ||||
|     if (!groupMap[major]) { | ||||
|       groupMap[major] = { userMajor: item.userMajor, persons: [] }; | ||||
|     } | ||||
|     // 添加当前人员到专业分组 | ||||
|     groupMap[major].persons.push({ userId: item.userId }); | ||||
|   }); | ||||
|   // 处理空分组(确保每组至少1个人员) | ||||
|   Object.values(groupMap).forEach((group) => { | ||||
|     if (group.persons.length == 0) group.persons.push({ userId: null }); | ||||
|   }); | ||||
|   return Object.values(groupMap); | ||||
| }; | ||||
|  | ||||
| /** 新增专业配置行 */ | ||||
| const addMajor = () => { | ||||
|   form.designers.push(createEmptyMajorGroup()); | ||||
|   form.reviewers.push(createEmptyMajorGroup()); | ||||
|  | ||||
|   // 滚动到新增的专业配置行 | ||||
|   setTimeout(() => { | ||||
|     const elements = document.querySelectorAll(`[data-v-${proxy?.$options.__scopeId}] .el-select`); | ||||
|     if (elements.length > 0) { | ||||
|       elements[elements.length - 1].scrollIntoView({ behavior: 'smooth', block: 'nearest' }); | ||||
|     const groups = document.querySelectorAll(`[data-v-${proxy?.$options.__scopeId}] .animate-fadeIn`); | ||||
|     if (groups.length > 0) { | ||||
|       groups[groups.length - 1].scrollIntoView({ behavior: 'smooth', block: 'nearest' }); | ||||
|     } | ||||
|   }, 100); | ||||
| }; | ||||
|  | ||||
| /** 移除人员 */ | ||||
| const removePerson = (type: 'designers' | 'reviewers', index: number) => { | ||||
|   if (form[type].length <= 1) { | ||||
|     ElMessage.warning('至少保留一个人员'); | ||||
| /** 删除专业配置行 */ | ||||
| const removeMajor = (configIndex: number) => { | ||||
|   if (form.designers.length <= 1) { | ||||
|     ElMessage.warning('至少保留一个专业配置'); | ||||
|     return; | ||||
|   } | ||||
|   form[type].splice(index, 1); | ||||
|   form.designers.splice(configIndex, 1); | ||||
|   form.reviewers.splice(configIndex, 1); | ||||
| }; | ||||
|  | ||||
| // ===================== 核心修改:重复校验逻辑 ===================== | ||||
| /** 给指定专业配置行添加人员 */ | ||||
| const addPerson = (type: 'designers' | 'reviewers', configIndex: number) => { | ||||
|   form[type][configIndex].persons.push({ userId: null }); | ||||
|  | ||||
|   // 滚动到新增的人员选择框 | ||||
|   setTimeout(() => { | ||||
|     const personSelects = document.querySelectorAll(`[data-v-${proxy?.$options.__scopeId}] .el-select`); | ||||
|     if (personSelects.length > 0) { | ||||
|       personSelects[personSelects.length - 1].scrollIntoView({ behavior: 'smooth', block: 'nearest' }); | ||||
|     } | ||||
|   }, 100); | ||||
| }; | ||||
|  | ||||
| /** 从指定专业配置行删除人员 */ | ||||
| const removePerson = (type: 'designers' | 'reviewers', configIndex: number, personIndex: number) => { | ||||
|   const targetGroup = form[type][configIndex]; | ||||
|   if (targetGroup.persons.length <= 1) { | ||||
|     ElMessage.warning(`该专业至少保留一个${type == 'designers' ? '设计' : '校审'}人员`); | ||||
|     return; | ||||
|   } | ||||
|   targetGroup.persons.splice(personIndex, 1); | ||||
| }; | ||||
|  | ||||
| /** 专业变更时:清空当前专业下的人员(避免专业与人员不匹配) */ | ||||
| const handleMajorChange = (newMajor: string, configIndex: number) => { | ||||
|   // 直接修改原始数据源,确保响应式生效 | ||||
|   form.designers[configIndex].userMajor = newMajor; | ||||
|   form.reviewers[configIndex].userMajor = newMajor; | ||||
|   form.designers[configIndex].persons = [{ userId: null }]; | ||||
|   form.reviewers[configIndex].persons = [{ userId: null }]; | ||||
|   // ElMessage.info(`已重置「${getMajorLabel(newMajor)}」专业下的人员,请重新选择`); | ||||
| }; | ||||
|  | ||||
| // ========== 核心:重复校验逻辑 ========== | ||||
| /** | ||||
|  * 校验同一角色内(设计/校审)的「专业+人员」组合唯一性 | ||||
|  * @param current 当前操作的人员对象 | ||||
|  * @param role 角色类型(designers/reviewers) | ||||
|  * @param currentIndex 当前操作的索引 | ||||
|  */ | ||||
| const checkDuplicate = (current: { userId: number | null; userMajor: string | null }, role: 'designers' | 'reviewers', currentIndex: number) => { | ||||
|   // 未选完专业/人员时不校验 | ||||
|   if (!current.userId || !current.userMajor) return; | ||||
| const checkDuplicate = (current: { userId: number | null }, role: 'designers' | 'reviewers', configIndex: number, personIndex: number) => { | ||||
|   console.log(`校验触发 - 角色: ${role}, 专业索引: ${configIndex}, 人员索引: ${personIndex}, 人员ID: ${current.userId}`); | ||||
|   console.log(form); | ||||
|  | ||||
|   const currentGroup = form[role][configIndex]; | ||||
|   // 未选专业/人员时不校验 | ||||
|   if (!currentGroup.userMajor || !current.userId) return; | ||||
|  | ||||
|   // 只获取当前角色的所有人员(设计只查设计,校审只查校审) | ||||
|   const targetList = form[role]; | ||||
|   // 生成当前「专业+人员」唯一标识 | ||||
|   const currentKey = `${current.userMajor}-${current.userId}`; | ||||
|   const currentKey = `${currentGroup.userMajor}-${current.userId}`; | ||||
|   let duplicateItem = null; | ||||
|  | ||||
|   // 检查当前角色内是否有重复 | ||||
|   const duplicateItem = targetList.find((item, index) => { | ||||
|     // 排除当前操作项本身 | ||||
|     if (index === currentIndex) return false; | ||||
|     // 对比「专业+人员」组合 | ||||
|     return `${item.userMajor}-${item.userId}` === currentKey; | ||||
|   // 1. 检查当前专业配置行内是否有重复人员 | ||||
|   duplicateItem = currentGroup.persons.find((item, idx) => { | ||||
|     return idx !== personIndex && item.userId == current.userId; | ||||
|   }); | ||||
|   if (duplicateItem) { | ||||
|     ElMessage.warning(`当前专业下「${getUserName(current.userId)}」已存在,请重新选择`); | ||||
|     current.userId = null; | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   // 2. 检查同一角色内其他专业配置行是否有重复(专业+人员唯一) | ||||
|   form[role].forEach((group, gIdx) => { | ||||
|     if (gIdx == configIndex) return; // 跳过当前配置行 | ||||
|     group.persons.forEach((item) => { | ||||
|       if (`${group.userMajor}-${item.userId}` == currentKey) { | ||||
|         duplicateItem = item; | ||||
|       } | ||||
|     }); | ||||
|   }); | ||||
|  | ||||
|   // 存在重复时提示并清空当前选择 | ||||
|   if (duplicateItem) { | ||||
|     ElMessage.warning(`当前「${getMajorLabel(current.userMajor)}+${getUserName(current.userId)}」组合已存在,请重新选择`); | ||||
|     // 清空当前项的选择 | ||||
|     ElMessage.warning(`「${getMajorLabel(currentGroup.userMajor)}+${getUserName(current.userId)}」组合已存在,请重新选择`); | ||||
|     current.userId = null; | ||||
|     current.userMajor = null; | ||||
|   } | ||||
| }; | ||||
|  | ||||
| /** 辅助函数:通过专业值获取专业名称 */ | ||||
| const getMajorLabel = (majorValue: string | null) => { | ||||
|   if (!majorValue || !des_user_major.value) return ''; | ||||
|   const major = des_user_major.value.find((item: any) => item.value === majorValue); | ||||
|   const major = des_user_major.value.find((item: any) => item.value == majorValue); | ||||
|   return major ? major.label : majorValue; | ||||
| }; | ||||
|  | ||||
| /** 辅助函数:通过用户ID获取用户名 */ | ||||
| const getUserName = (userId: number | null) => { | ||||
|   if (!userId || !userList.value.length) return ''; | ||||
|   const user = userList.value.find((item: any) => item.userId === userId); | ||||
|   const user = userList.value.find((item: any) => item.userId == userId); | ||||
|   return user ? user.nickName : userId; | ||||
| }; | ||||
|  | ||||
| /** 提交表单(补充提交前重复校验) */ | ||||
| /** 提交表单(保持原有数据结构) */ | ||||
| const submitForm = async () => { | ||||
|   if (!leaveFormRef.value) return; | ||||
|   try { | ||||
|     // 1. 先做基础表单验证 | ||||
|     // 1. 基础表单验证 | ||||
|     await leaveFormRef.value.validate(); | ||||
|  | ||||
|     // 2. 提交前二次校验:确保当前角色内无重复(防止手动修改数据绕过实时校验) | ||||
|     // 2. 提交前二次校验:「专业+人员」组合唯一性 | ||||
|     let hasDuplicate = false; | ||||
|     const allKeys: string[] = []; | ||||
|  | ||||
|     // 收集所有「专业+人员」组合(设计+校审分开校验) | ||||
|     const collectKeys = (roleGroups: MajorGroup[], roleName: string) => { | ||||
|       roleGroups.forEach((group) => { | ||||
|         if (!group.userMajor) return; | ||||
|         group.persons.forEach((person) => { | ||||
|           if (!person.userId) return; | ||||
|           const key = `${group.userMajor}-${person.userId}`; | ||||
|           if (allKeys.includes(key)) { | ||||
|             hasDuplicate = true; | ||||
|             ElMessage.error(`${roleName}中存在重复的「专业+人员」组合,请检查`); | ||||
|           } | ||||
|           allKeys.push(key); | ||||
|         }); | ||||
|       }); | ||||
|     }; | ||||
|  | ||||
|     // 校验设计人员 | ||||
|     const designKeys = form.designers.map((item) => `${item.userMajor}-${item.userId}`); | ||||
|     if (new Set(designKeys).size !== designKeys.length) { | ||||
|       hasDuplicate = true; | ||||
|       ElMessage.error('设计人员中存在重复的「专业+人员」组合,请检查'); | ||||
|     } | ||||
|     // 校验校审人员(不校验设计与校审之间) | ||||
|     if (!hasDuplicate) { | ||||
|       const reviewKeys = form.reviewers.map((item) => `${item.userMajor}-${item.userId}`); | ||||
|       if (new Set(reviewKeys).size !== reviewKeys.length) { | ||||
|         hasDuplicate = true; | ||||
|         ElMessage.error('校审人员中存在重复的「专业+人员」组合,请检查'); | ||||
|       } | ||||
|     } | ||||
|     // 有重复则终止提交 | ||||
|     collectKeys(form.designers, '设计人员'); | ||||
|     if (hasDuplicate) return; | ||||
|  | ||||
|     // 3. 构建提交数据(原有逻辑不变) | ||||
|     // 清空临时数组,校验校审人员(不校验设计与校审之间) | ||||
|     allKeys.length = 0; | ||||
|     collectKeys(form.reviewers, '校审人员'); | ||||
|     if (hasDuplicate) return; | ||||
|  | ||||
|     // 3. 构建提交数据(适配后端原有数据格式) | ||||
|     const submitData = { | ||||
|       projectId: form.projectId, | ||||
|       personnel: [ | ||||
| @ -371,29 +550,33 @@ const submitForm = async () => { | ||||
|           userType: 'designLeader', | ||||
|           userMajor: null | ||||
|         }, | ||||
|         // 设计人员 | ||||
|         ...form.designers.map((designer) => ({ | ||||
|           userId: designer.userId, | ||||
|           userType: 'designer', | ||||
|           userMajor: designer.userMajor | ||||
|         })), | ||||
|         // 校审人员 | ||||
|         ...form.reviewers.map((reviewer) => ({ | ||||
|           userId: reviewer.userId, | ||||
|           userType: 'reviewer', | ||||
|           userMajor: reviewer.userMajor | ||||
|         })) | ||||
|         // 设计人员:展开专业分组,每个人员单独作为一条数据 | ||||
|         ...form.designers.flatMap((group) => | ||||
|           group.persons.map((person) => ({ | ||||
|             userId: person.userId, | ||||
|             userType: 'designer', | ||||
|             userMajor: group.userMajor | ||||
|           })) | ||||
|         ), | ||||
|         // 校审人员:展开专业分组,每个人员单独作为一条数据 | ||||
|         ...form.reviewers.flatMap((group) => | ||||
|           group.persons.map((person) => ({ | ||||
|             userId: person.userId, | ||||
|             userType: 'reviewer', | ||||
|             userMajor: group.userMajor | ||||
|           })) | ||||
|         ) | ||||
|       ] | ||||
|     }; | ||||
|  | ||||
|     // 数据处理(原有逻辑不变) | ||||
|     // 4. 数据处理(保持原有逻辑不变) | ||||
|     const arr = []; | ||||
|     userList.value.forEach((item) => { | ||||
|       submitData.personnel.forEach((item1) => { | ||||
|         if (item1.userId === item.userId) { | ||||
|         if (item1.userId == item.userId) { | ||||
|           let userType = 1; | ||||
|           if (item1.userType === 'designer') userType = 2; | ||||
|           else if (item1.userType === 'reviewer') userType = 3; | ||||
|           if (item1.userType == 'designer') userType = 2; | ||||
|           else if (item1.userType == 'reviewer') userType = 3; | ||||
|           arr.push({ | ||||
|             userName: item.nickName, | ||||
|             projectId: submitData.projectId, | ||||
| @ -405,13 +588,13 @@ const submitForm = async () => { | ||||
|       }); | ||||
|     }); | ||||
|  | ||||
|     // 4. 提交到后端(原有逻辑不变) | ||||
|     // 5. 提交到后端(保持原有逻辑不变) | ||||
|     const loading = ElLoading.service({ text: '提交中...', background: 'rgba(255,255,255,0.7)' }); | ||||
|     const res = await designUserAdd({ | ||||
|       list: arr, | ||||
|       projectId: currentProject.value?.id | ||||
|     }); | ||||
|     if (res.code === 200) { | ||||
|     if (res.code == 200) { | ||||
|       disabledForm.value = true; | ||||
|       ElMessage.success('提交成功'); | ||||
|     } else { | ||||
| @ -424,12 +607,13 @@ const submitForm = async () => { | ||||
|   } | ||||
| }; | ||||
|  | ||||
| /** 重置表单 */ | ||||
| /** 重置表单(适配新数据结构) */ | ||||
| const resetForm = () => { | ||||
|   if (leaveFormRef.value) { | ||||
|     leaveFormRef.value.resetFields(); | ||||
|     form.designers = [{ userId: null, userMajor: null }]; | ||||
|     form.reviewers = [{ userId: null, userMajor: null }]; | ||||
|     // 重置为默认空状态(1个专业分组,每组1个空人员) | ||||
|     form.designers = [createEmptyMajorGroup()]; | ||||
|     form.reviewers = [createEmptyMajorGroup()]; | ||||
|     ElMessage.info('表单已重置'); | ||||
|   } | ||||
| }; | ||||
| @ -446,7 +630,7 @@ const listeningProject: WatchStopHandle = watch( | ||||
|  | ||||
| // 页面生命周期 | ||||
| onUnmounted(() => { | ||||
|   listeningProject(); // 修复原代码watchStopHandle调用问题 | ||||
|   listeningProject(); | ||||
| }); | ||||
| onMounted(() => { | ||||
|   getDeptAllUser(userStore.deptId).then(() => { | ||||
| @ -455,16 +639,19 @@ onMounted(() => { | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="scss"> | ||||
| <style lang="scss" scoped> | ||||
| .appWidth { | ||||
|   width: 50vw; | ||||
|   max-width: 1200px; | ||||
|   width: 70vw; | ||||
|   max-width: 1600px; | ||||
|  | ||||
|   .el-select__wrapper { | ||||
|     width: 16vw !important; | ||||
|     width: 100% !important; | ||||
|   } | ||||
|  | ||||
|   .el-button--small { | ||||
|     margin-bottom: 10px; | ||||
|     margin-bottom: 0; | ||||
|   } | ||||
|  | ||||
|   .fonts { | ||||
|     .el-form-item--default .el-form-item__label { | ||||
|       font-size: 18px !important; | ||||
| @ -499,7 +686,6 @@ onMounted(() => { | ||||
|   &__label { | ||||
|     font-weight: 500; | ||||
|     color: #4e5969; | ||||
|     padding: 0 0 8px 0; | ||||
|   } | ||||
|  | ||||
|   &__content { | ||||
| @ -539,6 +725,21 @@ onMounted(() => { | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   &--success { | ||||
|     background-color: #67c23a; | ||||
|     border-color: #67c23a; | ||||
|  | ||||
|     &:hover { | ||||
|       background-color: #85ce61; | ||||
|       border-color: #85ce61; | ||||
|     } | ||||
|  | ||||
|     &:disabled { | ||||
|       background-color: #b3e099; | ||||
|       border-color: #b3e099; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   &--danger { | ||||
|     background-color: #f56c6c; | ||||
|     border-color: #f56c6c; | ||||
| @ -554,6 +755,15 @@ onMounted(() => { | ||||
|       cursor: not-allowed; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   &--text { | ||||
|     color: #f56c6c; | ||||
|  | ||||
|     &:hover { | ||||
|       color: #f78989; | ||||
|       background-color: rgba(245, 108, 108, 0.05); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| // 响应式网格布局 | ||||
| @ -573,7 +783,7 @@ onMounted(() => { | ||||
|   gap: 1rem; | ||||
| } | ||||
|  | ||||
| // 适配小屏幕 | ||||
| // 适配小屏幕(小于768px时,垂直排列) | ||||
| @media (max-width: 768px) { | ||||
|   .appWidth { | ||||
|     width: 95vw; | ||||
| @ -586,5 +796,10 @@ onMounted(() => { | ||||
|   ::v-deep .el-form-item__label { | ||||
|     width: 100px; | ||||
|   } | ||||
|  | ||||
|   // 小屏幕下各列上下间距 | ||||
|   ::v-deep .el-col-xs-24 + .el-col-xs-24 { | ||||
|     margin-top: 12px; | ||||
|   } | ||||
| } | ||||
| </style> | ||||
|  | ||||
		Reference in New Issue
	
	Block a user