Gqingci hai 2 meses
pai
achega
42675c9a97

+ 37 - 0
.codebuddy/rules/yue.mdc

@@ -0,0 +1,37 @@
+---
+description: 
+alwaysApply: true
+enabled: true
+updatedAt: 2026-03-28T11:26:45.269Z
+provider: 
+---
+
+**前端Vue3规则**
+
+1.所有对话交互必须全程且强制使用中文。
+2.助手的内部思考、分析、推理过程必须以中文形式完整展示。
+3.项目所有源代码中的注释必须使用中文。
+4.助手执行的整个过程,包括步骤说明、输出等,必须用中文显示。
+5.不要运行任何命令,全程只允许说明和编辑文件
+6.所有的API调用都需要按照项目的风格,统一在api目录下定义
+- 方法(index.ts中调用接口)和接口(types.ts中定义interface,负责类型),然后在页面中进行调用,如果是uniapp的js项目,那么就只定义方法,不定义类型
+- 其中文件夹的路径设置规则:根据请求路径,如'/system/user/getInfo'为'api/system/user/index.ts(types.ts)'
+7.公共组件必须放入src/components/{组件名}/index.vue中,不能还带任何网络请求,只能完成UI和基本的交互;普通组件必须在页面的当前目录下创建一个components文件夹,将组建放入其中[{组件名}.vue]
+8.绘制静态页面时,mockData需要使用JSON文件存储,放入到src/mock中,使用{组件名/页面名}.json来命名
+9.所有的展示所需的枚举需要使用公共的文件夹src/json,命名为:{业务名}.json
+
+**移动端uniapp**
+
+1.所有对话交互必须全程且强制使用中文。
+2.助手的内部思考、分析、推理过程必须以中文形式完整展示。
+3.项目所有源代码中的注释必须使用中文。
+4.助手执行的整个过程,包括步骤说明、输出等,必须用中文显示。
+5.不要运行任何命令,全程只允许说明和编辑文件
+6.所有的API调用都需要按照项目的风格,统一在api目录下定义
+- 使用url路径进行安放文件,例如"/system/merchant/list",则放在/api/system/merchant.js,在其中定义该接口
+7.公共组件必须放入components/{组件名}/index.vue中,不能还带任何网络请求,只能完成UI和基本的交互;普通组件必须在页面的当前目录下创建一个components文件夹,将组建放入其中[{组件名}.vue]
+8.绘制静态页面时,mockData需要使用JSON文件存储,放入到src/mock中,使用{组件名/页面名}.json来命名
+9.所有的展示所需的枚举需要使用公共的文件夹src/json,命名为:{业务名}.json
+
+**公共规则**
+1.当前的变成环境为win11,node版本为22

+ 1 - 0
package.json

@@ -29,6 +29,7 @@
     "axios": "1.13.1",
     "crypto-js": "4.2.0",
     "echarts": "5.6.0",
+    "element-china-area-data": "^6.1.0",
     "element-plus": "2.11.7",
     "file-saver": "2.0.5",
     "highlight.js": "11.11.1",

+ 18 - 0
src/api/main/audit/index.ts

@@ -0,0 +1,18 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { MainAuditQuery, MainAuditVO } from './types';
+
+export function getMainAudit(id: string | number): AxiosPromise<MainAuditVO> {
+  return request({
+    url: '/main/audit/' + id,
+    method: 'get'
+  });
+}
+
+export function listMainAudit(query: MainAuditQuery): AxiosPromise<MainAuditVO[]> {
+  return request({
+    url: '/main/audit/list',
+    method: 'get',
+    params: query
+  });
+}

+ 19 - 0
src/api/main/audit/types.ts

@@ -0,0 +1,19 @@
+export interface MainAuditVO extends BaseEntity {
+  id: number | string;
+  auditType?: number;
+  targetId?: number | string;
+  auditResult?: number;
+  auditRemark?: string;
+  deposit?: number;
+  finalPayment?: number;
+  auditTime?: string;
+  auditBy?: number | string;
+  tenantId?: string;
+}
+
+export interface MainAuditQuery extends PageQuery {
+  auditType?: number;
+  targetId?: number | string;
+  auditResult?: number;
+  auditRemark?: string;
+}

+ 18 - 0
src/api/main/evaluation/index.ts

@@ -0,0 +1,18 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { MainExamEvaluationQuery, MainExamEvaluationVO } from './types';
+
+export function listEvaluation(query: MainExamEvaluationQuery): AxiosPromise<MainExamEvaluationVO[]> {
+  return request({
+    url: '/main/examEvaluation/list',
+    method: 'get',
+    params: query
+  });
+}
+
+export function updateEvaluationStatus(id: string | number, status: string) {
+  return request({
+    url: `/main/examEvaluation/status/${id}/${status}`,
+    method: 'put'
+  });
+}

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

@@ -0,0 +1,30 @@
+export interface MainExamEvaluationVO extends BaseEntity {
+  id: number | string;
+  evaluationName?: string;
+  grade?: string;
+  position?: string;
+  positionType?: string;
+  tags?: string;
+  mainImage?: string | number;
+  mainImageUrl?: string;
+  imageAlbum?: string;
+  detail?: string;
+  price?: number | string;
+  onTime?: string;
+  downTime?: string;
+  status?: string;
+  delFlag?: string;
+  tenantId?: string;
+  remark?: string;
+  updateTime?: string;
+}
+
+export interface MainExamEvaluationQuery extends PageQuery {
+  evaluationName?: string;
+  grade?: string;
+  position?: string;
+  positionType?: string;
+  status?: string;
+  beginTime?: string;
+  endTime?: string;
+}

+ 55 - 0
src/api/main/postManage/index.ts

@@ -0,0 +1,55 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { MainPostApplyForm, MainPostApplyQuery, MainPostApplyVO } from './types';
+
+export function listPostManage(query: MainPostApplyQuery): AxiosPromise<MainPostApplyVO[]> {
+  return request({
+    url: '/main/postApply/list',
+    method: 'get',
+    params: query
+  });
+}
+
+export function getPostManage(id: string | number): AxiosPromise<MainPostApplyVO> {
+  return request({
+    url: '/main/postApply/' + id,
+    method: 'get'
+  });
+}
+
+export function addPostManage(data: MainPostApplyForm) {
+  return request({
+    url: '/main/postApply/add',
+    method: 'post',
+    data
+  });
+}
+
+export function updatePostManage(data: MainPostApplyForm) {
+  return request({
+    url: '/main/postApply/edit',
+    method: 'put',
+    data
+  });
+}
+
+export function publishPostManage(id: string | number) {
+  return request({
+    url: '/main/postApply/publish/' + id,
+    method: 'post'
+  });
+}
+
+export function unpublishPostManage(id: string | number) {
+  return request({
+    url: '/main/postApply/unpublish/' + id,
+    method: 'post'
+  });
+}
+
+export function delPostManage(id: string | number | Array<string | number>) {
+  return request({
+    url: '/main/postApply/' + id,
+    method: 'delete'
+  });
+}

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

@@ -0,0 +1,86 @@
+export interface MainPostApplyVO extends BaseEntity {
+  id: number | string;
+  applyNo?: string;
+  tenantId?: string;
+  postName: string;
+  companyName?: string;
+  postDescription?: string;
+  workProvince?: string;
+  workCity?: string;
+  workDistrict?: string;
+  workAddress?: string;
+  postType?: string;
+  educationRequirement?: string;
+  salaryType?: string;
+  salaryRange?: string;
+  recruitNum?: number;
+  registrationStartDate?: string;
+  registrationEndDate?: string;
+  isUrgent?: number;
+  schoolRequirement?: string;
+  genderRequirement?: string;
+  gradeRequirement?: string;
+  arrivalTime?: string;
+  internshipDuration?: string;
+  willingToTravel?: number;
+  welfareTags?: string;
+  jobRequirement?: string;
+  postLevel?: string;
+  assessmentTime?: string;
+  gradeA?: number;
+  gradeB?: number;
+  gradeC?: number;
+  applyStatus?: number;
+  status?: string;
+  auditId?: number | string;
+  postId?: number | string;
+  rejectReason?: string;
+}
+
+export interface MainPostApplyForm {
+  id?: number | string;
+  applyNo?: string;
+  tenantId?: string;
+  postName: string;
+  companyName?: string;
+  postDescription?: string;
+  workProvince?: string;
+  workCity?: string;
+  workDistrict?: string;
+  workAddress?: string;
+  postType?: string;
+  educationRequirement?: string;
+  salaryType?: string;
+  salaryRange?: string;
+  recruitNum?: number;
+  registrationStartDate?: string;
+  registrationEndDate?: string;
+  isUrgent?: number;
+  schoolRequirement?: string;
+  genderRequirement?: string;
+  gradeRequirement?: string;
+  arrivalTime?: string;
+  internshipDuration?: string;
+  willingToTravel?: number;
+  welfareTags?: string;
+  jobRequirement?: string;
+  postLevel?: string;
+  assessmentTime?: string;
+  gradeA?: number;
+  gradeB?: number;
+  gradeC?: number;
+  applyStatus?: number;
+  status?: string;
+}
+
+export interface MainPostApplyQuery extends PageQuery {
+  applyNo?: string;
+  tenantId?: string;
+  postName?: string;
+  companyName?: string;
+  applyStatus?: number | string;
+  postType?: string;
+  isUrgent?: number | string;
+  postLevel?: string;
+  workCity?: string;
+}

+ 19 - 0
src/api/system/industry/index.ts

@@ -0,0 +1,19 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { IndustryQuery, IndustrySkillQuery, IndustrySkillVO, IndustryVO } from './types';
+
+export function listIndustry(query?: IndustryQuery): AxiosPromise<IndustryVO[]> {
+  return request({
+    url: '/system/industry/list',
+    method: 'get',
+    params: query
+  });
+}
+
+export function listIndustrySkill(query?: IndustrySkillQuery): AxiosPromise<IndustrySkillVO[]> {
+  return request({
+    url: '/system/industry/skill/list',
+    method: 'get',
+    params: query
+  });
+}

+ 31 - 0
src/api/system/industry/types.ts

@@ -0,0 +1,31 @@
+export interface IndustryVO {
+  industryId: number | string;
+  parentId: number | string;
+  industryName: string;
+  orderNum?: number;
+  status?: string;
+  createTime?: string;
+  remark?: string;
+}
+
+export interface IndustryQuery {
+  parentId?: number | string;
+  status?: string;
+  industryName?: string;
+}
+
+export interface IndustrySkillVO {
+  skillId: number | string;
+  industryId: number | string;
+  skillName: string;
+  orderNum?: number;
+  status?: string;
+  createTime?: string;
+  remark?: string;
+}
+
+export interface IndustrySkillQuery {
+  industryId?: number | string;
+  status?: string;
+  skillName?: string;
+}

+ 18 - 1
src/api/system/post/index.ts

@@ -1,5 +1,5 @@
 import request from '@/utils/request';
-import { PostForm, PostQuery, PostVO } from './types';
+import { PostAuditRemarkVO, PostForm, PostQuery, PostVO } from './types';
 import { AxiosPromise } from 'axios';
 import { DeptTreeVO } from '../dept/types';
 
@@ -50,6 +50,23 @@ export function updatePost(data: PostForm) {
   });
 }
 
