Explorar o código

员工档案里资质,学历,技能等样式采用table的样式展示

lihaiting hai 1 mes
pai
achega
026977aa65

+ 200 - 90
src/api/organization/index.js

@@ -19,6 +19,30 @@ function compact(object) {
   }, {});
 }
 
+function isHttpUrl(value) {
+  return typeof value === "string" && /^https?:\/\//i.test(value);
+}
+
+/** 证照附件:接口字段 fileObj 为文件对象数组 */
+function toFileObj(value) {
+  if (value == null || value === "") return [];
+  const list = Array.isArray(value) ? value : [value];
+  return list
+    .map((item) => {
+      if (item == null || item === "") return null;
+      if (typeof item === "object") return item;
+      if (isHttpUrl(item)) return null;
+      return { id: item };
+    })
+    .filter(Boolean);
+}
+
+function toFileIds(value) {
+  return toFileObj(value)
+    .map((item) => item.id ?? item.fileId ?? null)
+    .filter((id) => id != null && id !== "");
+}
+
 function formatDate(value) {
   if (!value) return "";
   const text = String(value);
@@ -455,8 +479,13 @@ function adaptCertificates(list = []) {
     startDate: formatDate(item.validityStartTime),
     endDate: formatDate(item.validityEndTime),
     issueDate: formatDate(item.enactorTime),
-    attachment: [],
+    attachment: item.fileObj || item.fileIds || item.files || item.attachment || [],
     remark: item.remark || "",
+    hrCertificateId: item.hrCertificateId,
+    holder: item.holder || "",
+    holderId: item.holderId || "",
+    holderType: item.holderType || "1",
+    enactor: item.enactor || "",
   }));
 }
 
