Procházet zdrojové kódy

修改表头和表单颜色色号为#303133,新增编辑员工档案:部门可以多选,岗位改成单选

lihaiting před 2 dny
rodič
revize
21f24d47be

+ 47 - 19
src/api/organization/index.js

@@ -586,7 +586,19 @@ function toApiId(value) {
  * @param {object} [extras] profile / workExperiences 等补充数据
  */
 export function adaptEmployee(raw = {}, extras = {}) {
-  const deptId = (raw.deptIds && raw.deptIds[0]) || raw.deptId || "";
+  const deptIds = Array.from(
+    new Set(
+      (Array.isArray(raw.deptIds)
+        ? raw.deptIds
+        : raw.deptId != null && raw.deptId !== ""
+          ? [raw.deptId]
+          : []
+      )
+        .map((id) => String(id).trim())
+        .filter(Boolean),
+    ),
+  );
+  const deptId = deptIds[0] || "";
   const listStatus = toListStatus(raw);
   const leaveDate = formatDate(raw.leaveDate);
   const positionBindings = adaptPositionBindings(raw);
@@ -637,6 +649,7 @@ export function adaptEmployee(raw = {}, extras = {}) {
     legalEntity: raw.groupName || "",
     factory: raw.factoryName || "",
     factoryId: raw.factoryId || raw.factoriesId || null,
+    deptIds,
     deptId: deptId ? String(deptId) : "",
     deptName: raw.deptName || raw.deptNames || "",
     position: positionNames.join("、") || raw.postName || "",
@@ -751,11 +764,16 @@ export function serializeEmployee(form = {}) {
   const education = form.educations?.[0] || {};
   const bankAccounts = form.bankAccounts || [];
   const firstBank = bankAccounts[0] || {};
-  const positionIds = Array.isArray(form.positionIds)
-    ? form.positionIds.filter(Boolean).map(String)
-    : form.positionId
-      ? [String(form.positionId)]
-      : [];
+  const positionIds = (
+    form.positionId
+      ? [form.positionId]
+      : Array.isArray(form.positionIds)
+        ? form.positionIds
+        : []
+  )
+    .filter(Boolean)
+    .map(String)
+    .slice(0, 1);
   const professionIds = (form.workTypes || [])
     .map((item) => item.id)
     .filter((id) => id != null && id !== "");
@@ -811,25 +829,35 @@ export function serializeEmployee(form = {}) {
     workTypeId: professionIds.length ? toApiId(professionIds[0]) : undefined,
     remark: form.remarks || "",
     groupId: form.groupId || undefined,
-    deptIds: form.deptId ? [toApiId(form.deptId)].filter((id) => id != null) : undefined,
+    deptIds: (Array.isArray(form.deptIds) && form.deptIds.length
+      ? form.deptIds
+      : form.deptId
+        ? [form.deptId]
+        : []
+    )
+      .map((id) => toApiId(id))
+      .filter((id) => id != null),
     deptName: form.deptName || "",
     managerUserId: toApiId(form.managerId || form.managerUserId),
     // 岗位:界面只选岗位;提交仍需 positionList 必填的层级/序列主键(由前端按岗位档案解析)
     postId: positionIds.length ? positionIds.join(",") : undefined,
     postName: form.position || undefined,
     positionList: positionIds.length
-      ? positionIds.map((id, index) => {
-          const binding =
-            (Array.isArray(form.positionBindings) ? form.positionBindings : []).find(
-              (item) => String(item.positionId) === String(id),
-            ) || {};
-          return {
-            positionId: toApiId(id),
-            positionLevelId: toApiId(binding.positionLevelId),
-            positionSequenceId: toApiId(binding.positionSequenceId),
-            primaryFlag: index === 0 ? 1 : 0,
-          };
-        })
+      ? [
+          (() => {
+            const id = positionIds[0];
+            const binding =
+              (Array.isArray(form.positionBindings) ? form.positionBindings : []).find(
+                (item) => String(item.positionId) === String(id),
+              ) || {};
+            return {
+              positionId: toApiId(id),
+              positionLevelId: toApiId(binding.positionLevelId),
+              positionSequenceId: toApiId(binding.positionSequenceId),
+              primaryFlag: 1,
+            };
+          })(),
+        ]
       : [],
     factoryId: form.factoryId ? toApiId(form.factoryId) : undefined,
     socialSecurityList: (form.socialSecurityRecords || [])

+ 49 - 1
src/styles/hr-list-standard.scss

@@ -41,6 +41,51 @@ $hr-list-pages: (
   }
 }
 
+/* 全项目表格表头:加粗 + 黑色 */
+.el-table th.el-table__cell,
+.el-table th.el-table__cell > .cell,
+.el-table__header-wrapper th.el-table__cell,
+.el-table__fixed-header-wrapper th.el-table__cell,
+.el-table__fixed-right-header-wrapper th.el-table__cell {
+  color: #303133 !important;
+  font-weight: 700 !important;
+}
+
+/* 全项目表单文字:标签与输入内容为黑色(占位符、校验错误色保持原样) */
+.el-form-item__label,
+.ele-form-search .el-form-item__label,
+.filter-field > label,
+.filter-field label,
+.cm-filter-label,
+.el-form .el-input__inner,
+.el-form .el-textarea__inner,
+.el-form .el-radio__label,
+.el-form .el-checkbox__label,
+.el-form .el-select .el-input__inner,
+.el-form .el-range-editor .el-range-input,
+.el-form .el-input-number .el-input__inner,
+.el-dialog .el-form-item__label,
+.el-drawer .el-form-item__label,
+.el-dialog .el-input__inner,
+.el-drawer .el-input__inner,
+.el-dialog .el-textarea__inner,
+.el-drawer .el-textarea__inner,
+.el-dialog .el-radio__label,
+.el-drawer .el-radio__label,
+.el-dialog .el-checkbox__label,
+.el-drawer .el-checkbox__label {
+  color: #303133 !important;
+}
+
+.el-form .el-input__inner::placeholder,
+.el-form .el-textarea__inner::placeholder,
+.el-dialog .el-input__inner::placeholder,
+.el-drawer .el-input__inner::placeholder,
+.el-dialog .el-textarea__inner::placeholder,
+.el-drawer .el-textarea__inner::placeholder {
+  color: #c0c4cc !important;
+}
+
 /* 所有表单 label、列表正文:小于 14px 的统一提到 14px */
 .el-form-item__label,
 .ele-form-search .el-form-item__label,
@@ -299,7 +344,8 @@ $hr-list-pages: (
     padding: 0 !important;
     font-size: 14px !important;
     line-height: 1 !important;
-    font-weight: 500 !important;
+    color: #303133 !important;
+    font-weight: 700 !important;
   }
 
   #{$root} .el-table th.el-table__cell > .cell {
@@ -307,6 +353,8 @@ $hr-list-pages: (
     line-height: 1 !important;
     padding-top: 0 !important;
     padding-bottom: 0 !important;
+    color: #303133 !important;
+    font-weight: 700 !important;
     display: flex;
     align-items: center;
   }

+ 137 - 127
src/views/hrManagement/employeeArchive/components/EmployeeArchiveForm.vue

@@ -128,15 +128,16 @@
               </el-col>
               <el-col :xs="24" :sm="12"><el-form-item label="隶属部门"><el-cascader
                     :key="`dept-cascader-${form.groupId || 'none'}-${form.factoryId || 'none'}-${deptCascaderKey}`"
-                    v-model="form.deptId"
-                    :options="deptOptions" :props="organizationProps" filterable clearable class="full-width"
+                    v-model="form.deptIds"
+                    :options="deptOptions" :props="deptCascaderProps" filterable clearable collapse-tags
+                    class="full-width"
                     :disabled="!form.factoryId"
-                    :placeholder="form.factoryId ? '请选择隶属部门' : (form.groupId ? '请先选择所属工厂' : '请先选择所属机构')"
+                    :placeholder="form.factoryId ? '请选择隶属部门(可多选)' : (form.groupId ? '请先选择所属工厂' : '请先选择所属机构')"
                     @change="handleOrganizationChange" /></el-form-item></el-col>
               <el-col :xs="24" :sm="12">
                 <el-form-item label="直属上级">
                   <el-select v-model="form.managerId" filterable clearable class="full-width" :loading="managerLoading"
-                    :disabled="!form.deptId" :placeholder="form.deptId ? '请选择直属上级' : '请先选择隶属部门'"
+                    :disabled="!hasDept" :placeholder="hasDept ? '请选择直属上级' : '请先选择隶属部门'"
                     @change="handleManagerChange">
                     <el-option v-for="item in managerOptions" :key="item.id" :label="item.name" :value="item.id" />
                   </el-select>
@@ -149,12 +150,12 @@
                     value-format="yyyy-MM-dd" type="date" placeholder="转正生效日期"
                     class="full-width" /></el-form-item></el-col>
               <el-col :xs="24" :sm="12">
-                <el-form-item label="岗位" prop="positionIds">
-                  <el-select :key="`position-select-${positionSelectKey}`" v-model="form.positionIds" multiple
-                    filterable clearable class="full-width position-multi-select"
+                <el-form-item label="岗位" prop="positionId">
+                  <el-select :key="`position-select-${positionSelectKey}`" v-model="form.positionId"
+                    filterable clearable class="full-width"
                     :loading="positionLoading"
-                    :disabled="!form.deptId"
-                    :placeholder="form.deptId ? '请选择本部门已通过申请的岗位(可多选,第一项为主岗)' : '请先选择隶属部门'"
+                    :disabled="!hasDept"
+                    :placeholder="hasDept ? '请选择本部门已通过申请的岗位' : '请先选择隶属部门'"
                     @change="handlePositionChange">
                     <el-option v-for="item in positionOptions" :key="`pos-${String(item.id)}`"
                       :label="positionOptionLabel(item)" :value="String(item.id)" />
@@ -171,7 +172,7 @@
                   </el-select></el-form-item></el-col>
               <el-col :xs="24" :sm="12"><el-form-item label="岗位层级"><el-select v-model="form.staffingLevel"
                     class="full-width" :disabled="!staffingLevelOptions.length"
-                    :placeholder="form.positionIds?.length ? '请选择岗位层级' : '请先选择岗位'"
+                    :placeholder="form.positionId ? '请选择岗位层级' : '请先选择岗位'"
                     @change="handleStaffingLevelChange">
                     <el-option v-for="item in staffingLevelOptions" :key="item" :label="item"
                       :value="item" /></el-select></el-form-item></el-col>
@@ -482,7 +483,7 @@ const emptyContract = () => ({ category: '', name: '', signDate: '', startDate:
 const emptyForm = () => ({
   id: null, avatar: '', name: '', employeeNo: '', gender: '男', birthDate: '', age: null, idCard: '', idCardExpiry: '', ethnic: '汉族',
   maritalStatus: '未婚', politicalStatus: '群众', nativePlace: '', householdType: '', healthStatus: '优',
-  company: '中盈产业集团有限公司', legalEntity: '', groupId: null, factory: '', factoryId: null, deptId: null, deptName: '',
+  company: '中盈产业集团有限公司', legalEntity: '', groupId: null, factory: '', factoryId: null, deptId: null, deptIds: [], deptName: '',
   position: '', positionId: null, positionIds: [], positionBindings: [],
   positionType: '', level: '', staffingLevel: '', dutyLevel: '', manager: '', managerId: null,
   employmentType: '', status: '全职', hireDate: '', regularDate: '', account: '', enabled: true,
@@ -569,7 +570,7 @@ export default {
       sequenceLevelMap: {},
       // 强制重建隶属部门级联,避免 options 清空后内部 activePath 残留导致 node.level 报错
       deptCascaderKey: 0,
-      // 岗位多选回显:用 key 重建,避免临时清空 positionIds 触发 required 校验告警
+      // 岗位单选回显:用 key 重建,避免 options 后到时只显示 ID
       positionSelectKey: 0,
       positionLoading: false,
       // 直属上级:按隶属部门联动人员
@@ -630,12 +631,11 @@ export default {
           },
           trigger: 'change',
         }],
-        positionIds: [{
+        positionId: [{
           required: true,
-          type: 'array',
           validator: (rule, value, callback) => {
             if (this._skipFormValidate) return callback();
-            if (!Array.isArray(value) || !value.length) {
+            if (value == null || value === '') {
               callback(new Error('请选择岗位'));
               return;
             }
@@ -719,14 +719,20 @@ export default {
           });
       return sanitize(source);
     },
+    deptCascaderProps() {
+      return { ...this.organizationProps, multiple: true };
+    },
+    hasDept() {
+      return this.normalizeDeptIds(this.form.deptIds).length > 0;
+    },
     requiredProgress() {
-      const keys = ['name', 'employeeNo', 'gender', 'groupId', 'status', 'hireDate', 'positionIds', 'phone'];
+      const keys = ['name', 'employeeNo', 'gender', 'groupId', 'status', 'hireDate', 'positionId', 'phone'];
       return { base: `${keys.filter((key) => { const value = this.form[key]; return Array.isArray(value) ? value.length : Boolean(value); }).length}/${keys.length}` };
     },
     sectionProgress() {
       const count = (keys) => `${keys.filter((key) => { const value = this.form[key]; return Array.isArray(value) ? value.length : Boolean(value); }).length}/${keys.length}`;
       return {
-        base: count(['name', 'employeeNo', 'groupId', 'factory', 'deptId', 'status', 'hireDate', 'positionIds', 'employmentType', 'staffingLevel', 'gender', 'birthDate', 'idCard', 'ethnic', 'politicalStatus', 'maritalStatus', 'nativePlace', 'education', 'school', 'phone', 'email', 'wechat', 'currentAddress', 'registeredAddress', 'workAddress', 'emergencyContact', 'emergencyPhone', 'keyProcess']),
+        base: count(['name', 'employeeNo', 'groupId', 'factory', 'deptIds', 'status', 'hireDate', 'positionId', 'employmentType', 'staffingLevel', 'gender', 'birthDate', 'idCard', 'ethnic', 'politicalStatus', 'maritalStatus', 'nativePlace', 'education', 'school', 'phone', 'email', 'wechat', 'currentAddress', 'registeredAddress', 'workAddress', 'emergencyContact', 'emergencyPhone', 'keyProcess']),
         experience: count(['educations', 'previousJobs']),
         contract: `${[this.form.contract.number, this.form.contract.type, this.form.bankName, this.form.bankAccount].filter(Boolean).length}/4`,
         social: count(['socialSecurityRecords', 'workTypes', 'certificates', 'skills', 'files']),
@@ -734,7 +740,7 @@ export default {
       };
     },
     completionRate() {
-      const values = [this.form.name, this.form.employeeNo, this.form.groupId, this.form.positionIds?.length, this.form.status, this.form.hireDate, this.form.phone, this.form.gender, this.form.email, this.form.birthDate, this.form.education, this.form.school, this.form.currentAddress, this.form.contract.number, this.form.bankAccount, this.form.insuredDate, this.form.skills.length, this.form.files.length];
+      const values = [this.form.name, this.form.employeeNo, this.form.groupId, this.form.positionId, this.form.status, this.form.hireDate, this.form.phone, this.form.gender, this.form.email, this.form.birthDate, this.form.education, this.form.school, this.form.currentAddress, this.form.contract.number, this.form.bankAccount, this.form.insuredDate, this.form.skills.length, this.form.files.length];
       return Math.round(values.filter(Boolean).length / values.length * 100);
     },
     contractState() { return this.form.contract.name ? (this.form.contract.endDate ? '合同资料已填写' : '待补结束日期') : '待录入合同'; }
@@ -758,13 +764,15 @@ export default {
         hireDate: source.hireDate || source.joinDate || '',
         factoryId: source.factoryId != null && source.factoryId !== '' ? String(source.factoryId) : null,
         factory: source.factory || source.factoryName || '',
-        deptId: source.deptId != null && source.deptId !== '' ? String(source.deptId) : null,
+        deptIds: this.normalizeDeptIds(source.deptIds?.length ? source.deptIds : source.deptId),
         deptName: source.deptName || '',
         managerId: source.managerId != null && source.managerId !== '' ? String(source.managerId) : null,
         manager: source.manager || '',
-        positionIds: this.normalizePositionIds(source),
-        positionBindings: Array.isArray(source.positionBindings) ? source.positionBindings : [],
-        positionId: source.positionId != null ? String(source.positionId) : null,
+        positionIds: this.normalizePositionIds(source).slice(0, 1),
+        positionBindings: Array.isArray(source.positionBindings)
+          ? source.positionBindings.slice(0, 1)
+          : [],
+        positionId: this.normalizePositionIds(source)[0] || null,
         position: source.position || source.postName || '',
         contract: { ...emptyContract(), ...(source.contract || {}) },
         leaveInfo: { ...empty.leaveInfo, ...(source.leaveInfo || {}) },
@@ -996,63 +1004,63 @@ export default {
         if (!needNameFill) return;
       }
 
-      this.form.positionIds = nextIds;
-      this.form.positionBindings = nextBindings.map((item, index) => ({
+      this.form.positionIds = nextIds.slice(0, 1);
+      this.form.positionBindings = nextBindings.slice(0, 1).map((item) => ({
         ...item,
-        positionName: item.positionName || fallbackNames[index] || '',
-        primaryFlag: index === 0 ? 1 : 0,
+        positionName: item.positionName || fallbackNames[0] || '',
+        primaryFlag: 1,
       }));
-      this.form.positionId = nextIds[0] || null;
+      this.form.positionId = this.form.positionIds[0] || null;
       this.form.position =
-        this.form.positionBindings.map((item) => item.positionName).filter(Boolean).join('、') ||
-        this.form.position;
+        this.form.positionBindings[0]?.positionName || this.form.position;
       this.syncPositionDisplay();
     },
     /** Element 多选在 options 异步到达后需重建一次,否则标签可能只剩裸 ID */
     refreshPositionSelectEcho() {
-      const ids = Array.from(new Set((this.form.positionIds || []).map(String).filter(Boolean)));
-      this.form.positionIds = ids;
+      const id = this.form.positionId != null && this.form.positionId !== ''
+        ? String(this.form.positionId)
+        : (this.form.positionIds || []).map(String).filter(Boolean)[0] || null;
+      this.form.positionId = id;
+      this.form.positionIds = id ? [id] : [];
       this.positionSelectKey += 1;
       this.$nextTick(() => {
         this.syncPositionDisplay();
-        this.$refs.form?.clearValidate('positionIds');
+        this.$refs.form?.clearValidate('positionId');
       });
     },
     syncPositionDisplay() {
       const fallbackNames = this.positionFallbackNames();
-      const selected = (this.form.positionIds || []).map((id, index) => {
-        const key = String(id);
-        const opt = this.positionOptions.find((item) => String(item.id) === key) || {};
-        const binding =
-          (this.form.positionBindings || []).find((item) => String(item.positionId) === key) || {};
-          const meta = this.resolvePositionLevelMeta(opt, {
-            ...binding,
-            positionLevelName:
-              index === 0 && this.form.staffingLevel
-                ? this.form.staffingLevel
-                : binding.positionLevelName,
-          });
-        return {
-          positionId: key,
-          positionName:
-            opt.positionName ||
-            opt.name ||
-            binding.positionName ||
-            fallbackNames[index] ||
-            '',
-          positionLevelId: meta.positionLevelId,
-          positionLevelName: meta.positionLevelName,
-          positionSequenceId: meta.positionSequenceId,
-          positionSequenceName: meta.positionSequenceName,
-          primaryFlag: 0,
-        };
-      });
-      selected.forEach((item, index) => {
-        item.primaryFlag = index === 0 ? 1 : 0;
-      });
+      const key = this.form.positionId != null && this.form.positionId !== ''
+        ? String(this.form.positionId)
+        : '';
+      const selected = key
+        ? (() => {
+            const opt = this.positionOptions.find((item) => String(item.id) === key) || {};
+            const binding =
+              (this.form.positionBindings || []).find((item) => String(item.positionId) === key) || {};
+            const meta = this.resolvePositionLevelMeta(opt, {
+              ...binding,
+              positionLevelName: this.form.staffingLevel || binding.positionLevelName,
+            });
+            return [{
+              positionId: key,
+              positionName:
+                opt.positionName ||
+                opt.name ||
+                binding.positionName ||
+                fallbackNames[0] ||
+                '',
+              positionLevelId: meta.positionLevelId,
+              positionLevelName: meta.positionLevelName,
+              positionSequenceId: meta.positionSequenceId,
+              positionSequenceName: meta.positionSequenceName,
+              primaryFlag: 1,
+            }];
+          })()
+        : [];
       this.form.positionBindings = selected;
-      const names = selected.map((item) => item.positionName).filter(Boolean);
-      if (names.length) this.form.position = names.join('、');
+      this.form.positionIds = key ? [key] : [];
+      this.form.position = selected[0]?.positionName || '';
       this.form.positionId = selected[0]?.positionId || null;
       this.form.level = selected[0]?.positionLevelName || this.form.level;
       if (selected[0]?.positionLevelName) {
@@ -1325,28 +1333,43 @@ export default {
       });
     },
     generateEmployeeNo() { const maxNo = this.existingNumbers.reduce((max, item) => Math.max(max, Number(String(item).match(/\d+/)?.[0] || 0)), 0); this.form.employeeNo = `ZY${String(maxNo + 1).padStart(4, '0')}`; this.$nextTick(() => this.$refs.form?.clearValidate('employeeNo')); },
-    handleOrganizationChange(id) {
-      if (id == null || id === '') {
-        this.form.deptId = null;
-        this.form.deptName = '';
+    normalizeDeptIds(value) {
+      const list = Array.isArray(value) ? value : value != null && value !== '' ? [value] : [];
+      return Array.from(
+        new Set(
+          list
+            .flat(2)
+            .map((id) => String(id == null ? '' : id).trim())
+            .filter((id) => id && id !== 'null' && id !== 'undefined'),
+        ),
+      );
+    },
+    findDeptNode(id, nodes = this.deptOptions) {
+      for (const node of nodes || []) {
+        if (!node) continue;
+        if (String(node.id) === String(id)) return node;
+        const matched = this.findDeptNode(id, node.children || []);
+        if (matched) return matched;
+      }
+      return null;
+    },
+    applyDeptIds(ids, { resetDependents = false } = {}) {
+      const list = this.normalizeDeptIds(ids).filter(
+        (id) => String(id) !== String(this.form.groupId || ''),
+      );
+      this.form.deptIds = list;
+      this.form.deptId = list[0] || null;
+      this.form.deptName = list
+        .map((id) => this.findDeptNode(id)?.name)
+        .filter(Boolean)
+        .join('、');
+      if (resetDependents) {
         this.loadManagerOptions({ keepSelection: false });
         this.resetPositionsByDepartment();
-        return;
       }
-      const find = (nodes) => {
-        for (const node of nodes || []) {
-          if (!node) continue;
-          if (String(node.id) === String(id)) return node;
-          const result = find(node.children || []);
-          if (result) return result;
-        }
-        return null;
-      };
-      const selected = find(this.deptOptions);
-      this.form.deptId = id != null && id !== '' ? String(id) : null;
-      this.form.deptName = selected?.name || '';
-      this.loadManagerOptions({ keepSelection: false });
-      this.resetPositionsByDepartment();
+    },
+    handleOrganizationChange(ids) {
+      this.applyDeptIds(ids, { resetDependents: true });
     },
     handleLegalEntityChange(id) {
       const selected = this.legalEntities.find((item) => String(item.id) === String(id));
@@ -1356,8 +1379,7 @@ export default {
       this.form.factoryId = null;
       this.form.factory = '';
       this.factoryOptions = [];
-      this.form.deptId = null;
-      this.form.deptName = '';
+      this.applyDeptIds([]);
       this.form.managerId = null;
       this.form.manager = '';
       this.managerOptions = [];
@@ -1365,33 +1387,11 @@ export default {
       this.loadFactories();
     },
     normalizeDeptAgainstOptions() {
-      // 历史数据若选中了一级公司本身,清空;仅保留二级及以下部门
-      if (this.form.deptId == null || this.form.deptId === '') {
-        this.form.deptId = null;
-        return;
-      }
-      if (String(this.form.deptId) === String(this.form.groupId)) {
-        this.form.deptId = null;
-        this.form.deptName = '';
-        return;
-      }
-      const find = (nodes) => {
-        for (const node of nodes || []) {
-          if (!node) continue;
-          if (String(node.id) === String(this.form.deptId)) return node;
-          const matched = find(node.children || []);
-          if (matched) return matched;
-        }
-        return null;
-      };
-      const matched = find(this.deptOptions);
-      if (!matched) {
-        this.form.deptId = null;
-        this.form.deptName = '';
-      } else {
-        this.form.deptId = String(matched.id);
-        this.form.deptName = matched.name;
-      }
+      const ids = this.normalizeDeptIds(
+        this.form.deptIds?.length ? this.form.deptIds : this.form.deptId,
+      ).filter((id) => String(id) !== String(this.form.groupId || ''));
+      const valid = ids.filter((id) => this.findDeptNode(id));
+      this.applyDeptIds(valid.length ? valid : ids);
     },
     handleManagerChange(id) {
       const matched = this.managerOptions.find((item) => String(item.id) === String(id));
@@ -1569,18 +1569,22 @@ export default {
       this.form.factory = matched?.name || '';
       this.form.factoryId = id != null && id !== '' ? String(id) : null;
       this.deptCascaderKey += 1;
-      this.form.deptId = null;
-      this.form.deptName = '';
+      this.applyDeptIds([]);
       this.form.managerId = null;
       this.form.manager = '';
       this.managerOptions = [];
       this.resetPositionsByDepartment();
     },
     handlePositionChange() {
+      this.form.positionId =
+        this.form.positionId != null && this.form.positionId !== ''
+          ? String(this.form.positionId)
+          : null;
+      this.form.positionIds = this.form.positionId ? [this.form.positionId] : [];
       this.syncStaffingLevelsFromPosition();
       this.syncPositionDisplay();
       this.enrichSelectedPositions();
-      this.$nextTick(() => this.$refs.form?.validateField('positionIds'));
+      this.$nextTick(() => this.$refs.form?.validateField('positionId'));
     },
     handleAvatarUpload(file) { if (file.size > 2 * 1024 * 1024) { this.$message.warning('员工照片不能超过 2MB'); return false; } const reader = new FileReader(); reader.onload = (event) => { this.form.avatar = event.target.result; }; reader.readAsDataURL(file); return false; },
     addEducation() { this.form.educations.push({ education: '本科', degree: '学士', graduationDate: '', school: '', major: '' }); },
@@ -1634,10 +1638,10 @@ export default {
       if (text.length === 18) this.parseIdCard();
     },
     sameDepartment(item = {}) {
-      const deptId = this.form.deptId != null ? String(this.form.deptId) : '';
-      if (!deptId) return false;
+      const selected = this.normalizeDeptIds(this.form.deptIds);
+      if (!selected.length) return false;
       if (item.deptId == null || item.deptId === '') return true;
-      return String(item.deptId) === deptId;
+      return selected.includes(String(item.deptId));
     },
     extractPositionLevelNames(opt = {}) {
       const names = [];
@@ -1661,7 +1665,7 @@ export default {
       return names;
     },
     syncStaffingLevelsFromPosition() {
-      const primaryId = (this.form.positionIds || [])[0];
+      const primaryId = this.form.positionId || (this.form.positionIds || [])[0];
       if (!primaryId) {
         this.staffingLevelOptions = [];
         this.form.staffingLevel = '';
@@ -1736,7 +1740,8 @@ export default {
       }
     },
     async loadPositions() {
-      if (!this.form.deptId) {
+      const deptIds = this.normalizeDeptIds(this.form.deptIds);
+      if (!deptIds.length) {
         this.positionOptions = [];
         this.hrPositionIdSet = {};
         this.positionLoading = false;
@@ -1744,13 +1749,18 @@ export default {
       }
       this.positionLoading = true;
       try {
-        const data = await getPositionPage({
-          pageNum: 1,
-          size: 1000,
-          status: 1,
-          deptId: this.form.deptId,
-        });
-        const list = (data?.list || [])
+        const pages = await Promise.all(
+          deptIds.map((deptId) =>
+            getPositionPage({
+              pageNum: 1,
+              size: 1000,
+              status: 1,
+              deptId,
+            }),
+          ),
+        );
+        const list = pages
+          .flatMap((data) => data?.list || [])
           .map((item) => adaptPosition(item))
           .filter((item) => {
             const name = String(item.positionName || item.name || '').trim();