+// 提交岗位审核
+export function submitPostAudit(postId: string | number) {
+  return request({
+    url: '/system/post/submitAudit/' + postId,
+    method: 'post'
+  });
+}
+
+// 查询岗位审核说明
+export function getPostAuditRemark(postId: string | number): AxiosPromise<PostAuditRemarkVO> {
+  return request({
+    url: '/system/post/auditRemark/' + postId,
+    method: 'post',
+    data: {}
+  });
+}
+
 // 删除岗位
 export function delPost(postId: string | number | (string | number)[]) {
   return request({

+ 7 - 0
src/api/system/post/types.ts

@@ -6,6 +6,8 @@ export interface PostVO extends BaseEntity {
   postCategory: string;
   deptName: string;
   postSort: number;
+  auditStatus?: string;
+  auditRemark?: string;
   status: string;
   remark: string;
 }
@@ -21,6 +23,11 @@ export interface PostForm {
   remark: string;
 }
 
+export interface PostAuditRemarkVO {
+  auditStatus?: string;
+  auditRemark?: string;
+}
+
 export interface PostQuery extends PageQuery {
   deptId: number | string;
   belongDeptId: number | string;

+ 11 - 0
src/api/system/tag/index.ts

@@ -0,0 +1,11 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { TagQuery, TagVO } from './types';
+
+export function listTag(query: TagQuery): AxiosPromise<any> {
+  return request({
+    url: '/system/tag/list',
+    method: 'get',
+    params: query
+  });
+}

+ 11 - 0
src/api/system/tag/types.ts

@@ -0,0 +1,11 @@
+export interface TagVO extends BaseEntity {
+  id: number | string;
+  name: string;
+  description?: string;
+  status?: string | number;
+}
+
+export interface TagQuery extends PageQuery {
+  name?: string;
+  status?: string | number;
+}

+ 3 - 0
src/api/system/user/types.ts

@@ -14,6 +14,7 @@ export interface UserInfo {
  * 用户查询对象类型
  */
 export interface UserQuery extends PageQuery {
+  keyword?: string;
   userName?: string;
   nickName?: string;
   phonenumber?: string;
@@ -38,11 +39,13 @@ export interface UserVO extends BaseEntity {
   sex: string;
   avatar: string;
   status: string;
+  isEmployed?: string;
   delFlag: string;
   loginIp: string;
   loginDate: string;
   remark: string;
   deptName: string;
+  roleName?: string;
   roles: RoleVO[];
   roleIds: any;
   postIds: any;

BIN=BIN
src/assets/images/user.jpg


+ 27 - 0
src/components/PageShell/index.vue

@@ -0,0 +1,27 @@
+ <template>
+  <div class="page-shell">
+    <div class="page-shell__card">
+      <slot />
+    </div>
+  </div>
+ </template>
+ 
+ <style scoped lang="scss">
+ .page-shell {
+   width: 100%;
+   height: calc(100vh - 84px);
+   padding: 24px;
+   background: #f5f7fa;
+   box-sizing: border-box;
+ }
+
+ .page-shell__card {
+   width: 100%;
+   height: 100%;
+   padding: 16px;
+   border-radius: 12px;
+   background: #fff;
+   box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
+   box-sizing: border-box;
+  }
+ </style>

+ 59 - 3
src/router/index.ts

@@ -88,13 +88,69 @@ export const constantRoutes: RouteRecordRaw[] = [
         meta: { title: '个人中心', icon: 'user' }
       }
     ]
+  },
+  {
+    path: '/postManage',
+    component: Layout,
+    redirect: '/postManage',
+    name: 'PostManage',
+    hidden: true,
+    children: [
+      {
+        path: '',
+        component: () => import('@/views/postManage/index.vue'),
+        name: 'PostManageIndex',
+        meta: { title: '岗位管理', icon: 'post' }
+      },
+      {
+        path: 'create',
+        component: () => import('@/views/postManage/create.vue'),
+        name: 'PostManageCreate',
+        meta: { title: '添加岗位', icon: 'post', activeMenu: '/postManage' }
+      },
+      {
+        path: 'apply-list',
+        component: () => import('@/views/postManage/apply-list.vue'),
+        name: 'PostManageApplyList',
+        meta: { title: '报名列表', icon: 'post', activeMenu: '/postManage' }
+      },
+      {
+        path: 'apply-detail',
+        component: () => import('@/views/postManage/apply-detail.vue'),
+        name: 'PostManageApplyDetail',
+        meta: { title: '报名详情', icon: 'post', activeMenu: '/postManage' }
+      },
+      {
+        path: 'evaluation-view',
+        component: () => import('@/views/postManage/evaluation-view.vue'),
+        name: 'PostManageEvaluationView',
+        meta: { title: '测评查看', icon: 'post', activeMenu: '/postManage' }
+      }
+    ]
+  },
+  {
+    path: '/evaluation',
+    component: Layout,
+    hidden: true,
+    children: [
+      {
+        path: 'apply-list',
+        component: () => import('@/views/evaluation/apply-list.vue'),
+        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' }
+      }
+    ]
   }
 ];
 
 // 动态路由,基于用户权限动态去加载
-export const dynamicRoutes: RouteRecordRaw[] = [
-
-];
+export const dynamicRoutes: RouteRecordRaw[] = [];
 
 /**
  * 创建路由

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

@@ -0,0 +1,335 @@
+<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>

+ 342 - 0
src/views/evaluation/apply-list.vue

@@ -0,0 +1,342 @@
+<template>
+  <PageShell>
+    <div class="apply-page">
+      <div class="apply-card">
+        <div class="apply-title">报名列表</div>
+
+        <div class="apply-toolbar">
+          <div class="apply-search-box">
+            <el-input v-model="queryParams.keyword" placeholder="请输入" clearable @keyup.enter="handleQuery" />
+            <el-button type="primary" class="apply-search-button" @click="handleQuery">
+              <el-icon><Search /></el-icon>
+            </el-button>
+          </div>
+
+          <div class="apply-toolbar-actions">
+            <el-button type="primary" plain @click="handleAddEmployee">添加员工</el-button>
+            <el-button type="primary" @click="handleExport">导出列表</el-button>
+          </div>
+        </div>
+
+        <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>
+            </template>
+          </el-table-column>
+          <el-table-column label="性别" prop="gender" width="80" align="center" />
+          <el-table-column label="部门" prop="department" width="100" align="center" />
+          <el-table-column label="手机号" prop="phone" min-width="160" align="center" />
+          <el-table-column label="状态" min-width="160" align="center">
+            <template #default="{ row }">
+              <span class="apply-status-text">
+                <span class="apply-status-dot" :class="`is-${row.statusType}`"></span>
+                {{ row.statusText }}
+              </span>
+            </template>
+          </el-table-column>
+          <el-table-column label="测评时间" prop="evaluateTime" min-width="180" align="center" />
+          <el-table-column label="操作" width="160" align="center">
+            <template #default="{ row }">
+              <el-button v-if="row.actionType === 'detail'" link type="primary" @click="handleOpenDetail(row)">测评详情</el-button>
+              <el-button v-else link type="primary" @click="handleRemove(row)">移除</el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+
+        <pagination
+          v-show="total > 0"
+          v-model:page="queryParams.pageNum"
+          v-model:limit="queryParams.pageSize"
+          :total="total"
+          @pagination="handlePagination"
+        />
+      </div>
+
+      <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" />
+            </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>
+          </div>
+        </template>
+      </el-dialog>
+    </div>
+  </PageShell>
+</template>
+
+<script setup name="EvaluationApplyList" lang="ts">
+import { computed, getCurrentInstance, reactive, ref, type ComponentInternalInstance } from 'vue';
+import { useRoute, useRouter } from 'vue-router';
+import PageShell from '@/components/PageShell/index.vue';
+
+type ApplyRow = {
+  id: number;
+  name: string;
+  gender: string;
+  department: string;
+  phone: string;
+  statusText: string;
+  statusType: 'success' | 'danger' | 'warning' | 'info';
+  evaluateTime: string;
+  actionType: 'detail' | 'remove';
+};
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const router = useRouter();
+const route = useRoute();
+const modal = proxy?.$modal as any;
+const syncCompanyOptions = ['张三', '李四', '王五', '赵六'];
+const syncDialog = reactive({
+  visible: false,
+  companies: ['张三', '李四'] as string[]
+});
+
+const queryParams = reactive({
+  pageNum: 1,
+  pageSize: 10,
+  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 filteredApplyList = computed(() => {
+  const keyword = queryParams.keyword.trim();
+  if (!keyword) {
+    return allApplyList.value;
+  }
+  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);
+});
+
+const handleQuery = () => {
+  queryParams.pageNum = 1;
+};
+
+const handlePagination = () => {
+  // Pagination component already updates pageNum/pageSize via v-model.
+};
+
+const handleAddEmployee = () => {
+  syncDialog.visible = true;
+};
+
+const handleExport = () => {
+  modal?.msgSuccess('导出列表功能待接入');
+};
+
+const handleConfirmSync = () => {
+  syncDialog.visible = false;
+  modal?.msgSuccess('同步成功');
+};
+
+const handleRemove = (row: ApplyRow) => {
+  allApplyList.value = allApplyList.value.filter((item) => item.id !== row.id);
+  modal?.msgSuccess('已移除');
+  const maxPage = Math.max(1, Math.ceil(total.value / queryParams.pageSize));
+  if (queryParams.pageNum > maxPage) {
+    queryParams.pageNum = maxPage;
+  }
+};
+
+const handleOpenDetail = (row: ApplyRow) => {
+  router.push({
+    path: '/evaluation/apply-detail',
+    query: {
+      applyId: String(row.id),
+      name: row.name,
+      evaluationId: String(route.query.evaluationId || '')
+    }
+  });
+};
+</script>
+
+<style scoped>
+.apply-page {
+  min-height: 100%;
+}
+
+.apply-card {
+  min-height: calc(100vh - 180px);
+  padding: 18px 18px 12px;
+  background: #fff;
+  border-radius: 4px;
+  display: flex;
+  flex-direction: column;
+  box-sizing: border-box;
+}
+
+.apply-title {
+  margin-bottom: 10px;
+  font-size: 16px;
+  font-weight: 600;
+  color: #303133;
+}
+
+.apply-toolbar {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 16px;
+  margin-bottom: 14px;
+}
+
+.apply-search-box {
+  display: flex;
+  align-items: stretch;
+  width: 290px;
+}
+
+.apply-search-box :deep(.el-input__wrapper) {
+  border-radius: 4px 0 0 4px;
+}
+
+.apply-search-button {
+  width: 36px;
+  padding: 0;
+  border-radius: 0 4px 4px 0;
+}
+
+.apply-toolbar-actions {
+  display: flex;
+  align-items: center;
+  gap: 14px;
+}
+
+.apply-list-table {
+  flex: 1;
+}
+
+.apply-list-table :deep(.apply-table-header th) {
+  height: 48px;
+  color: #303133;
+  font-weight: 600;
+  background: #eef4ff;
+}
+
+.apply-list-table :deep(.el-table__row td) {
+  height: 46px;
+}
+
+.apply-status-text {
+  display: inline-flex;
+  align-items: center;
+  gap: 6px;
+}
+
+.apply-status-dot {
+  width: 6px;
+  height: 6px;
+  border-radius: 50%;
+  background: #c0c4cc;
+}
+
+.apply-status-dot.is-success {
+  background: #67c23a;
+}
+
+.apply-status-dot.is-danger {
+  background: #f56c6c;
+}
+
+.apply-status-dot.is-warning {
+  background: #e6a23c;
+}
+
+.apply-status-dot.is-info {
+  background: #909399;
+}
+
+.apply-card :deep(.pagination-container) {
+  display: flex;
+  justify-content: flex-end;
+  margin-top: auto;
+  padding: 16px 0 0;
+}
+
+.sync-dialog-content {
+  padding: 10px 4px 24px;
+}
+
+.sync-dialog-row {
+  display: flex;
+  align-items: center;
+  gap: 16px;
+}
+
+.sync-dialog-label {
+  width: 56px;
+  color: #303133;
+  flex-shrink: 0;
+}
+
+.sync-dialog-select {
+  flex: 1;
+}
+
+.sync-dialog-footer {
+  display: flex;
+  justify-content: flex-end;
+  gap: 12px;
+}
+</style>

+ 108 - 0
src/views/evaluation/components/EvaluationFilter.vue

@@ -0,0 +1,108 @@
+<template>
+  <el-form :model="formModel" inline class="filter-bar">
+    <el-form-item prop="grade" class="filter-item">
+      <el-select :model-value="grade" placeholder="岗位等级" clearable @update:model-value="emit('update:grade', $event || '')">
+        <el-option v-for="item in gradeOptions" :key="item.value" :label="item.label" :value="item.value" />
+      </el-select>
+    </el-form-item>
+    <el-form-item prop="positionType" class="filter-item">
+      <el-select :model-value="positionType" placeholder="岗位类型" clearable @update:model-value="emit('update:positionType', $event || '')">
+        <el-option v-for="item in positionTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
+      </el-select>
+    </el-form-item>
+    <el-form-item prop="status" class="filter-item">
+      <el-select :model-value="status" placeholder="状态" clearable @update:model-value="emit('update:status', $event || '')">
+        <el-option label="启用" value="0" />
+        <el-option label="停用" value="1" />
+      </el-select>
+    </el-form-item>
+    <el-form-item class="filter-item filter-item--date">
+      <el-date-picker
+        :model-value="dateRange"
+        type="daterange"
+        range-separator="-"
+        start-placeholder="上架开始"
+        end-placeholder="上架结束"
+        value-format="YYYY-MM-DD"
+        @update:model-value="emit('update:dateRange', $event || [])"
+      />
+    </el-form-item>
+    <el-form-item prop="evaluationName" class="filter-item filter-item--keyword">
+      <el-input
+        :model-value="evaluationName"
+        placeholder="请输入测评名称"
+        clearable
+        @update:model-value="emit('update:evaluationName', $event || '')"
+        @keyup.enter="emit('search')"
+      />
+    </el-form-item>
+    <el-form-item class="filter-actions">
+      <el-button @click="emit('reset')">重置</el-button>
+      <el-button type="primary" @click="emit('search')">搜索</el-button>
+    </el-form-item>
+  </el-form>
+</template>
+
+<script setup lang="ts">
+import { computed } from 'vue';
+
+const props = defineProps<{
+  evaluationName: string;
+  grade: string;
+  positionType: string;
+  status: string;
+  dateRange: [string, string] | [];
+  gradeOptions: Array<{ label: string; value: string }>;
+  positionTypeOptions: Array<{ label: string; value: string }>;
+}>();
+
+const emit = defineEmits<{
+  search: [];
+  reset: [];
+  'update:evaluationName': [string];
+  'update:grade': [string];
+  'update:positionType': [string];
+  'update:status': [string];
+  'update:dateRange': [[string, string] | []];
+}>();
+
+const formModel = computed(() => ({
+  evaluationName: props.evaluationName,
+  grade: props.grade,
+  positionType: props.positionType,
+  status: props.status
+}));
+</script>
+
+<style scoped lang="scss">
+.filter-bar {
+  display: flex;
+  align-items: flex-start;
+  flex-wrap: wrap;
+  gap: 8px 12px;
+  margin-bottom: 16px;
+}
+
+.filter-item {
+  margin-right: 0;
+  margin-bottom: 0;
+}
+
+.filter-item :deep(.el-input),
+.filter-item :deep(.el-select) {
+  width: 180px;
+}
+
+.filter-item--keyword :deep(.el-input) {
+  width: 240px;
+}
+
+.filter-item--date :deep(.el-date-editor) {
+  width: 260px;
+}
+
+.filter-actions {
+  margin-left: auto;
+  margin-right: 0;
+}
+</style>

+ 194 - 0
src/views/evaluation/components/EvaluationTable.vue

@@ -0,0 +1,194 @@
+<template>
+  <el-table v-loading="loading" :data="list" border class="evaluation-table">
+    <el-table-column width="60" align="center">
+      <template #header>
+        <el-checkbox :model-value="isAllChecked" :indeterminate="isIndeterminate" @change="handleCheckAllChange" />
+      </template>
+      <template #default="{ row }">
+        <el-checkbox :model-value="isRowChecked(row)" @change="(checked) => handleRowCheckedChange(row, checked)" />
+      </template>
+    </el-table-column>
+    <el-table-column label="测评图片" width="88" align="center">
+      <template #default="{ row }">
+        <img :src="getImageUrl(row)" class="evaluation-image" alt="测评图片" />
+      </template>
+    </el-table-column>
+    <el-table-column label="测评名称" prop="evaluationName" min-width="220">
+      <template #default="{ row }">
+        <div class="evaluation-info">
+          <div class="evaluation-name">{{ row.evaluationName || '--' }}</div>
+          <div class="evaluation-id">ID: {{ row.id }}</div>
+        </div>
+      </template>
+    </el-table-column>
+    <el-table-column label="岗位类型" prop="positionType" min-width="140">
+      <template #default="{ row }">
+        <span>{{ getOptionLabel(positionTypeOptions, row.positionType) }}</span>
+      </template>
+    </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>
+      </template>
+    </el-table-column>
+    <el-table-column label="状态" width="110" align="center">
+      <template #default="{ row }">
+        <el-switch
+          :model-value="row.status === '0'"
+          inline-prompt
+          active-text="开"
+          inactive-text="关"
+          :loading="switchLoadingMap[String(row.id)]"
+          @change="emit('toggleStatus', row, $event)"
+        />
+      </template>
+    </el-table-column>
+    <el-table-column label="同步时间" min-width="170" align="center">
+      <template #default="{ row }">
+        <span>{{ formatDateTime(row.updateTime) }}</span>
+      </template>
+    </el-table-column>
+    <el-table-column label="状态" min-width="120" align="center">
+      <template #default="{ row }">
+        <el-tag :type="getSaleStatusMeta(row).type" effect="light">
+          {{ getSaleStatusMeta(row).label }}
+        </el-tag>
+      </template>
+    </el-table-column>
+
+    <el-table-column label="操作" width="120" align="center" fixed="right">
+      <template #default="{ row }">
+        <el-button link type="primary" @click="emit('viewApplyList', row)">查看</el-button>
+      </template>
+    </el-table-column>
+  </el-table>
+
+  <div class="table-footer">
+    <div class="table-total">共 {{ total }} 条</div>
+    <pagination
+      v-show="total > 0"
+      :page="pageNum"
+      :limit="pageSize"
+      :total="total"
+      @update:page="emit('update:pageNum', $event)"
+      @update:limit="emit('update:pageSize', $event)"
+      @pagination="emit('pagination')"
+    />
+  </div>
+</template>
+
+<script setup lang="ts">
+import { computed, ref } from 'vue';
+import type { MainExamEvaluationVO } from '@/api/main/evaluation/types';
+import { formatDateTime, getImageUrl, getSaleStatusMeta } from '../utils';
+
+const props = defineProps<{
+  loading: boolean;
+  list: MainExamEvaluationVO[];
+  total: number;
+  pageNum: number;
+  pageSize: number;
+  gradeOptions: Array<{ label: string; value: string }>;
+  positionTypeOptions: Array<{ label: string; value: string }>;
+  switchLoadingMap: Record<string, boolean>;
+}>();
+
+const emit = defineEmits<{
+  pagination: [];
+  'update:pageNum': [number];
+  'update:pageSize': [number];
+  toggleStatus: [MainExamEvaluationVO, boolean | string | number];
+  viewApplyList: [MainExamEvaluationVO];
+}>();
+
+const getOptionLabel = (options: Array<{ label: string; value: string }>, value?: string) => {
+  if (!value) {
+    return '--';
+  }
+  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 isIndeterminate = computed(() => {
+  const checkedCount = currentPageIds.value.filter((id) => checkedIds.value.includes(id)).length;
+  return checkedCount > 0 && checkedCount < currentPageIds.value.length;
+});
+
+const isRowChecked = (row: MainExamEvaluationVO) => checkedIds.value.includes(row.id);
+
+const handleCheckAllChange = (checked: boolean | string | number) => {
+  if (checked) {
+    checkedIds.value = Array.from(new Set([...checkedIds.value, ...currentPageIds.value]));
+    return;
+  }
+  checkedIds.value = checkedIds.value.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]));
+    return;
+  }
+  checkedIds.value = checkedIds.value.filter((id) => id !== row.id);
+};
+</script>
+
+<style scoped lang="scss">
+.evaluation-table {
+  flex: 1;
+}
+
+.evaluation-table :deep(.el-checkbox__inner) {
+  border-radius: 2px;
+}
+
+.evaluation-table :deep(.el-checkbox__input.is-indeterminate .el-checkbox__inner) {
+  border-radius: 2px;
+}
+
+.evaluation-table :deep(.el-table__header th) {
+  background: #f7f8fa;
+  color: #303133;
+  font-weight: 600;
+}
+
+.evaluation-image {
+  width: 48px;
+  height: 48px;
+  border-radius: 6px;
+  object-fit: cover;
+  background: #f5f7fa;
+}
+
+.evaluation-info {
+  line-height: 1.6;
+}
+
+.evaluation-name {
+  color: #303133;
+  font-size: 14px;
+}
+
+.evaluation-id {
+  color: #909399;
+  font-size: 12px;
+}
+
+.table-footer {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 12px;
+  padding-top: 16px;
+}
+
+.table-total {
+  color: #909399;
+  font-size: 12px;
+}
+</style>

+ 271 - 0
src/views/evaluation/index.vue

@@ -0,0 +1,271 @@
+<template>
+  <PageShell>
+    <div class="evaluation-page">
+      <div class="evaluation-card">
+        <EvaluationFilter
+          :evaluation-name="queryParams.evaluationName || ''"
+          :grade="queryParams.grade || ''"
+          :position-type="queryParams.positionType || ''"
+          :status="queryParams.status || ''"
+          :date-range="dateRange"
+          :grade-options="gradeOptions"
+          :position-type-options="positionTypeOptions"
+          @search="handleQuery"
+          @reset="resetQuery"
+          @update:evaluation-name="queryParams.evaluationName = $event"
+          @update:grade="queryParams.grade = $event"
+          @update:position-type="queryParams.positionType = $event"
+          @update:status="queryParams.status = $event"
+          @update:date-range="dateRange = $event"
+        />
+
+        <div class="evaluation-toolbar">
+          <el-button class="evaluation-toolbar-button" @click="handleExport">导出数据</el-button>
+          <el-button class="evaluation-toolbar-button" @click="getList">刷新</el-button>
+          <el-button class="evaluation-toolbar-button" @click="handleOpenSyncDialog">同步至员工</el-button>
+        </div>
+
+        <EvaluationTable
+          :loading="loading"
+          :list="evaluationList"
+          :total="total"
+          :page-num="queryParams.pageNum"
+          :page-size="queryParams.pageSize"
+          :grade-options="gradeOptions"
+          :position-type-options="positionTypeOptions"
+          :switch-loading-map="switchLoadingMap"
+          @update:page-num="queryParams.pageNum = $event"
+          @update:page-size="queryParams.pageSize = $event"
+          @pagination="getList"
+          @toggle-status="handleToggleStatus"
+          @view-apply-list="handleViewApplyList"
+        />
+      </div>
+
+      <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" />
+            </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>
+          </div>
+        </template>
+      </el-dialog>
+    </div>
+  </PageShell>
+</template>
+
+<script setup lang="ts">
+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 { 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 };
+
+const router = useRouter();
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const modal = proxy?.$modal as any;
+const loading = ref(false);
+const total = ref(0);
+const evaluationList = ref<MainExamEvaluationVO[]>([]);
+const dateRange = ref<[string, string] | []>([]);
+const gradeOptions = ref<FilterOption[]>([]);
+const positionTypeOptions = ref<FilterOption[]>([]);
+const switchLoadingMap = reactive<Record<string, boolean>>({});
+const syncCompanyOptions = ['张三', '李四', '王五', '赵六'];
+const syncDialog = reactive({
+  visible: false,
+  companies: ['张三', '李四'] as string[]
+});
+
+const queryParams = reactive<MainExamEvaluationQuery>({
+  pageNum: 1,
+  pageSize: 10,
+  evaluationName: '',
+  grade: '',
+  positionType: '',
+  status: ''
+});
+
+const normalizedQuery = computed(() => ({
+  ...queryParams,
+  beginTime: dateRange.value?.[0] || '',
+  endTime: dateRange.value?.[1] || ''
+}));
+
+const mapDictOptions = (list: DictDataVO[] = []): FilterOption[] =>
+  list.map((item) => ({
+    label: item.dictLabel,
+    value: item.dictValue
+  }));
+
+const mergeFieldOptions = (baseOptions: FilterOption[], rows: MainExamEvaluationVO[], field: 'grade' | 'positionType') => {
+  const optionMap = new Map(baseOptions.map((item) => [item.value, item]));
+  rows.forEach((item) => {
+    const value = item[field];
+    if (value && !optionMap.has(value)) {
+      optionMap.set(value, { label: value, value });
+    }
+  });
+  return Array.from(optionMap.values());
+};
+
+const loadFilterOptions = async () => {
+  const [gradeRes, positionTypeRes] = await Promise.allSettled([getDicts('main_position_level'), getDicts('main_position_type')]);
+
+  if (gradeRes.status === 'fulfilled') {
+    gradeOptions.value = mapDictOptions(gradeRes.value.data || []);
+  }
+  if (positionTypeRes.status === 'fulfilled') {
+    positionTypeOptions.value = mapDictOptions(positionTypeRes.value.data || []);
+  }
+};
+
+const getList = async () => {
+  loading.value = true;
+  try {
+    const res = await listEvaluation(normalizedQuery.value);
+    evaluationList.value = res.rows || [];
+    total.value = res.total || 0;
+    gradeOptions.value = mergeFieldOptions(gradeOptions.value, evaluationList.value, 'grade');
+    positionTypeOptions.value = mergeFieldOptions(positionTypeOptions.value, evaluationList.value, 'positionType');
+  } finally {
+    loading.value = false;
+  }
+};
+
+const handleQuery = () => {
+  queryParams.pageNum = 1;
+  getList();
+};
+
+const resetQuery = () => {
+  queryParams.evaluationName = '';
+  queryParams.grade = '';
+  queryParams.position = '';
+  queryParams.positionType = '';
+  queryParams.status = '';
+  dateRange.value = [];
+  queryParams.pageNum = 1;
+  queryParams.pageSize = 10;
+  getList();
+};
+
+const handleToggleStatus = async (row: MainExamEvaluationVO, value: boolean | string | number) => {
+  const rowKey = String(row.id);
+  try {
+    switchLoadingMap[rowKey] = true;
+    await updateEvaluationStatus(row.id, value ? '0' : '1');
+    modal?.msgSuccess('状态更新成功');
+    await getList();
+  } finally {
+    switchLoadingMap[rowKey] = false;
+  }
+};
+
+const handleViewApplyList = (row: MainExamEvaluationVO) => {
+  router.push({
+    path: '/evaluation/apply-list',
+    query: {
+      evaluationId: String(row.id),
+      evaluationName: row.evaluationName || '',
+      backPath: '/evaluation'
+    }
+  });
+};
+
+const handleExport = () => {
+  modal?.msgSuccess('导出数据功能待接入');
+};
+
+const handleOpenSyncDialog = () => {
+  syncDialog.visible = true;
+};
+
+const handleConfirmSync = () => {
+  syncDialog.visible = false;
+  modal?.msgSuccess('同步成功');
+};
+
+onMounted(() => {
+  loadFilterOptions().finally(() => {
+    getList();
+  });
+});
+</script>
+
+<style scoped lang="scss">
+.evaluation-page {
+  min-height: 100%;
+}
+
+.evaluation-card {
+  display: flex;
+  flex-direction: column;
+  min-height: calc(100vh - 180px);
+  padding: 16px;
+  border-radius: 8px;
+  background: #fff;
+  box-sizing: border-box;
+}
+
+.evaluation-toolbar {
+  display: flex;
+  align-items: center;
+  justify-content: flex-end;
+  gap: 12px;
+  margin-bottom: 16px;
+}
+
+.evaluation-toolbar-button {
+  color: #303133;
+  background: #fff;
+  border-color: #303133;
+}
+
+.evaluation-toolbar-button:hover,
+.evaluation-toolbar-button:focus {
+  color: #303133;
+  background: #fff;
+  border-color: #303133;
+}
+
+.sync-dialog-content {
+  padding: 10px 4px 24px;
+}
+
+.sync-dialog-row {
+  display: flex;
+  align-items: center;
+  gap: 16px;
+}
+
+.sync-dialog-label {
+  width: 56px;
+  color: #303133;
+  flex-shrink: 0;
+}
+
+.sync-dialog-select {
+  flex: 1;
+}
+
+.sync-dialog-footer {
+  display: flex;
+  justify-content: flex-end;
+  gap: 12px;
+}
+</style>

+ 71 - 0
src/views/evaluation/utils.ts

@@ -0,0 +1,71 @@
+import type { MainExamEvaluationVO } from '@/api/main/evaluation/types';
+
+const placeholderImage =
+  'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48"><rect width="48" height="48" rx="6" fill="%23f2f3f5"/><path d="M14 32l7-8 5 5 4-4 4 7H14z" fill="%23c0c4cc"/><circle cx="19" cy="18" r="3" fill="%23c0c4cc"/></svg>';
+
+export const getImageUrl = (row: MainExamEvaluationVO) => {
+  if (row.mainImageUrl) {
+    return row.mainImageUrl;
+  }
+  if (row.imageAlbum) {
+    const firstImage = row.imageAlbum
+      .split(',')
+      .map((item) => item.trim())
+      .find((item) => /^https?:\/\//.test(item));
+    if (firstImage) {
+      return firstImage;
+    }
+  }
+  return placeholderImage;
+};
+
+export const getStatusMeta = (status?: string) => {
+  if (status === '0') {
+    return { label: '启用', type: 'success' as const };
+  }
+  if (status === '1') {
+    return { label: '停用', type: 'info' as const };
+  }
+  return { label: status || '--', type: 'info' as const };
+};
+
+export const formatPrice = (value?: number | string) => {
+  if (value === undefined || value === null || value === '') {
+    return '--';
+  }
+  return `¥${value}`;
+};
+
+export const formatDateTime = (value?: string) => value || '--';
+
+const toDateValue = (value?: string) => {
+  if (!value) {
+    return undefined;
+  }
+  const time = new Date(value).getTime();
+  return Number.isNaN(time) ? undefined : time;
+};
+
+export const getSaleStatusMeta = (row: MainExamEvaluationVO) => {
+  if (row.delFlag === '1') {
+    return { label: '删除', type: 'danger' as const };
+  }
+
+  const now = Date.now();
+  const onTime = toDateValue(row.onTime);
+  const downTime = toDateValue(row.downTime);
+
+  if (onTime && now < onTime) {
+    return { label: '待上架', type: 'warning' as const };
+  }
+
+  if (downTime && now > downTime) {
+    return { label: '已下架', type: 'info' as const };
+  }
+
+  if ((!onTime || now >= onTime) && (!downTime || now <= downTime)) {
+    return { label: '在售', type: 'success' as const };
+  }
+
+  return { label: '--', type: 'info' as const };
+};

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

@@ -0,0 +1,335 @@
+<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>

+ 385 - 0
src/views/postManage/apply-list.vue

@@ -0,0 +1,385 @@
+<template>
+  <PageShell>
+    <div class="apply-page">
+      <div class="apply-card">
+        <div class="apply-header">
+          <div class="apply-title">{{ pageTitle }}</div>
+          <div class="apply-actions">
+            <el-button @click="handleBack">返回</el-button>
+          </div>
+        </div>
+
+        <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>
+              <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-form-item>
+          <el-form-item class="apply-search-select-item">
+            <el-select v-model="queryParams.auditStatus" placeholder="审核状态" clearable>
+              <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-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 type="primary">导出列表</el-button>
+          </div>
+        </el-form>
+
+        <el-table :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>
+            </template>
+          </el-table-column>
+          <el-table-column label="性别" prop="gender" width="100" align="center" />
+          <el-table-column label="手机号" prop="phone" min-width="180" align="center" />
+          <el-table-column label="状态" min-width="160" align="center">
+            <template #default="scope">
+              <span class="apply-status-text">
+                <span class="apply-status-dot" :class="`is-${scope.row.statusType}`"></span>
+                {{ scope.row.statusText }}
+              </span>
+            </template>
+          </el-table-column>
+          <el-table-column label="测评时间" prop="evaluateTime" min-width="180" align="center" />
+          <el-table-column label="操作" min-width="220" align="center">
+            <template #default="scope">
+              <template v-if="scope.row.statusText !== '学员已拒绝'">
+                <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>
+                </template>
+              </template>
+            </template>
+          </el-table-column>
+        </el-table>
+
+        <pagination v-show="total > 0" :page="queryParams.pageNum" :limit="queryParams.pageSize" :total="total" />
+      </div>
+
+      <el-dialog v-model="hireDialog.visible" title="审核说明" width="680px" append-to-body>
+        <div class="result-dialog-content">
+          <div class="result-line"><span class="result-label">结果:</span><span class="result-value">录用</span></div>
+          <div class="result-upload-row">
+            <el-button>上传文件</el-button>
+          </div>
+          <div class="result-file-list">
+            <div v-for="item in hireDialog.files" :key="item" class="result-file-item">{{ item }}</div>
+          </div>
+          <div class="result-remark-row">
+            <span class="result-label top">说明</span>
+            <el-input v-model="hireDialog.remark" type="textarea" :rows="5" placeholder="请输入" />
+          </div>
+          <el-checkbox v-model="hireDialog.agree">我已阅读并同意服务 offer 确认前免责协议</el-checkbox>
+        </div>
+        <template #footer>
+          <div class="dialog-footer">
+            <el-button @click="hireDialog.visible = false">返回</el-button>
+            <el-button type="primary" @click="hireDialog.visible = false">确定</el-button>
+          </div>
+        </template>
+      </el-dialog>
+
+      <el-dialog v-model="rejectDialog.visible" title="审核说明" width="520px" append-to-body>
+        <div class="result-dialog-content compact">
+          <div class="result-line"><span class="result-label">结果:</span><span class="result-value">不录用</span></div>
+          <div class="result-remark-row compact">
+            <span class="result-label top">备注:</span>
+            <el-input v-model="rejectDialog.remark" type="textarea" :rows="4" placeholder="说明" />
+          </div>
+        </div>
+        <template #footer>
+          <div class="dialog-footer">
+            <el-button @click="rejectDialog.visible = false">取消</el-button>
+            <el-button type="primary" @click="rejectDialog.visible = false">确定</el-button>
+          </div>
+        </template>
+      </el-dialog>
+    </div>
+  </PageShell>
+</template>
+
+<script setup name="PostManageApplyList" lang="ts">
+import { computed, getCurrentInstance, reactive, type ComponentInternalInstance } from 'vue';
+import { useRoute, useRouter } from 'vue-router';
+import PageShell from '@/components/PageShell/index.vue';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const router = useRouter();
+const route = useRoute();
+const pageTitle = computed(() => {
+  const evaluationName = route.query.evaluationName as string | undefined;
+  return evaluationName ? `${evaluationName} 报名列表` : '报名列表';
+});
+
+const queryParams = reactive({
+  pageNum: 1,
+  pageSize: 10,
+  keyword: '',
+  auditStatus: '',
+  ability: ''
+});
+
+const total = 243;
+
+const applyStatusOptions = [
+  { label: '录用', value: 'adopted' },
+  { label: '不采用', value: 'rejected' },
+  { label: '待回复', value: 'pending' },
+  { label: '学员已拒绝', value: 'closed' }
+];
+
+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 hireDialog = reactive({
+  visible: false,
+  remark: '',
+  agree: true,
+  files: ['文档.docx', '附件.png(上传...)', '组件123.jpg']
+});
+
+const rejectDialog = reactive({
+  visible: false,
+  remark: ''
+});
+
+const handleBack = () => {
+  proxy?.$tab.closePage();
+  router.push((route.query.backPath as string) || '/postManage');
+};
+
+const handleHire = () => {
+  hireDialog.visible = true;
+};
+
+const handleReject = () => {
+  rejectDialog.visible = true;
+};
+
+const handleOpenDetail = (row: { id: number; name: string }) => {
+  router.push({
+    path: '/postManage/apply-detail',
+    query: {
+      applyId: String(row.id),
+      name: row.name,
+      postId: String(route.query.postId || '')
+    }
+  });
+};
+</script>
+
+<style scoped>
+.apply-page {
+  min-height: 100%;
+}
+
+.apply-card {
+  min-height: calc(100vh - 180px);
+  padding: 12px 16px 16px;
+  background: #fff;
+  border-radius: 4px;
+  display: flex;
+  flex-direction: column;
+}
+
+.apply-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 16px;
+}
+
+.apply-title {
+  font-size: 16px;
+  font-weight: 600;
+  color: #303133;
+}
+
+.apply-search-form {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  flex-wrap: wrap;
+  margin-bottom: 16px;
+}
+
+.apply-search-name-item {
+  width: 280px;
+  margin-bottom: 0;
+}
+.apply-search-btn-item {
+  margin-left: -80px;
+}
+
+.apply-search-btn-item,
+.apply-search-select-item {
+  margin-bottom: 0;
+}
+
+.apply-search-select-item {
+  width: 140px;
+}
+
+.apply-toolbar-actions {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  margin-left: auto;
+}
+
+.apply-list-table :deep(.el-table__header th) {
+  background: #eef4ff;
+}
+
+.apply-list-table {
+  flex: 1;
+}
+
+.apply-status-text {
+  display: inline-flex;
+  align-items: center;
+  gap: 6px;
+}
+
+.apply-status-dot {
+  width: 6px;
+  height: 6px;
+  border-radius: 50%;
+  background: #c0c4cc;
+}
+
+.apply-status-dot.is-success {
+  background: #67c23a;
+}
+
+.apply-status-dot.is-danger {
+  background: #f56c6c;
+}
+
+.apply-status-dot.is-warning {
+  background: #e6a23c;
+}
+
+.apply-card :deep(.pagination-container) {
+  display: flex;
+  justify-content: flex-end;
+  margin-top: auto;
+  padding-top: 20px;
+  padding: 0;
+}
+
+.dialog-footer {
+  display: flex;
+  justify-content: center;
+  gap: 16px;
+}
+
+.result-dialog-content {
+  display: flex;
+  flex-direction: column;
+  gap: 14px;
+  padding: 8px 8px 0;
+}
+
+.result-dialog-content.compact {
+  gap: 16px;
+}
+
+.result-line {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.result-label {
+  width: 44px;
+  color: #303133;
+  flex-shrink: 0;
+}
+
+.result-label.top {
+  padding-top: 8px;
+}
+
+.result-value {
+  color: #303133;
+}
+
+.result-upload-row {
+  padding-left: 44px;
+}
+
+.result-file-list {
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+  padding-left: 44px;
+}
+
+.result-file-item {
+  color: #606266;
+  font-size: 13px;
+}
+
+.result-remark-row {
+  display: flex;
+  align-items: flex-start;
+  gap: 8px;
+}
+
+.result-remark-row.compact :deep(.el-textarea) {
+  flex: 1;
+}
+</style>

+ 770 - 0
src/views/postManage/create.vue

@@ -0,0 +1,770 @@
+<template>
+  <PageShell>
+    <div class="post-create-page">
+      <div class="post-create-card">
+        <div class="post-create-header">
+          <div>
+            <div class="post-create-title">{{ pageTitle }}</div>
+            <div class="post-create-subtitle">分三步完成岗位信息填写</div>
+          </div>
+          <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>
+
+        <div class="post-create-body">
+          <el-form ref="formRef" :model="form" :rules="rules" label-width="110px" class="post-create-form">
+            <div v-show="activeStep === 0" class="step-panel">
+              <el-form-item label="岗位名称" prop="postNamePath">
+                <el-cascader
+                  v-model="form.postNamePath"
+                  :options="postNameOptions"
+                  :props="postNameProps"
+                  clearable
+                  filterable
+                  placeholder="请选择岗位名称"
+                  class="full-width-select"
+                  @change="handlePostNameChange"
+                />
+              </el-form-item>
+              <el-form-item label="岗位编码" prop="postCode">
+                <el-input v-model="form.postCode" placeholder="请输入岗位编码" maxlength="64" />
+              </el-form-item>
+              <el-form-item label="地区" prop="regionCodes">
+                <el-cascader v-model="form.regionCodes" :options="regionOptions" :props="regionProps" clearable filterable placeholder="请选择地区" />
+              </el-form-item>
+              <el-form-item label="详细地址" prop="addressDetail">
+                <el-input v-model="form.addressDetail" placeholder="请输入详细地址" maxlength="200" />
+              </el-form-item>
+              <el-form-item label="岗位类型" prop="jobType">
+                <el-select v-model="form.jobType" placeholder="请选择岗位类型" clearable>
+                  <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>
+              <el-form-item label="岗位标签" prop="tags">
+                <el-select
+                  v-model="form.tags"
+                  multiple
+                  filterable
+                  collapse-tags
+                  collapse-tags-tooltip
+                  placeholder="请选择岗位标签"
+                  class="full-width-select"
+                >
+                  <el-option v-for="item in tagOptions" :key="item.id" :label="item.name" :value="item.name" />
+                </el-select>
+              </el-form-item>
+              <el-form-item label="招聘人数" prop="recruitCount">
+                <div class="inline-mixed-field">
+                  <el-input-number v-model="form.recruitCount" :min="1" :disabled="form.unlimitedRecruitCount" placeholder="请输入招聘人数" />
+                  <el-checkbox v-model="form.unlimitedRecruitCount" @change="handleRecruitUnlimitedChange">不限</el-checkbox>
+                </div>
+              </el-form-item>
+              <el-form-item label="报名时间" prop="applyTimeRange">
+                <div class="inline-mixed-field full-width-row">
+                  <el-date-picker
+                    v-model="form.applyTimeRange"
+                    type="daterange"
+                    is-range
+                    range-separator="至"
+                    start-placeholder="开始日期"
+                    end-placeholder="结束日期"
+                    format="YYYY-MM-DD"
+                    value-format="YYYY-MM-DD"
+                    :disabled="form.unlimitedApplyTime"
+                  />
+                  <el-checkbox v-model="form.unlimitedApplyTime" @change="handleApplyUnlimitedChange">不限</el-checkbox>
+                </div>
+              </el-form-item>
+              <el-form-item label="急招" prop="urgent">
+                <el-radio-group v-model="form.urgent">
+                  <el-radio :value="'0'">否</el-radio>
+                  <el-radio :value="'1'">是</el-radio>
+                </el-radio-group>
+              </el-form-item>
+              <el-form-item label="学校要求" prop="schoolRequirement">
+                <el-select v-model="form.schoolRequirement" placeholder="请选择学校要求" clearable>
+                  <el-option v-for="item in schoolRequirementOptions" :key="item" :label="item" :value="item" />
+                </el-select>
+              </el-form-item>
+              <el-form-item label="性别" prop="genderRequirement">
+                <el-select v-model="form.genderRequirement" placeholder="请选择性别" clearable>
+                  <el-option v-for="item in genderOptions" :key="item.value" :label="item.label" :value="item.value" />
+                </el-select>
+              </el-form-item>
+              <el-form-item label="年级" prop="gradeRequirement">
+                <el-select v-model="form.gradeRequirement" placeholder="请选择年级" clearable>
+                  <el-option v-for="item in gradeOptions" :key="item" :label="item" :value="item" />
+                </el-select>
+              </el-form-item>
+              <el-form-item label="到岗时间" prop="arrivalTime">
+                <el-select v-model="form.arrivalTime" placeholder="请选择到岗时间" clearable>
+                  <el-option v-for="item in arrivalTimeOptions" :key="item" :label="item" :value="item" />
+                </el-select>
+              </el-form-item>
+              <el-form-item label="实习时间" prop="internshipDuration">
+                <el-select v-model="form.internshipDuration" placeholder="请选择实习时间" clearable>
+                  <el-option v-for="item in internshipDurationOptions" :key="item" :label="item" :value="item" />
+                </el-select>
+              </el-form-item>
+              <el-form-item label="是否出差" prop="travelRequired">
+                <el-radio-group v-model="form.travelRequired">
+                  <el-radio :value="'0'">否</el-radio>
+                  <el-radio :value="'1'">是</el-radio>
+                </el-radio-group>
+              </el-form-item>
+              <el-form-item label="岗位描述" prop="jobDescription">
+                <el-input v-model="form.jobDescription" type="textarea" :rows="5" placeholder="请输入岗位描述" maxlength="500" show-word-limit />
+              </el-form-item>
+            </div>
+
+            <div v-show="activeStep === 1" class="step-panel">
+              <el-form-item label="岗位级别" prop="postCategory">
+                <el-select v-model="form.postCategory" placeholder="请选择岗位级别" clearable>
+                  <el-option v-for="item in postLevelOptions" :key="item.dictValue" :label="item.dictLabel" :value="item.dictValue" />
+                </el-select>
+              </el-form-item>
+              <el-form-item label="测评时长" prop="evaluationDuration">
+                <div class="inline-mixed-field input-unit-field">
+                  <el-input v-model="form.evaluationDuration" placeholder="请输入" maxlength="10" />
+                  <span class="field-unit">分钟</span>
+                </div>
+              </el-form-item>
+              <el-form-item label="及格线" prop="passingScores">
+                <div class="score-list">
+                  <div v-for="(item, index) in form.passingScores" :key="item.key" class="score-row">
+                    <span class="score-label">能力{{ index + 1 }}(总分100)</span>
+                    <div class="inline-mixed-field input-unit-field score-input-wrap">
+                      <el-input v-model="item.score" placeholder="请输入及格分" maxlength="10" />
+                      <span class="field-unit">分</span>
+                    </div>
+                  </div>
+                </div>
+              </el-form-item>
+            </div>
+            <div v-show="activeStep === 2" class="step-panel finish-panel">
+              <div class="finish-icon">✓</div>
+              <div class="finish-title">创建成功</div>
+              <div class="finish-subtitle">快去平台审核</div>
+            </div>
+
+            <div class="post-create-footer">
+              <template v-if="activeStep < 2">
+                <el-button @click="handleCancel">取消</el-button>
+                <el-button v-if="activeStep > 0" @click="activeStep -= 1">上一步</el-button>
+                <el-button v-if="activeStep === 0" type="primary" @click="handleNext">下一步</el-button>
+                <el-button v-if="activeStep === 1" type="primary" :loading="submitting" @click="handleSubmit">{{ submitButtonText }}</el-button>
+              </template>
+              <el-button v-else type="primary" @click="handleFinish">完成</el-button>
+            </div>
+          </el-form>
+        </div>
+      </div>
+    </div>
+  </PageShell>
+</template>
+
+<script setup name="PostManageCreate" lang="ts">
+import { computed, getCurrentInstance, onMounted, reactive, ref, toRefs, type ComponentInternalInstance } from 'vue';
+import { useRoute, useRouter } from 'vue-router';
+import { regionData, codeToText } from 'element-china-area-data';
+import type { CascaderOption } from 'element-plus';
+import PageShell from '@/components/PageShell/index.vue';
+import { addPostManage, getPostManage, updatePostManage } from '@/api/main/postManage';
+import type { MainPostApplyVO } from '@/api/main/postManage/types';
+import { getDicts } from '@/api/system/dict/data';
+import { listIndustry, listIndustrySkill } from '@/api/system/industry';
+import type { IndustrySkillVO, IndustryVO } from '@/api/system/industry/types';
+import { listTag } from '@/api/system/tag';
+import type { TagVO } from '@/api/system/tag/types';
+import type { DictDataVO } from '@/api/system/dict/data/types';
+import { useUserStore } from '@/store/modules/user';
+
+interface PostCreateFormModel {
+  id?: number | string;
+  auditId?: number | string;
+  postName: string;
+  postNamePath: Array<string | number>;
+  regionCodes: string[];
+  addressDetail: string;
+  jobType: string;
+  salary: string;
+  tags: string[];
+  recruitCount: number | undefined;
+  unlimitedRecruitCount: boolean;
+  applyTimeRange: string[];
+  unlimitedApplyTime: boolean;
+  urgent: string;
+  schoolRequirement: string;
+  genderRequirement: string;
+  gradeRequirement: string;
+  arrivalTime: string;
+  internshipDuration: string;
+  travelRequired: string;
+  jobDescription: string;
+  postCode: string;
+  postCategory: string;
+  evaluationDuration: string;
+  passingScores: Array<{ key: string; score: string }>;
+  postSort: number;
+  status: string;
+  remark: string;
+}
+
+const router = useRouter();
+const route = useRoute();
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const { sys_normal_disable } = toRefs<any>(proxy?.useDict('sys_normal_disable'));
+const userStore = useUserStore();
+
+const formRef = ref<ElFormInstance>();
+const activeStep = ref(0);
+const submitting = ref(false);
+const editId = computed(() => route.query.id as string | undefined);
+const isEdit = computed(() => Boolean(editId.value));
+const pageTitle = computed(() => (isEdit.value ? '编辑岗位' : '添加岗位'));
+const submitButtonText = computed(() => (isEdit.value ? '保存并提交审核' : '提交审核'));
+const regionOptions = regionData as unknown as CascaderOption[];
+const regionProps = { value: 'code', label: 'label', children: 'children' };
+const postNameOptions = ref<CascaderOption[]>([]);
+const postNameProps = { value: 'value', label: 'label', children: 'children', emitPath: true, checkStrictly: false };
+const jobTypeOptions = ref<DictDataVO[]>([]);
+const postLevelOptions = ref<DictDataVO[]>([]);
+const tagOptions = ref<TagVO[]>([]);
+const schoolRequirementOptions = ['不限', '大专', '本科', '硕士及以上', '双一流优先'];
+const genderOptions = [
+  { label: '不限', value: '0' },
+  { label: '男', value: '1' },
+  { label: '女', value: '2' }
+];
+const gradeOptions = ['不限', '大一', '大二', '大三', '大四', '研一', '研二', '研三'];
+const arrivalTimeOptions = ['立即到岗', '3天内', '一周内', '两周内', '一个月内'];
+const internshipDurationOptions = ['不限', '1个月', '3个月', '6个月', '长期'];
+
+const form = reactive<PostCreateFormModel>({
+  id: undefined,
+  auditId: undefined,
+  postName: '',
+  postNamePath: [],
+  regionCodes: [],
+  addressDetail: '',
+  jobType: '',
+  salary: '',
+  tags: [],
+  recruitCount: undefined,
+  unlimitedRecruitCount: false,
+  applyTimeRange: [],
+  unlimitedApplyTime: false,
+  urgent: '0',
+  schoolRequirement: '',
+  genderRequirement: '0',
+  gradeRequirement: '',
+  arrivalTime: '',
+  internshipDuration: '',
+  travelRequired: '0',
+  jobDescription: '',
+  postCode: '',
+  postCategory: '',
+  evaluationDuration: '',
+  passingScores: [
+    { key: 'ability-1', score: '' },
+    { key: 'ability-2', score: '' },
+    { key: 'ability-3', score: '' }
+  ],
+  postSort: 0,
+  status: '0',
+  remark: ''
+});
+
+const rules = reactive({
+  postNamePath: [{ required: true, message: '岗位名称不能为空', trigger: 'change' }],
+  regionCodes: [{ required: true, message: '地区不能为空', trigger: 'change' }],
+  addressDetail: [{ required: true, message: '详细地址不能为空', trigger: 'blur' }],
+  jobType: [{ required: true, message: '岗位类型不能为空', trigger: 'change' }],
+  salary: [{ required: true, message: '薪资不能为空', trigger: 'blur' }],
+  postCode: [{ required: true, message: '岗位编码不能为空', trigger: 'blur' }],
+  postCategory: [{ required: true, message: '岗位级别不能为空', trigger: 'change' }],
+  evaluationDuration: [{ required: true, message: '测评时长不能为空', trigger: 'blur' }],
+  postSort: [{ required: true, message: '岗位顺序不能为空', trigger: 'blur' }]
+});
+
+const regionText = computed(() =>
+  form.regionCodes
+    .map((code) => codeToText[code] || '')
+    .filter(Boolean)
+    .join(' / ')
+);
+const applyTimeText = computed(() => (form.applyTimeRange.length === 2 ? `${form.applyTimeRange[0]} 至 ${form.applyTimeRange[1]}` : '--'));
+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 buildPostNameOptions = (industryList: IndustryVO[], skillList: IndustrySkillVO[]) => {
+  const rootIndustryMap = new Map<string, CascaderOption>();
+  const secondIndustryMap = new Map<string, CascaderOption>();
+
+  industryList.forEach((item) => {
+    const node: CascaderOption = {
+      value: item.industryId,
+      label: item.industryName,
+      children: []
+    };
+    if (Number(item.parentId) === 0) {
+      rootIndustryMap.set(String(item.industryId), node);
+      return;
+    }
+    secondIndustryMap.set(String(item.industryId), node);
+  });
+
+  industryList.forEach((item) => {
+    if (Number(item.parentId) === 0) {
+      return;
+    }
+    const parentNode = rootIndustryMap.get(String(item.parentId));
+    const currentNode = secondIndustryMap.get(String(item.industryId));
+    if (parentNode && currentNode) {
+      (parentNode.children ||= []).push(currentNode);
+    }
+  });
+
+  skillList.forEach((item) => {
+    const parentNode = secondIndustryMap.get(String(item.industryId));
+    if (!parentNode) {
+      return;
+    }
+    (parentNode.children ||= []).push({
+      value: item.skillId,
+      label: item.skillName
+    });
+  });
+
+  postNameOptions.value = Array.from(rootIndustryMap.values());
+};
+
+const findPostNameLabel = (options: CascaderOption[], values: Array<string | number>, depth = 0): string => {
+  const currentValue = values[depth];
+  const currentNode = options.find((item) => item.value === currentValue);
+  if (!currentNode) {
+    return '';
+  }
+  if (depth === values.length - 1 || !currentNode.children?.length) {
+    return String(currentNode.label || '');
+  }
+  return findPostNameLabel(currentNode.children as CascaderOption[], values, depth + 1);
+};
+
+const handlePostNameChange = (value: Array<string | number>) => {
+  form.postName = value.length ? findPostNameLabel(postNameOptions.value, value) : '';
+};
+
+const findPostNamePathByLabel = (options: CascaderOption[], label: string, path: Array<string | number> = []): Array<string | number> => {
+  for (const option of options) {
+    const nextPath = [...path, option.value as string | number];
+    if (String(option.label || '') === label && (!option.children || option.children.length === 0)) {
+      return nextPath;
+    }
+    if (option.children?.length) {
+      const childPath = findPostNamePathByLabel(option.children as CascaderOption[], label, nextPath);
+      if (childPath.length) {
+        return childPath;
+      }
+    }
+  }
+  return [];
+};
+
+const findRegionPathByLabels = (province?: string, city?: string, district?: string): string[] => {
+  if (!province) {
+    return [];
+  }
+
+  const provinceOption = regionOptions.find((item) => String(item.label || '') === province);
+  if (!provinceOption) {
+    return [];
+  }
+
+  const provinceCode = String((provinceOption as any).code || provinceOption.value || '');
+  if (!city) {
+    return provinceCode ? [provinceCode] : [];
+  }
+
+  const cityOptions = (provinceOption.children || []) as CascaderOption[];
+  const cityOption = cityOptions.find((item) => String(item.label || '') === city);
+  if (!cityOption) {
+    return provinceCode ? [provinceCode] : [];
+  }
+
+  const cityCode = String((cityOption as any).code || cityOption.value || '');
+  if (!district) {
+    return [provinceCode, cityCode].filter(Boolean);
+  }
+
+  const districtOptions = (cityOption.children || []) as CascaderOption[];
+  const districtOption = districtOptions.find((item) => String(item.label || '') === district);
+  if (!districtOption) {
+    return [provinceCode, cityCode].filter(Boolean);
+  }
+
+  const districtCode = String((districtOption as any).code || districtOption.value || '');
+  return [provinceCode, cityCode, districtCode].filter(Boolean);
+};
+
+const loadPostNameOptions = async () => {
+  const [industryRes, skillRes] = await Promise.all([listIndustry({ status: '0' }), listIndustrySkill({ status: '0' })]);
+  buildPostNameOptions(industryRes.data || [], skillRes.data || []);
+};
+
+const loadJobTypeOptions = async () => {
+  const res = await getDicts('main_position_type');
+  jobTypeOptions.value = res.data || [];
+};
+
+const loadPostLevelOptions = async () => {
+  const res = await getDicts('main_position_level');
+  postLevelOptions.value = res.data || [];
+};
+
+const loadTagOptions = async () => {
+  const res = await listTag({
+    pageNum: 1,
+    pageSize: 1000
+  });
+  tagOptions.value = res.rows || [];
+};
+
+const fillForm = (data: MainPostApplyVO) => {
+  form.id = data.id;
+  form.auditId = data.auditId;
+  form.postName = data.postName || '';
+  form.postNamePath = data.postName ? findPostNamePathByLabel(postNameOptions.value, data.postName) : [];
+  form.postCode = data.applyNo || '';
+  form.regionCodes = [];
+  form.addressDetail = data.workAddress || '';
+  form.jobType = data.postType || '';
+  form.salary = data.salaryRange || '';
+  form.tags = data.welfareTags ? data.welfareTags.split(',').filter(Boolean) : [];
+  form.recruitCount = data.recruitNum && data.recruitNum !== 99999 ? data.recruitNum : undefined;
+  form.unlimitedRecruitCount = data.recruitNum === 99999;
+  form.applyTimeRange = data.registrationStartDate && data.registrationEndDate ? [data.registrationStartDate, data.registrationEndDate] : [];
+  form.unlimitedApplyTime = !(data.registrationStartDate && data.registrationEndDate);
+  form.urgent = String(data.isUrgent ?? 0);
+  form.schoolRequirement = data.schoolRequirement || '';
+  form.genderRequirement = data.genderRequirement || '0';
+  form.gradeRequirement = data.gradeRequirement || '';
+  form.arrivalTime = data.arrivalTime || '';
+  form.internshipDuration = data.internshipDuration || '';
+  form.travelRequired = String(data.willingToTravel ?? 0);
+  form.jobDescription = data.jobRequirement || data.postDescription || '';
+  form.postCategory = data.postLevel || '';
+  form.evaluationDuration = data.assessmentTime || '';
+  form.status = data.status || '0';
+  form.passingScores = [
+    { key: 'ability-1', score: data.gradeA != null ? String(data.gradeA) : '' },
+    { key: 'ability-2', score: data.gradeB != null ? String(data.gradeB) : '' },
+    { key: 'ability-3', score: data.gradeC != null ? String(data.gradeC) : '' }
+  ];
+  form.regionCodes = findRegionPathByLabels(data.workProvince, data.workCity, data.workDistrict);
+};
+
+const loadDetail = async () => {
+  if (!editId.value) {
+    return;
+  }
+  const res = await getPostManage(editId.value);
+  fillForm(res.data);
+};
+
+// TODO: 待实现部门相关接口
+// const getTreeSelect = async () => {
+//   const res = await deptTreeSelect();
+//   deptOptions.value = res.data;
+// };
+
+const handleRecruitUnlimitedChange = (value: boolean) => {
+  if (value) form.recruitCount = undefined;
+};
+
+const handleApplyUnlimitedChange = (value: boolean) => {
+  if (value) form.applyTimeRange = [];
+};
+
+const validateStep = async () => {
+  if (activeStep.value === 0) {
+    await formRef.value?.validateField(['postNamePath', 'postCode', 'regionCodes', 'addressDetail', 'jobType', 'salary']);
+    if (!form.unlimitedRecruitCount && !form.recruitCount) throw new Error('招聘人数不能为空');
+    if (!form.unlimitedApplyTime && form.applyTimeRange.length !== 2) throw new Error('报名时间不能为空');
+  }
+  if (activeStep.value === 1) {
+    await formRef.value?.validateField(['postCategory', 'evaluationDuration']);
+    if (form.passingScores.some((item) => !item.score)) throw new Error('及格线不能为空');
+  }
+};
+
+const handleNext = async () => {
+  try {
+    await validateStep();
+    activeStep.value += 1;
+  } catch (error: any) {
+    if (error?.message) proxy?.$modal.msgError(error.message);
+  }
+};
+
+const buildApplyNo = () =>
+  `PA${Date.now()}${Math.floor(Math.random() * 1000)
+    .toString()
+    .padStart(3, '0')}`;
+
+const buildPayload = () => ({
+  id: form.id,
+  applyNo: buildApplyNo(),
+  tenantId: userStore.tenantId,
+  postName: form.postName,
+  companyName: '',
+  postDescription: form.jobDescription,
+  workProvince: form.regionCodes[0] ? codeToText[form.regionCodes[0]] || '' : '',
+  workCity: form.regionCodes[1] ? codeToText[form.regionCodes[1]] || '' : '',
+  workDistrict: form.regionCodes[2] ? codeToText[form.regionCodes[2]] || '' : '',
+  workAddress: form.addressDetail,
+  postType: form.jobType,
+  salaryType: '',
+  salaryRange: form.salary,
+  recruitNum: form.unlimitedRecruitCount ? 99999 : form.recruitCount,
+  registrationStartDate: form.unlimitedApplyTime ? undefined : form.applyTimeRange[0],
+  registrationEndDate: form.unlimitedApplyTime ? undefined : form.applyTimeRange[1],
+  isUrgent: Number(form.urgent),
+  schoolRequirement: form.schoolRequirement,
+  genderRequirement: form.genderRequirement,
+  gradeRequirement: form.gradeRequirement,
+  arrivalTime: form.arrivalTime,
+  internshipDuration: form.internshipDuration,
+  willingToTravel: Number(form.travelRequired),
+  welfareTags: form.tags.join(','),
+  jobRequirement: form.jobDescription,
+  postLevel: form.postCategory,
+  assessmentTime: form.evaluationDuration,
+  gradeA: form.passingScores[0]?.score ? Number(form.passingScores[0].score) : undefined,
+  gradeB: form.passingScores[1]?.score ? Number(form.passingScores[1].score) : undefined,
+  gradeC: form.passingScores[2]?.score ? Number(form.passingScores[2].score) : undefined,
+  applyStatus: 0,
+  status: form.status
+});
+
+const handleSubmit = async () => {
+  try {
+    submitting.value = true;
+    await validateStep();
+    const payload = buildPayload();
+    if (isEdit.value) {
+      payload.applyNo = form.postCode;
+      await updatePostManage(payload);
+    } else {
+      await addPostManage(payload);
+    }
+    activeStep.value = 2;
+  } finally {
+    submitting.value = false;
+  }
+};
+
+const handleFinish = () => {
+  proxy?.$tab.closeOpenPage({ path: '/postManage', query: { refresh: 'true' } });
+};
+
+const handleCancel = () => {
+  proxy?.$tab.closePage();
+  router.push('/postManage');
+};
+
+onMounted(async () => {
+  await Promise.all([loadPostNameOptions(), loadJobTypeOptions(), loadPostLevelOptions(), loadTagOptions()]);
+  await loadDetail();
+});
+</script>
+
+<style scoped>
+.post-create-page {
+  min-height: 100%;
+  display: flex;
+  justify-content: center;
+}
+
+.post-create-card {
+  height: calc(100vh - 150px);
+  width: 960px;
+  max-width: calc(100vw - 48px);
+  padding: 20px 24px;
+  background: #fff;
+  border-radius: 6px;
+  display: flex;
+  flex-direction: column;
+}
+
+.post-create-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 16px;
+  margin-bottom: 20px;
+}
+
+.post-create-title {
+  font-size: 20px;
+  font-weight: 600;
+  color: #303133;
+}
+
+.post-create-subtitle {
+  margin-top: 6px;
+  font-size: 13px;
+  color: #909399;
+}
+
+.post-steps {
+  background: white;
+  margin-bottom: 24px;
+  max-width: 760px;
+  margin-left: auto;
+  margin-right: auto;
+}
+
+.post-steps :deep(.el-step) {
+  min-width: 0;
+}
+
+.post-steps :deep(.el-step__head) {
+  flex-shrink: 0;
+}
+
+.post-steps :deep(.el-step__title) {
+  white-space: nowrap;
+  font-size: 14px;
+  padding-right: 24px;
+}
+
+.post-steps :deep(.el-step.is-simple .el-step__title) {
+  margin-right: 20px;
+}
+
+.post-steps :deep(.el-step.is-simple:not(:last-child) .el-step__arrow) {
+  display: none;
+}
+
+.post-create-form {
+  width: 760px;
+  max-width: 100%;
+  margin: 0 auto;
+}
+
+.post-create-body {
+  flex: 1;
+  min-height: 0;
+  overflow-y: auto;
+  overflow-x: hidden;
+  padding-right: 8px;
+}
+
+.step-panel {
+  padding: 8px 0;
+}
+
+.post-create-form :deep(.el-input),
+.post-create-form :deep(.el-select),
+.post-create-form :deep(.el-tree-select),
+.post-create-form :deep(.el-cascader),
+.post-create-form :deep(.el-time-editor.el-input),
+.post-create-form :deep(.el-time-editor.el-input__wrapper),
+.full-width-select {
+  width: 100%;
+}
+
+.inline-mixed-field {
+  display: flex;
+  align-items: center;
+  gap: 16px;
+}
+
+.input-unit-field {
+  width: 100%;
+  flex-wrap: nowrap;
+}
+
+.field-unit {
+  white-space: nowrap;
+  flex-shrink: 0;
+}
+
+.score-list {
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+  width: 100%;
+}
+
+.score-row {
+  display: flex;
+  align-items: center;
+  gap: 16px;
+}
+
+.score-label {
+  width: 120px;
+  line-height: 32px;
+  flex-shrink: 0;
+}
+
+.score-input-wrap {
+  flex: 1;
+}
+
+.full-width-row :deep(.el-date-editor) {
+  width: 100%;
+}
+
+.finish-panel {
+  min-height: 300px;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  gap: 12px;
+}
+
+.finish-icon {
+  width: 48px;
+  height: 48px;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: #e8f8ec;
+  color: #35c759;
+  font-size: 28px;
+  font-weight: 700;
+}
+
+.finish-title {
+  font-size: 16px;
+  font-weight: 600;
+  color: #303133;
+}
+
+.finish-subtitle {
+  font-size: 13px;
+  color: #606266;
+}
+
+.post-create-footer {
+  display: flex;
+  justify-content: center;
+  gap: 12px;
+  margin-top: 24px;
+  width: 100%;
+}
+</style>

+ 221 - 0
src/views/postManage/evaluation-view.vue

@@ -0,0 +1,221 @@
+<template>
+  <PageShell>
+    <div class="evaluation-view-page">
+      <div class="left-card">
+        <div class="card-title">考生信息</div>
+        <div class="info-list">
+          <div class="info-item"><span>姓名:</span><span>张三</span></div>
+          <div class="info-item"><span>报名岗位:</span><span>审计</span></div>
+          <div class="info-item"><span>开始时间:</span><span>2024-06-13 09:49:47</span></div>
+          <div class="info-item"><span>结束时间:</span><span>2024-06-13 09:50:26</span></div>
+          <div class="info-item"><span>答题时长:</span><span>0分39秒</span></div>
+          <div class="info-item"><span>浏览器:</span><span>unknown Browser</span></div>
+          <div class="info-item"><span>终端设备:</span><span>iPhone iOS 17.5.1</span></div>
+          <div class="info-item"><span>用户IP:</span><span>59.46.39.185</span></div>
+        </div>
+
+        <div class="card-title second">答题信息</div>
+        <div class="answer-list">
+          <div class="answer-item"><span>能力测试:10/30</span><el-tag type="success">通过</el-tag></div>
+          <div class="answer-item"><span>实操能力:20/40</span><el-tag type="success">通过</el-tag></div>
+          <div class="answer-item"><span>性格:12/34</span><el-tag type="danger">未通过</el-tag></div>
+        </div>
+
+        <div class="card-title second">维度分析</div>
+        <div class="chart-placeholder">维度图</div>
+        <div class="footer-btn">
+          <el-button @click="handleBack">返回</el-button>
+          <el-button type="primary">下一份</el-button>
+        </div>
+      </div>
+
+      <div class="right-card">
+        <div v-for="item in questionList" :key="item.id" class="question-block">
+          <div class="question-head">
+            <span>第{{ item.id }}题</span>
+            <div class="question-result">
+              <el-tag :type="item.correct ? 'success' : 'danger'">{{ item.correct ? '正确' : '错误' }}</el-tag>
+              <span>得分</span>
+              <el-input :model-value="String(item.score)" class="score-input" readonly />
+            </div>
+          </div>
+          <div class="question-title">请选择一个选项</div>
+          <div class="question-options">
+            <div v-for="option in item.options" :key="option.label" class="question-option">
+              <el-radio :model-value="item.answer" :label="option.label">{{ option.label }}.{{ option.text }}</el-radio>
+              <span v-if="option.correct" class="correct-text">正确答案</span>
+            </div>
+          </div>
+          <div v-if="item.analysis" class="question-analysis">
+            <div>答案解析:</div>
+            <div>{{ item.analysis }}</div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </PageShell>
+</template>
+
+<script setup name="PostManageEvaluationView" lang="ts">
+import { getCurrentInstance, type ComponentInternalInstance } from 'vue';
+import { useRouter } from 'vue-router';
+import PageShell from '@/components/PageShell/index.vue';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const router = useRouter();
+
+const questionList = [
+  {
+    id: 1,
+    correct: true,
+    score: 5,
+    answer: 'A',
+    options: [
+      { label: 'A', text: '选项1', correct: true },
+      { label: 'B', text: '选项2', correct: false },
+      { label: 'C', text: '选项3', correct: false },
+      { label: 'D', text: '选项4', correct: false }
+    ],
+    analysis: ''
+  },
+  {
+    id: 2,
+    correct: true,
+    score: 5,
+    answer: 'A',
+    options: [
+      { label: 'A', text: '选项1', correct: true },
+      { label: 'B', text: '选项2', correct: false },
+      { label: 'C', text: '选项3', correct: false },
+      { label: 'D', text: '选项4', correct: false }
+    ],
+    analysis: ''
+  },
+  {
+    id: 3,
+    correct: false,
+    score: 0,
+    answer: 'C',
+    options: [
+      { label: 'A', text: '选项1', correct: true },
+      { label: 'B', text: '选项2', correct: false },
+      { label: 'C', text: '选项3', correct: false },
+      { label: 'D', text: '选项4', correct: false }
+    ],
+    analysis: '解析解析解析解析解析解析解析解析'
+  }
+];
+
+const handleBack = () => {
+  proxy?.$tab.closePage();
+  router.back();
+};
+</script>
+
+<style scoped>
+.evaluation-view-page {
+  display: grid;
+  grid-template-columns: 180px 1fr;
+  gap: 16px;
+}
+
+.left-card,
+.right-card {
+  background: #fff;
+  border-radius: 6px;
+  padding: 16px;
+}
+
+.card-title {
+  font-size: 16px;
+  font-weight: 600;
+  margin-bottom: 12px;
+}
+
+.card-title.second {
+  margin-top: 20px;
+}
+
+.info-list,
+.answer-list {
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+  font-size: 12px;
+  color: #606266;
+}
+
+.info-item,
+.answer-item {
+  display: flex;
+  justify-content: space-between;
+  gap: 8px;
+}
+
+.chart-placeholder {
+  height: 180px;
+  border: 1px dashed #dcdfe6;
+  border-radius: 8px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: #909399;
+}
+
+.footer-btn {
+  margin-top: 16px;
+  display: flex;
+  justify-content: flex-end;
+  gap: 12px;
+}
+
+.question-block {
+  padding-bottom: 18px;
+  margin-bottom: 18px;
+  border-bottom: 1px dashed #ebeef5;
+}
+
+.question-head {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 16px;
+}
+
+.question-result {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.score-input {
+  width: 56px;
+}
+
+.question-title {
+  margin-bottom: 12px;
+}
+
+.question-options {
+  display: flex;
+  flex-direction: column;
+  gap: 10px;
+}
+
+.question-option {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.correct-text {
+  color: #67c23a;
+  font-size: 12px;
+}
+
+.question-analysis {
+  margin-top: 16px;
+  color: #606266;
+  font-size: 13px;
+}
+</style>

+ 566 - 0
src/views/postManage/index.vue

@@ -0,0 +1,566 @@
+<template>
+  <PageShell>
+    <div class="post-page">
+      <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
+        <div v-show="showSearch" class="search-panel">
+          <el-form ref="queryFormRef" :model="queryParams" :inline="true" class="search-form">
+            <el-form-item prop="postName" class="search-form-item search-keyword-item">
+              <el-input v-model="queryParams.postName" placeholder="请输入岗位" clearable @keyup.enter="handleQuery">
+                <template #prefix>
+                  <el-icon><Search /></el-icon>
+                </template>
+              </el-input>
+            </el-form-item>
+            <el-form-item class="search-form-item search-action-item">
+              <el-button type="primary" icon="Search" @click="handleQuery" />
+            </el-form-item>
+            <el-form-item prop="applyStatus" class="search-form-item search-select-item">
+              <el-select v-model="queryParams.applyStatus" placeholder="审核状态" clearable>
+                <el-option v-for="item in auditStatusOptions" :key="item.value" :label="item.label" :value="item.value" />
+              </el-select>
+            </el-form-item>
+            <el-form-item prop="postType" class="search-form-item search-select-item">
+              <el-select v-model="queryParams.postType" placeholder="岗位类型" clearable>
+                <el-option v-for="item in postTypeOptions" :key="item" :label="item" :value="item" />
+              </el-select>
+            </el-form-item>
+            <el-form-item prop="postLevel" class="search-form-item search-select-item">
+              <el-select v-model="queryParams.postLevel" placeholder="岗位级别" clearable>
+                <el-option v-for="item in postCategoryOptions" :key="item" :label="item" :value="item" />
+              </el-select>
+            </el-form-item>
+          </el-form>
+        </div>
+      </transition>
+
+      <div class="post-card">
+        <div class="table-toolbar">
+          <div class="page-title">岗位列表</div>
+          <div class="toolbar-actions">
+            <el-button v-hasPermi="['system:post:add']" type="primary" icon="Plus" @click="handleAdd">添加</el-button>
+            <el-button v-hasPermi="['system:post:export']" icon="Download" @click="handleImportOpen">批量导入</el-button>
+            <el-button v-hasPermi="['system:post:remove']" :disabled="multiple" icon="Delete" @click="handleDelete()">批量删除</el-button>
+          </div>
+        </div>
+
+        <el-table v-loading="loading" border :data="postList" class="post-table" @selection-change="handleSelectionChange">
+          <el-table-column type="selection" width="55" align="center" />
+          <el-table-column label="岗位名称" align="center" prop="postName" min-width="140" />
+          <el-table-column label="岗位类型" align="center" prop="postType" min-width="120">
+            <template #default="scope">
+              <span>{{ scope.row.postType || '--' }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column label="岗位级别" align="center" prop="postLevel" min-width="120">
+            <template #default="scope">
+              <span>{{ scope.row.postLevel || '--' }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column label="审核状态" align="center" min-width="180">
+            <template #default="scope">
+              <div class="audit-status-cell">
+                <el-tag
+                  v-if="getAuditStatusMeta(getAuditStatusText(scope.row.applyStatus))"
+                  :type="getAuditStatusMeta(getAuditStatusText(scope.row.applyStatus))?.type"
+                  effect="dark"
+                >
+                  {{ getAuditStatusMeta(getAuditStatusText(scope.row.applyStatus))?.label }}
+                </el-tag>
+                <span v-else class="audit-status-empty"></span>
+                <el-button
+                  v-if="showAuditRemark(getAuditStatusText(scope.row.applyStatus))"
+                  link
+                  type="primary"
+                  class="audit-remark-button"
+                  @click="handleOpenAuditRemark(scope.row)"
+                >
+                  说明
+                </el-button>
+              </div>
+            </template>
+          </el-table-column>
+          <el-table-column label="岗位人数" align="center" min-width="100">
+            <template #default="scope">
+              <span>{{ scope.row.recruitNum ?? '--' }}</span>
+            </template>
+          </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>
+            </template>
+          </el-table-column>
+          <el-table-column label="发布状态" align="center" min-width="100">
+            <template #default="scope">
+              <el-switch
+                :model-value="scope.row.status === '0'"
+                inline-prompt
+                active-text="开"
+                inactive-text="关"
+                :disabled="scope.row.applyStatus !== 2 || publishLoadingMap[String(scope.row.id)]"
+                @change="handlePublishChange(scope.row, $event)"
+              />
+            </template>
+          </el-table-column>
+          <el-table-column label="添加时间" align="center" prop="createTime" min-width="180">
+            <template #default="scope">
+              <span>{{ proxy?.parseTime(scope.row.createTime) || '--' }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column label="操作" fixed="right" width="180" align="center">
+            <template #default="scope">
+              <el-button v-if="scope.row.applyStatus !== 0" link type="primary" @click="handleEdit(scope.row)">编辑</el-button>
+              <el-button v-hasPermi="['system:post:remove']" link type="primary" @click="handleDelete(scope.row)">删除</el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+
+        <pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
+      </div>
+
+      <el-dialog v-model="auditRemarkDialog.visible" title="审核状态说明" width="520px" append-to-body>
+        <div class="audit-remark-content">
+          <div class="audit-remark-item">
+            <span class="audit-remark-label">审核状态</span>
+            <span class="audit-remark-value">{{ auditRemarkDialog.data.auditStatus || '--' }}</span>
+          </div>
+          <div class="audit-remark-item align-start">
+            <span class="audit-remark-label">说明</span>
+            <div class="audit-remark-value remark-text">{{ auditRemarkDialog.data.auditRemark || '暂无说明' }}</div>
+          </div>
+        </div>
+      </el-dialog>
+
+      <el-dialog v-model="importDialog.visible" :title="importDialog.title" width="420px" append-to-body>
+        <div class="import-notice">
+          <span class="import-notice-text">首次导入先下载导入模板。</span>
+          <el-link type="primary" :underline="false" class="import-notice-link" @click="handleDownloadImportTemplate">立即下载</el-link>
+        </div>
+        <el-upload :limit="1" accept=".xlsx,.xls" :auto-upload="false" drag>
+          <el-icon class="el-icon--upload">
+            <i-ep-upload-filled />
+          </el-icon>
+          <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
+          <template #tip>
+            <div class="el-upload__tip import-upload-tip">
+              <span>仅允许导入 xls、xlsx 格式文件。</span>
+            </div>
+          </template>
+        </el-upload>
+        <template #footer>
+          <div class="dialog-footer import-dialog-footer">
+            <el-link type="primary" :underline="false" class="import-template-link" @click="handleDownloadImportTemplate">模板下载</el-link>
+            <div class="import-dialog-actions">
+              <el-button type="primary" @click="submitImportForm">确 定</el-button>
+              <el-button @click="importDialog.visible = false">取 消</el-button>
+            </div>
+          </div>
+        </template>
+      </el-dialog>
+    </div>
+  </PageShell>
+</template>
+
+<script setup name="Post" lang="ts">
+import { getCurrentInstance, onMounted, reactive, ref, type ComponentInternalInstance } from 'vue';
+import { useRouter } from 'vue-router';
+import { getMainAudit, listMainAudit } from '@/api/main/audit';
+import PageShell from '@/components/PageShell/index.vue';
+import { delPostManage, listPostManage, publishPostManage, unpublishPostManage } from '@/api/main/postManage';
+import { MainPostApplyQuery, MainPostApplyVO } from '@/api/main/postManage/types';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const router = useRouter();
+const modal = proxy?.$modal as any;
+
+type AuditTagType = 'primary' | 'warning' | 'danger';
+type PostAuditRemarkVO = {
+  auditStatus?: string;
+  auditRemark?: string;
+};
+
+const postList = ref<MainPostApplyVO[]>([]);
+const loading = ref(true);
+const showSearch = ref(true);
+const ids = ref<Array<number | string>>([]);
+const multiple = ref(true);
+const total = ref(0);
+const publishLoadingMap = reactive<Record<string, boolean>>({});
+const postCategoryOptions = ref<string[]>([]);
+const postTypeOptions = ref<string[]>([]);
+const queryFormRef = ref<ElFormInstance>();
+const auditStatusOptions = [
+  { label: '待审核', value: 0 },
+  { label: '审核中', value: 1 },
+  { label: '审核通过', value: 2 },
+  { label: '驳回', value: 3 }
+];
+
+const importDialog = reactive<DialogOption>({
+  visible: false,
+  title: '批量导入'
+});
+
+const auditRemarkDialog = reactive<{ visible: boolean; data: PostAuditRemarkVO }>({
+  visible: false,
+  data: {
+    auditStatus: '',
+    auditRemark: ''
+  }
+});
+
+const auditStatusMap: Record<string, { label: string; type: AuditTagType }> = {
+  pending: { label: '待审核', type: 'warning' },
+  approved: { label: '审核通过', type: 'primary' },
+  processing: { label: '审核中', type: 'warning' },
+  rejected: { label: '驳回', type: 'danger' },
+  '待审核': { label: '待审核', type: 'warning' },
+  '审核通过': { label: '审核通过', type: 'primary' },
+  '审核中': { label: '审核中', type: 'warning' },
+  '驳回': { label: '驳回', type: 'danger' }
+};
+
+const queryParams = reactive<MainPostApplyQuery>({
+  pageNum: 1,
+  pageSize: 10,
+  applyStatus: undefined,
+  postName: '',
+  postType: '',
+  postLevel: ''
+});
+
+const normalizeAuditStatus = (status?: string) => (status || '').trim();
+
+const getAuditStatusMeta = (status?: string) => {
+  const normalizedStatus = normalizeAuditStatus(status);
+  return normalizedStatus ? auditStatusMap[normalizedStatus] : undefined;
+};
+
+const getAuditStatusText = (status?: number) => {
+  switch (status) {
+    case 2:
+      return '审核通过';
+    case 3:
+      return '驳回';
+    case 1:
+      return '审核中';
+    case 0:
+    default:
+      return '待审核';
+  }
+};
+
+const showAuditRemark = (status?: string) => {
+  const normalizedStatus = normalizeAuditStatus(status);
+  return normalizedStatus === '审核通过' || normalizedStatus === 'approved' || normalizedStatus === '驳回' || normalizedStatus === 'rejected';
+};
+
+/** 查询岗位列表 */
+const getList = async () => {
+  loading.value = true;
+  try {
+    const res = await listPostManage(queryParams);
+    postList.value = res.rows;
+    total.value = res.total;
+    postTypeOptions.value = Array.from(new Set(res.rows.map((item) => item.postType).filter(Boolean) as string[]));
+    postCategoryOptions.value = Array.from(new Set(res.rows.map((item) => item.postLevel).filter(Boolean) as string[]));
+  } finally {
+    loading.value = false;
+  }
+};
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNum = 1;
+  getList();
+};
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields?.();
+  queryParams.pageNum = 1;
+  queryParams.applyStatus = undefined;
+  queryParams.postName = '';
+  queryParams.postType = '';
+  queryParams.postLevel = '';
+  handleQuery();
+};
+
+/** 多选框选中数据 */
+const handleSelectionChange = (selection: MainPostApplyVO[]) => {
+  ids.value = selection.map((item) => item.id);
+  multiple.value = !selection.length;
+};
+
+/** 新增按钮操作 */
+const handleAdd = () => {
+  router.push('/postManage/create');
+};
+
+const handleEdit = (row: MainPostApplyVO) => {
+  router.push({
+    path: '/postManage/create',
+    query: {
+      id: String(row.id)
+    }
+  });
+};
+
+const handleOpenApplyList = (row: MainPostApplyVO) => {
+  router.push({
+    path: '/postManage/apply-list',
+    query: {
+      postId: String(row.postId ?? row.id ?? ''),
+      applyId: String(row.id ?? ''),
+      postName: row.postName || ''
+    }
+  });
+};
+
+const handlePublishChange = async (row: MainPostApplyVO, value: boolean | string | number) => {
+  const rowKey = String(row.id);
+  try {
+    publishLoadingMap[rowKey] = true;
+    if (Boolean(value)) {
+      await publishPostManage(row.id);
+      modal?.msgSuccess('发布成功');
+    } else {
+      await unpublishPostManage(row.id);
+      modal?.msgSuccess('取消发布成功');
+    }
+    await getList();
+  } finally {
+    publishLoadingMap[rowKey] = false;
+  }
+};
+
+const handleOpenAuditRemark = async (row: MainPostApplyVO) => {
+  let auditRemark = row.rejectReason || '';
+
+  if (row.auditId) {
+    const auditRes = await getMainAudit(row.auditId);
+    auditRemark = auditRes.data?.auditRemark || auditRemark;
+  } else if (row.id) {
+    const auditListRes = await listMainAudit({
+      pageNum: 1,
+      pageSize: 1,
+      auditType: 2,
+      targetId: row.id
+    });
+    auditRemark = auditListRes.rows?.[0]?.auditRemark || auditRemark;
+  }
+
+  auditRemarkDialog.data = {
+    auditStatus: getAuditStatusText(row.applyStatus),
+    auditRemark
+  };
+  auditRemarkDialog.visible = true;
+};
+
+/** 删除按钮操作 */
+const handleDelete = async (row?: MainPostApplyVO) => {
+  const postIds = row?.id || ids.value;
+  await modal?.confirm('是否确认删除岗位编号为"' + postIds + '"的数据项?');
+  await delPostManage(postIds);
+  await getList();
+  modal?.msgSuccess('删除成功');
+};
+
+const handleImportOpen = () => {
+  importDialog.visible = true;
+};
+
+const submitImportForm = () => {
+  importDialog.visible = false;
+};
+
+const handleDownloadImportTemplate = () => {
+  modal?.msgSuccess('待接入模板下载');
+};
+
+onMounted(() => {
+  getList();
+});
+</script>
+
+<style scoped>
+.post-page {
+  min-height: 100%;
+}
+
+.search-panel {
+  margin-bottom: 14px;
+}
+
+.search-form {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+  flex-wrap: wrap;
+}
+
+.search-form-item {
+  margin-bottom: 0;
+}
+
+.search-keyword-item {
+  width: 280px;
+}
+
+.search-keyword-item :deep(.el-input__wrapper) {
+  border-radius: 4px 0 0 4px;
+}
+
+.search-select-item {
+  width: 160px;
+}
+
+.search-action-item :deep(.el-button) {
+  width: 40px;
+  padding: 0;
+  margin-left: -80px;
+  border-radius: 0 4px 4px 0;
+}
+
+.post-card {
+  display: flex;
+  flex-direction: column;
+  min-height: calc(100vh - 220px);
+  padding: 12px 16px 16px;
+  background: #fff;
+  border-radius: 4px;
+}
+
+.table-toolbar {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 12px;
+  margin-bottom: 14px;
+}
+
+.page-title {
+  font-size: 16px;
+  font-weight: 600;
+  color: #303133;
+}
+
+.toolbar-actions {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+}
+
+.post-table :deep(.el-table__header th) {
+  background: #f5f5f5;
+  color: #303133;
+  font-weight: 600;
+}
+
+.post-table :deep(.el-button.is-link) {
+  padding: 0 6px;
+}
+
+.post-card :deep(.pagination-container) {
+  display: flex;
+  justify-content: center;
+  margin-top: auto;
+  padding-top: 16px;
+}
+
+.audit-status-cell {
+  display: inline-flex;
+  align-items: center;
+  gap: 8px;
+  min-height: 24px;
+}
+
+.audit-status-empty {
+  display: inline-block;
+  width: 12px;
+}
+
+.audit-remark-button {
+  padding: 0;
+}
+
+.audit-remark-content {
+  display: flex;
+  flex-direction: column;
+  gap: 16px;
+}
+
+.audit-remark-item {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+}
+
+.audit-remark-item.align-start {
+  align-items: flex-start;
+}
+
+.audit-remark-label {
+  width: 72px;
+  color: #606266;
+  flex-shrink: 0;
+}
+
+.audit-remark-value {
+  color: #303133;
+}
+
+.remark-text {
+  white-space: pre-wrap;
+  word-break: break-all;
+  line-height: 22px;
+}
+
+.import-notice {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 16px;
+  padding: 10px 12px;
+  color: #1677ff;
+  background: #edf5ff;
+  border-radius: 6px;
+  line-height: 20px;
+  text-align: left;
+}
+
+.import-upload-tip {
+  display: flex;
+  flex-direction: column;
+  align-items: flex-start;
+  gap: 6px;
+  margin-top: 8px;
+  padding-left: 2px;
+}
+
+.import-template-link {
+  font-size: 12px;
+  line-height: 20px;
+}
+
+.import-dialog-footer {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  width: 100%;
+}
+
+.import-dialog-actions {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+}
+
+.import-notice-text {
+  color: #1677ff;
+}
+
+.import-notice-link {
+  font-size: 12px;
+  flex-shrink: 0;
+}
+</style>

+ 149 - 0
src/views/test/1.vue

@@ -0,0 +1,149 @@
+<template>
+  <PageShell>
+    <div class="page-one">
+      <div class="status-grid">
+        <div class="status-card">
+          <div class="status-icon success">
+            <el-icon><Select /></el-icon>
+          </div>
+          <div class="status-text">恭喜你!通过测评</div>
+        </div>
+
+        <div class="status-card">
+          <div class="status-icon danger">
+            <el-icon><CloseBold /></el-icon>
+          </div>
+          <div class="status-text">很遗憾!未通过测评</div>
+        </div>
+
+        <div class="status-card">
+          <div class="status-hourglass" aria-hidden="true">
+            <span class="hourglass-top"></span>
+            <span class="hourglass-middle"></span>
+            <span class="hourglass-bottom"></span>
+          </div>
+          <div class="status-text">提交成功,请耐心等待人工评分</div>
+        </div>
+      </div>
+    </div>
+  </PageShell>
+</template>
+
+<script setup name="TestPageOne" lang="ts">
+import { CloseBold, Select } from '@element-plus/icons-vue';
+import PageShell from '@/components/PageShell/index.vue';
+</script>
+
+<style scoped lang="scss">
+.page-one {
+  min-height: calc(100vh - 150px);
+  background: #fff;
+  border-radius: 6px;
+  padding: 120px 60px 40px;
+}
+
+.status-grid {
+  display: grid;
+  grid-template-columns: repeat(3, minmax(220px, 1fr));
+  gap: 48px;
+}
+
+.status-card {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  gap: 18px;
+  min-height: 160px;
+}
+
+.status-icon {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 32px;
+  height: 32px;
+  border-radius: 50%;
+  font-size: 20px;
+  color: #fff;
+}
+
+.status-icon.success {
+  background: #67c23a;
+}
+
+.status-icon.danger {
+  background: #f56c6c;
+}
+
+.status-hourglass {
+  position: relative;
+  width: 34px;
+  height: 46px;
+  color: #f2c15d;
+}
+
+.status-hourglass::before,
+.status-hourglass::after {
+  content: '';
+  position: absolute;
+  left: 3px;
+  width: 28px;
+  border-top: 3px solid #f2c15d;
+}
+
+.status-hourglass::before {
+  top: 0;
+}
+
+.status-hourglass::after {
+  bottom: 0;
+}
+
+.hourglass-top,
+.hourglass-bottom {
+  position: absolute;
+  left: 7px;
+  width: 20px;
+  height: 14px;
+  border-left: 3px solid #f2c15d;
+  border-right: 3px solid #f2c15d;
+}
+
+.hourglass-top {
+  top: 3px;
+  clip-path: polygon(0 0, 100% 0, 58% 100%, 42% 100%);
+}
+
+.hourglass-bottom {
+  bottom: 3px;
+  clip-path: polygon(42% 0, 58% 0, 100% 100%, 0 100%);
+}
+
+.hourglass-middle {
+  position: absolute;
+  top: 20px;
+  left: 14px;
+  width: 6px;
+  height: 6px;
+  border-radius: 50%;
+  background: #f2c15d;
+}
+
+.status-text {
+  color: #303133;
+  font-size: 18px;
+  text-align: center;
+}
+
+@media (max-width: 960px) {
+  .page-one {
+    padding-top: 60px;
+  }
+
+  .status-grid {
+    grid-template-columns: 1fr;
+    gap: 24px;
+  }
+}
+</style>

+ 155 - 0
src/views/test/2.vue

@@ -0,0 +1,155 @@
+<template>
+  <PageShell>
+    <div class="page-two">
+      <div class="toolbar">
+        <div class="search-box">
+          <el-input v-model="keyword" placeholder="请输入" clearable @keyup.enter="handleQuery" />
+          <el-button type="primary" class="search-button" @click="handleQuery">
+            <el-icon><Search /></el-icon>
+          </el-button>
+        </div>
+      </div>
+
+      <el-table :data="pagedList" class="result-table" header-row-class-name="result-table-header">
+        <el-table-column label="测评名称" prop="name" min-width="160" />
+        <el-table-column label="岗位类型" prop="postType" min-width="120" align="center" />
+        <el-table-column label="岗位等级" prop="postLevel" min-width="120" align="center" />
+        <el-table-column label="状态" min-width="140" align="center">
+          <template #default="{ row }">
+            <span class="status-text">
+              <span class="status-dot" :class="`is-${row.statusType}`"></span>
+              {{ row.statusText }}
+            </span>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" min-width="120" align="center">
+          <template #default="{ row }">
+            <el-button v-if="row.actionText" link type="primary">{{ row.actionText }}</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <div class="table-footer">
+        <div class="table-total">共 243 条</div>
+        <pagination v-model:page="pageNum" v-model:limit="pageSize" :total="243" />
+      </div>
+    </div>
+  </PageShell>
+</template>
+
+<script setup name="TestPageTwo" lang="ts">
+import { computed, ref } from 'vue';
+import { Search } from '@element-plus/icons-vue';
+import PageShell from '@/components/PageShell/index.vue';
+
+type ResultRow = {
+  id: number;
+  name: string;
+  postType: string;
+  postLevel: string;
+  statusText: string;
+  statusType: 'success' | 'danger' | 'warning' | 'info';
+  actionText: string;
+};
+
+const keyword = ref('');
+const pageNum = ref(11);
+const pageSize = ref(10);
+const sourceList = ref<ResultRow[]>([
+  { id: 1, name: '审计岗位A1', postType: '全职', postLevel: 'A1', statusText: '通过', statusType: 'success', actionText: '' },
+  { id: 2, name: '审计岗位A1', postType: '兼职', postLevel: 'A1', statusText: '未通过', statusType: 'danger', actionText: '' },
+  { id: 3, name: '审计岗位A1', postType: '实习', postLevel: 'A1', statusText: '待评分', statusType: 'warning', actionText: '' },
+  { id: 4, name: '审计岗位A1', postType: '', postLevel: '', statusText: '待测评', statusType: 'info', actionText: '开始测评' }
+]);
+
+const filteredList = computed(() => {
+  const value = keyword.value.trim();
+  if (!value) {
+    return sourceList.value;
+  }
+  return sourceList.value.filter((item) => [item.name, item.postType, item.postLevel, item.statusText].some((field) => field.includes(value)));
+});
+
+const pagedList = computed(() => filteredList.value.slice(0, pageSize.value));
+
+const handleQuery = () => {
+  pageNum.value = 1;
+};
+</script>
+
+<style scoped lang="scss">
+.page-two {
+  min-height: calc(100vh - 150px);
+  background: #fff;
+  border-radius: 6px;
+  padding: 14px 18px 18px;
+}
+
+.toolbar {
+  margin-bottom: 14px;
+}
+
+.search-box {
+  display: flex;
+  align-items: stretch;
+  width: 248px;
+}
+
+.search-box :deep(.el-input__wrapper) {
+  border-radius: 4px 0 0 4px;
+}
+
+.search-button {
+  width: 32px;
+  padding: 0;
+  border-radius: 0 4px 4px 0;
+}
+
+.result-table :deep(.result-table-header th) {
+  background: #f7f8fa;
+  color: #606266;
+  font-weight: 500;
+}
+
+.status-text {
+  display: inline-flex;
+  align-items: center;
+  gap: 6px;
+}
+
+.status-dot {
+  width: 6px;
+  height: 6px;
+  border-radius: 50%;
+  background: #c0c4cc;
+}
+
+.status-dot.is-success {
+  background: #67c23a;
+}
+
+.status-dot.is-danger {
+  background: #f56c6c;
+}
+
+.status-dot.is-warning {
+  background: #e6a23c;
+}
+
+.status-dot.is-info {
+  background: #909399;
+}
+
+.table-footer {
+  display: flex;
+  align-items: center;
+  justify-content: flex-end;
+  gap: 16px;
+  padding-top: 18px;
+}
+
+.table-total {
+  color: #606266;
+  font-size: 13px;
+}
+</style>

+ 192 - 0
src/views/test/3.vue

@@ -0,0 +1,192 @@
+<template>
+  <PageShell>
+    <div class="page-three">
+      <div class="answer-card">
+        <div class="answer-title">答题卡</div>
+        <div class="answer-grid">
+          <button class="answer-index active">1</button>
+          <button class="answer-index">2</button>
+        </div>
+        <div class="answer-legend">
+          <span><i class="legend-dot answered"></i>已答 1</span>
+          <span><i class="legend-dot pending"></i>未答 1</span>
+        </div>
+      </div>
+
+      <div class="question-panel">
+        <div class="question-title">考试标题</div>
+        <div class="question-desc">感谢您能抽出几分钟时间来参加本次答题,现在我们就马上开始吧!</div>
+
+        <div class="question-block">
+          <div class="question-label"><span class="required">*</span>1. 请选择一个选项</div>
+          <el-radio-group v-model="selectedAnswer" class="option-group">
+            <el-radio value="A">A.选项1</el-radio>
+            <el-radio value="B">B.选项2</el-radio>
+            <el-radio value="C">C.选项3</el-radio>
+            <el-radio value="D">D.选项4</el-radio>
+          </el-radio-group>
+        </div>
+
+        <div class="question-block">
+          <div class="question-label"><span class="required">*</span>2. 请简要阐述你的观点</div>
+          <el-input v-model="comment" type="textarea" :rows="4" placeholder="请输入" />
+        </div>
+
+        <div class="submit-row">
+          <el-button type="primary" class="submit-button">提交</el-button>
+        </div>
+      </div>
+    </div>
+  </PageShell>
+</template>
+
+<script setup name="TestPageThree" lang="ts">
+import { ref } from 'vue';
+import PageShell from '@/components/PageShell/index.vue';
+
+const selectedAnswer = ref('A');
+const comment = ref('');
+</script>
+
+<style scoped lang="scss">
+.page-three {
+  display: grid;
+  grid-template-columns: 150px minmax(0, 1fr);
+  gap: 12px;
+  min-height: calc(100vh - 150px);
+}
+
+.answer-card,
+.question-panel {
+  background: #fff;
+  border-radius: 8px;
+}
+
+.answer-card {
+  align-self: start;
+  padding: 12px;
+}
+
+.answer-title {
+  position: relative;
+  padding-left: 8px;
+  margin-bottom: 12px;
+  color: #303133;
+  font-weight: 600;
+}
+
+.answer-title::before {
+  content: '';
+  position: absolute;
+  left: 0;
+  top: 3px;
+  width: 2px;
+  height: 14px;
+  background: #2f6cf6;
+}
+
+.answer-grid {
+  display: flex;
+  gap: 10px;
+  margin-bottom: 18px;
+}
+
+.answer-index {
+  width: 28px;
+  height: 28px;
+  border: none;
+  border-radius: 4px;
+  color: #909399;
+  background: #f5f7fa;
+  cursor: pointer;
+}
+
+.answer-index.active {
+  color: #2f6cf6;
+  background: #eef3ff;
+}
+
+.answer-legend {
+  display: flex;
+  justify-content: space-between;
+  color: #606266;
+  font-size: 12px;
+}
+
+.legend-dot {
+  display: inline-block;
+  width: 8px;
+  height: 8px;
+  margin-right: 4px;
+  border-radius: 50%;
+}
+
+.legend-dot.answered {
+  background: #2f6cf6;
+}
+
+.legend-dot.pending {
+  background: #c0c4cc;
+}
+
+.question-panel {
+  padding: 38px 26px 44px;
+}
+
+.question-title {
+  margin-bottom: 14px;
+  color: #303133;
+  font-size: 22px;
+  font-weight: 700;
+  text-align: center;
+}
+
+.question-desc {
+  margin-bottom: 34px;
+  color: #606266;
+  font-size: 14px;
+}
+
+.question-block {
+  margin-bottom: 32px;
+}
+
+.question-label {
+  margin-bottom: 18px;
+  color: #303133;
+  font-size: 16px;
+  font-weight: 600;
+}
+
+.required {
+  margin-right: 4px;
+  color: #f56c6c;
+}
+
+.option-group {
+  display: flex;
+  flex-direction: column;
+  gap: 18px;
+  align-items: flex-start;
+}
+
+.submit-row {
+  display: flex;
+  justify-content: center;
+  padding-top: 10px;
+}
+
+.submit-button {
+  min-width: 132px;
+}
+
+@media (max-width: 960px) {
+  .page-three {
+    grid-template-columns: 1fr;
+  }
+
+  .answer-card {
+    order: 2;
+  }
+}
+</style>

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

@@ -0,0 +1,335 @@
+<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>