فهرست منبع

编辑员工档案工种和资质改成table显示,岗位设定里的资质也是改成table,规范UI样式

lihaiting 2 روز پیش
والد
کامیت
2ebb5eeb14

+ 6 - 5
src/api/organization/index.js

@@ -463,15 +463,16 @@ function adaptCertificates(list = []) {
 function adaptWorkTypes(list = []) {
   return (list || []).map((item) => ({
     id: item.id,
-    type: "",
+    type: item.type || "",
     code: item.code || "",
     name: item.name || "",
     level: item.level || "",
+    hourCost: item.hourCost ?? 0,
+    unit: item.unit || "",
     hourlyWage: item.hourCost ?? 0,
-    wageUnit: item.unit || "元/小时",
-    healthStatus: "优",
-    specialOperator: "否",
-    photo: [],
+    wageUnit: item.unit || "",
+    userId: item.userId || item.relatedUserId || "",
+    userName: item.userName || item.relatedUserName || "",
   }));
 }
 

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

@@ -48,7 +48,7 @@
 .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 span { padding: 3px 7px; border-radius: 3px; color: #8492a3; font-size: 14px; background: #f1f4f7; }
-.info-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: 26px; }
+.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; }
 .info-grid .wide { grid-column: 1 / -1; }
 .info-grid span, .info-grid strong { display: block; }