@@ -635,6 +664,7 @@ export function adaptEmployee(raw = {}, extras = {}) {
     education: raw.educationBackground || "",
     school: raw.school || "",
     major: raw.major || "",
+    degree: raw.degree || "",
     graduationDate: formatDate(raw.graduateDate),
     registeredAddress: raw.censusAddress || "",
     currentAddress: raw.currentAddress || "",
@@ -691,11 +721,11 @@ export function adaptEmployee(raw = {}, extras = {}) {
     certificates: adaptCertificates(raw.identityPhotos),
     workTypes: adaptWorkTypes(raw.professions),
     previousJobs: adaptWorkExperiences(extras.workExperiences || []),
-    educations: raw.educationBackground
+    educations: raw.educationBackground || raw.school || raw.major || raw.degree || raw.graduateDate
       ? [
           {
             education: raw.educationBackground || "",
-            degree: "",
+            degree: raw.degree || "",
             graduationDate: formatDate(raw.graduateDate),
             school: raw.school || "",
             major: raw.major || "",
@@ -740,17 +770,142 @@ export function adaptEmployee(raw = {}, extras = {}) {
 /** 合并 profile 聚合视图 */
 export function adaptEmployeeProfile(profile = {}) {
   const user = profile.user || {};
-  return adaptEmployee(user, {
+  return adaptEmployee(
+    {
+      ...user,
+      identityPhotos: user.identityPhotos || profile.identityPhotos || [],
+    },
+    {
     workExperiences: profile.workExperiences || [],
     history: profile.history || [],
     performanceRecords: profile.performanceRecords || [],
     changes: profile.changes || [],
-  });
+    },
+  );
+}
+
+function pickEducation(form = {}) {
+  const row = Array.isArray(form.educations) && form.educations[0] ? form.educations[0] : {};
+  return {
+    educationBackground: row.education || form.education || "",
+    degree: row.degree || form.degree || "",
+    school: row.school || form.school || "",
+    major: row.major || form.major || "",
+    graduateDate: row.graduationDate || form.graduationDate || "",
+  };
+}
+
+function serializeBankCardList(form = {}) {
+  const bankAccounts = Array.isArray(form.bankAccounts) ? form.bankAccounts : [];
+  return bankAccounts
+    .filter((item) => item.bankName && item.bankAccount)
+    .map((item) => ({
+      openingBank: String(item.bankName).trim(),
+      cardNumber: String(item.bankAccount).trim(),
+      accountName: String(item.bankAccountName || form.name || "").trim(),
+    }))
+    .filter((item) => item.accountName);
+}
+
+function serializeSocialSecurityList(form = {}) {
+  return (form.socialSecurityRecords || [])
+    .filter((item) => item.type)
+    .map((item) =>
+      compact({
+        socialType: item.type || "",
+        socialBase: Number(item.base) || undefined,
+        insuredDate: item.insuredDate || "",
+        stopDate: item.stopDate || "",
+        stopRecord: item.stopReason || "",
+      }),
+    );
+}
+
+function serializeIdentityPhotos(form = {}) {
+  return (form.certificates || [])
+    .filter((item) => item.type || item.number)
+    .map((item) =>
+      compact({
+        id: toApiId(item.id),
+        type: CERT_TYPE[item.type] ?? 4,
+        code: item.number || "",
+        level: CERT_LEVEL[item.level] || item.level || "",
+        status: item.status === "失效" ? 0 : 1,
+        validityStartTime: item.startDate || "",
+        validityEndTime: item.endDate || "",
+        enactor: item.enactor || "",
+        enactorTime: item.issueDate || "",
+        remark: item.remark || "",
+        holderType: item.holderType || "1",
+        holder: item.holder || form.name || "",
+        holderId: item.holderId != null && item.holderId !== "" ? String(item.holderId) : form.id != null ? String(form.id) : undefined,
+        fileObj: toFileObj(item.attachment),
+        hrCertificateId: toApiId(item.hrCertificateId),
+      }),
+    );
+}
+
+function serializeRewards(form = {}) {
+  return (form.rewardPunishmentRecords || []).map((item) =>
+    compact({
+      userId: toApiId(form.id),
+      type: /惩罚|处分|警告/.test(item.type) ? 2 : 1,
+      desce: item.description || item.name || "",
+      startTime: item.startDate || item.date || "",
+      endTime: item.endDate || "",
+      status: REWARD_STATUS[item.status] ?? 2,
+      remark: item.remark || "",
+      fileAttach: toFileObj(item.attachment),
+    }),
+  );
+}
+
+function serializePositionList(form = {}, positionIds = []) {
+  if (!positionIds.length) return [];
+  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,
+    },
+  ];
+}
+
+function serializeCriticalTaskList(form = {}) {
+  const text = String(form.keyProcess || "").trim();
+  if (!text) return [];
+  const rawList = Array.isArray(form.raw?.criticalTaskList) ? form.raw.criticalTaskList : [];
+  return text
+    .split(/[,,]/)
+    .map((item) => String(item).trim())
+    .filter(Boolean)
+    .map((value) => {
+      if (/^\d+$/.test(value)) return compact({ taskId: Number(value), taskName: value });
+      const matched = rawList.find(
+        (item) => String(item.taskName) === value || String(item.taskId) === value,
+      );
+      if (matched) {
+        return compact({
+          taskId: toApiId(matched.taskId),
+          taskName: matched.taskName || value,
+        });
+      }
+      return compact({ taskName: value });
+    });
 }
 
 /**
  * 员工档案表单 → 后端「员工新增或修改参数」
- * 合同 contracts 已废弃,不提交;工作履历走独立接口
+ * 对照 POST /main/user/saveNew:只提交文档中的字段。
+ * 岗位 / 部门 / 银行卡 / 社保 / 证照 / 奖惩 / 关键工序:未传保持、空数组清空、非空整单覆盖。
+ * 合同 contracts、技能 skills、培训 trainRecords、考勤 attendanceRecords 已废弃,禁止提交。
+ * 工作履历走独立接口。学历提交 educationBackground / degree / school / major / graduateDate。
  */
 export function serializeEmployee(form = {}) {
   const statusText = form.formStatus || form.status;
@@ -762,9 +917,9 @@ export function serializeEmployee(form = {}) {
         ? 5
         : Number(statusText) || 1);
 
-  const education = form.educations?.[0] || {};
-  const bankAccounts = form.bankAccounts || [];
-  const firstBank = bankAccounts[0] || {};
+  const education = pickEducation(form);
+  const bankCardList = serializeBankCardList(form);
+  const firstBank = bankCardList[0] || {};
   const positionIds = (
     form.positionId
       ? [form.positionId]
@@ -778,13 +933,24 @@ export function serializeEmployee(form = {}) {
   const professionIds = (form.workTypes || [])
     .map((item) => item.id)
     .filter((id) => id != null && id !== "");
-  const avatarUrl =
-    typeof form.avatar === "string" && /^https?:\/\//i.test(form.avatar)
-      ? form.avatar
+  const avatarUrl = isHttpUrl(form.avatar) ? form.avatar : undefined;
+  const avatarFiles = Array.isArray(form.avatar) && form.avatar.length && typeof form.avatar[0] === "object"
+    ? form.avatar
+    : Array.isArray(form.avatarFiles) && form.avatarFiles.length
+      ? form.avatarFiles
       : undefined;
+  const deptIds = (Array.isArray(form.deptIds) && form.deptIds.length
+    ? form.deptIds
+    : form.deptId
+      ? [form.deptId]
+      : []
+  )
+    .map((id) => toApiId(id))
+    .filter((id) => id != null);
 
   return compact({
-    id: form.id || undefined,
+    id: toApiId(form.id),
+    accountId: toApiId(form.accountId || form.raw?.accountId),
     name: form.name || "",
     jobNumber: form.employeeNo || "",
     sex: EMPLOYEE_SEX[form.gender] ?? form.gender,
@@ -799,10 +965,11 @@ export function serializeEmployee(form = {}) {
     censusAddress: form.registeredAddress || "",
     currentAddress: form.currentAddress || "",
     address: form.workAddress || "",
-    educationBackground: education.education || form.education || "",
-    school: education.school || form.school || "",
-    major: education.major || form.major || "",
-    graduateDate: education.graduationDate || form.graduationDate || "",
+    educationBackground: education.educationBackground,
+    degree: education.degree,
+    school: education.school,
+    major: education.major,
+    graduateDate: education.graduateDate,
     phone: form.phone || "",
     email: form.email || "",
     wxId: form.wechat || "",
@@ -811,90 +978,33 @@ export function serializeEmployee(form = {}) {
     positiveDate: form.regularDate || "",
     leaveDate: form.leaveInfo?.leaveDate || "",
     isEnabled: form.enabled === false || statusText === "disabled" ? 0 : 1,
-    openingBank: firstBank.bankName || form.bankName || "",
-    cardNumber: firstBank.bankAccount || form.bankAccount || "",
-    bankCardList: bankAccounts.length
-      ? bankAccounts
-          .filter((item) => item.bankAccount || item.bankName)
-          .map((item) => ({
-            openingBank: item.bankName || "",
-            cardNumber: item.bankAccount || "",
-            accountName: item.bankAccountName || form.name || "",
-          }))
-      : undefined,
+    openingBank: firstBank.openingBank || form.bankName || "",
+    cardNumber: firstBank.cardNumber || form.bankAccount || "",
+    bankCardList,
     secondLinkName: form.emergencyContact || "",
     secondLinkPhone: form.emergencyPhone || "",
     secondLinkRelation: form.emergencyRelation || "",
     pic: avatarUrl,
+    avatar: avatarFiles,
     professionId: professionIds.length ? professionIds.map(String).join(",") : undefined,
     workTypeId: professionIds.length ? toApiId(professionIds[0]) : undefined,
     remark: form.remarks || "",
-    groupId: form.groupId || undefined,
-    deptIds: (Array.isArray(form.deptIds) && form.deptIds.length
-      ? form.deptIds
-      : form.deptId
-        ? [form.deptId]
-        : []
-    )
-      .map((id) => toApiId(id))
-      .filter((id) => id != null),
+    groupId: toApiId(form.groupId),
+    deptIds,
     deptName: form.deptName || "",
     managerUserId: toApiId(form.managerId || form.managerUserId),
-    // 岗位:界面只选岗位;提交仍需 positionList 必填的层级/序列主键(由前端按岗位档案解析)
     postId: positionIds.length ? positionIds.join(",") : undefined,
     postName: form.position || undefined,
-    positionList: positionIds.length
-      ? [
-          (() => {
-            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 || [])
-      .filter((item) => item.type || item.insuredDate)
-      .map((item) => ({
-        socialType: item.type || "",
-        socialBase: Number(item.base) || 0,
-        insuredDate: item.insuredDate || "",
-        stopDate: item.stopDate || "",
-        stopRecord: item.stopReason || "",
-      })),
-    // skills / trainRecords 已废弃:非空提交会被后端拒绝,技能/培训走独立 HR / EHS 接口
-    rewardsPunishments: (form.rewardPunishmentRecords || []).map((item) => ({
-      id: item.id || undefined,
-      type: /惩罚|处分|警告/.test(item.type) ? 2 : 1,
-      desce: item.description || item.name || "",
-      startTime: item.startDate || item.date || "",
-      endTime: item.endDate || "",
-      status: REWARD_STATUS[item.status] ?? 2,
-      remark: item.remark || "",
-    })),
-    identityPhotos: (form.certificates || [])
-      .filter((item) => item.type || item.number)
-      .map((item) => ({
-        id: item.id || undefined,
-        type: CERT_TYPE[item.type] ?? 4,
-        code: item.number || "",
-        level: CERT_LEVEL[item.level] || item.level || "",
-        status: item.status === "失效" ? 0 : 1,
-        validityStartTime: item.startDate || "",
-        validityEndTime: item.endDate || "",
-        enactorTime: item.issueDate || "",
-        remark: item.remark || "",
-        holderType: "1",
-        holder: form.name || "",
-      })),
+    positionList: serializePositionList(form, positionIds),
+    factoryId: toApiId(form.factoryId),
+    criticalTaskList: serializeCriticalTaskList(form),
+    socialSecurityList: serializeSocialSecurityList(form),
+    rewardsPunishments: serializeRewards(form),
+    identityPhotos: serializeIdentityPhotos(form),
+    socialSecurityAccount: form.socialSecurityAccount || form.raw?.socialSecurityAccount || undefined,
+    socialSecurityAddress: form.socialSecurityAddress || form.raw?.socialSecurityAddress || undefined,
+    socialSecurityAddressId: toApiId(form.socialSecurityAddressId || form.raw?.socialSecurityAddressId),
+    accumulationFundAccount: form.accumulationFundAccount || form.raw?.accumulationFundAccount || undefined,
   });
 }
 

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

@@ -153,6 +153,10 @@ $hr-list-pages: (
 .el-dialog .el-table,
 .el-dialog .el-table .cell,
 .el-dialog .el-tabs__item,
+.el-dialog .el-descriptions,
+.el-dialog .el-descriptions--small,
+.el-dialog .el-descriptions-item__label,
+.el-dialog .el-descriptions-item__content,
 .el-drawer .el-drawer__body,
 .el-drawer .el-form-item__label,
 .el-drawer .el-form-item__error,
@@ -167,7 +171,11 @@ $hr-list-pages: (
 .el-drawer .el-link,
 .el-drawer .el-table,
 .el-drawer .el-table .cell,
-.el-drawer .el-tabs__item {
+.el-drawer .el-tabs__item,
+.el-drawer .el-descriptions,
+.el-drawer .el-descriptions--small,
+.el-drawer .el-descriptions-item__label,
+.el-drawer .el-descriptions-item__content {
   font-size: 14px !important;
 }
 

+ 7 - 7
src/styles/views/contractManagement/index.scss

@@ -1,4 +1,4 @@
-
+
 .cm-page,.cm-page *{box-sizing:border-box}
 .cm-page{
   min-height:calc(100vh - 100px);
@@ -107,9 +107,9 @@
 .cm-change-grid .el-input__inner,.cm-change-grid .el-select .el-input__inner{height:32px;line-height:32px;font-size:13px}
 .cm-change-section .el-form-item.is-required .el-form-item__label::before{content:"*";color:#d55752;margin-right:3px}
 .cm-sign-method{display:flex;gap:8px}
-.cm-sign-method .cm-method-btn{flex:1;height:34px;border:1px solid #1768e5;border-radius:4px;background:#fff;color:#1768e5;font-size:13px;cursor:pointer;transition:all .15s}
-.cm-sign-method .cm-method-btn.is-active{background:#1768e5;color:#fff;box-shadow:0 2px 6px rgba(23,104,229,.3)}
-.cm-sign-method .cm-method-btn:hover:not(.is-active){background:#eaf2ff}
+.cm-sign-method .cm-method-btn{flex:1;height:34px;border:1px solid var(--color-primary);border-radius:4px;background:#fff;color:var(--color-primary);font-size:13px;cursor:pointer;transition:all .15s}
+.cm-sign-method .cm-method-btn.is-active{background:var(--color-primary);color:#fff;box-shadow:0 2px 6px var(--color-primary-2)}
+.cm-sign-method .cm-method-btn:hover:not(.is-active){background:var(--color-primary-1)}
 .cm-change-notes{font-size:11px;color:#7e8b9d;line-height:1.8;padding:10px 12px;background:#fafbfd;border-radius:4px;margin-top:8px}
 .cm-change-notes p{margin:0}
 .cm-change-notes p+p{margin-top:3px}
@@ -177,9 +177,9 @@
 .cm-create-grid .el-input__inner,.cm-create-grid .el-select .el-input__inner{height:32px;line-height:32px;font-size:13px}
 .cm-create-grid .el-input-number .el-input__inner{height:32px}
 .cm-create-sign-method{display:flex;gap:8px}
-.cm-create-sign-method .cm-method-btn{flex:1;height:34px;border:1px solid #1768e5;border-radius:4px;background:#fff;color:#1768e5;font-size:13px;cursor:pointer;transition:all .15s}
-.cm-create-sign-method .cm-method-btn.is-active{background:#1768e5;color:#fff;box-shadow:0 2px 6px rgba(23,104,229,.3)}
-.cm-create-sign-method .cm-method-btn:hover:not(.is-active){background:#eaf2ff}
+.cm-create-sign-method .cm-method-btn{flex:1;height:34px;border:1px solid var(--color-primary);border-radius:4px;background:#fff;color:var(--color-primary);font-size:13px;cursor:pointer;transition:all .15s}
+.cm-create-sign-method .cm-method-btn.is-active{background:var(--color-primary);color:#fff;box-shadow:0 2px 6px var(--color-primary-2)}
+.cm-create-sign-method .cm-method-btn:hover:not(.is-active){background:var(--color-primary-1)}
 .cm-create-tips{font-size:11px;color:#7e8b9d;line-height:1.8;padding:10px 12px;background:#fafbfd;border-radius:4px;margin-top:8px}
 .cm-create-tips p{margin:0}
 .cm-create-tips p+p{margin-top:3px}

+ 6 - 6
src/styles/views/employeeArchive/components/EmployeeArchiveDetail.scss

@@ -5,7 +5,7 @@
 .drawer-close:hover { border-color: #b9c7d6; color: #27415f; background: #f4f7fa; }
 .profile-actions { position: absolute; top: 16px; right: 22px; display: flex; gap: 8px; }
 .profile-actions ::v-deep .el-button { color: #465b73; border-color: #d7e0e9; border-radius: 4px; background: #fff; }
-.profile-actions ::v-deep .el-button:hover { color: #1768e5; border-color: #b8d0f2; background: #f5f9ff; }
+.profile-actions ::v-deep .el-button:hover { color: var(--color-primary); border-color: var(--color-primary-2); background: var(--color-primary-1); }
 .avatar { width: 78px; height: 78px; flex: 0 0 auto; display: grid; place-items: center; overflow: hidden; border: 4px solid #fff; border-radius: 50%; box-shadow: 0 0 0 1px #cad6e3, 0 6px 16px rgba(42, 67, 97, .13); color: #fff; font-size: 29px; font-weight: 600; background: var(--avatar-color); }
 .avatar img { width: 100%; height: 100%; object-fit: cover; }
 .profile-title { min-width: 0; padding-top: 3px; }
@@ -27,14 +27,14 @@
 .quick-stats strong { margin-top: 4px; color: #30445b; font-size: 14px; font-weight: 600; }
 .quick-stats strong.warning { color: #c77912; }
 .completion-stat i { width: 72%; max-width: 90px; height: 4px; display: block; overflow: hidden; margin-top: 6px; border-radius: 2px; background: #e2e8ef; }
-.completion-stat i b { height: 100%; display: block; border-radius: inherit; background: #1768e5; transition: width .25s ease; }
+.completion-stat i b { height: 100%; display: block; border-radius: inherit; background: var(--color-primary); transition: width .25s ease; }
 .profile-tabs { min-height: 0; flex: 1; display: flex; flex-direction: column; background: #fff; }
 .profile-tabs ::v-deep > .el-tabs__header { flex: 0 0 auto; margin: 0; padding: 0 26px; border-bottom: 1px solid #e6ebf1; background: #fff; }
 .profile-tabs ::v-deep .el-tabs__nav-wrap::after { display: none; }
 .profile-tabs ::v-deep .el-tabs__item { height: 50px; padding: 0 18px; color: #617289; font-size: 14px; line-height: 50px; }
 .profile-tabs ::v-deep .el-tabs__item:hover,
-.profile-tabs ::v-deep .el-tabs__item.is-active { color: #1768e5; }
-.profile-tabs ::v-deep .el-tabs__active-bar { height: 2px; background: #1768e5; }
+.profile-tabs ::v-deep .el-tabs__item.is-active { color: var(--color-primary); }
+.profile-tabs ::v-deep .el-tabs__active-bar { height: 2px; background: var(--color-primary); }
 .profile-tabs ::v-deep > .el-tabs__content { min-height: 0; flex: 1; overflow-y: auto; scrollbar-color: #c7d1dd transparent; scrollbar-width: thin; }
 .profile-tabs ::v-deep > .el-tabs__content::-webkit-scrollbar { width: 6px; }
 .profile-tabs ::v-deep > .el-tabs__content::-webkit-scrollbar-thumb { border-radius: 3px; background: #c7d1dd; }
@@ -46,7 +46,7 @@
 .detail-section:last-child { padding-bottom: 4px; border-bottom: 0; }
 .section-title { display: flex; align-items: center; justify-content: space-between; margin-bottom: 17px; }
 .section-title h3 { position: relative; margin: 0; padding-left: 11px; color: #2b3f57; font-size: 15px; font-weight: 600; }
-.section-title h3::before { position: absolute; top: 3px; bottom: 3px; left: 0; width: 3px; border-radius: 2px; background: #1768e5; content: ''; }
+.section-title h3::before { position: absolute; top: 3px; bottom: 3px; left: 0; width: 3px; border-radius: 2px; background: var(--color-primary); content: ''; }
 .section-title span { padding: 3px 7px; border-radius: 3px; color: #8492a3; font-size: 14px; background: #f1f4f7; }
 .info-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); column-gap: 26px; }
 .info-grid > div { min-width: 0; min-height: 60px; padding: 8px 4px 10px; border-bottom: 1px solid #edf1f5; }
@@ -59,7 +59,7 @@
 .history-rail::after { position: absolute; top: 16px; bottom: -4px; width: 1px; background: #d9e1ea; content: ''; }
 .history-list article:last-child .history-rail::after { display: none; }
 .history-rail i { position: relative; z-index: 1; width: 9px; height: 9px; margin-top: 4px; border: 2px solid #a8b4c2; border-radius: 50%; background: #fff; }
-.history-rail i.active { border-color: #1768e5; box-shadow: 0 0 0 4px #e8f1ff; }
+.history-rail i.active { border-color: var(--color-primary); box-shadow: 0 0 0 4px var(--color-primary-1); }
 .history-list time { color: #8e9bac; font-size: 14px; }
 .history-list strong { display: block; margin-top: 4px; color: #32445a; font-size: 14px; }
 .history-list p { margin: 4px 0; color: #66758a; font-size: 14px; }

+ 27 - 13
src/styles/views/employeeArchive/components/EmployeeArchiveForm.global.scss

@@ -1,6 +1,6 @@
 .employee-archive-dialog { display: flex; flex-direction: column; height: 94vh; max-height: 820px; border: 1px solid #dce3eb; border-radius: 7px; overflow: hidden; box-shadow: 0 18px 48px rgba(24, 39, 58, .22); }
 .employee-archive-dialog .el-dialog__header { position: relative; z-index: 2; flex: 0 0 auto; display: flex; align-items: center; padding: 14px 12px 14px 22px; border-bottom: 1px solid #e5eaf0; background: #fff; }
-.employee-archive-dialog .el-dialog__header::before { position: absolute; bottom: -1px; left: 22px; width: 32px; height: 2px; background: #1768e5; content: ''; }
+.employee-archive-dialog .el-dialog__header::before { position: absolute; bottom: -1px; left: 22px; width: 32px; height: 2px; background: var(--color-primary); content: ''; }
 .employee-archive-dialog .el-dialog__title { flex: 1; min-width: 0; line-height: 30px; color: #24364b; font-size: 16px; font-weight: 600; }
 .employee-archive-dialog .archive-dialog-title { display: flex; align-items: center; width: 100%; min-width: 0; gap: 4px; }
 .employee-archive-dialog .archive-dialog-title > span { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@@ -8,7 +8,7 @@
 .employee-archive-dialog .el-dialog__headerbtn { position: relative; top: auto; right: auto; z-index: 3; flex: none; width: 30px; height: 30px; margin: 0; padding: 0; border: 0; border-radius: 4px; color: #7b899a; background: transparent; font-size: 16px; line-height: 30px; cursor: pointer; transition: background .2s, color .2s; }
 .employee-archive-dialog .el-dialog__headerbtn { margin-left: 4px; }
 .employee-archive-dialog .archive-dialog-fullscreen:hover,
-.employee-archive-dialog .el-dialog__headerbtn:hover { color: #1768e5; background: #f1f4f7; }
+.employee-archive-dialog .el-dialog__headerbtn:hover { color: var(--color-primary); background: #f1f4f7; }
 .employee-archive-dialog .el-dialog__headerbtn .el-dialog__close { color: inherit; font-size: 20px; }
 .employee-archive-dialog.is-fullscreen { width: 100% !important; height: 100vh !important; max-height: none; margin: 0 !important; border-radius: 0; }
 .employee-archive-dialog .el-dialog__body { min-height: 0; flex: 1; display: flex; flex-direction: column; overflow: hidden; padding: 14px 20px 0; background: #fff; }
@@ -21,12 +21,12 @@
 .employee-archive-dialog .archive-form-tabs .el-tabs__nav-wrap.is-left::after { display: none; }
 .employee-archive-dialog .archive-form-tabs .el-tabs__item.is-left { position: relative; height: 57px; margin-bottom: 2px; padding: 2px 9px !important; border: 1px solid transparent; border-radius: 5px; line-height: normal; transition: background .18s, border-color .18s; }
 .employee-archive-dialog .archive-form-tabs .el-tabs__item.is-left:hover { border-color: #e5eaf0; background: #f7f9fb; }
-.employee-archive-dialog .archive-form-tabs .el-tabs__item.is-left.is-active { border-color: #d7e4f7; background: #edf4ff; }
+.employee-archive-dialog .archive-form-tabs .el-tabs__item.is-left.is-active { border-color: var(--color-primary-2); background: var(--color-primary-1); }
 .employee-archive-dialog .archive-form-tabs .el-tabs__item.is-left.is-active .section-tab-label > i,
-.employee-archive-dialog .archive-form-tabs .el-tabs__item.is-left.is-active .section-tab-label strong { color: #1768e5; }
+.employee-archive-dialog .archive-form-tabs .el-tabs__item.is-left.is-active .section-tab-label strong { color: var(--color-primary); }
 .employee-archive-dialog .archive-form-tabs .el-tabs__item.is-left.is-active .section-tab-label > i { background: #fff; box-shadow: 0 1px 3px rgba(27, 76, 139, .1); }
-.employee-archive-dialog .archive-form-tabs .el-tabs__item.is-left.is-active .section-tab-label em { color: #1768e5; background: #fff; }
-.employee-archive-dialog .archive-form-tabs .el-tabs__item.is-left.is-active::after { position: absolute; top: 10px; right: 0; bottom: 10px; width: 3px; border-radius: 2px; background: #1768e5; content: ''; }
+.employee-archive-dialog .archive-form-tabs .el-tabs__item.is-left.is-active .section-tab-label em { color: var(--color-primary); background: #fff; }
+.employee-archive-dialog .archive-form-tabs .el-tabs__item.is-left.is-active::after { position: absolute; top: 10px; right: 0; bottom: 10px; width: 3px; border-radius: 2px; background: var(--color-primary); content: ''; }
 .employee-archive-dialog .archive-form-tabs .el-tabs__active-bar.is-left { display: none; }
 .employee-archive-dialog .el-row { display: flex; flex-wrap: wrap; }
 .employee-archive-dialog .el-form-item { margin-bottom: 22px; }
@@ -37,7 +37,7 @@
 .employee-archive-dialog .el-textarea__inner:hover { border-color: #b6c4d3; }
 .employee-archive-dialog .el-input.is-active .el-input__inner,
 .employee-archive-dialog .el-input__inner:focus,
-.employee-archive-dialog .el-textarea__inner:focus { border-color: #1768e5; box-shadow: 0 0 0 2px rgba(23, 104, 229, .08); }
+.employee-archive-dialog .el-textarea__inner:focus { border-color: var(--color-primary); box-shadow: 0 0 0 2px var(--color-primary-1); }
 .employee-archive-dialog .el-input-group__append,
 .employee-archive-dialog .el-input-group__prepend { border-color: #dce3eb; color: #62738a; background: #f5f7fa; }
 .employee-archive-dialog .el-form-item.is-error .el-input__inner,
@@ -57,14 +57,28 @@
   margin: 2px 0 2px 6px;
 }
 .employee-archive-dialog .el-button { border-radius: 4px; }
-.employee-archive-dialog .el-button--primary { border-color: #1768e5; background: #1768e5; box-shadow: 0 2px 5px rgba(23, 104, 229, .18); }
-.employee-archive-dialog .el-button--primary:hover { border-color: #2d78e8; background: #2d78e8; }
+.employee-archive-dialog .el-link--primary { color: var(--color-primary); }
 .employee-archive-dialog .repeat-block .el-form-item { margin-bottom: 18px; }
-.employee-archive-dialog .certificate-table .el-table__cell { padding: 8px 6px; }
+.employee-archive-dialog .certificate-table .el-table__cell,
+.employee-archive-dialog .skill-table .el-table__cell,
+.employee-archive-dialog .bank-table .el-table__cell,
+.employee-archive-dialog .social-table .el-table__cell,
+.employee-archive-dialog .education-table .el-table__cell,
+.employee-archive-dialog .job-table .el-table__cell { padding: 8px 6px; }
 .employee-archive-dialog .certificate-table .el-date-editor.el-input,
-.employee-archive-dialog .certificate-table .el-date-editor.el-input__inner { width: 100%; }
-.employee-archive-dialog .certificate-table .file-main { display: inline-flex; }
-.employee-archive-dialog .certificate-table .file-main .item { display: inline-flex; vertical-align: middle; }
+.employee-archive-dialog .certificate-table .el-date-editor.el-input__inner,
+.employee-archive-dialog .social-table .el-date-editor.el-input,
+.employee-archive-dialog .social-table .el-date-editor.el-input__inner,
+.employee-archive-dialog .education-table .el-date-editor.el-input,
+.employee-archive-dialog .education-table .el-date-editor.el-input__inner,
+.employee-archive-dialog .job-table .el-date-editor.el-input,
+.employee-archive-dialog .job-table .el-date-editor.el-input__inner { width: 100%; }
+.employee-archive-dialog .certificate-table .file-main,
+.employee-archive-dialog .skill-table .file-main { display: inline-flex; }
+.employee-archive-dialog .certificate-table .file-main .item,
+.employee-archive-dialog .skill-table .file-main .item { display: inline-flex; vertical-align: middle; }
+.employee-archive-dialog .skill-table .el-input-number,
+.employee-archive-dialog .social-table .el-input-number { width: 100%; }
 
 @media (max-width: 1320px) { .employee-archive-dialog { width: calc(100vw - 28px) !important; height: 96vh; margin-top: 2vh !important; max-height: none; } }
 @media (max-width: 760px) {

+ 18 - 8
src/styles/views/employeeArchive/components/EmployeeArchiveForm.scss

@@ -1,14 +1,14 @@
 .archive-form-summary { min-height: 66px; flex: 0 0 auto; display: flex; align-items: center; gap: 12px; margin-bottom: 12px; padding: 9px 14px; border: 1px solid #dce5f0; border-radius: 6px; background: #f7faff; box-shadow: 0 1px 2px rgba(31, 55, 84, .04); }
-.summary-icon { width: 38px; height: 38px; flex: 0 0 auto; display: grid; place-items: center; border: 1px solid #d4e3fb; border-radius: 6px; color: #1768e5; font-size: 17px; background: #edf4ff; }
+.summary-icon { width: 38px; height: 38px; flex: 0 0 auto; display: grid; place-items: center; border: 1px solid var(--color-primary-2); border-radius: 6px; color: var(--color-primary); font-size: 17px; background: var(--color-primary-1); }
 .summary-copy { min-width: 150px; }
 .summary-copy strong { display: block; overflow: hidden; color: #26384e; font-size: 14px; font-weight: 600; text-overflow: ellipsis; white-space: nowrap; }
 .archive-form-summary p { margin: 4px 0 0; color: #7e8c9e; font-size: 14px; }
 .archive-form-summary p em { width: 3px; height: 3px; display: inline-block; margin: 0 7px; border-radius: 50%; background: #b4beca; vertical-align: middle; }
 .summary-completion { width: 164px; margin-left: auto; }
 .summary-completion > div { display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px; color: #738297; font-size: 14px; }
-.summary-completion > div strong { color: #1768e5; font-size: 14px; font-weight: 600; }
+.summary-completion > div strong { color: var(--color-primary); font-size: 14px; font-weight: 600; }
 .completion-track { height: 4px; display: block; overflow: hidden; border-radius: 2px; background: #dfe7f1; }
-.completion-track i { height: 100%; display: block; border-radius: inherit; background: #1768e5; transition: width .25s ease; }
+.completion-track i { height: 100%; display: block; border-radius: inherit; background: var(--color-primary); transition: width .25s ease; }
 .archive-form-summary > .el-tag { margin-left: auto; }
 .archive-form-shell { min-height: 0; flex: 1; }
 .archive-form-tabs { height: 100%; min-height: 0; }
@@ -22,7 +22,7 @@
 .section-title { min-height: 60px; display: flex; align-items: center; justify-content: space-between; margin-bottom: 18px; padding: 0 2px 12px; border-bottom: 1px solid #e5ebf1; }
 .section-title h3, .section-title p { margin: 0; }
 .section-title h3 { position: relative; padding-left: 11px; color: #283b52; font-size: 15px; font-weight: 600; letter-spacing: 0; }
-.section-title h3::before { position: absolute; top: 3px; bottom: 3px; left: 0; width: 3px; border-radius: 2px; background: #1768e5; content: ''; }
+.section-title h3::before { position: absolute; top: 3px; bottom: 3px; left: 0; width: 3px; border-radius: 2px; background: var(--color-primary); content: ''; }
 .section-title p { margin-top: 5px; padding-left: 11px; color: #8391a3; font-size: 14px; }
 .section-title > span { padding: 4px 9px; border: 1px solid #e0e6ed; border-radius: 4px; color: #66768a; font-size: 14px; background: #f7f9fb; }
 .profile-section { display: grid; grid-template-columns: 124px minmax(0, 1fr); gap: 22px; }
@@ -38,7 +38,7 @@
 .avatar-preview:hover .avatar-mask { transform: translateY(0); }
 .subsection-title { height: 42px; display: flex; align-items: center; justify-content: space-between; margin: 8px 0 16px; padding: 0 10px; border: 1px solid #e8edf3; border-radius: 5px; background: #f8fafc; }
 .subsection-title strong { color: #3e5067; font-size: 14px; font-weight: 600; }
-.subsection-title strong::before { width: 3px; height: 12px; display: inline-block; margin-right: 8px; border-radius: 2px; background: #4b83d3; vertical-align: -2px; content: ''; }
+.subsection-title strong::before { width: 3px; height: 12px; display: inline-block; margin-right: 8px; border-radius: 2px; background: var(--color-primary); vertical-align: -2px; content: ''; }
 .attachment-title { margin-top: 18px; }
 .full-width { width: 100%; }
 .gender-group { width: 100%; display: flex; }
@@ -57,9 +57,19 @@
 .inline-empty { margin-bottom: 18px; padding: 22px 15px; border: 1px dashed #d8e0e9; border-radius: 5px; color: #8d9aaa; font-size: 14px; text-align: center; background: #fafcfe; }
 .work-type-table { width: 100%; margin-bottom: 18px; }
 .work-type-table .el-link { margin-right: 8px; }
-.certificate-table { width: 100%; margin-bottom: 18px; }
-.certificate-table .cell-required::before { color: #f56c6c; margin-right: 4px; content: '*'; }
-.certificate-table .certificate-delete-btn { padding: 0; color: #f56c6c; }
+.certificate-table, .skill-table, .bank-table, .social-table, .education-table, .job-table { width: 100%; margin-bottom: 18px; }
+.certificate-table .cell-required::before,
+.skill-table .cell-required::before,
+.bank-table .cell-required::before,
+.social-table .cell-required::before,
+.education-table .cell-required::before,
+.job-table .cell-required::before { color: #f56c6c; margin-right: 4px; content: '*'; }
+.certificate-table .certificate-delete-btn,
+.skill-table .skill-delete-btn,
+.bank-table .bank-delete-btn,
+.social-table .social-delete-btn,
+.education-table .education-delete-btn,
+.job-table .job-delete-btn { padding: 0; color: #f56c6c; }
 .file-list { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 15px; }
 .file-list > span { max-width: 260px; height: 36px; display: flex; align-items: center; gap: 7px; padding: 0 10px; border: 1px solid #dce5ef; border-radius: 5px; color: #3f76b8; background: #f8fbff; }
 .file-list strong { min-width: 0; overflow: hidden; color: #526278; font-size: 14px; font-weight: 500; text-overflow: ellipsis; white-space: nowrap; }

+ 4 - 4
src/styles/views/employeeArchive/components/EmployeeArchiveRecords.scss

@@ -1,17 +1,17 @@
 .records-heading { min-height: 60px; display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; padding: 0 2px 12px; border-bottom: 1px solid #e5ebf1; }
 .records-heading h3, .records-heading p { margin: 0; }
 .records-heading h3 { position: relative; padding-left: 11px; color: #283b52; font-size: 15px; font-weight: 600; }
-.records-heading h3::before { position: absolute; top: 3px; bottom: 3px; left: 0; width: 3px; border-radius: 2px; background: #1768e5; content: ''; }
+.records-heading h3::before { position: absolute; top: 3px; bottom: 3px; left: 0; width: 3px; border-radius: 2px; background: var(--color-primary); content: ''; }
 .records-heading p { margin-top: 5px; padding-left: 11px; color: #8391a3; font-size: 14px; }
 .records-heading > span { padding: 4px 9px; border: 1px solid #e0e6ed; border-radius: 4px; color: #66768a; font-size: 14px; background: #f7f9fb; }
 .record-tabs ::v-deep .el-tabs__header { margin-bottom: 8px; }
 .record-tabs ::v-deep .el-tabs__nav-wrap::after { height: 1px; background: #e4eaf0; }
 .record-tabs ::v-deep .el-tabs__item { height: 38px; padding: 0 12px; color: #65758a; font-size: 14px; }
 .record-tabs ::v-deep .el-tabs__item:hover,
-.record-tabs ::v-deep .el-tabs__item.is-active { color: #1768e5; }
-.record-tabs ::v-deep .el-tabs__active-bar { height: 2px; background: #1768e5; }
+.record-tabs ::v-deep .el-tabs__item.is-active { color: var(--color-primary); }
+.record-tabs ::v-deep .el-tabs__active-bar { height: 2px; background: var(--color-primary); }
 .record-tabs ::v-deep .el-tabs__item em { min-width: 17px; height: 17px; display: inline-flex; align-items: center; justify-content: center; margin-left: 5px; padding: 0 4px; border-radius: 9px; color: #6f7f92; font-size: 14px; font-style: normal; background: #edf1f5; }
-.record-tabs ::v-deep .el-tabs__item.is-active em { color: #1768e5; background: #e8f1ff; }
+.record-tabs ::v-deep .el-tabs__item.is-active em { color: var(--color-primary); background: var(--color-primary-1); }
 .record-toolbar { height: 44px; display: flex; align-items: center; justify-content: space-between; padding: 0 2px; }
 .record-toolbar strong { color: #405168; font-size: 14px; font-weight: 600; }
 .record-empty { height: 118px; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px; border: 1px dashed #d8e0e9; border-radius: 5px; color: #8d9aaa; font-size: 14px; background: #fafcfe; }

+ 1 - 1
src/styles/views/employeeArchive/components/EmployeeArchiveRecordsDetail.scss

@@ -4,7 +4,7 @@
 .record-section:last-child { border-bottom: 0; }
 .record-title { display: flex; align-items: center; justify-content: space-between; margin-bottom: 15px; }
 .record-title h3 { position: relative; margin: 0; padding-left: 11px; color: #2b3f57; font-size: 14px; font-weight: 600; }
-.record-title h3::before { position: absolute; top: 2px; bottom: 2px; left: 0; width: 3px; border-radius: 2px; background: #1768e5; content: ''; }
+.record-title h3::before { position: absolute; top: 2px; bottom: 2px; left: 0; width: 3px; border-radius: 2px; background: var(--color-primary); content: ''; }
 .record-title span { padding: 3px 7px; border-radius: 3px; color: #8492a3; font-size: 14px; background: #f1f4f7; }
 .record-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
 .record-grid article { min-width: 0; display: flex; align-items: center; gap: 10px; padding: 11px; border: 1px solid #e5eaf0; border-radius: 5px; background: #fafbfd; transition: border-color .2s, background .2s; }

+ 18 - 3
src/styles/views/offerNotify/index.scss

@@ -51,16 +51,16 @@
 .two-line small {
   margin-top: 4px;
   color: #99a4b4;
-  font-size: 11px;
+  font-size: 14px;
 }
 .two-line strong {
   color: #536177;
-  font-size: 13px;
+  font-size: 14px;
   font-weight: 500;
 }
 .field-help {
   color: #98a3b3;
-  font-size: 12px;
+  font-size: 14px;
 }
 
 /* ===== Offer 模板自动渲染预览 / 归档查看 ===== */
@@ -92,6 +92,21 @@
   background: #fff;
   border-radius: 4px;
   min-height: 420px;
+  font-size: 14px;
+}
+.offer-paper small {
+  font-size: 14px;
+}
+.offer-paper ::v-deep .el-descriptions,
+.offer-paper ::v-deep .el-descriptions--small,
+.offer-paper ::v-deep .el-descriptions-item__label,
+.offer-paper ::v-deep .el-descriptions-item__content {
+  font-size: 14px;
+}
+.offer-paper ::v-deep .el-button,
+.offer-paper ::v-deep .el-button--small,
+.offer-paper ::v-deep .el-button--mini {
+  font-size: 14px;
 }
 .preview-block {
   margin-bottom: 8px;

+ 9 - 0
src/views/attendance/components/RuleWorkTimePanel.vue

@@ -354,6 +354,15 @@ export default {
 .rest-mode-group >>> .el-radio-button__inner {
   padding: 8px 16px;
 }
+.rest-mode-group >>> .el-radio-button:hover .el-radio-button__inner {
+  color: var(--color-primary);
+}
+.rest-mode-group >>> .el-radio-button__orig-radio:checked + .el-radio-button__inner {
+  color: #fff;
+  background: var(--color-primary);
+  border-color: var(--color-primary);
+  box-shadow: -1px 0 0 0 var(--color-primary);
+}
 .rule-tip {
   color: #8a96a7;
   font-size: 12px;

+ 13 - 13
src/views/attendance/rule/index.vue

@@ -1347,22 +1347,22 @@ export default {
 }
 
 .rule-dialog .tab-chip:hover {
-  color: #1b8eb1;
-  border-color: #1b8eb1;
+  color: var(--color-primary);
+  border-color: var(--color-primary);
   background: #fff
 }
 
 .rule-dialog .tab-chip.active {
   color: #fff;
-  background: #1b8eb1;
-  border-color: #1b8eb1;
+  background: var(--color-primary);
+  border-color: var(--color-primary);
   font-weight: 600;
-  box-shadow: 0 2px 6px rgba(27, 142, 177, .25)
+  box-shadow: 0 2px 6px var(--color-primary-2)
 }
 
 .rule-dialog .tab-chip.active:hover {
-  background: #157a99;
-  border-color: #157a99
+  background: var(--color-primary);
+  border-color: var(--color-primary)
 }
 
 .rule-dialog .rule-dialog-body {
@@ -1599,19 +1599,19 @@ export default {
 }
 
 .rule-dialog .tab-panel .week-chips .el-checkbox-button:hover .el-checkbox-button__inner {
-  border-color: #1b8eb1;
-  color: #1b8eb1
+  border-color: var(--color-primary);
+  color: var(--color-primary)
 }
 
 .rule-dialog .tab-panel .week-chips .el-checkbox-button.is-checked .el-checkbox-button__inner {
-  background: #1b8eb1 !important;
-  border-color: #1b8eb1 !important;
+  background: var(--color-primary) !important;
+  border-color: var(--color-primary) !important;
   color: #fff !important;
-  box-shadow: 0 2px 6px rgba(27, 142, 177, .25) !important
+  box-shadow: 0 2px 6px var(--color-primary-2) !important
 }
 
 .rule-dialog .tab-panel .week-chips .el-checkbox-button.is-checked .el-checkbox-button__inner:hover {
-  background: #157a99 !important
+  background: var(--color-primary) !important
 }
 
 .rule-dialog .tab-panel .lunch-row {

+ 1 - 1
src/views/contractManagement/index.vue

@@ -900,7 +900,7 @@ export default {
       columns: [
         { type: 'index', label: '序号', width: 50, align: 'center', headerAlign: 'center' },
         { prop: 'contractNo', label: '合同编号', minWidth: 140, showOverflowTooltip: true },
-        { prop: 'employeeName', label: '员工', minWidth: 120, slot: 'employee' },
+        { prop: 'employeeName', label: '员工', minWidth: 200, slot: 'employee' },
         { prop: 'legalEntity', label: '所属机构', minWidth: 150, showOverflowTooltip: true },
         { prop: 'department', label: '所属部门', minWidth: 120, showOverflowTooltip: true },
         { prop: 'positionName', label: '岗位名称', minWidth: 120, showOverflowTooltip: true },

+ 2 - 2
src/views/hrManagement/employeeArchive/components/EmployeeArchiveDetail.vue

@@ -355,11 +355,11 @@ export default {
     educationList() {
       const employee = this.employee || {};
       if (employee.educations?.length) return employee.educations;
-      if (employee.education || employee.school || employee.major) {
+      if (employee.education || employee.school || employee.major || employee.degree) {
         return [{
           education: employee.education,
           school: employee.school,
-          degree: '',
+          degree: employee.degree || '',
           graduationDate: employee.graduationDate,
           major: employee.major,
         }];

+ 210 - 111
src/views/hrManagement/employeeArchive/components/EmployeeArchiveForm.vue

@@ -212,61 +212,102 @@
                 <p>最高教育经历和入职前工作经历</p>
               </div><span>{{ form.previousJobs.length }} 段履历</span>
             </header>
-            <div class="subsection-title"><strong>教育经历</strong><el-button type="text" icon="el-icon-plus"
+            <div class="subsection-title"><strong>教育经历</strong><el-button type="primary" size="mini"
                 @click="addEducation">添加教育</el-button></div>
             <el-alert v-if="form.educations.length > 1" type="warning" :closable="false" show-icon
-              title="后端目前仅支持一条最高学历字段(educationBackground/school/major),多条教育经历保存时只会提交第一条。" style="margin-bottom: 10px" />
-            <div v-if="form.educations.length" class="repeat-list">
-              <div v-for="(item, index) in form.educations" :key="`edu-${index}`" class="repeat-block">
-                <div class="repeat-block-heading"><strong>教育经历 {{ index + 1 }}</strong><el-button type="text"
-                    icon="el-icon-delete" @click="removeItem('educations', index)">删除</el-button></div>
-                <el-row :gutter="14">
-                  <el-col :xs="12" :sm="6"><el-form-item label="最高学历"><el-select v-model="item.education"
-                        class="full-width"><el-option v-for="opt in educationOptions" :key="opt" :label="opt"
-                          :value="opt" /></el-select></el-form-item></el-col>
-                  <el-col :xs="12" :sm="6"><el-form-item label="学位"><el-select v-model="item.degree" clearable
-                        class="full-width" placeholder="请选择"><el-option v-for="opt in ['无', '学士', '硕士', '博士']" :key="opt"
-                          :label="opt" :value="opt" /></el-select></el-form-item></el-col>
-                  <el-col :xs="12" :sm="6"><el-form-item label="毕业日期"><el-date-picker v-model="item.graduationDate"
-                        value-format="yyyy-MM-dd" type="date" placeholder="请选择"
-                        class="full-width" /></el-form-item></el-col>
-                  <el-col :xs="12" :sm="6"><el-form-item label="毕业院校"><el-input v-model.trim="item.school"
-                        maxlength="40" placeholder="请输入毕业院校" /></el-form-item></el-col>
-                  <el-col :xs="24" :sm="12"><el-form-item label="所学专业"><el-input v-model.trim="item.major"
-                        maxlength="40" placeholder="请输入所学专业" /></el-form-item></el-col>
-                </el-row>
-              </div>
-            </div>
-            <div v-else class="inline-empty">暂无教育经历</div>
-            <div class="subsection-title"><strong>工作履历</strong><el-button type="text" icon="el-icon-plus"
+              title="多条教育经历保存时只会提交第一条。" style="margin-bottom: 10px" />
+            <el-table :data="form.educations" border size="small" class="education-table" empty-text="暂无教育经历">
+              <el-table-column type="index" label="序号" width="55" align="center" />
+              <el-table-column label="最高学历" min-width="120">
+                <template slot-scope="{ row }">
+                  <el-select v-model="row.education" placeholder="请选择" class="full-width">
+                    <el-option v-for="opt in educationOptions" :key="opt" :label="opt" :value="opt" />
+                  </el-select>
+                </template>
+              </el-table-column>
+              <el-table-column label="学位" min-width="110">
+                <template slot-scope="{ row }">
+                  <el-select v-model="row.degree" clearable placeholder="请选择" class="full-width">
+                    <el-option v-for="opt in ['无', '学士', '硕士', '博士']" :key="opt" :label="opt" :value="opt" />
+                  </el-select>
+                </template>
+              </el-table-column>
+              <el-table-column label="毕业日期" min-width="150">
+                <template slot-scope="{ row }">
+                  <el-date-picker v-model="row.graduationDate" value-format="yyyy-MM-dd" type="date" placeholder="选择日期"
+                    class="full-width" />
+                </template>
+              </el-table-column>
+              <el-table-column label="毕业院校" min-width="160">
+                <template slot-scope="{ row }">
+                  <el-input v-model.trim="row.school" maxlength="40" placeholder="请输入毕业院校" />
+                </template>
+              </el-table-column>
+              <el-table-column label="所学专业" min-width="150">
+                <template slot-scope="{ row }">
+                  <el-input v-model.trim="row.major" maxlength="40" placeholder="请输入所学专业" />
+                </template>
+              </el-table-column>
+              <el-table-column label="操作" width="90" align="center">
+                <template slot-scope="{ $index }">
+                  <el-button type="text" icon="el-icon-delete" class="education-delete-btn"
+                    @click="removeItem('educations', $index)">删除</el-button>
+                </template>
+              </el-table-column>
+            </el-table>
+
+            <div class="subsection-title"><strong>工作履历</strong><el-button type="primary" size="mini"
                 @click="addPreviousJob">添加履历</el-button></div>
-            <div v-if="form.previousJobs.length" class="repeat-list">
-              <div v-for="(item, index) in form.previousJobs" :key="`job-${index}`" class="repeat-block">
-                <div class="repeat-block-heading"><strong>工作经历 {{ index + 1 }}</strong><el-button type="text"
-                    icon="el-icon-delete" @click="removeItem('previousJobs', index)">删除</el-button></div>
-                <el-row :gutter="14">
-                  <el-col :xs="12" :sm="4"><el-form-item label="序号"><el-input :value="index + 1"
-                        readonly /></el-form-item></el-col>
-                  <el-col :xs="12" :sm="5"><el-form-item label="工作开始日期" required><el-date-picker
-                        v-model="item.startDate" value-format="yyyy-MM-dd" type="date" placeholder="开始日期"
-                        class="full-width" /></el-form-item></el-col>
-                  <el-col :xs="12" :sm="5"><el-form-item label="工作结束日期" required><el-date-picker v-model="item.endDate"
-                        value-format="yyyy-MM-dd" type="date" placeholder="结束日期"
-                        class="full-width" /></el-form-item></el-col>
-                  <el-col :xs="24" :sm="5"><el-form-item label="前公司名称" required><el-input v-model.trim="item.company"
-                        maxlength="50" placeholder="请输入公司名称" /></el-form-item></el-col>
-                  <el-col :xs="24" :sm="5"><el-form-item label="前公司岗位" required><el-input v-model.trim="item.position"
-                        maxlength="30" /></el-form-item></el-col>
-                  <el-col :xs="24" :sm="6"><el-form-item label="前公司部门"><el-input v-model.trim="item.department"
-                        maxlength="30" /></el-form-item></el-col>
-                  <el-col :xs="24" :sm="6"><el-form-item label="离职原因"><el-input v-model.trim="item.leaveReason"
-                        maxlength="40" /></el-form-item></el-col>
-                  <el-col :span="24"><el-form-item label="工作描述"><el-input v-model.trim="item.description"
-                        type="textarea" :rows="2" maxlength="300" show-word-limit /></el-form-item></el-col>
-                </el-row>
-              </div>
-            </div>
-            <div v-else class="inline-empty">暂无入职前工作履历</div>
+            <el-table :data="form.previousJobs" border size="small" class="job-table" empty-text="暂无入职前工作履历">
+              <el-table-column type="index" label="序号" width="55" align="center" />
+              <el-table-column min-width="150">
+                <template slot="header"><span class="cell-required">工作开始日期</span></template>
+                <template slot-scope="{ row }">
+                  <el-date-picker v-model="row.startDate" value-format="yyyy-MM-dd" type="date" placeholder="开始日期"
+                    class="full-width" />
+                </template>
+              </el-table-column>
+              <el-table-column min-width="150">
+                <template slot="header"><span class="cell-required">工作结束日期</span></template>
+                <template slot-scope="{ row }">
+                  <el-date-picker v-model="row.endDate" value-format="yyyy-MM-dd" type="date" placeholder="结束日期"
+                    class="full-width" />
+                </template>
+              </el-table-column>
+              <el-table-column min-width="150">
+                <template slot="header"><span class="cell-required">前公司名称</span></template>
+                <template slot-scope="{ row }">
+                  <el-input v-model.trim="row.company" maxlength="50" placeholder="请输入公司名称" />
+                </template>
+              </el-table-column>
+              <el-table-column min-width="130">
+                <template slot="header"><span class="cell-required">前公司岗位</span></template>
+                <template slot-scope="{ row }">
+                  <el-input v-model.trim="row.position" maxlength="30" placeholder="请输入岗位" />
+                </template>
+              </el-table-column>
+              <el-table-column label="前公司部门" min-width="130">
+                <template slot-scope="{ row }">
+                  <el-input v-model.trim="row.department" maxlength="30" placeholder="请输入部门" />
+                </template>
+              </el-table-column>
+              <el-table-column label="离职原因" min-width="130">
+                <template slot-scope="{ row }">
+                  <el-input v-model.trim="row.leaveReason" maxlength="40" placeholder="请输入" />
+                </template>
+              </el-table-column>
+              <el-table-column label="工作描述" min-width="180">
+                <template slot-scope="{ row }">
+                  <el-input v-model.trim="row.description" maxlength="300" placeholder="请输入工作描述" />
+                </template>
+              </el-table-column>
+              <el-table-column label="操作" width="90" align="center">
+                <template slot-scope="{ $index }">
+                  <el-button type="text" icon="el-icon-delete" class="job-delete-btn"
+                    @click="removeItem('previousJobs', $index)">删除</el-button>
+                </template>
+              </el-table-column>
+            </el-table>
           </div>
 
           <div v-else-if="section.name === 'contract'" class="section-content">
@@ -305,42 +346,74 @@
             </header>
             <div class="subsection-title"><strong>薪酬银行</strong><el-button type="primary" size="mini"
                 @click="addBankAccount">添加银行</el-button></div>
-            <div v-if="form.bankAccounts.length" class="repeat-list compact-list">
-              <div v-for="(item, index) in form.bankAccounts" :key="`bank-${index}`" class="repeat-row bank-row">
-                <el-input v-model.trim="item.bankName" maxlength="50" placeholder="开户行" />
-                <el-input v-model.trim="item.bankAccount" maxlength="24" placeholder="银行卡号" />
-                <el-input v-model.trim="item.bankAccountName" maxlength="20" placeholder="姓名(必填)" />
-                <el-tooltip content="删除" placement="top"><el-button class="remove-button" icon="el-icon-delete"
-                    @click="removeItem('bankAccounts', index)"></el-button></el-tooltip>
-              </div>
-            </div>
-            <div v-else class="inline-empty">暂无银行账户</div>
+            <el-table :data="form.bankAccounts" border size="small" class="bank-table" empty-text="暂无银行账户">
+              <el-table-column type="index" label="序号" width="55" align="center" />
+              <el-table-column label="开户行" min-width="180">
+                <template slot-scope="{ row }">
+                  <el-input v-model.trim="row.bankName" maxlength="50" placeholder="请输入开户行" />
+                </template>
+              </el-table-column>
+              <el-table-column label="银行卡号" min-width="180">
+                <template slot-scope="{ row }">
+                  <el-input v-model.trim="row.bankAccount" maxlength="24" placeholder="请输入银行卡号" />
+                </template>
+              </el-table-column>
+              <el-table-column min-width="140">
+                <template slot="header"><span class="cell-required">姓名</span></template>
+                <template slot-scope="{ row }">
+                  <el-input v-model.trim="row.bankAccountName" maxlength="20" placeholder="请输入开户人" />
+                </template>
+              </el-table-column>
+              <el-table-column label="操作" width="90" align="center">
+                <template slot-scope="{ $index }">
+                  <el-button type="text" icon="el-icon-delete" class="bank-delete-btn"
+                    @click="removeItem('bankAccounts', $index)">删除</el-button>
+                </template>
+              </el-table-column>
+            </el-table>
 
             <div class="subsection-title"><strong>社保公积金</strong><el-button type="primary" size="mini"
                 @click="addSocialSecurity">添加记录</el-button></div>
-            <div v-if="form.socialSecurityRecords.length" class="repeat-list">
-              <div v-for="(item, index) in form.socialSecurityRecords" :key="`ss-${index}`" class="repeat-block">
-                <div class="repeat-block-heading"><strong>社保记录 {{ index + 1 }}</strong><el-button type="text"
-                    icon="el-icon-delete" @click="removeItem('socialSecurityRecords', index)">删除</el-button></div>
-                <el-row :gutter="14">
-                  <el-col :xs="12" :sm="6"><el-form-item label="社保类型"><el-select v-model="item.type" clearable
-                        class="full-width"><el-option v-for="opt in socialSecurityOptions" :key="opt" :label="opt"
-                          :value="opt" /></el-select></el-form-item></el-col>
-                  <el-col :xs="12" :sm="6"><el-form-item label="基数"><el-input-number v-model="item.base" :min="0"
-                        :precision="2" :controls="false" class="full-width" /></el-form-item></el-col>
-                  <el-col :xs="12" :sm="6"><el-form-item label="参保日期"><el-date-picker v-model="item.insuredDate"
-                        value-format="yyyy-MM-dd" type="date" placeholder="请选择"
-                        class="full-width" /></el-form-item></el-col>
-                  <el-col :xs="12" :sm="6"><el-form-item label="停保日期"><el-date-picker v-model="item.stopDate"
-                        value-format="yyyy-MM-dd" type="date" placeholder="在职可为空"
-                        class="full-width" /></el-form-item></el-col>
-                  <el-col :xs="24" :sm="8"><el-form-item label="停缴记录"><el-select v-model="item.stopReason" clearable
-                        class="full-width" placeholder="请选择"><el-option v-for="opt in ['离职', '调转', '其他']" :key="opt"
-                          :label="opt" :value="opt" /></el-select></el-form-item></el-col>
-                </el-row>
-              </div>
-            </div>
-            <div v-else class="inline-empty">暂无社保记录</div>
+            <el-table :data="form.socialSecurityRecords" border size="small" class="social-table" empty-text="暂无社保记录">
+              <el-table-column type="index" label="序号" width="55" align="center" />
+              <el-table-column label="社保类型" min-width="140">
+                <template slot-scope="{ row }">
+                  <el-select v-model="row.type" clearable placeholder="请选择" class="full-width">
+                    <el-option v-for="opt in socialSecurityOptions" :key="opt" :label="opt" :value="opt" />
+                  </el-select>
+                </template>
+              </el-table-column>
+              <el-table-column label="基数" min-width="120">
+                <template slot-scope="{ row }">
+                  <el-input-number v-model="row.base" :min="0" :precision="2" :controls="false" class="full-width" />
+                </template>
+              </el-table-column>
+              <el-table-column label="参保日期" min-width="150">
+                <template slot-scope="{ row }">
+                  <el-date-picker v-model="row.insuredDate" value-format="yyyy-MM-dd" type="date" placeholder="选择日期"
+                    class="full-width" />
+                </template>
+              </el-table-column>
+              <el-table-column label="停保日期" min-width="150">
+                <template slot-scope="{ row }">
+                  <el-date-picker v-model="row.stopDate" value-format="yyyy-MM-dd" type="date" placeholder="在职可为空"
+                    class="full-width" />
+                </template>
+              </el-table-column>
+              <el-table-column label="停缴记录" min-width="130">
+                <template slot-scope="{ row }">
+                  <el-select v-model="row.stopReason" clearable placeholder="请选择" class="full-width">
+                    <el-option v-for="opt in ['离职', '调转', '其他']" :key="opt" :label="opt" :value="opt" />
+                  </el-select>
+                </template>
+              </el-table-column>
+              <el-table-column label="操作" width="90" align="center">
+                <template slot-scope="{ $index }">
+                  <el-button type="text" icon="el-icon-delete" class="social-delete-btn"
+                    @click="removeItem('socialSecurityRecords', $index)">删除</el-button>
+                </template>
+              </el-table-column>
+            </el-table>
           </div>
 
           <div v-else-if="section.name === 'qualification'" class="section-content">
@@ -447,32 +520,52 @@
               </el-table-column>
             </el-table>
 
-            <div class="subsection-title"><strong>技能列表</strong><el-button type="text" icon="el-icon-plus"
+            <div class="subsection-title"><strong>技能列表</strong><el-button type="primary" size="mini"
                 @click="addSkill">添加技能</el-button></div>
-            <div v-if="form.skills.length" class="repeat-list">
-              <div v-for="(item, index) in form.skills" :key="`skill-${index}`" class="repeat-block">
-                <div class="repeat-block-heading"><strong>技能 {{ index + 1 }}</strong><el-button type="text"
-                    icon="el-icon-delete" @click="removeItem('skills', index)">删除</el-button></div>
-                <el-row :gutter="14">
-                  <el-col :xs="12" :sm="6"><el-form-item label="分类"><el-select v-model="item.category" clearable
-                        class="full-width"><el-option v-for="opt in skillCategoryOptions" :key="opt" :label="opt"
-                          :value="opt" /></el-select></el-form-item></el-col>
-                  <el-col :xs="12" :sm="6"><el-form-item label="技能名称" required><el-input v-model.trim="item.name"
-                        maxlength="30" /></el-form-item></el-col>
-                  <el-col :xs="12" :sm="6"><el-form-item label="技能等级"><el-select v-model="item.level" clearable
-                        class="full-width"><el-option v-for="opt in skillLevelOptions" :key="opt" :label="opt"
-                          :value="opt" /></el-select></el-form-item></el-col>
-                  <el-col :xs="12" :sm="6"><el-form-item label="技能经验(年限)"><el-input-number v-model="item.years" :min="0"
-                        :precision="1" :controls="false" class="full-width" /></el-form-item></el-col>
-                  <el-col :xs="24" :sm="12"><el-form-item label="证照附件">
-                      <fileMain v-model="item.attachment" size="mini" />
-                    </el-form-item></el-col>
-                  <el-col :span="24"><el-form-item label="备注"><el-input v-model.trim="item.remark" type="textarea"
-                        :rows="2" maxlength="200" placeholder="培训经历、考核情况" /></el-form-item></el-col>
-                </el-row>
-              </div>
-            </div>
-            <div v-else class="inline-empty">暂无技能记录</div>
+            <el-table :data="form.skills" border size="small" class="skill-table" empty-text="暂无技能记录">
+              <el-table-column type="index" label="序号" width="55" align="center" />
+              <el-table-column label="分类" min-width="130">
+                <template slot-scope="{ row }">
+                  <el-select v-model="row.category" clearable placeholder="请选择" class="full-width">
+                    <el-option v-for="opt in skillCategoryOptions" :key="opt" :label="opt" :value="opt" />
+                  </el-select>
+                </template>
+              </el-table-column>
+              <el-table-column min-width="140">
+                <template slot="header"><span class="cell-required">技能名称</span></template>
+                <template slot-scope="{ row }">
+                  <el-input v-model.trim="row.name" maxlength="30" placeholder="请输入" />
+                </template>
+              </el-table-column>
+              <el-table-column label="技能等级" min-width="120">
+                <template slot-scope="{ row }">
+                  <el-select v-model="row.level" clearable placeholder="请选择" class="full-width">
+                    <el-option v-for="opt in skillLevelOptions" :key="opt" :label="opt" :value="opt" />
+                  </el-select>
+                </template>
+              </el-table-column>
+              <el-table-column label="技能经验(年限)" min-width="130">
+                <template slot-scope="{ row }">
+                  <el-input-number v-model="row.years" :min="0" :precision="1" :controls="false" class="full-width" />
+                </template>
+              </el-table-column>
+              <el-table-column label="证照附件" min-width="140">
+                <template slot-scope="{ row }">
+                  <fileMain v-model="row.attachment" size="mini" />
+                </template>
+              </el-table-column>
+              <el-table-column label="备注" min-width="160">
+                <template slot-scope="{ row }">
+                  <el-input v-model.trim="row.remark" maxlength="200" placeholder="培训经历、考核情况" />
+                </template>
+              </el-table-column>
+              <el-table-column label="操作" width="90" align="center">
+                <template slot-scope="{ $index }">
+                  <el-button type="text" icon="el-icon-delete" class="skill-delete-btn"
+                    @click="removeItem('skills', $index)">删除</el-button>
+                </template>
+              </el-table-column>
+            </el-table>
 
             <div class="subsection-title attachment-title"><strong>档案附件</strong>
               <fileMain v-model="form.files" size="mini" />
@@ -532,7 +625,7 @@ const emptyForm = () => ({
   employmentType: '', status: '全职', hireDate: '', regularDate: '', account: '', enabled: true,
   keyProcess: '',
   phone: '', email: '', wechat: '', workAddress: '', currentAddress: '', registeredAddress: '', emergencyContact: '', emergencyRelation: '', emergencyPhone: '', familyMembers: [],
-  education: '本科', school: '',
+  education: '本科', school: '', major: '', graduationDate: '',
   educations: [],
   previousJobs: [],
   contract: emptyContract(), bankAccounts: [],
@@ -2017,12 +2110,18 @@ export default {
         if ((this.form.educations || []).length > 1) {
           this.$message.info('多条教育经历仅第一条会写入员工主数据字段');
         }
+        const firstEducation = (this.form.educations || [])[0] || {};
         const payload = {
           ...this.form,
           certificates,
           contract,
           awards,
           bankAccounts,
+          education: firstEducation.education || this.form.education || '',
+          degree: firstEducation.degree || this.form.degree || '',
+          school: firstEducation.school || this.form.school || '',
+          major: firstEducation.major || this.form.major || '',
+          graduationDate: firstEducation.graduationDate || this.form.graduationDate || '',
           status: leaveEnabled ? '离职' : this.form.status,
           age: this.calculateAge(this.form.birthDate),
         };