Browse Source

修复bug

Gqingci 6 days ago
parent
commit
52e99e9cf1

+ 39 - 1
src/api/main/evaluation/index.ts

@@ -1,6 +1,6 @@
 import request from '@/utils/request';
 import { AxiosPromise } from 'axios';
-import { MainExamEvaluationQuery, MainExamEvaluationVO } from './types';
+import { EvaluationApplyRow, EvaluationParticipantStats, EvaluationSyncEmployeeOption, EvaluationSyncRequest, MainExamEvaluationQuery, MainExamEvaluationVO } from './types';
 
 export function listEvaluation(query: MainExamEvaluationQuery): AxiosPromise<MainExamEvaluationVO[]> {
   return request({
@@ -23,3 +23,41 @@ export function updateEvaluationStatus(id: string | number, status: string) {
     method: 'put'
   });
 }
+
+export function getEvaluationParticipantStats(ids: Array<string | number>): AxiosPromise<EvaluationParticipantStats> {
+  return request({
+    url: '/main/examEvaluation/stats/participant',
+    method: 'get',
+    params: { ids: ids.join(',') }
+  });
+}
+
+export function getEvaluationSyncEmployeeOptions(): AxiosPromise<EvaluationSyncEmployeeOption[]> {
+  return request({
+    url: '/main/examEvaluation/sync/employeeOptions',
+    method: 'get'
+  });
+}
+
+export function syncEvaluationToEmployees(data: EvaluationSyncRequest) {
+  return request({
+    url: '/main/examEvaluation/sync/employees',
+    method: 'post',
+    data
+  });
+}
+
+export function listEvaluationApply(query: { evaluationId: string | number; keyword?: string; pageNum: number; pageSize: number }): AxiosPromise<EvaluationApplyRow[]> {
+  return request({
+    url: '/main/examEvaluation/applyList',
+    method: 'get',
+    params: query
+  });
+}
+
+export function removeEvaluationApply(applyId: string | number) {
+  return request({
+    url: `/main/examEvaluation/apply/${applyId}`,
+    method: 'delete'
+  });
+}

+ 35 - 0
src/api/main/evaluation/types.ts

@@ -17,6 +17,8 @@ export interface MainExamEvaluationVO extends BaseEntity {
   tenantId?: string;
   remark?: string;
   updateTime?: string;
+  participantCount?: number;
+  totalCount?: number;
   abilityConfigs?: Array<{
     id?: number | string;
     abilityName?: string;
@@ -36,3 +38,36 @@ export interface MainExamEvaluationQuery extends PageQuery {
   beginTime?: string;
   endTime?: string;
 }
+
+export interface EvaluationParticipantStatItem {
+  participantCount?: number;
+  totalCount?: number;
+}
+
+export type EvaluationParticipantStats = Record<string, EvaluationParticipantStatItem>;
+
+export interface EvaluationSyncEmployeeOption {
+  id: number | string;
+  name?: string;
+  mobile?: string;
+  studentNo?: string;
+}
+
+export interface EvaluationSyncRequest {
+  evaluationIds: Array<string | number>;
+  studentIds: Array<string | number>;
+}
+
+export interface EvaluationApplyRow {
+  id: string | number;
+  evaluationId?: string | number;
+  studentId?: string | number;
+  name?: string;
+  gender?: string;
+  department?: string;
+  phone?: string;
+  statusText?: string;
+  statusType?: 'success' | 'danger' | 'warning' | 'info';
+  evaluateTime?: string;
+  actionType?: 'detail' | 'remove';
+}

+ 45 - 0
src/api/main/postManage/candidate.ts

@@ -0,0 +1,45 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+
+export interface PostCandidateQuery extends PageQuery {
+  postId?: string | number;
+  keyword?: string;
+  status?: string;
+}
+
+export interface PostCandidateVO {
+  id: string | number;
+  postId?: string | number;
+  studentId?: string | number;
+  name?: string;
+  gender?: string;
+  phone?: string;
+  status?: string;
+  statusText?: string;
+  statusType?: string;
+  evaluateTime?: string;
+  resumeFile?: string | number;
+  remark?: string;
+}
+
+export interface PostCandidateStatusForm {
+  id: string | number;
+  status: string;
+  remark?: string;
+}
+
+export function listPostCandidates(query: PostCandidateQuery): AxiosPromise<PostCandidateVO[]> {
+  return request({
+    url: '/main/postCandidate/list',
+    method: 'get',
+    params: query
+  });
+}
+
+export function updatePostCandidateStatus(data: PostCandidateStatusForm) {
+  return request({
+    url: '/main/postCandidate/status',
+    method: 'put',
+    data
+  });
+}

+ 5 - 0
src/api/main/postManage/types.ts

@@ -13,6 +13,8 @@ export interface MainPostApplyVO extends BaseEntity {
   educationRequirement?: string;
   salaryType?: string;
   salaryRange?: string;
+  minSalary?: number;
+  maxSalary?: number;
   recruitNum?: number;
   registrationStartDate?: string;
   registrationEndDate?: string;
@@ -35,6 +37,7 @@ export interface MainPostApplyVO extends BaseEntity {
   auditId?: number | string;
   postId?: number | string;
   rejectReason?: string;
+  applyCount?: number;
 }
 
 export interface MainPostApplyForm {
@@ -52,6 +55,8 @@ export interface MainPostApplyForm {
   educationRequirement?: string;
   salaryType?: string;
   salaryRange?: string;
+  minSalary?: number;
+  maxSalary?: number;
   recruitNum?: number;
   registrationStartDate?: string;
   registrationEndDate?: string;

+ 0 - 6
src/router/index.ts

@@ -145,12 +145,6 @@ export const constantRoutes: RouteRecordRaw[] = [
         name: 'EvaluationApplyList',
         meta: { title: '测评报名列表', icon: 'post', activeMenu: '/evaluation' }
       },
-      {
-        path: 'apply-detail',
-        component: () => import('@/views/evaluation/apply-detail.vue'),
-        name: 'EvaluationApplyDetail',
-        meta: { title: '测评报名详情', icon: 'post', activeMenu: '/evaluation' }
-      }
     ]
   }
 ];

+ 0 - 335
src/views/evaluation/apply-detail.vue

@@ -1,335 +0,0 @@
-<template>
-  <PageShell>
-    <div class="apply-detail-page">
-      <div class="apply-detail-card profile-card">
-        <div class="profile-left">
-          <img class="profile-avatar" :src="profile.avatar || defaultAvatar" alt="avatar" />
-          <el-link type="primary" :underline="false">一周内到岗</el-link>
-        </div>
-        <div class="profile-main">
-          <div class="profile-name-row">
-            <span class="profile-name">张三</span>
-            <span class="profile-gender">♂</span>
-            <span class="profile-gender female">♀</span>
-          </div>
-          <div class="profile-grid">
-            <div class="profile-item"><span>身份证号</span><span>110101213432431234</span></div>
-            <div class="profile-item"><span>联系电话</span><span>138567875678</span></div>
-            <div class="profile-item"><span>电子邮箱</span><span>wangxm@example.com</span></div>
-            <div class="profile-item"><span>求职意向</span><span>审计A,审计B</span></div>
-            <div class="profile-item"><span>求职类型</span><span>实习,兼职</span></div>
-            <div class="profile-item"><span>毕业院校</span><span>复旦大学一年级本科</span></div>
-            <div class="profile-item"><span>实习时长</span><span>6个月</span></div>
-            <div class="profile-item"><span>注册日期</span><span>2023-05-12 09:34:21</span></div>
-            <div class="profile-item"><span>最后更新</span><span>2023-08-05 15:22:10</span></div>
-          </div>
-        </div>
-      </div>
-
-      <div class="apply-detail-card tabs-card">
-        <el-tabs v-model="activeTab">
-          <el-tab-pane label="在线简历" name="resume" />
-          <el-tab-pane label="测评信息" name="evaluation" />
-          <el-tab-pane label="培训信息" name="training" />
-          <el-tab-pane label="任职信息" name="job" />
-        </el-tabs>
-
-        <div v-if="activeTab === 'resume'" class="detail-section-list">
-          <div class="detail-section">
-            <div class="section-title">教育经历</div>
-            <el-table :data="educationList" border>
-              <el-table-column label="学校" prop="school" min-width="120" />
-              <el-table-column label="学历" prop="degree" min-width="120" />
-              <el-table-column label="时间" prop="period" min-width="160" />
-              <el-table-column label="专业" prop="major" min-width="120" />
-              <el-table-column label="在校经历" prop="experience" min-width="180" />
-            </el-table>
-          </div>
-
-          <div class="detail-section">
-            <div class="section-title">工作经历</div>
-            <el-table :data="workList" border>
-              <el-table-column label="公司" prop="company" min-width="140" />
-              <el-table-column label="行业" prop="industry" min-width="120" />
-              <el-table-column label="时间" prop="period" min-width="160" />
-              <el-table-column label="职位" prop="position" min-width="120" />
-              <el-table-column label="所属部门" prop="department" min-width="140" />
-              <el-table-column label="工作内容" prop="content" min-width="160" />
-            </el-table>
-          </div>
-
-          <div class="detail-section">
-            <div class="section-title">项目经历</div>
-            <el-table :data="projectList" border>
-              <el-table-column label="项目" prop="project" min-width="140" />
-              <el-table-column label="担任角色" prop="role" min-width="120" />
-              <el-table-column label="时间" prop="period" min-width="160" />
-              <el-table-column label="描述" prop="description" min-width="160" />
-              <el-table-column label="业绩" prop="achievement" min-width="160" />
-              <el-table-column label="链接" prop="link" min-width="120" />
-            </el-table>
-          </div>
-        </div>
-
-        <div v-else-if="activeTab === 'evaluation'" class="detail-section-list">
-          <div class="detail-section">
-            <div class="section-title">测评信息</div>
-            <el-table :data="evaluationList" border>
-              <el-table-column label="名称" prop="name" min-width="140" />
-              <el-table-column label="岗位" prop="post" min-width="140" />
-              <el-table-column label="结果" prop="result" min-width="160" />
-              <el-table-column label="测评时间" prop="evaluateTime" min-width="180" />
-              <el-table-column label="操作" min-width="180">
-                <template #default="scope">
-                  <el-button link type="primary" @click="handleOpenEvaluation(scope.row)">查看</el-button>
-                  <template v-if="scope.row.result === '未通过'">
-                    <el-button link type="primary">录用</el-button>
-                    <el-button link type="primary">不录用</el-button>
-                  </template>
-                </template>
-              </el-table-column>
-            </el-table>
-          </div>
-        </div>
-
-        <div v-else-if="activeTab === 'training'" class="detail-section-list">
-          <div class="detail-section">
-            <div class="section-title">培训信息</div>
-            <el-table :data="trainingList" border>
-              <el-table-column label="名称" prop="name" min-width="180" />
-              <el-table-column label="培训方式" prop="type" min-width="140" />
-              <el-table-column label="培训时间" prop="time" min-width="180" />
-            </el-table>
-          </div>
-        </div>
-
-        <div v-else-if="activeTab === 'job'" class="detail-section-list">
-          <div class="detail-section">
-            <div class="section-title">任职信息</div>
-            <el-table :data="jobList" border>
-              <el-table-column label="公司" prop="company" min-width="140" />
-              <el-table-column label="岗位" prop="post" min-width="120" />
-              <el-table-column label="岗位类型" prop="postType" min-width="140" />
-              <el-table-column label="任职状态" prop="status" min-width="140" />
-              <el-table-column label="入职时间" prop="entryTime" min-width="160" />
-              <el-table-column label="离职时间" prop="leaveTime" min-width="160" />
-              <el-table-column label="离职原因" prop="leaveReason" min-width="140" />
-              <el-table-column label="操作" min-width="140">
-                <template #default>
-                  <el-button link type="primary" @click="handleOpenCompanyRate">查看该公司评价</el-button>
-                </template>
-              </el-table-column>
-            </el-table>
-          </div>
-        </div>
-
-        <el-dialog v-model="companyRateDialog.visible" title="评价" width="460px" append-to-body>
-          <div class="company-rate-content">
-            <div class="company-rate-item">
-              <div class="company-rate-head">
-                <span class="company-rate-label">综合评价</span>
-                <el-rate v-model="companyRateDialog.form.totalRate" disabled show-score text-color="#303133" score-template="{value}星" />
-              </div>
-              <el-input v-model="companyRateDialog.form.totalRemark" type="textarea" :rows="3" readonly />
-            </div>
-            <div class="company-rate-item">
-              <div class="company-rate-head">
-                <span class="company-rate-label">能力A</span>
-                <el-rate v-model="companyRateDialog.form.abilityARate" disabled show-score text-color="#303133" score-template="{value}星" />
-              </div>
-              <el-input v-model="companyRateDialog.form.abilityARemark" type="textarea" :rows="3" readonly />
-            </div>
-            <div class="company-rate-item">
-              <div class="company-rate-head">
-                <span class="company-rate-label">能力B</span>
-                <el-rate v-model="companyRateDialog.form.abilityBRate" disabled show-score text-color="#303133" score-template="{value}星" />
-              </div>
-              <el-input v-model="companyRateDialog.form.abilityBRemark" type="textarea" :rows="3" readonly />
-            </div>
-          </div>
-        </el-dialog>
-      </div>
-    </div>
-  </PageShell>
-</template>
-
-<script setup name="PostManageApplyDetail" lang="ts">
-import { reactive, ref } from 'vue';
-import { useRouter } from 'vue-router';
-import PageShell from '@/components/PageShell/index.vue';
-import defaultAvatar from '@/assets/images/user.jpg';
-
-const router = useRouter();
-const activeTab = ref('resume');
-
-const profile = {
-  avatar: ''
-};
-
-const educationList = [
-  { school: '复旦大学', degree: '本科-全日制', period: '2022.9——2026.7', major: '会计', experience: '在校经历XXXXXXX' }
-];
-
-const workList = [
-  { company: 'XXXX公司', industry: '服务业', period: '2022.9——2026.7(实习)', position: '会计', department: '市场部', content: '工作内容XXXXXXXX' }
-];
-
-const projectList = [
-  { project: 'XXXX项目', role: '负责人', period: '2022.9——2026.7', description: '描述XXXX', achievement: '业绩XXXXXX', link: 'https://qq.cn' }
-];
-
-const evaluationList = [
-  { name: '审计A1测试', post: '审计A1', result: '通过', evaluateTime: '2025.12.12 12:12' },
-  { name: '', post: '', result: '未通过', evaluateTime: '' }
-];
-
-const trainingList = [{ name: '审计A1培训', type: '线下', time: '2025.12.12 12:12' }];
-
-const jobList = [
-  { company: 'XXXXX公司', post: '审计A', postType: '全职、实习、兼职', status: '在职', entryTime: '2025.12.12', leaveTime: '', leaveReason: '' },
-  { company: 'XXXXX公司', post: '审计A', postType: '全职', status: '离职', entryTime: '2024.12.12', leaveTime: '2025.12.01', leaveReason: '个人发展' }
-];
-
-const companyRateDialog = reactive({
-  visible: false,
-  form: {
-    totalRate: 3.5,
-    totalRemark: '在职期间表现佳',
-    abilityARate: 3,
-    abilityARemark: '在职期间表现佳',
-    abilityBRate: 3,
-    abilityBRemark: '在职期间表现佳'
-  }
-});
-
-const handleOpenEvaluation = (row: { name: string }) => {
-  router.push({
-    path: '/postManage/evaluation-view',
-    query: {
-      name: row.name
-    }
-  });
-};
-
-const handleOpenCompanyRate = () => {
-  companyRateDialog.visible = true;
-};
-</script>
-
-<style scoped>
-.apply-detail-page {
-  display: flex;
-  flex-direction: column;
-  gap: 12px;
-}
-
-.apply-detail-card {
-  padding: 16px 20px;
-  background: #fff;
-  border-radius: 6px;
-}
-
-.profile-card {
-  display: flex;
-  gap: 16px;
-}
-
-.profile-left {
-  width: 96px;
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  gap: 12px;
-}
-
-.profile-avatar {
-  width: 96px;
-  height: 96px;
-  border-radius: 12px;
-  object-fit: cover;
-}
-
-.profile-main {
-  flex: 1;
-}
-
-.profile-name-row {
-  display: flex;
-  align-items: center;
-  gap: 6px;
-  margin-bottom: 12px;
-}
-
-.profile-name {
-  font-size: 28px;
-  font-weight: 600;
-  color: #303133;
-}
-
-.profile-gender {
-  color: #409eff;
-}
-
-.profile-gender.female {
-  color: #f56c6c;
-}
-
-.profile-grid {
-  display: grid;
-  grid-template-columns: repeat(3, minmax(0, 1fr));
-  gap: 12px 24px;
-}
-
-.profile-item {
-  display: flex;
-  flex-direction: column;
-  gap: 4px;
-  color: #606266;
-  font-size: 13px;
-}
-
-.profile-item span:last-child {
-  color: #303133;
-}
-
-.tabs-card :deep(.el-tabs__header) {
-  margin-bottom: 16px;
-}
-
-.detail-section-list {
-  display: flex;
-  flex-direction: column;
-  gap: 18px;
-}
-
-.section-title {
-  margin-bottom: 12px;
-  font-size: 16px;
-  font-weight: 600;
-  color: #303133;
-}
-
-.company-rate-content {
-  display: flex;
-  flex-direction: column;
-  gap: 16px;
-}
-
-.company-rate-item {
-  display: flex;
-  flex-direction: column;
-  gap: 10px;
-}
-
-.company-rate-head {
-  display: flex;
-  align-items: center;
-  gap: 12px;
-}
-
-.company-rate-label {
-  width: 56px;
-  color: #303133;
-  flex-shrink: 0;
-}
-</style>

+ 113 - 90
src/views/evaluation/apply-list.vue

@@ -13,6 +13,7 @@
           </div>
 
           <div class="apply-toolbar-actions">
+            <el-button type="primary" plain @click="handleBack">返回</el-button>
             <el-button type="primary" plain @click="handleAddEmployee">添加员工</el-button>
             <el-button type="primary" @click="handleExport">导出列表</el-button>
           </div>
@@ -21,7 +22,8 @@
         <el-table :data="pagedApplyList" class="apply-list-table" header-row-class-name="apply-table-header">
           <el-table-column label="姓名" prop="name" min-width="160">
             <template #default="{ row }">
-              <el-button link type="primary" @click="handleOpenDetail(row)">{{ row.name }}</el-button>
+              <el-button v-if="row.statusText !== '待测评'" link type="primary" @click="handleOpenDetail(row)">{{ row.name }}</el-button>
+              <span v-else>{{ row.name }}</span>
             </template>
           </el-table-column>
           <el-table-column label="性别" prop="gender" width="80" align="center" />
@@ -56,16 +58,24 @@
       <el-dialog v-model="syncDialog.visible" title="同步测评" width="460px" append-to-body>
         <div class="sync-dialog-content">
           <div class="sync-dialog-row">
-            <span class="sync-dialog-label">同步公司</span>
-            <el-select v-model="syncDialog.companies" multiple collapse-tags collapse-tags-tooltip placeholder="请选择" class="sync-dialog-select">
-              <el-option v-for="item in syncCompanyOptions" :key="item" :label="item" :value="item" />
+            <span class="sync-dialog-label">同步员工</span>
+            <el-select
+              v-model="syncDialog.employeeIds"
+              multiple
+              collapse-tags
+              collapse-tags-tooltip
+              placeholder="请选择"
+              class="sync-dialog-select"
+              :loading="syncOptionLoading"
+            >
+              <el-option v-for="item in employeeOptions" :key="item.id" :label="item.label" :value="item.id" />
             </el-select>
           </div>
         </div>
         <template #footer>
           <div class="sync-dialog-footer">
             <el-button @click="syncDialog.visible = false">取消</el-button>
-            <el-button type="primary" @click="handleConfirmSync">确定</el-button>
+            <el-button type="primary" :loading="syncLoading" @click="handleConfirmSync">确定</el-button>
           </div>
         </template>
       </el-dialog>
@@ -74,31 +84,27 @@
 </template>
 
 <script setup name="EvaluationApplyList" lang="ts">
-import { computed, getCurrentInstance, reactive, ref, type ComponentInternalInstance } from 'vue';
+import { computed, getCurrentInstance, onMounted, reactive, ref, type ComponentInternalInstance } from 'vue';
 import { useRoute, useRouter } from 'vue-router';
 import PageShell from '@/components/PageShell/index.vue';
+import { getEvaluationSyncEmployeeOptions, listEvaluationApply, removeEvaluationApply, syncEvaluationToEmployees } from '@/api/main/evaluation';
+import type { EvaluationApplyRow, EvaluationSyncEmployeeOption } from '@/api/main/evaluation/types';
 
-type ApplyRow = {
-  id: number;
-  name: string;
-  gender: string;
-  department: string;
-  phone: string;
-  statusText: string;
-  statusType: 'success' | 'danger' | 'warning' | 'info';
-  evaluateTime: string;
-  actionType: 'detail' | 'remove';
-};
+type SyncEmployeeOption = EvaluationSyncEmployeeOption & { label: string };
 
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 const router = useRouter();
 const route = useRoute();
 const modal = proxy?.$modal as any;
-const syncCompanyOptions = ['张三', '李四', '王五', '赵六'];
+const loading = ref(false);
+const syncLoading = ref(false);
+const syncOptionLoading = ref(false);
+const employeeOptions = ref<SyncEmployeeOption[]>([]);
 const syncDialog = reactive({
   visible: false,
-  companies: ['张三', '李四'] as string[]
+  employeeIds: [] as Array<string | number>
 });
+const evaluationId = computed(() => String(route.query.evaluationId || ''));
 
 const queryParams = reactive({
   pageNum: 1,
@@ -106,109 +112,126 @@ const queryParams = reactive({
   keyword: ''
 });
 
-const allApplyList = ref<ApplyRow[]>([
-  {
-    id: 1,
-    name: '小张小张白有主张',
-    gender: '男',
-    department: 'A',
-    phone: '18438573743',
-    statusText: '通过',
-    statusType: 'success',
-    evaluateTime: '2025-03-21 10:27:56',
-    actionType: 'detail'
-  },
-  {
-    id: 2,
-    name: '小张小张白有主张',
-    gender: '男',
-    department: 'A',
-    phone: '18438573743',
-    statusText: '未通过',
-    statusType: 'danger',
-    evaluateTime: '2025-03-21 10:27:56',
-    actionType: 'detail'
-  },
-  {
-    id: 3,
-    name: '小张小张白有主张',
-    gender: '男',
-    department: 'A',
-    phone: '18438573743',
-    statusText: '待评分',
-    statusType: 'warning',
-    evaluateTime: '2025-03-21 10:27:56',
-    actionType: 'detail'
-  },
-  {
-    id: 4,
-    name: '小张小张白有主张',
-    gender: '男',
-    department: 'A',
-    phone: '18438573743',
-    statusText: '待测评',
-    statusType: 'info',
-    evaluateTime: '2025-03-21 10:27:56',
-    actionType: 'remove'
+const allApplyList = ref<EvaluationApplyRow[]>([]);
+const total = ref(0);
+const pagedApplyList = computed(() => allApplyList.value);
+
+const loadSyncEmployeeOptions = async () => {
+  syncOptionLoading.value = true;
+  try {
+    const res = await getEvaluationSyncEmployeeOptions();
+    const rows = res.data || [];
+    employeeOptions.value = rows.map((item) => ({
+      ...item,
+      label: [item.name, item.mobile, item.studentNo].filter(Boolean).join(' / ')
+    }));
+  } finally {
+    syncOptionLoading.value = false;
   }
-]);
+};
 
-const filteredApplyList = computed(() => {
-  const keyword = queryParams.keyword.trim();
-  if (!keyword) {
-    return allApplyList.value;
+const getList = async () => {
+  if (!evaluationId.value) {
+    allApplyList.value = [];
+    total.value = 0;
+    return;
   }
-  return allApplyList.value.filter((item) => [item.name, item.phone, item.department, item.statusText].some((field) => field.includes(keyword)));
-});
-
-const total = computed(() => filteredApplyList.value.length);
-
-const pagedApplyList = computed(() => {
-  const start = (queryParams.pageNum - 1) * queryParams.pageSize;
-  const end = start + queryParams.pageSize;
-  return filteredApplyList.value.slice(start, end);
-});
+  loading.value = true;
+  try {
+    const res = await listEvaluationApply({
+      evaluationId: evaluationId.value,
+      keyword: queryParams.keyword.trim(),
+      pageNum: queryParams.pageNum,
+      pageSize: queryParams.pageSize
+    });
+    allApplyList.value = res.rows || [];
+    total.value = res.total || 0;
+  } finally {
+    loading.value = false;
+  }
+};
 
 const handleQuery = () => {
   queryParams.pageNum = 1;
+  getList();
 };
 
 const handlePagination = () => {
-  // Pagination component already updates pageNum/pageSize via v-model.
+  getList();
 };
 
 const handleAddEmployee = () => {
+  syncDialog.employeeIds = [];
   syncDialog.visible = true;
+  if (!employeeOptions.value.length) {
+    loadSyncEmployeeOptions();
+  }
 };
 
 const handleExport = () => {
   modal?.msgSuccess('导出列表功能待接入');
 };
 
-const handleConfirmSync = () => {
-  syncDialog.visible = false;
-  modal?.msgSuccess('同步成功');
+const handleConfirmSync = async () => {
+  if (!syncDialog.employeeIds.length) {
+    modal?.msgWarning('请选择员工');
+    return;
+  }
+  syncLoading.value = true;
+  try {
+    await syncEvaluationToEmployees({
+      evaluationIds: [evaluationId.value],
+      studentIds: syncDialog.employeeIds
+    });
+    syncDialog.visible = false;
+    modal?.msgSuccess('同步成功');
+    await getList();
+  } finally {
+    syncLoading.value = false;
+  }
 };
 
-const handleRemove = (row: ApplyRow) => {
-  allApplyList.value = allApplyList.value.filter((item) => item.id !== row.id);
+const handleRemove = async (row: EvaluationApplyRow) => {
+  await removeEvaluationApply(row.id);
   modal?.msgSuccess('已移除');
-  const maxPage = Math.max(1, Math.ceil(total.value / queryParams.pageSize));
-  if (queryParams.pageNum > maxPage) {
-    queryParams.pageNum = maxPage;
-  }
+  await getList();
 };
 
-const handleOpenDetail = (row: ApplyRow) => {
+const handleOpenDetail = (row: EvaluationApplyRow) => {
   router.push({
     path: '/evaluation/evaluation-view',
     query: {
       applyId: String(row.id),
-      name: row.name,
+      name: row.name || '',
       evaluationId: String(route.query.evaluationId || '')
     }
   });
 };
+
+const handleBack = () => {
+  const raw = route.query.backPath;
+  const backPath = Array.isArray(raw) ? raw[0] : raw || '';
+  if (!backPath) {
+    router.back();
+    return;
+  }
+  const url = String(backPath + 'Manage');
+  // 如果是完整的 http(s) 链接,直接跳转到该地址
+  if (/^https?:\/\//i.test(url)) {
+    window.location.href = url;
+    return;
+  }
+  // 否则使用 vue-router 跳转(相对或绝对路径)
+  try {
+    router.push({ path: url });
+  } catch (e) {
+    router.push(url as any);
+  }
+};
+
+onMounted(() => {
+  getList();
+});
 </script>
 
 <style scoped>

+ 11 - 11
src/views/evaluation/components/EvaluationTable.vue

@@ -28,7 +28,7 @@
     </el-table-column>
     <el-table-column label="参与数" min-width="130" align="center">
       <template #default="{ row }">
-        <el-button link type="primary" @click="emit('viewApplyList', row)">0/0</el-button>
+        <el-button link type="primary" @click="emit('viewApplyList', row)">{{ Number(row.participantCount || 0) }}/{{ Number(row.totalCount || 0) }}</el-button>
       </template>
     </el-table-column>
     <el-table-column label="状态" width="110" align="center">
@@ -78,7 +78,7 @@
 </template>
 
 <script setup lang="ts">
-import { computed, ref } from 'vue';
+import { computed } from 'vue';
 import type { MainExamEvaluationVO } from '@/api/main/evaluation/types';
 import { formatDateTime, getImageUrl, getSaleStatusMeta } from '../utils';
 
@@ -91,12 +91,14 @@ const props = defineProps<{
   gradeOptions: Array<{ label: string; value: string }>;
   positionTypeOptions: Array<{ label: string; value: string }>;
   switchLoadingMap: Record<string, boolean>;
+  selectedIds: Array<string | number>;
 }>();
 
 const emit = defineEmits<{
   pagination: [];
   'update:pageNum': [number];
   'update:pageSize': [number];
+  'update:selectedIds': [Array<string | number>];
   toggleStatus: [MainExamEvaluationVO, boolean | string | number];
   viewApplyList: [MainExamEvaluationVO];
 }>();
@@ -108,33 +110,31 @@ const getOptionLabel = (options: Array<{ label: string; value: string }>, value?
   return options.find((item) => item.value === value)?.label || value;
 };
 
-const checkedIds = ref<Array<string | number>>([]);
-
 const currentPageIds = computed(() => props.list.map((item) => item.id).filter((id): id is string | number => id !== undefined && id !== null));
 
-const isAllChecked = computed(() => currentPageIds.value.length > 0 && currentPageIds.value.every((id) => checkedIds.value.includes(id)));
+const isAllChecked = computed(() => currentPageIds.value.length > 0 && currentPageIds.value.every((id) => props.selectedIds.includes(id)));
 
 const isIndeterminate = computed(() => {
-  const checkedCount = currentPageIds.value.filter((id) => checkedIds.value.includes(id)).length;
+  const checkedCount = currentPageIds.value.filter((id) => props.selectedIds.includes(id)).length;
   return checkedCount > 0 && checkedCount < currentPageIds.value.length;
 });
 
-const isRowChecked = (row: MainExamEvaluationVO) => checkedIds.value.includes(row.id);
+const isRowChecked = (row: MainExamEvaluationVO) => props.selectedIds.includes(row.id);
 
 const handleCheckAllChange = (checked: boolean | string | number) => {
   if (checked) {
-    checkedIds.value = Array.from(new Set([...checkedIds.value, ...currentPageIds.value]));
+    emit('update:selectedIds', Array.from(new Set([...props.selectedIds, ...currentPageIds.value])));
     return;
   }
-  checkedIds.value = checkedIds.value.filter((id) => !currentPageIds.value.includes(id));
+  emit('update:selectedIds', props.selectedIds.filter((id) => !currentPageIds.value.includes(id)));
 };
 
 const handleRowCheckedChange = (row: MainExamEvaluationVO, checked: boolean | string | number) => {
   if (checked) {
-    checkedIds.value = Array.from(new Set([...checkedIds.value, row.id]));
+    emit('update:selectedIds', Array.from(new Set([...props.selectedIds, row.id])));
     return;
   }
-  checkedIds.value = checkedIds.value.filter((id) => id !== row.id);
+  emit('update:selectedIds', props.selectedIds.filter((id) => id !== row.id));
 };
 </script>
 

+ 76 - 12
src/views/evaluation/index.vue

@@ -34,8 +34,10 @@
           :grade-options="gradeOptions"
           :position-type-options="positionTypeOptions"
           :switch-loading-map="switchLoadingMap"
+          :selected-ids="selectedEvaluationIds"
           @update:page-num="queryParams.pageNum = $event"
           @update:page-size="queryParams.pageSize = $event"
+          @update:selected-ids="selectedEvaluationIds = $event"
           @pagination="getList"
           @toggle-status="handleToggleStatus"
           @view-apply-list="handleViewApplyList"
@@ -45,16 +47,16 @@
       <el-dialog v-model="syncDialog.visible" title="同步测评" width="460px" append-to-body>
         <div class="sync-dialog-content">
           <div class="sync-dialog-row">
-            <span class="sync-dialog-label">同步公司</span>
-            <el-select v-model="syncDialog.companies" multiple collapse-tags collapse-tags-tooltip placeholder="请选择" class="sync-dialog-select">
-              <el-option v-for="item in syncCompanyOptions" :key="item" :label="item" :value="item" />
+            <span class="sync-dialog-label">同步员工</span>
+            <el-select v-model="syncDialog.employeeIds" multiple collapse-tags collapse-tags-tooltip placeholder="请选择" class="sync-dialog-select" :loading="syncOptionLoading">
+              <el-option v-for="item in employeeOptions" :key="item.id" :label="item.label" :value="item.id" />
             </el-select>
           </div>
         </div>
         <template #footer>
           <div class="sync-dialog-footer">
             <el-button @click="syncDialog.visible = false">取消</el-button>
-            <el-button type="primary" @click="handleConfirmSync">确定</el-button>
+            <el-button type="primary" :loading="syncLoading" @click="handleConfirmSync">确定</el-button>
           </div>
         </template>
       </el-dialog>
@@ -66,14 +68,15 @@
 import { computed, getCurrentInstance, onMounted, reactive, ref, type ComponentInternalInstance } from 'vue';
 import { useRouter } from 'vue-router';
 import PageShell from '@/components/PageShell/index.vue';
-import { listEvaluation, updateEvaluationStatus } from '@/api/main/evaluation';
-import { MainExamEvaluationQuery, MainExamEvaluationVO } from '@/api/main/evaluation/types';
+import { getEvaluationParticipantStats, getEvaluationSyncEmployeeOptions, listEvaluation, syncEvaluationToEmployees, updateEvaluationStatus } from '@/api/main/evaluation';
+import { EvaluationSyncEmployeeOption, MainExamEvaluationQuery, MainExamEvaluationVO } from '@/api/main/evaluation/types';
 import { getDicts } from '@/api/system/dict/data';
 import type { DictDataVO } from '@/api/system/dict/data/types';
 import EvaluationFilter from './components/EvaluationFilter.vue';
 import EvaluationTable from './components/EvaluationTable.vue';
 
 type FilterOption = { label: string; value: string };
+type SyncEmployeeOption = EvaluationSyncEmployeeOption & { label: string };
 
 const router = useRouter();
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@@ -84,11 +87,14 @@ const evaluationList = ref<MainExamEvaluationVO[]>([]);
 const dateRange = ref<[string, string] | []>([]);
 const gradeOptions = ref<FilterOption[]>([]);
 const positionTypeOptions = ref<FilterOption[]>([]);
+const employeeOptions = ref<SyncEmployeeOption[]>([]);
+const syncLoading = ref(false);
+const syncOptionLoading = ref(false);
+const selectedEvaluationIds = ref<Array<string | number>>([]);
 const switchLoadingMap = reactive<Record<string, boolean>>({});
-const syncCompanyOptions = ['张三', '李四', '王五', '赵六'];
 const syncDialog = reactive({
   visible: false,
-  companies: ['张三', '李四'] as string[]
+  employeeIds: [] as Array<string | number>
 });
 
 const queryParams = reactive<MainExamEvaluationQuery>({
@@ -134,11 +140,46 @@ const loadFilterOptions = async () => {
   }
 };
 
+const loadParticipantStats = async (rows: MainExamEvaluationVO[]) => {
+  const ids = rows.map((item) => item.id).filter((id): id is string | number => id !== undefined && id !== null);
+  if (!ids.length) {
+    return rows.map((item) => ({
+      ...item,
+      participantCount: 0,
+      totalCount: 0
+    }));
+  }
+  const res = await getEvaluationParticipantStats(ids);
+  const statsMap = res.data || {};
+  return rows.map((item) => {
+    const stats = statsMap[String(item.id)] || statsMap[item.id as keyof typeof statsMap];
+    return {
+      ...item,
+      participantCount: Number(stats?.participantCount || 0),
+      totalCount: Number(stats?.totalCount || 0)
+    };
+  });
+};
+
+const loadSyncEmployeeOptions = async () => {
+  syncOptionLoading.value = true;
+  try {
+    const res = await getEvaluationSyncEmployeeOptions();
+    const rows = res.data || [];
+    employeeOptions.value = rows.map((item) => ({
+      ...item,
+      label: [item.name, item.mobile, item.studentNo].filter(Boolean).join(' / ')
+    }));
+  } finally {
+    syncOptionLoading.value = false;
+  }
+};
+
 const getList = async () => {
   loading.value = true;
   try {
     const res = await listEvaluation(normalizedQuery.value);
-    evaluationList.value = res.rows || [];
+    evaluationList.value = await loadParticipantStats(res.rows || []);
     total.value = res.total || 0;
     gradeOptions.value = mergeFieldOptions(gradeOptions.value, evaluationList.value, 'grade');
     positionTypeOptions.value = mergeFieldOptions(positionTypeOptions.value, evaluationList.value, 'positionType');
@@ -192,12 +233,35 @@ const handleExport = () => {
 };
 
 const handleOpenSyncDialog = () => {
+  if (!selectedEvaluationIds.value.length) {
+    modal?.msgWarning('请先勾选测评');
+    return;
+  }
+  syncDialog.employeeIds = [];
   syncDialog.visible = true;
+  if (!employeeOptions.value.length) {
+    loadSyncEmployeeOptions();
+  }
 };
 
-const handleConfirmSync = () => {
-  syncDialog.visible = false;
-  modal?.msgSuccess('同步成功');
+const handleConfirmSync = async () => {
+  if (!syncDialog.employeeIds.length) {
+    modal?.msgWarning('请选择员工');
+    return;
+  }
+  syncLoading.value = true;
+  try {
+    await syncEvaluationToEmployees({
+      evaluationIds: selectedEvaluationIds.value,
+      studentIds: syncDialog.employeeIds
+    });
+    syncDialog.visible = false;
+    selectedEvaluationIds.value = [];
+    modal?.msgSuccess('同步成功');
+    await getList();
+  } finally {
+    syncLoading.value = false;
+  }
 };
 
 onMounted(() => {

+ 105 - 58
src/views/postManage/apply-list.vue

@@ -11,32 +11,32 @@
 
         <el-form :model="queryParams" :inline="true" class="apply-search-form">
           <el-form-item class="apply-search-name-item">
-            <el-input v-model="queryParams.keyword" placeholder="请输入岗位" clearable>
+            <el-input v-model="queryParams.keyword" placeholder="请输入姓名/手机号" clearable @keyup.enter="handleQuery">
               <template #prefix>
                 <el-icon><Search /></el-icon>
               </template>
             </el-input>
           </el-form-item>
           <el-form-item class="apply-search-btn-item">
-            <el-button type="primary" icon="Search" />
+            <el-button type="primary" icon="Search" @click="handleQuery" />
           </el-form-item>
           <el-form-item class="apply-search-select-item">
-            <el-select v-model="queryParams.auditStatus" placeholder="审核状态" clearable>
+            <el-select v-model="queryParams.status" placeholder="审核状态" clearable @change="handleQuery">
               <el-option v-for="item in applyStatusOptions" :key="item.value" :label="item.label" :value="item.value" />
             </el-select>
           </el-form-item>
-          <el-form-item class="apply-search-select-item">
+          <el-form-item class="apply-search-select-item" v-if="false">
             <el-select v-model="queryParams.ability" placeholder="测评能力" clearable>
               <el-option v-for="item in abilityOptions" :key="item" :label="item" :value="item" />
             </el-select>
           </el-form-item>
           <div class="apply-toolbar-actions">
-            <el-button>录用</el-button>
+            <el-button disabled>录用</el-button>
             <el-button type="primary">导出列表</el-button>
           </div>
         </el-form>
 
-        <el-table :data="applyList" class="apply-list-table">
+        <el-table v-loading="loading" :data="applyList" class="apply-list-table">
           <el-table-column label="姓名" prop="name" min-width="160">
             <template #default="scope">
               <el-button link type="primary" @click="handleOpenDetail(scope.row)">{{ scope.row.name }}</el-button>
@@ -59,15 +59,21 @@
                 <el-button link type="primary">测评详情</el-button>
                 <el-button link type="primary">下载附件简历</el-button>
                 <template v-if="scope.row.statusText === '待回复'">
-                  <el-button link type="primary" @click="handleHire">录用</el-button>
-                  <el-button link type="primary" @click="handleReject">不录用</el-button>
+                  <el-button link type="primary" @click="handleHire(scope.row)">录用</el-button>
+                  <el-button link type="primary" @click="handleReject(scope.row)">不录用</el-button>
                 </template>
               </template>
             </template>
           </el-table-column>
         </el-table>
 
-        <pagination v-show="total > 0" :page="queryParams.pageNum" :limit="queryParams.pageSize" :total="total" />
+        <pagination
+          v-show="total > 0"
+          v-model:page="queryParams.pageNum"
+          v-model:limit="queryParams.pageSize"
+          :total="total"
+          @pagination="getList"
+        />
       </div>
 
       <el-dialog v-model="hireDialog.visible" title="审核说明" width="680px" append-to-body>
@@ -88,7 +94,7 @@
         <template #footer>
           <div class="dialog-footer">
             <el-button @click="hireDialog.visible = false">返回</el-button>
-            <el-button type="primary" @click="hireDialog.visible = false">确定</el-button>
+            <el-button type="primary" :loading="hireDialog.submitting" @click="submitHire">确定</el-button>
           </div>
         </template>
       </el-dialog>
@@ -104,7 +110,7 @@
         <template #footer>
           <div class="dialog-footer">
             <el-button @click="rejectDialog.visible = false">取消</el-button>
-            <el-button type="primary" @click="rejectDialog.visible = false">确定</el-button>
+            <el-button type="primary" :loading="rejectDialog.submitting" @click="submitReject">确定</el-button>
           </div>
         </template>
       </el-dialog>
@@ -113,27 +119,30 @@
 </template>
 
 <script setup name="PostManageApplyList" lang="ts">
-import { computed, getCurrentInstance, reactive, type ComponentInternalInstance } from 'vue';
+import { computed, getCurrentInstance, onMounted, reactive, ref, type ComponentInternalInstance } from 'vue';
 import { useRoute, useRouter } from 'vue-router';
+import { listPostCandidates, updatePostCandidateStatus, type PostCandidateVO } from '@/api/main/postManage/candidate';
 import PageShell from '@/components/PageShell/index.vue';
 
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 const router = useRouter();
 const route = useRoute();
+const modal = proxy?.$modal as any;
 const pageTitle = computed(() => {
-  const evaluationName = route.query.evaluationName as string | undefined;
-  return evaluationName ? `${evaluationName} 报名列表` : '报名列表';
+  const postName = route.query.postName as string | undefined;
+  return postName ? `${postName} 报名列表` : '报名列表';
 });
 
 const queryParams = reactive({
   pageNum: 1,
   pageSize: 10,
   keyword: '',
-  auditStatus: '',
+  status: '',
   ability: ''
 });
 
-const total = 243;
+const loading = ref(false);
+const total = ref(0);
 
 const applyStatusOptions = [
   { label: '录用', value: 'adopted' },
@@ -144,70 +153,104 @@ const applyStatusOptions = [
 
 const abilityOptions = ['测评能力'];
 
-const applyList = reactive([
-  {
-    id: 1,
-    name: '小张小赵白有主张',
-    gender: '男',
-    phone: '18438573743',
-    statusText: '录用',
-    statusType: 'success',
-    evaluateTime: '2025-03-21 10:27:56'
-  },
-  {
-    id: 2,
-    name: '小张小赵白有主张',
-    gender: '男',
-    phone: '18438573743',
-    statusText: '不录用',
-    statusType: 'danger',
-    evaluateTime: '2025-03-21 10:27:56'
-  },
-  {
-    id: 3,
-    name: '小张小赵白有主张',
-    gender: '男',
-    phone: '18438573743',
-    statusText: '待回复',
-    statusType: 'warning',
-    evaluateTime: '2025-03-21 10:27:56'
-  },
-  {
-    id: 4,
-    name: '小张小赵白有主张',
-    gender: '男',
-    phone: '18438573743',
-    statusText: '学员已拒绝',
-    statusType: 'danger',
-    evaluateTime: '2025-03-21 10:27:56'
-  }
-]);
+const applyList = ref<PostCandidateVO[]>([]);
 
 const hireDialog = reactive({
   visible: false,
   remark: '',
   agree: true,
-  files: ['文档.docx', '附件.png(上传...)', '组件123.jpg']
+  files: ['文档.docx', '附件.png(上传...)', '组件123.jpg'],
+  currentId: '',
+  submitting: false
 });
 
 const rejectDialog = reactive({
   visible: false,
-  remark: ''
+  remark: '',
+  currentId: '',
+  submitting: false
 });
 
+const getList = async () => {
+  const postId = route.query.postId as string | undefined;
+  if (!postId) {
+    applyList.value = [];
+    total.value = 0;
+    return;
+  }
+  loading.value = true;
+  try {
+    const res = await listPostCandidates({
+      postId,
+      pageNum: queryParams.pageNum,
+      pageSize: queryParams.pageSize,
+      keyword: queryParams.keyword,
+      status: queryParams.status
+    });
+    applyList.value = res.rows || [];
+    total.value = res.total || 0;
+  } finally {
+    loading.value = false;
+  }
+};
+
+const handleQuery = () => {
+  queryParams.pageNum = 1;
+  getList();
+};
+
 const handleBack = () => {
   proxy?.$tab.closePage();
   router.push((route.query.backPath as string) || '/postManage');
 };
 
-const handleHire = () => {
+const handleHire = (row: PostCandidateVO) => {
+  hireDialog.currentId = String(row.id);
+  hireDialog.remark = row.remark || '';
+  hireDialog.agree = true;
   hireDialog.visible = true;
 };
 
-const handleReject = () => {
+const handleReject = (row: PostCandidateVO) => {
+  rejectDialog.currentId = String(row.id);
+  rejectDialog.remark = row.remark || '';
   rejectDialog.visible = true;
 };
 
+const submitHire = async () => {
+  if (!hireDialog.currentId) return;
+  hireDialog.submitting = true;
+  try {
+    await updatePostCandidateStatus({
+      id: hireDialog.currentId,
+      status: 'adopted',
+      remark: hireDialog.remark
+    });
+    hireDialog.visible = false;
+    modal?.msgSuccess('录用成功');
+    await getList();
+  } finally {
+    hireDialog.submitting = false;
+  }
+};
+
+const submitReject = async () => {
+  if (!rejectDialog.currentId) return;
+  rejectDialog.submitting = true;
+  try {
+    await updatePostCandidateStatus({
+      id: rejectDialog.currentId,
+      status: 'rejected',
+      remark: rejectDialog.remark
+    });
+    rejectDialog.visible = false;
+    modal?.msgSuccess('操作成功');
+    await getList();
+  } finally {
+    rejectDialog.submitting = false;
+  }
+};
+
 const handleOpenDetail = (row: { id: number; name: string }) => {
   router.push({
     path: '/postManage/apply-detail',
@@ -218,6 +261,10 @@ const handleOpenDetail = (row: { id: number; name: string }) => {
     }
   });
 };
+
+onMounted(() => {
+  getList();
+});
 </script>
 
 <style scoped>

+ 86 - 19
src/views/postManage/create.vue

@@ -10,10 +10,10 @@
           <el-button @click="handleCancel">返回</el-button>
         </div>
 
-        <el-steps :active="activeStep" finish-status="success" simple class="post-steps">
-          <el-step title="第一步骤" />
-          <el-step title="第二步骤" />
-          <el-step title="第三步骤" />
+        <el-steps :active="activeStep" finish-status="success" align-center class="post-steps">
+          <el-step title="岗位基本信息" description="" />
+          <el-step title="岗位测评信息" description="" />
+          <el-step title="完成创建" description="" />
         </el-steps>
 
         <div class="post-create-body">
@@ -45,8 +45,14 @@
                   <el-option v-for="item in jobTypeOptions" :key="item.dictValue" :label="item.dictLabel" :value="item.dictValue" />
                 </el-select>
               </el-form-item>
-              <el-form-item label="薪资" prop="salary">
-                <el-input v-model="form.salary" placeholder="请输入薪资" maxlength="50" />
+              <el-form-item label="薪资" prop="salaryMin">
+                <div class="inline-mixed-field input-unit-field salary-range-field">
+                  <el-input v-model="form.salaryMin" placeholder="最低薪资" maxlength="10" />
+                  <span class="field-unit">k</span>
+                  <span class="salary-range-separator">-</span>
+                  <el-input v-model="form.salaryMax" placeholder="最高薪资" maxlength="10" />
+                  <span class="field-unit">k</span>
+                </div>
               </el-form-item>
               <el-form-item label="岗位标签" prop="tags">
                 <el-select
@@ -195,7 +201,8 @@ interface PostCreateFormModel {
   regionCodes: string[];
   addressDetail: string;
   jobType: string;
-  salary: string;
+  salaryMin: string;
+  salaryMax: string;
   tags: string[];
   recruitCount: number | undefined;
   unlimitedRecruitCount: boolean;
@@ -256,7 +263,8 @@ const form = reactive<PostCreateFormModel>({
   regionCodes: [],
   addressDetail: '',
   jobType: '',
-  salary: '',
+  salaryMin: '',
+  salaryMax: '',
   tags: [],
   recruitCount: undefined,
   unlimitedRecruitCount: false,
@@ -288,7 +296,8 @@ const rules = reactive({
   regionCodes: [{ required: true, message: '地区不能为空', trigger: 'change' }],
   addressDetail: [{ required: true, message: '详细地址不能为空', trigger: 'blur' }],
   jobType: [{ required: true, message: '岗位类型不能为空', trigger: 'change' }],
-  salary: [{ required: true, message: '薪资不能为空', trigger: 'blur' }],
+  salaryMin: [{ required: true, message: '最低薪资不能为空', trigger: 'blur' }],
+  salaryMax: [{ required: true, message: '最高薪资不能为空', trigger: 'blur' }],
   postCode: [{ required: true, message: '岗位编码不能为空', trigger: 'blur' }],
   postCategory: [{ required: true, message: '岗位级别不能为空', trigger: 'change' }],
   evaluationDuration: [{ required: true, message: '测评时长不能为空', trigger: 'blur' }],
@@ -305,6 +314,23 @@ const applyTimeText = computed(() => (form.applyTimeRange.length === 2 ? `${form
 const statusText = computed(() => sys_normal_disable.value?.find((item: any) => item.value === form.status)?.label || '--');
 const yesNoText = (value: string) => (value === '1' ? '是' : '否');
 const genderText = (value: string) => genderOptions.find((item) => item.value === value)?.label || '--';
+const parseSalaryRange = (salaryRange?: string) => {
+  const value = (salaryRange || '').trim().toLowerCase().replace(/\s+/g, '');
+  if (!value) {
+    return { min: '', max: '' };
+  }
+  const match = value.match(/^(\d+(?:\.\d+)?)[-~至](\d+(?:\.\d+)?)(?:k)?$/i);
+  if (match) {
+    return { min: match[1], max: match[2] };
+  }
+  const singleMatch = value.match(/^(\d+(?:\.\d+)?)(?:k)?$/i);
+  if (singleMatch) {
+    return { min: singleMatch[1], max: '' };
+  }
+  return { min: '', max: '' };
+};
+
+const buildSalaryRange = () => `${form.salaryMin}-${form.salaryMax}k`;
 
 const buildPostNameOptions = (industryList: IndustryVO[], skillList: IndustrySkillVO[]) => {
   const rootIndustryMap = new Map<string, CascaderOption>();
@@ -468,7 +494,9 @@ const fillForm = (data: MainPostApplyVO) => {
   form.regionCodes = [];
   form.addressDetail = data.workAddress || '';
   form.jobType = data.postType || '';
-  form.salary = data.salaryRange || '';
+  const salaryRange = parseSalaryRange(data.salaryRange);
+  form.salaryMin = salaryRange.min;
+  form.salaryMax = salaryRange.max;
   form.tags = data.welfareTags ? data.welfareTags.split(',').filter(Boolean) : [];
   form.recruitCount = data.recruitNum && data.recruitNum !== 99999 ? data.recruitNum : undefined;
   form.unlimitedRecruitCount = data.recruitNum === 99999;
@@ -517,7 +545,8 @@ const handleApplyUnlimitedChange = (value: boolean) => {
 
 const validateStep = async () => {
   if (activeStep.value === 0) {
-    await formRef.value?.validateField(['postNamePath', 'postCode', 'regionCodes', 'addressDetail', 'jobType', 'salary']);
+    await formRef.value?.validateField(['postNamePath', 'postCode', 'regionCodes', 'addressDetail', 'jobType', 'salaryMin', 'salaryMax']);
+    if (Number(form.salaryMin) > Number(form.salaryMax)) throw new Error('最低薪资不能大于最高薪资');
     if (!form.unlimitedRecruitCount && !form.recruitCount) throw new Error('招聘人数不能为空');
     if (!form.unlimitedApplyTime && form.applyTimeRange.length !== 2) throw new Error('报名时间不能为空');
   }
@@ -554,7 +583,9 @@ const buildPayload = () => ({
   workAddress: form.addressDetail,
   postType: form.jobType,
   salaryType: '',
-  salaryRange: form.salary,
+  salaryRange: buildSalaryRange(),
+  minSalary: form.salaryMin ? Number(form.salaryMin) : undefined,
+  maxSalary: form.salaryMax ? Number(form.salaryMax) : undefined,
   recruitNum: form.unlimitedRecruitCount ? 99999 : form.recruitCount,
   registrationStartDate: form.unlimitedApplyTime ? undefined : form.applyTimeRange[0],
   registrationEndDate: form.unlimitedApplyTime ? undefined : form.applyTimeRange[1],
@@ -663,28 +694,64 @@ onMounted(async () => {
   margin-right: auto;
 }
 
+.salary-range-field {
+  width: 100%;
+}
+
+.salary-range-field .el-input {
+  flex: 1;
+}
+
+.salary-range-separator {
+  color: #606266;
+  padding: 0 4px;
+}
+
 .post-steps :deep(.el-step) {
   min-width: 0;
+  flex: 0 1 180px;
+  margin: 0 58px;
 }
 
-.post-steps :deep(.el-step__head) {
-  flex-shrink: 0;
+.post-steps :deep(.el-step__icon) {
+  width: 36px;
+  height: 36px;
+  border: none;
+  background: #f0f2f5;
+  color: #909399;
+}
+
+.post-steps :deep(.el-step__head.is-process .el-step__icon) {
+  background: #e9edf3;
+  color: #303133;
+}
+
+.post-steps :deep(.el-step__head.is-finish .el-step__icon) {
+  background: #eef3f8;
+  color: #409eff;
 }
 
 .post-steps :deep(.el-step__title) {
-  white-space: nowrap;
   font-size: 14px;
-  padding-right: 24px;
+  line-height: 22px;
+  white-space: nowrap;
 }
 
-.post-steps :deep(.el-step.is-simple .el-step__title) {
-  margin-right: 20px;
+.post-steps :deep(.el-step__description) {
+  margin-top: 6px;
+  font-size: 12px;
+  line-height: 18px;
+  white-space: nowrap;
 }
 
-.post-steps :deep(.el-step.is-simple:not(:last-child) .el-step__arrow) {
+.post-steps :deep(.el-step__line) {
   display: none;
 }
 
+.post-steps :deep(.el-step__main) {
+  margin-top: 10px;
+}
+
 .post-create-form {
   width: 760px;
   max-width: 100%;

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

@@ -86,7 +86,7 @@
           </el-table-column>
           <el-table-column label="报名人数" align="center" min-width="100">
             <template #default="scope">
-              <el-button link type="primary" @click="handleOpenApplyList(scope.row)">0</el-button>
+              <el-button link type="primary" @click="handleOpenApplyList(scope.row)">{{ scope.row.applyCount ?? 0 }}</el-button>
             </template>
           </el-table-column>
           <el-table-column label="发布状态" align="center" min-width="100">