@@ -98,6 +98,11 @@
 .record-list strong, .record-list small { display: block; }
 .record-list strong { color: #405168; font-size: 14px; }
 .record-list small { margin-top: 3px; color: #96a1af; font-size: 14px; }
+.detail-data-table { width: 100%; margin-bottom: 4px; }
+
+@media (max-width: 900px) {
+  .info-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
+}
 
 @media (max-width: 620px) {
   .profile-header { padding: 58px 18px 20px; }

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

@@ -2,14 +2,14 @@
 .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__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; min-width: 0; }
-.employee-archive-dialog .archive-dialog-title > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
+.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; }
 .employee-archive-dialog .archive-dialog-fullscreen,
-.employee-archive-dialog .el-dialog__headerbtn { position: relative; top: auto; right: auto; z-index: 3; flex: none; width: 30px; height: 30px; margin-left: 4px; 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 .archive-dialog-fullscreen { margin-left: auto; }
+.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 .el-dialog__close { color: inherit; font-size: 17px; }
+.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; }
 .employee-archive-dialog .el-dialog__footer { position: relative; z-index: 2; flex: 0 0 auto; padding: 12px 20px; border-top: 1px solid #e2e8ef; background: #f8fafc; box-shadow: 0 -4px 12px rgba(35, 55, 78, .04); }
@@ -19,14 +19,15 @@
 .employee-archive-dialog .archive-form-tabs > .el-tabs__content::-webkit-scrollbar-thumb { border-radius: 3px; background: #c7d1dd; }
 .employee-archive-dialog .archive-form-tabs > .el-tabs__content::-webkit-scrollbar-track { background: transparent; }
 .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 { 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 { 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 .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 > 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__active-bar.is-left { width: 3px; border-radius: 2px; background: #1768e5; }
+.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__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; }
 .employee-archive-dialog .el-form-item__label { padding-bottom: 6px; color: #536379; font-size: 14px; font-weight: 500; line-height: 17px; }
@@ -44,12 +45,28 @@
 .employee-archive-dialog .el-form-item__error { position: static !important; padding-top: 6px; font-size: 14px; line-height: 18px; }
 .employee-archive-dialog .position-multi-select .el-select__tags { max-width: calc(100% - 36px); }
 .employee-archive-dialog .position-multi-select .el-select__tags .el-tag { max-width: 100%; }
+.employee-archive-dialog .dept-multi-cascader .el-cascader__tags {
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+  max-width: calc(100% - 30px);
+  padding: 2px 0;
+}
+.employee-archive-dialog .dept-multi-cascader .el-cascader__tags .el-tag {
+  max-width: 100%;
+  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 .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-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; }
 
-@media (max-width: 1120px) { .employee-archive-dialog { width: calc(100vw - 28px) !important; height: 96vh; margin-top: 2vh !important; max-height: none; } }
+@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) {
   .employee-archive-dialog .el-dialog__header { padding: 15px 16px 13px; }
   .employee-archive-dialog .el-dialog__header::before { left: 16px; }

+ 5 - 0
src/styles/views/employeeArchive/components/EmployeeArchiveForm.scss

@@ -55,6 +55,11 @@
 .remove-button { width: 32px; padding: 8px 0; border-color: #eadfe1; color: #b45d69; background: #fff; }
 .remove-button:hover { border-color: #d8aeb4; color: #9d3f4c; background: #fff7f8; }
 .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; }
 .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; }

+ 23 - 14
src/styles/views/positionManagement/components/PositionEditDialog.scss

@@ -281,6 +281,22 @@
     font-weight: 700;
   }
 
+  .form-subtitle-row {
+    height: 42px;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    margin: 8px 0 12px;
+
+    .form-subtitle {
+      margin: 0;
+    }
+  }
+
+  .form-subtitle-row + .skill-cert-table {
+    margin-bottom: 18px;
+  }
+
   .required-mark {
     margin-left: 4px;
     color: #f56c6c;
@@ -305,20 +321,17 @@
     }
   }
 
-  .dynamic-row {
-    margin-bottom: 10px;
-    display: grid;
-    grid-template-columns: minmax(0, 1.4fr) 150px 110px 40px;
-    gap: 10px;
-    align-items: center;
+  .skill-cert-table {
+    width: 100%;
   }
 
-  .cert-row {
-    grid-template-columns: minmax(0, 1.4fr) 150px 40px;
+  .skill-cert-table ::v-deep .el-table__cell {
+    padding: 8px 6px;
   }
 
-  .add-line {
-    margin-bottom: 20px;
+  .skill-cert-delete {
+    padding: 0;
+    color: #f56c6c;
   }
 
   @media screen and (max-width: 720px) {
@@ -328,10 +341,6 @@
     .qualification-section { padding: 16px 14px 4px; }
     .qualification-section .section-heading { margin-bottom: 15px; }
     .age-panel, .date-panel { height: auto; min-height: 0; margin-bottom: 12px; }
-    .dynamic-row,
-    .cert-row {
-      grid-template-columns: 1fr;
-    }
   }
 
 .basic-section { margin-bottom: 14px; }

+ 98 - 82
src/views/hrManagement/employeeArchive/components/EmployeeArchiveDetail.vue

@@ -5,7 +5,7 @@
     :with-header="false"
     append-to-body
     custom-class="employee-detail-drawer"
-    @closed="activeTab = 'overview'"
+    @closed="activeTab = 'base'"
   >
     <div v-if="employee" class="detail-shell">
       <header class="profile-header">
@@ -43,7 +43,7 @@
       </div>
 
       <el-tabs v-model="activeTab" class="profile-tabs">
-        <el-tab-pane label="档案概览" name="overview">
+        <el-tab-pane label="基本信息" name="base">
           <div class="tab-content">
             <section class="detail-section">
               <div class="section-title">
@@ -54,15 +54,14 @@
                 <div><span>姓名</span><strong>{{ employee.name || '-' }}</strong></div>
                 <div><span>性别</span><strong>{{ employee.gender || '-' }}</strong></div>
                 <div><span>身份证号</span><strong>{{ employee.idCard || '-' }}</strong></div>
-                <div><span>出生日期</span><strong>{{ employee.birthDate || '-' }}({{ employee.age || '-' }}岁)</strong></div>
+                <div><span>出生日期</span><strong>{{ employee.birthDate || '-' }}</strong></div>
+                <div><span>年龄</span><strong>{{ employee.age || '-' }}</strong></div>
                 <div><span>民族</span><strong>{{ employee.ethnic || '-' }}</strong></div>
                 <div><span>政治面貌</span><strong>{{ employee.politicalStatus || '-' }}</strong></div>
-                <div><span>婚姻状</span><strong>{{ employee.maritalStatus || '-' }}</strong></div>
+                <div><span>婚姻状</span><strong>{{ employee.maritalStatus || '-' }}</strong></div>
                 <div><span>籍贯</span><strong>{{ employee.nativePlace || '-' }}</strong></div>
                 <div><span>最高学历</span><strong>{{ employee.education || '-' }}</strong></div>
                 <div><span>毕业院校</span><strong>{{ employee.school || '-' }}</strong></div>
-                <div><span>所学专业</span><strong>{{ employee.major || '-' }}</strong></div>
-                <div><span>毕业日期</span><strong>{{ employee.graduationDate || '-' }}</strong></div>
                 <div class="wide"><span>户籍地址</span><strong>{{ employee.registeredAddress || '-' }}</strong></div>
                 <div class="wide"><span>现居住地址</span><strong>{{ employee.currentAddress || '-' }}</strong></div>
                 <div class="wide"><span>工作地址</span><strong>{{ employee.workAddress || '-' }}</strong></div>
@@ -81,12 +80,12 @@
                 <div><span>所属工厂</span><strong>{{ employee.factory || '-' }}</strong></div>
                 <div><span>隶属部门</span><strong>{{ employee.deptName || '-' }}</strong></div>
                 <div><span>直属上级</span><strong>{{ employee.manager || '未设置' }}</strong></div>
-                <div><span>岗位</span><strong>{{ employee.position || '-' }}</strong></div>
-                <div><span>岗位层级</span><strong>{{ positionLevelText }}</strong></div>
-                <div><span>岗位性质</span><strong>{{ employee.employmentType || '-' }}</strong></div>
-                <div><span>员工状态</span><strong>{{ statusLabel(employee.status) }}</strong></div>
                 <div><span>入职时间</span><strong>{{ employee.hireDate || '-' }}</strong></div>
                 <div><span>转正日期</span><strong>{{ employee.regularDate || '-' }}</strong></div>
+                <div><span>岗位</span><strong>{{ employee.position || '-' }}</strong></div>
+                <div><span>员工状态</span><strong>{{ statusLabel(employee.status) }}</strong></div>
+                <div><span>岗位性质</span><strong>{{ employee.employmentType || '-' }}</strong></div>
+                <div><span>岗位层级</span><strong>{{ positionLevelText }}</strong></div>
                 <div><span>关键工序</span><strong>{{ employee.keyProcess || '-' }}</strong></div>
                 <div><span>是否启用</span><strong>{{ enabledText }}</strong></div>
               </div>
@@ -98,8 +97,8 @@
                 <span>CONTACT</span>
               </div>
               <div class="info-grid">
-                <div><span>手机号</span><strong>{{ employee.phone || '-' }}</strong></div>
-                <div><span>企业邮箱</span><strong>{{ employee.email || '-' }}</strong></div>
+                <div><span>手机号</span><strong>{{ employee.phone || '-' }}</strong></div>
+                <div><span>邮箱</span><strong>{{ employee.email || '-' }}</strong></div>
                 <div><span>微信号</span><strong>{{ employee.wechat || '-' }}</strong></div>
                 <div><span>紧急联系人</span><strong>{{ employee.emergencyContact || '-' }}</strong></div>
                 <div><span>紧急联系人关系</span><strong>{{ employee.emergencyRelation || '-' }}</strong></div>
@@ -107,6 +106,20 @@
               </div>
             </section>
 
+            <section v-if="employee.familyMembers?.length" class="detail-section">
+              <div class="section-title"><h3>家庭成员</h3><span>{{ employee.familyMembers.length }} 人</span></div>
+              <div class="record-list">
+                <article v-for="(item, index) in employee.familyMembers" :key="`family-${index}`">
+                  <span class="record-icon family"><i class="el-icon-user"></i></span>
+                  <div><strong>{{ item.name || '-' }} · {{ item.relation || '-' }}</strong><small>{{ item.phone || '-' }} · {{ item.workUnit || '-' }}</small></div>
+                </article>
+              </div>
+            </section>
+          </div>
+        </el-tab-pane>
+
+        <el-tab-pane label="教育与履历" name="experience">
+          <div class="tab-content">
             <section class="detail-section">
               <div class="section-title">
                 <h3>教育经历</h3>
@@ -125,18 +138,8 @@
               <el-empty v-else description="暂无教育经历" :image-size="60" />
             </section>
 
-            <section v-if="employee.familyMembers?.length" class="detail-section">
-              <div class="section-title"><h3>家庭成员</h3><span>{{ employee.familyMembers.length }} 人</span></div>
-              <div class="record-list">
-                <article v-for="(item, index) in employee.familyMembers" :key="`family-${index}`">
-                  <span class="record-icon family"><i class="el-icon-user"></i></span>
-                  <div><strong>{{ item.name || '-' }} · {{ item.relation || '-' }}</strong><small>{{ item.phone || '-' }} · {{ item.workUnit || '-' }}</small></div>
-                </article>
-              </div>
-            </section>
-
             <section class="detail-section">
-              <div class="section-title"><h3>入职前工作履历</h3><span>{{ previousJobs.length }} 条</span></div>
+              <div class="section-title"><h3>工作履历</h3><span>{{ previousJobs.length }} 条</span></div>
               <div v-if="previousJobs.length" class="history-list previous-job-list">
                 <article v-for="(item, index) in previousJobs" :key="`previous-${index}`">
                   <div class="history-rail"><i :class="index === 0 ? 'active' : ''"></i></div>
@@ -150,26 +153,10 @@
               </div>
               <el-empty v-else description="暂无入职前工作履历" :image-size="60" />
             </section>
-
-            <section class="detail-section">
-              <div class="section-title"><h3>异动履历</h3><span>{{ employee.workHistory?.length || 0 }} 条</span></div>
-              <div v-if="employee.workHistory?.length" class="history-list">
-                <article v-for="(item, index) in employee.workHistory" :key="item.date + item.title">
-                  <div class="history-rail"><i :class="index === 0 ? 'active' : ''"></i></div>
-                  <div>
-                    <time>{{ item.date }}</time>
-                    <strong>{{ item.title }}</strong>
-                    <p>{{ item.detail }}</p>
-                    <small>{{ item.orderNo }}</small>
-                  </div>
-                </article>
-              </div>
-              <el-empty v-else description="暂无异动记录" :image-size="60" />
-            </section>
           </div>
         </el-tab-pane>
 
-        <el-tab-pane label="合同与薪酬" name="contract">
+        <el-tab-pane label="合同" name="contract">
           <div class="tab-content">
             <section class="contract-banner" :class="{ warning: contractDays < 120 }">
               <i class="el-icon-document-checked"></i>
@@ -180,20 +167,23 @@
               </div>
               <em>{{ contract.status || '未签署' }}</em>
             </section>
-
             <section class="detail-section">
-              <div class="section-title"><h3>合同信息</h3><span>CONTRACT</span></div>
+              <div class="section-title"><h3>劳动合同(只读)</h3><span>{{ contract.status || '未签署' }}</span></div>
               <div class="info-grid">
-                <div><span>合同分类</span><strong>{{ contract.category || '-' }}</strong></div>
+                <div><span>分类</span><strong>{{ contract.category || '-' }}</strong></div>
                 <div><span>合同编号/名称</span><strong>{{ contract.number || contract.name || '-' }}</strong></div>
                 <div><span>签订时间</span><strong>{{ contract.signDate || '-' }}</strong></div>
+                <div><span>开始时间</span><strong>{{ contract.startDate || '-' }}</strong></div>
+                <div><span>结束时间</span><strong>{{ contract.endDate || '无固定期限/未同步' }}</strong></div>
                 <div><span>生效状态</span><strong>{{ contract.status || '未签署' }}</strong></div>
-                <div><span>开始日期</span><strong>{{ contract.startDate || '-' }}</strong></div>
-                <div><span>结束日期</span><strong>{{ contract.endDate || '无固定期限/未同步' }}</strong></div>
                 <div class="wide"><span>备注</span><strong>{{ contract.remark || '-' }}</strong></div>
               </div>
             </section>
+          </div>
+        </el-tab-pane>
 
+        <el-tab-pane label="薪酬与社保" name="payroll">
+          <div class="tab-content">
             <section class="detail-section">
               <div class="section-title"><h3>薪酬银行</h3><span>{{ bankList.length }} 个账户</span></div>
               <div v-if="bankList.length" class="record-list">
@@ -208,7 +198,6 @@
               </div>
               <el-empty v-else description="暂无银行账户" :image-size="60" />
             </section>
-
             <section class="detail-section">
               <div class="section-title"><h3>社保公积金</h3><span>{{ socialList.length }} 条记录</span></div>
               <div v-if="socialList.length" class="record-list">
@@ -227,26 +216,23 @@
           </div>
         </el-tab-pane>
 
-        <el-tab-pane label="资质与技能" name="qualification">
+        <el-tab-pane label="资质证照" name="qualification">
           <div class="tab-content">
             <section class="detail-section">
               <div class="section-title"><h3>工种</h3><span>{{ workTypeList.length }} 条</span></div>
-              <div v-if="workTypeList.length" class="record-list">
-                <article v-for="(item, index) in workTypeList" :key="`work-${item.id || index}`">
-                  <span class="record-icon certificate"><i class="el-icon-suitcase"></i></span>
-                  <div>
-                    <strong>{{ item.name || '-' }}<template v-if="item.code">({{ item.code }})</template></strong>
-                    <small>类型:{{ item.type || '-' }} · 等级:{{ item.level || '-' }}</small>
-                    <small>标准工时费:{{ wageText(item) }} · 健康状况:{{ item.healthStatus || '-' }}</small>
-                    <small>是否特种作业人员:{{ item.specialOperator || '否' }}</small>
-                  </div>
-                </article>
-              </div>
-              <el-empty v-else description="暂无工种记录" :image-size="60" />
+              <el-table :data="workTypeList" border size="small" class="detail-data-table" empty-text="暂无工种记录">
+                <el-table-column type="index" label="序号" width="55" align="center" />
+                <el-table-column label="类型" min-width="110" show-overflow-tooltip :formatter="workTypeTypeLabel" />
+                <el-table-column prop="code" label="编码" min-width="110" show-overflow-tooltip />
+                <el-table-column prop="name" label="名称" min-width="110" show-overflow-tooltip />
+                <el-table-column label="等级" min-width="110" show-overflow-tooltip :formatter="workTypeLevelLabel" />
+                <el-table-column label="标准工时费" min-width="110" show-overflow-tooltip :formatter="workTypeHourCostLabel" />
+                <el-table-column label="工时费单位" min-width="110" show-overflow-tooltip :formatter="workTypeUnitLabel" />
+              </el-table>
             </section>
 
             <section class="detail-section">
-              <div class="section-title"><h3>证照列表</h3><span>{{ certificateList.length }} 项</span></div>
+              <div class="section-title"><h3>资质证照列表</h3><span>{{ certificateList.length }} 项</span></div>
               <div v-if="certificateList.length" class="record-list">
                 <article v-for="(item, index) in certificateList" :key="`cert-${item.id || index}`">
                   <span class="record-icon award"><i class="el-icon-medal"></i></span>
@@ -258,7 +244,7 @@
                   </div>
                 </article>
               </div>
-              <el-empty v-else description="暂无证照记录" :image-size="60" />
+              <el-empty v-else description="暂无资质证照记录" :image-size="60" />
             </section>
 
             <section class="detail-section">
@@ -293,15 +279,22 @@
           </div>
         </el-tab-pane>
 
-        <el-tab-pane label="成长与分析(AI)" name="growth">
+        <el-tab-pane label="业务记录" name="records">
           <div class="tab-content">
-            <section class="insight-panel">
-              <div class="insight-heading"><i class="el-icon-data-analysis"></i><div><strong>档案分析摘要</strong><small>基于当前档案规则生成</small></div></div>
-              <p>{{ insightText }}</p>
-              <div class="ability-tags">
-                <el-tag v-for="item in skillList" :key="`tag-${item.name}`" size="small">{{ item.name }}</el-tag>
-                <el-tag v-for="item in workTypeList" :key="`tag-work-${item.name}`" size="small" type="success">{{ item.name }}</el-tag>
+            <section class="detail-section">
+              <div class="section-title"><h3>异动履历</h3><span>{{ employee.workHistory?.length || 0 }} 条</span></div>
+              <div v-if="employee.workHistory?.length" class="history-list">
+                <article v-for="(item, index) in employee.workHistory" :key="item.date + item.title">
+                  <div class="history-rail"><i :class="index === 0 ? 'active' : ''"></i></div>
+                  <div>
+                    <time>{{ item.date }}</time>
+                    <strong>{{ item.title }}</strong>
+                    <p>{{ item.detail }}</p>
+                    <small>{{ item.orderNo }}</small>
+                  </div>
+                </article>
               </div>
+              <el-empty v-else description="暂无异动记录" :image-size="60" />
             </section>
             <section class="detail-section">
               <div class="section-title"><h3>评优获奖</h3><span>{{ employee.awards?.length || 0 }} 项</span></div>
@@ -313,12 +306,9 @@
               </div>
               <el-empty v-else description="暂无获奖记录" :image-size="60" />
             </section>
+            <employee-archive-records-detail :employee="employee" />
           </div>
         </el-tab-pane>
-
-        <el-tab-pane label="业务记录" name="records">
-          <div class="tab-content"><employee-archive-records-detail :employee="employee" /></div>
-        </el-tab-pane>
       </el-tabs>
     </div>
   </el-drawer>
@@ -326,18 +316,34 @@
 
 <script>
 import EmployeeArchiveRecordsDetail from './EmployeeArchiveRecordsDetail.vue';
+import dictMixins from '@/mixins/dictMixins';
 
 export default {
   name: 'EmployeeArchiveDetail',
   components: { EmployeeArchiveRecordsDetail },
+  mixins: [dictMixins],
   props: { visible: Boolean, employee: { type: Object, default: null } },
-  data() { return { activeTab: 'overview', windowWidth: window.innerWidth }; },
+  data() {
+    return {
+      activeTab: 'base',
+      windowWidth: window.innerWidth,
+      workTypeLevelOptions: [
+        { label: '初级', value: '1' },
+        { label: '中级', value: '2' },
+        { label: '高级', value: '3' }
+      ]
+    };
+  },
   computed: {
     drawerVisible: {
       get() { return this.visible; },
       set(value) { this.$emit('update:visible', value); }
     },
-    drawerSize() { return this.windowWidth < 720 ? '100%' : this.windowWidth < 1200 ? '620px' : '680px'; },
+    drawerSize() {
+      if (this.windowWidth < 720) return '100%';
+      if (this.windowWidth < 1100) return '92%';
+      return '1080px';
+    },
     avatarStyle() {
       const tones = ['#1768e5', '#13a471', '#8b5cf6', '#d97706', '#dc5064'];
       const code = [...(this.employee?.name || '')].reduce((sum, char) => sum + char.charCodeAt(0), 0);
@@ -404,21 +410,31 @@ export default {
       const dept = employee.deptName || '';
       if (company && dept && company !== dept) return `${company} · ${dept}`;
       return company || dept || '-';
-    },
-    insightText() {
-      const employee = this.employee || {};
-      const contractTip = this.contractDays < 0 ? '当前劳动合同已经到期,请尽快核验合同状态。' : this.contractDays < 120 ? `当前合同将在 ${this.contractDays} 天后到期,建议提前启动续签评估。` : '当前合同有效期充足,暂无续签风险。';
-      return `${employee.name || '该员工'}目前担任${employee.position || '现岗位'},已有${this.tenureText}司龄,共记录 ${employee.workHistory?.length || 0} 次人事异动与 ${employee.awards?.length || 0} 项评优获奖。${contractTip}`;
     }
   },
+  created() {
+    this.requestDict('工种类型');
+  },
   mounted() { window.addEventListener('resize', this.handleResize); },
   beforeDestroy() { window.removeEventListener('resize', this.handleResize); },
   methods: {
     handleResize() { this.windowWidth = window.innerWidth; },
     statusLabel(status) { return { active: '在职', probation: '试用期', inactive: '已离职', disabled: '待岗' }[status] || '未知'; },
-    wageText(item = {}) {
-      const wage = item.hourlyWage;
-      return wage || wage === 0 ? `${wage} ${item.wageUnit || '元/小时'}` : '-';
+    workTypeTypeLabel(row) {
+      return this.getDictValue('工种类型', row.type) || row.type || '-';
+    },
+    workTypeLevelLabel(row) {
+      const hit = this.workTypeLevelOptions.find(
+        (item) => String(item.value) === String(row.level) || item.label === row.level,
+      );
+      return hit?.label || row.level || '-';
+    },
+    workTypeHourCostLabel(row) {
+      const wage = row.hourCost ?? row.hourlyWage;
+      return wage || wage === 0 ? wage : '-';
+    },
+    workTypeUnitLabel(row) {
+      return row.unit || row.wageUnit || '-';
     }
   }
 };

+ 214 - 106
src/views/hrManagement/employeeArchive/components/EmployeeArchiveForm.vue

@@ -1,11 +1,12 @@
 <template>
+  <div>
   <el-dialog
     :visible.sync="dialogVisible"
-    :width="fullscreen ? '100%' : '1080px'"
+    :width="fullscreen ? '100%' : '1280px'"
     :top="fullscreen ? '0' : '3vh'"
     append-to-body
     :close-on-click-modal="false"
-    :close-on-press-escape="true"
+    :close-on-press-escape="!showWorkTypeSelect"
     :before-close="handleBeforeClose"
     :custom-class="'employee-archive-dialog' + (fullscreen ? ' is-fullscreen' : '')"
     @closed="resetForm"
@@ -18,7 +19,7 @@
         :title="fullscreen ? '退出全屏' : '全屏'"
         @click.stop="toggleFullscreen"
       >
-        <i :class="fullscreen ? 'el-icon-copy-document' : 'el-icon-full-screen'"></i>
+        <i :class="fullscreen ? 'el-icon-_compress' : 'el-icon-full-screen'"></i>
       </button>
     </div>
     <div class="archive-form-summary">
@@ -91,8 +92,8 @@
             <div class="subsection-title"><strong>组织与任职</strong></div>
             <el-row :gutter="16">
               <el-col :xs="24" :sm="12"><el-form-item label="工号" prop="employeeNo"><el-input
-                    v-model.trim="form.employeeNo" maxlength="20" placeholder="集团全局唯一"><el-button slot="append"
-                      :disabled="isEdit" @click="generateEmployeeNo">生成</el-button></el-input></el-form-item></el-col>
+                    v-model.trim="form.employeeNo" maxlength="20" placeholder="集团全局唯一" disabled><el-button
+                      v-if="!isEdit" slot="append" @click="generateEmployeeNo">生成</el-button></el-input></el-form-item></el-col>
               <el-col :xs="24" :sm="12"><el-form-item label="用户账号"><el-input v-model.trim="form.account" maxlength="30"
                     placeholder="系统登录账号,支持自动生成、手动修改"><template
                       slot="prepend">@</template></el-input></el-form-item></el-col>
@@ -129,8 +130,9 @@
               <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.deptIds"
-                    :options="deptOptions" :props="deptCascaderProps" filterable clearable collapse-tags
-                    class="full-width"
+                    :options="deptOptions" :props="deptCascaderProps" filterable clearable
+                    :show-all-levels="false"
+                    class="full-width dept-multi-cascader"
                     :disabled="!form.factoryId"
                     :placeholder="form.factoryId ? '请选择隶属部门(可多选)' : (form.groupId ? '请先选择所属工厂' : '请先选择所属机构')"
                     @change="handleOrganizationChange" /></el-form-item></el-col>
@@ -270,8 +272,8 @@
           <div v-else-if="section.name === 'contract'" class="section-content">
             <header class="section-title">
               <div>
-                <h3>合同与薪酬银行</h3>
-                <p>劳动合同只读同步自合同管理;工资卡可在此维护</p>
+                <h3>劳动合同</h3>
+                <p>劳动合同只读同步自合同管理</p>
               </div><span>{{ contractState }}</span>
             </header>
             <div class="subsection-title"><strong>劳动合同(只读)</strong></div>
@@ -292,7 +294,16 @@
               <el-col :span="24"><el-form-item label="备注"><el-input :value="form.contract.remark" type="textarea"
                     :rows="2" readonly placeholder="—" /></el-form-item></el-col>
             </el-row>
-            <div class="subsection-title"><strong>薪酬银行</strong><el-button type="text" icon="el-icon-plus"
+          </div>
+
+          <div v-else-if="section.name === 'payroll'" class="section-content">
+            <header class="section-title">
+              <div>
+                <h3>薪酬与社保</h3>
+                <p>工资卡银行账户与社保公积金参保记录</p>
+              </div><span>{{ form.socialSecurityRecords.length }} 条社保记录</span>
+            </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">
@@ -304,17 +315,8 @@
               </div>
             </div>
             <div v-else class="inline-empty">暂无银行账户</div>
-          </div>
 
-          <div v-else-if="section.name === 'social'" class="section-content">
-            <header class="section-title">
-              <div>
-                <h3>社保、资质与技能</h3>
-                <p>社保公积金、工种、证照与技能列表</p>
-              </div><span>{{ form.socialSecurityRecords.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="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">
@@ -339,79 +341,111 @@
               </div>
             </div>
             <div v-else class="inline-empty">暂无社保记录</div>
+          </div>
 
-            <div class="subsection-title"><strong>工种</strong><el-button type="text" icon="el-icon-plus"
-                @click="addWorkType">添加工种</el-button></div>
-            <div v-if="form.workTypes.length" class="repeat-list">
-              <div v-for="(item, index) in form.workTypes" :key="`wt-${index}`" class="repeat-block">
-                <div class="repeat-block-heading"><strong>工种 {{ index + 1 }}</strong><el-button type="text"
-                    icon="el-icon-delete" @click="removeItem('workTypes', 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 workTypeCategoryOptions" :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 v-model.trim="item.code"
-                        maxlength="20" /></el-form-item></el-col>
-                  <el-col :xs="12" :sm="6"><el-form-item label="名称"><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 workTypeLevelOptions" :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.hourlyWage"
-                        :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-select v-model="item.wageUnit" clearable
-                        class="full-width"><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-select v-model="item.healthStatus" clearable
-                        class="full-width"><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-select v-model="item.specialOperator"
-                        class="full-width"><el-option label="是" value="是" /><el-option label="否"
-                          value="否" /></el-select></el-form-item></el-col>
-                  <el-col :span="24"><el-form-item label="证件照">
-                      <fileMain v-model="item.photo" size="mini" />
-                    </el-form-item></el-col>
-                </el-row>
-              </div>
-            </div>
-            <div v-else class="inline-empty">暂无工种记录</div>
+          <div v-else-if="section.name === 'qualification'" class="section-content">
+            <header class="section-title">
+              <div>
+                <h3>资质与技能</h3>
+                <p>工种、证照、技能与档案附件</p>
+              </div><span>{{ form.workTypes.length + form.certificates.length + form.skills.length }} 条资质记录</span>
+            </header>
 
-            <div class="subsection-title"><strong>证照列表</strong><el-button type="text" icon="el-icon-plus"
-                @click="addCertificate">添加证照</el-button></div>
-            <div v-if="form.certificates.length" class="repeat-list">
-              <div v-for="(item, index) in form.certificates" :key="`cert-${index}`" class="repeat-block">
-                <div class="repeat-block-heading"><strong>证照 {{ index + 1 }}</strong><el-button type="text"
-                    icon="el-icon-delete" @click="removeItem('certificates', index)">删除</el-button></div>
-                <el-row :gutter="14">
-                  <el-col :xs="12" :sm="6"><el-form-item label="证照类型" required><el-select v-model="item.type"
-                        class="full-width"><el-option v-for="opt in certificateTypeOptions" :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.number"
-                        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 certificateLevelOptions" :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-select v-model="item.status"
-                        class="full-width"><el-option label="有效" value="有效" /><el-option label="失效"
-                          value="失效" /></el-select></el-form-item></el-col>
-                  <el-col :xs="12" :sm="6"><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="6"><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="12" :sm="6"><el-form-item label="颁布日期"><el-date-picker v-model="item.issueDate"
-                        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="证照附件">
-                      <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" maxlength="200"
-                        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="primary" size="mini"
+                @click="openWorkTypeSelect">添加工种</el-button></div>
+            <el-table :data="form.workTypes" border size="small" class="work-type-table" empty-text="暂无工种记录">
+              <el-table-column type="index" label="序号" width="55" align="center" />
+              <el-table-column label="类型" min-width="110" show-overflow-tooltip
+                :formatter="workTypeTypeLabel" />
+              <el-table-column prop="code" label="编码" min-width="110" show-overflow-tooltip />
+              <el-table-column prop="name" label="名称" min-width="110" show-overflow-tooltip />
+              <el-table-column label="等级" min-width="110" show-overflow-tooltip
+                :formatter="workTypeLevelLabel" />
+              <el-table-column label="标准工时费" min-width="110" show-overflow-tooltip
+                :formatter="workTypeHourCostLabel" />
+              <el-table-column label="工时费单位" min-width="110" show-overflow-tooltip
+                :formatter="workTypeUnitLabel" />
+              <el-table-column label="操作" width="90" align="left">
+                <template slot-scope="{ $index }">
+                  <el-popconfirm class="ele-action" title="确定要删除当前工种吗?"
+                    @confirm="removeItem('workTypes', $index)">
+                    <el-link slot="reference" type="danger" :underline="false" icon="el-icon-delete">删除</el-link>
+                  </el-popconfirm>
+                </template>
+              </el-table-column>
+            </el-table>
+
+            <div class="subsection-title"><strong>资质证照列表</strong><el-button type="primary" size="mini"
+                @click="addCertificate">添加资质证照</el-button></div>
+            <el-table :data="form.certificates" border size="small" class="certificate-table" empty-text="暂无资质证照记录">
+              <el-table-column type="index" label="序号" width="55" align="center" />
+              <el-table-column min-width="130">
+                <template slot="header"><span class="cell-required">证照类型</span></template>
+                <template slot-scope="{ row }">
+                  <el-select v-model="row.type" placeholder="请选择" class="full-width">
+                    <el-option v-for="opt in certificateTypeOptions" :key="opt" :label="opt" :value="opt" />
+                  </el-select>
+                </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.number" maxlength="30" placeholder="请输入" />
+                </template>
+              </el-table-column>
+              <el-table-column label="等级" min-width="110">
+                <template slot-scope="{ row }">
+                  <el-select v-model="row.level" clearable placeholder="请选择" class="full-width">
+                    <el-option v-for="opt in certificateLevelOptions" :key="opt" :label="opt" :value="opt" />
+                  </el-select>
+                </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.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 label="颁布日期" min-width="150">
+                <template slot-scope="{ row }">
+                  <el-date-picker v-model="row.issueDate" value-format="yyyy-MM-dd" type="date" placeholder="选择日期"
+                    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 min-width="110">
+                <template slot="header"><span class="cell-required">状态</span></template>
+                <template slot-scope="{ row }">
+                  <el-select v-model="row.status" placeholder="请选择" class="full-width">
+                    <el-option label="有效" value="有效" />
+                    <el-option label="失效" value="失效" />
+                  </el-select>
+                </template>
+              </el-table-column>
+              <el-table-column label="备注" min-width="140">
+                <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="certificate-delete-btn"
+                    @click="removeItem('certificates', $index)">删除</el-button>
+                </template>
+              </el-table-column>
+            </el-table>
 
             <div class="subsection-title"><strong>技能列表</strong><el-button type="text" icon="el-icon-plus"
                 @click="addSkill">添加技能</el-button></div>
@@ -462,10 +496,19 @@
       </span>
     </span>
   </el-dialog>
+    <aptitude-dialog
+      ref="workTypeSelect"
+      :disabled-ids="selectedWorkTypeIds"
+      @success="handleWorkTypeSelect"
+      @update:visible="showWorkTypeSelect = $event"
+    />
+  </div>
 </template>
 
 <script>
 import EmployeeArchiveRecords from './EmployeeArchiveRecords.vue';
+import AptitudeDialog from '@/views/hrManagement/jobManagement/components/aptitudeDialog.vue';
+import dictMixins from '@/mixins/dictMixins';
 import {
   getPositionPage,
   getPositionById,
@@ -504,7 +547,8 @@ const emptyForm = () => ({
 
 export default {
   name: 'EmployeeArchiveForm',
-  components: { EmployeeArchiveRecords },
+  components: { EmployeeArchiveRecords, AptitudeDialog },
+  mixins: [dictMixins],
   props: {
     visible: Boolean,
     employee: { type: Object, default: null },
@@ -555,11 +599,18 @@ export default {
     };
     return {
       activeTab: 'base', saving: false, fullscreen: false, form: emptyForm(), organizationTree: [],
+      showWorkTypeSelect: false,
+      workTypeLevelOptions: [
+        { label: '初级', value: '1' },
+        { label: '中级', value: '2' },
+        { label: '高级', value: '3' }
+      ],
       sections: [
         { name: 'base', label: '基本信息', subtitle: '身份/任职/联系', icon: 'el-icon-user' },
         { name: 'experience', label: '教育与履历', subtitle: '学习与工作经历', icon: 'el-icon-notebook-2' },
-        { name: 'contract', label: '合同与薪酬', subtitle: '合同与银行', icon: 'el-icon-document-checked' },
-        { name: 'social', label: '社保与资质', subtitle: '证照与附件', icon: 'el-icon-medal' },
+        { name: 'contract', label: '合同', subtitle: '劳动合同信息', icon: 'el-icon-document-checked' },
+        { name: 'payroll', label: '薪酬与社保', subtitle: '银行/社保公积金', icon: 'el-icon-bank-card' },
+        { name: 'qualification', label: '资质证照', subtitle: '工种/证照/技能', icon: 'el-icon-medal' },
         { name: 'records', label: '业务记录', subtitle: '考勤绩效与异动', icon: 'el-icon-tickets' }
       ],
       // 岗位设定列表(岗位下拉数据源)
@@ -587,8 +638,6 @@ export default {
       factoryLoading: false,
       socialSecurityOptions: ['四险', '四险一金', '五险', '五险一金', '六险一金'],
       contractTypeOptions: ['固定期限', '无固定期限', '以完成一定工作任务为期限', '劳务协议', '实习协议'],
-      workTypeCategoryOptions: ['维修', '装配', '检验', '机加', '焊接', '其他'],
-      workTypeLevelOptions: ['初级', '中级', '高级', '技师', '高级技师'],
       certificateTypeOptions: ['身份证', '护照', '特种作业证', '职称证', '职业资格证', '其他'],
       certificateLevelOptions: ['初级', '中级', '高级'],
       skillCategoryOptions: ['专业技能', '办公技能', '语言技能', '特种操作技能', '其他'],
@@ -734,8 +783,9 @@ export default {
       return {
         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']),
+        contract: `${[this.form.contract.number || this.form.contract.name, this.form.contract.startDate, this.form.contract.endDate].filter(Boolean).length}/3`,
+        payroll: count(['bankAccounts', 'socialSecurityRecords']),
+        qualification: count(['workTypes', 'certificates', 'skills', 'files']),
         records: `${['attendanceRecords', 'performanceRecords', 'trainingRecords', 'rewardPunishmentRecords', 'awards', 'positionTransfers', 'contractChanges'].reduce((sum, key) => sum + this.form[key].length, 0) + (this.form.leaveInfo.enabled ? 1 : 0)}`
       };
     },
@@ -743,7 +793,15 @@ export default {
       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 ? '合同资料已填写' : '待补结束日期') : '待录入合同'; }
+    contractState() { return this.form.contract.name ? (this.form.contract.endDate ? '合同资料已填写' : '待补结束日期') : '待录入合同'; },
+    selectedWorkTypeIds() {
+      return (this.form.workTypes || [])
+        .map((item) => item.id)
+        .filter((id) => id != null && id !== '');
+    }
+  },
+  created() {
+    this.requestDict('工种类型');
   },
   watch: { visible(value) { if (value) this.loadForm(); } },
   methods: {
@@ -799,7 +857,7 @@ export default {
         skills: (source.skills || []).map((item) => (typeof item === 'string' ? { name: item, category: '', level: '', years: 0, attachment: [], remark: '' } : { ...item, attachment: toDocIds(item.attachment) })),
         certificates: (source.certificates || []).map((item) => ({ ...item, attachment: toDocIds(item.attachment) })),
         socialSecurityRecords: source.socialSecurityRecords || [],
-        workTypes: (source.workTypes || []).map((item) => ({ ...item, photo: toDocIds(item.photo) })),
+        workTypes: (source.workTypes || []).map((item) => this.mapProfessionToWorkType(item)),
         attendanceRecords: source.attendanceRecords || [], performanceRecords: source.performanceRecords || [],
         trainingRecords: (source.trainingRecords || []).map((item) => ({ ...item, attachment: toDocIds(item.attachment) })),
         rewardPunishmentRecords: (source.rewardPunishmentRecords || []).map((item) => ({ ...item, attachment: toDocIds(item.attachment) })), awards: source.awards || [], positionTransfers: source.positionTransfers || [], contractChanges: source.contractChanges || []
@@ -1325,6 +1383,7 @@ export default {
       this.factoryLoading = false;
       this.staffingLevelOptions = [];
       this.positionOptions = [];
+      this.showWorkTypeSelect = false;
       this._managerRequestKey = '';
       this.$nextTick(() => {
         this.form = emptyForm();
@@ -1591,7 +1650,56 @@ export default {
     addBankAccount() { this.form.bankAccounts.push({ bankName: '', bankAccount: '', bankAccountName: '' }); },
     addPreviousJob() { this.form.previousJobs.push({ startDate: '', endDate: '', company: '', position: '', department: '', leaveReason: '', description: '' }); },
     addSocialSecurity() { this.form.socialSecurityRecords.push({ type: '五险一金', base: 0, insuredDate: '', stopDate: '', stopReason: '' }); },
-    addWorkType() { this.form.workTypes.push({ type: '', code: '', name: '', level: '', hourlyWage: 0, wageUnit: '元/小时', healthStatus: '优', specialOperator: '否', photo: [] }); },
+    workTypeTypeLabel(_row, _column, cellValue) {
+      const type = _row?.type ?? cellValue;
+      return this.getDictValue('工种类型', type) || type || '-';
+    },
+    workTypeLevelLabel(_row, _column, cellValue) {
+      const level = _row?.level ?? cellValue;
+      const hit = this.workTypeLevelOptions.find(
+        (item) => String(item.value) === String(level) || item.label === level,
+      );
+      return hit?.label || level || '-';
+    },
+    workTypeHourCostLabel(row) {
+      const wage = row.hourCost ?? row.hourlyWage;
+      return wage || wage === 0 ? wage : '-';
+    },
+    workTypeUnitLabel(row) {
+      return row.unit || row.wageUnit || '-';
+    },
+    mapProfessionToWorkType(item = {}) {
+      const unit = item.unit || (item.money && item.date ? `${item.money}/${item.date}` : '') || item.wageUnit || '';
+      return {
+        id: item.id,
+        type: item.type || '',
+        code: item.code || '',
+        name: item.name || '',
+        level: item.level || '',
+        hourCost: item.hourCost ?? item.hourlyWage ?? '',
+        unit,
+        hourlyWage: item.hourCost ?? item.hourlyWage ?? 0,
+        wageUnit: unit,
+        userId: item.userId || '',
+        userName: item.userName || '',
+      };
+    },
+    openWorkTypeSelect() {
+      this.showWorkTypeSelect = true;
+      this.$refs.workTypeSelect?.open();
+    },
+    handleWorkTypeSelect(selection) {
+      const existIds = new Set(
+        (this.form.workTypes || []).map((item) => String(item.id)).filter((id) => id && id !== 'undefined'),
+      );
+      (selection || []).forEach((item) => {
+        const mapped = this.mapProfessionToWorkType(item);
+        const id = mapped.id == null || mapped.id === '' ? '' : String(mapped.id);
+        if (id && existIds.has(id)) return;
+        this.form.workTypes.push(mapped);
+        if (id) existIds.add(id);
+      });
+    },
     addCertificate() { this.form.certificates.push({ type: '', number: '', level: '', status: '有效', startDate: '', endDate: '', issueDate: '', attachment: [], remark: '' }); },
     addSkill() { this.form.skills.push({ category: '', name: '', level: '', years: 0, attachment: [], remark: '' }); },
     removeItem(key, index) { this.form[key].splice(index, 1); },
@@ -1860,22 +1968,22 @@ export default {
       // 劳动合同已改为只读,不再在档案页校验必填/日期
       // 薪酬银行:姓名必填
       for (const item of this.form.bankAccounts || []) {
-        if (!item.bankAccountName) return { tab: 'contract', message: '薪酬银行的姓名(开户人)必填' };
+        if (!item.bankAccountName) return { tab: 'payroll', message: '薪酬银行的姓名(开户人)必填' };
       }
       // 社保记录:停保≥参保
       for (const item of this.form.socialSecurityRecords || []) {
-        if (gt(item.insuredDate, item.stopDate)) return { tab: 'social', message: '社保停保日期不能早于参保日期' };
+        if (gt(item.insuredDate, item.stopDate)) return { tab: 'payroll', message: '社保停保日期不能早于参保日期' };
       }
       // 证照:类型/编号/状态/有效期必填,有效期结束≥开始
       for (const item of this.form.certificates || []) {
         if (!item.type || !item.number || !item.status || !item.startDate || !item.endDate) {
-          return { tab: 'social', message: '证照的必填项未完善(证照类型、证照编号、状态、有效期起止)' };
+          return { tab: 'qualification', message: '证照的必填项未完善(证照类型、证照编号、状态、有效期起止)' };
         }
-        if (gt(item.startDate, item.endDate)) return { tab: 'social', message: '证照有效期结束时间不能早于开始时间' };
+        if (gt(item.startDate, item.endDate)) return { tab: 'qualification', message: '证照有效期结束时间不能早于开始时间' };
       }
       // 技能:技能名称必填
       for (const item of this.form.skills || []) {
-        if (!item.name) return { tab: 'social', message: '技能名称必填' };
+        if (!item.name) return { tab: 'qualification', message: '技能名称必填' };
       }
       return null;
     },

+ 4 - 2
src/views/hrManagement/jobManagement/components/aptitudeDialog.vue

@@ -68,8 +68,9 @@
             width: 45,
             align: 'center',
             fixed: 'left',
-            selectable:(row)=>{
-              return !this.disabledIds.includes(row.id);
+            selectable: (row) => {
+              const id = String(row.id);
+              return !(this.disabledIds || []).some((item) => String(item) === id);
             }
           },
           {
@@ -164,6 +165,7 @@
       async open() {
         this.current = {};
         this.radio = null;
+        this.selection = [];
         this.visible = true;
         this.$nextTick(() => {
           this.$refs.table.reload({

+ 42 - 15
src/views/hrManagement/jobManagement/components/user-edit.vue

@@ -6,6 +6,7 @@
     :close-on-click-modal="false"
     custom-class="ele-dialog-form"
     :title="isView ? '查看工种' : isUpdate ? '修改工种' : '添加工种'"
+    append-to-body
     @update:visible="updateVisible"
     :maxable="true"
   >
@@ -73,7 +74,7 @@
           </el-form-item>
         </el-col>
       </el-row>
-      <el-row>
+      <el-row v-if="!hideRelatedUser">
         <el-col :span="10">
           <el-form-item label="关联人员:" prop="userId">
             <el-select
@@ -188,6 +189,10 @@
       typeList: {
         type: Array,
         default: () => []
+      },
+      hideRelatedUser: {
+        type: Boolean,
+        default: false
       }
     },
     components: {
@@ -359,11 +364,25 @@
             apiStr = saveJobTypes;
           }
           apiStr(this.form)
-            .then((msg) => {
+            .then((result) => {
+              const saved = { ...this.form };
+              const rawId =
+                result && typeof result === 'object'
+                  ? result.id ?? result.data?.id ?? result.data
+                  : result;
+              if (
+                rawId != null &&
+                rawId !== '' &&
+                typeof rawId !== 'object' &&
+                (/^\d+$/.test(String(rawId)) ||
+                  /^[0-9a-f-]{8,}$/i.test(String(rawId)))
+              ) {
+                saved.id = rawId;
+              }
               this.loading = false;
               this.$message.success((this.isUpdate ? '修改' : '新建') + '成功');
               this.updateVisible(false);
-              this.$emit('done');
+              this.$emit('done', saved);
             })
             .catch((e) => {
               this.loading = false;
@@ -382,23 +401,31 @@
       async visible(visible) {
         if (visible) {
           if (this.data) {
-            const res = await getProfessiongetById(this.data.id);
-            console.log(res);
-            this.$util.assignObject(this.form, {
-              ...res
-            });
-            this.form.money = (res.unit && res.unit.split('/')[0]) || this.form.money;
-            this.form.date = (res.unit && res.unit.split('/')[1]) || this.form.date;
-            this.assignRelatedUser(res);
-            this.isUpdate = true;
-            console.log(this.form);
+            if (this.data.id) {
+              const res = await getProfessiongetById(this.data.id);
+              this.$util.assignObject(this.form, {
+                ...res
+              });
+              this.form.money = (res.unit && res.unit.split('/')[0]) || this.form.money;
+              this.form.date = (res.unit && res.unit.split('/')[1]) || this.form.date;
+              this.assignRelatedUser(res);
+              this.isUpdate = true;
+            } else {
+              this.$util.assignObject(this.form, { ...this.data });
+              this.form.money = (this.data.unit && this.data.unit.split('/')[0]) || this.form.money;
+              this.form.date = (this.data.unit && this.data.unit.split('/')[1]) || this.form.date;
+              this.form.hourCost = this.data.hourCost ?? this.data.hourlyWage ?? this.form.hourCost;
+              this.assignRelatedUser(this.data);
+              this.isUpdate = false;
+            }
+            if (!this.hideRelatedUser) this.loadEmployeeOptions();
           } else {
             this.form = { ...this.defaultForm() };
             this.isUpdate = false;
+            if (!this.hideRelatedUser) this.loadEmployeeOptions();
           }
-          this.loadEmployeeOptions();
         } else {
-          this.$refs.form.clearValidate();
+          this.$refs.form && this.$refs.form.clearValidate();
           this.form = { ...this.defaultForm() };
         }
       }

+ 77 - 52
src/views/position/positionManagement/components/PositionEditDialog.vue

@@ -398,67 +398,92 @@
                   >
                 </div>
               </div>
-              <div class="form-subtitle" data-field-anchor="field-skills">
-                必备技能
-              </div>
-              <div
-                v-for="(skill, index) in form.skills"
-                :key="index"
-                class="dynamic-row"
-                :data-field-anchor="'field-skill-' + index"
-              >
-                <el-input v-model.trim="skill.name" placeholder="技能名称" />
-                <el-select v-model="skill.level" placeholder="等级">
-                  <el-option
-                    v-for="(item, optIdx) in skillLevelOptions"
-                    :key="'skill-level-' + optIdx + '-' + item.value"
-                    :label="item.label"
-                    :value="item.value"
-                  />
-                </el-select>
-                <el-switch v-model="skill.required" active-text="必备" />
+              <div class="form-subtitle-row" data-field-anchor="field-skills">
+                <div class="form-subtitle">必备技能</div>
                 <el-button
                   v-if="!readonly"
-                  icon="el-icon-delete"
-                  @click="removeSkill(index)"
-                />
+                  type="primary"
+                  size="mini"
+                  @click="addSkill"
+                >新增技能</el-button>
               </div>
-              <el-button
-                v-if="!readonly"
-                class="add-line"
-                icon="el-icon-plus"
-                @click="addSkill"
-                >新增技能</el-button
+              <el-table
+                :data="form.skills"
+                border
+                size="small"
+                class="skill-cert-table"
+                empty-text="暂无必备技能"
               >
+                <el-table-column type="index" label="序号" width="55" align="center" />
+                <el-table-column label="等级" width="160">
+                  <template slot-scope="{ row }">
+                    <el-select v-model="row.level" placeholder="等级" class="full-width">
+                      <el-option
+                        v-for="(item, optIdx) in skillLevelOptions"
+                        :key="'skill-level-' + optIdx + '-' + item.value"
+                        :label="item.label"
+                        :value="item.value"
+                      />
+                    </el-select>
+                  </template>
+                </el-table-column>
+                <el-table-column label="技能名称" min-width="320">
+                  <template slot-scope="{ row }">
+                    <el-input v-model.trim="row.name" placeholder="技能名称" />
+                  </template>
+                </el-table-column>
+                <el-table-column label="必备" width="110" align="center">
+                  <template slot-scope="{ row }">
+                    <el-switch v-model="row.required"/>
+                  </template>
+                </el-table-column>
+                <el-table-column v-if="!readonly" label="操作" width="90" align="center">
+                  <template slot-scope="{ $index }">
+                    <el-button type="text" class="skill-cert-delete" @click="removeSkill($index)">删除</el-button>
+                  </template>
+                </el-table-column>
+              </el-table>
 
-              <div class="form-subtitle">资质证书</div>
-              <div
-                v-for="(cert, index) in form.certificates"
-                :key="cert.name + index"
-                class="dynamic-row cert-row"
-              >
-                <el-input v-model.trim="cert.name" placeholder="证书名称" />
-                <el-select v-model="cert.category" placeholder="证书分类">
-                  <el-option
-                    v-for="(item, optIdx) in idTypeOptions"
-                    :key="'id-type-' + optIdx + '-' + item.value"
-                    :label="item.label"
-                    :value="item.value"
-                  />
-                </el-select>
+              <div class="form-subtitle-row">
+                <div class="form-subtitle">资质证书</div>
                 <el-button
                   v-if="!readonly"
-                  icon="el-icon-delete"
-                  @click="removeCert(index)"
-                />
+                  type="primary"
+                  size="mini"
+                  @click="addCert"
+                >新增证书</el-button>
               </div>
-              <el-button
-                v-if="!readonly"
-                class="add-line"
-                icon="el-icon-plus"
-                @click="addCert"
-                >新增证书</el-button
+              <el-table
+                :data="form.certificates"
+                border
+                size="small"
+                class="skill-cert-table"
+                empty-text="暂无资质证书"
               >
+                <el-table-column type="index" label="序号" width="55" align="center" />
+                <el-table-column label="证书分类" width="160">
+                  <template slot-scope="{ row }">
+                    <el-select v-model="row.category" placeholder="证书分类" class="full-width">
+                      <el-option
+                        v-for="(item, optIdx) in idTypeOptions"
+                        :key="'id-type-' + optIdx + '-' + item.value"
+                        :label="item.label"
+                        :value="item.value"
+                      />
+                    </el-select>
+                  </template>
+                </el-table-column>
+                <el-table-column label="证书名称" min-width="320">
+                  <template slot-scope="{ row }">
+                    <el-input v-model.trim="row.name" placeholder="证书名称" />
+                  </template>
+                </el-table-column>
+                <el-table-column v-if="!readonly" label="操作" width="90" align="center">
+                  <template slot-scope="{ $index }">
+                    <el-button type="text" class="skill-cert-delete" @click="removeCert($index)">删除</el-button>
+                  </template>
+                </el-table-column>
+              </el-table>
 
               <div class="form-subtitle">推荐培训</div>
               <el-select

+ 2 - 2
vue.config.js

@@ -41,8 +41,8 @@ function stripProxyCacheHeaders(proxyRes) {
 
 const apiProxy = resolveApiProxyTarget(
   process.env.VUE_APP_PROXY_TARGET ||
-    "http://192.168.1.147:18086",
-    // "http://aiot.zoomwin.com.cn:51010",
+    // "http://192.168.1.147:18090",
+    "http://aiot.zoomwin.com.cn:51010",
 );
 
 // element-ui / ele-admin 仍使用 Sass @import,静音 Dart Sass 弃用警告