Gqingci 15 小時之前
父節點
當前提交
ee42982f05

+ 22 - 1
src/api/system/backRecord/index.ts

@@ -1,6 +1,6 @@
 import request from '@/utils/request';
 import { AxiosPromise } from 'axios';
-import { BackRecordVO, BackRecordQuery, TableDataInfo } from './types';
+import { BackRecordVO, BackRecordQuery, TableDataInfo, BackCheckReportVO } from './types';
 
 /**
  * 查询背调执行记录列表(候选人列表)
@@ -8,3 +8,24 @@ import { BackRecordVO, BackRecordQuery, TableDataInfo } from './types';
 export function listBackRecord(params: BackRecordQuery): AxiosPromise<TableDataInfo<BackRecordVO>> {
   return request.get('/system/backRecord/list', { params });
 }
+
+/**
+ * 查询背调报告详情(表单数据+访谈记录)
+ */
+export function getBackCheckReport(recordId: string | number): AxiosPromise<BackCheckReportVO> {
+  return request({
+    url: `/system/backRecord/${recordId}/report`,
+    method: 'get'
+  });
+}
+
+/**
+ * 管理端提交背调报告(需登录,含三个核实结果)
+ */
+export function submitBackCheckReport(recordId: string | number, data: BackCheckReportVO): AxiosPromise<void> {
+  return request({
+    url: `/system/backRecord/${recordId}/report`,
+    method: 'put',
+    data
+  });
+}

+ 68 - 0
src/api/system/backRecord/types.ts

@@ -32,3 +32,71 @@ export interface TableDataInfo<T> {
   code: number;
   msg: string;
 }
+
+/**
+ * 背调报告访谈记录
+ */
+export interface InterviewVO {
+  id?: string | number;
+  intervieweeName?: string;
+  intervieweeRelation?: string;
+  intervieweeContact?: string;
+  qa1?: string;
+  qa2?: string;
+  qa3?: string;
+  qa4?: string;
+  qa5?: string;
+}
+
+/**
+ * 背调报告详情
+ */
+export interface BackCheckReportVO {
+  /** 报告状态: 0待填写,1已出具,2已审核,3已拒绝 */
+  reportStatus?: number;
+  candidateName?: string;
+  applyPosition?: string;
+  checkTime?: string;
+
+  gradSchool?: string;
+  eduCertNo?: string;
+  eduVerifyStatus?: string;
+  eduCheckResult?: string;
+
+  companyName?: string;
+  workStartTime?: string;
+  workEndTime?: string;
+  jobTitle?: string;
+  lastSalary?: string;
+  leaveReasonVerify?: string;
+  companyCheckRemark?: string;
+  companyCheckResult?: string;
+
+  leaderEvalAdvantage?: string;
+  leaderEvalImprove?: string;
+  leaderEvalProf?: string;
+  leaderEvalAttitude?: string;
+  leaderEvalTeam?: string;
+  leaderEvalMorals?: string;
+
+  colleagueEvalTeamwork?: string;
+  colleagueEvalProf?: string;
+
+  hrEvalDispute?: string;
+  hrEvalTransfer?: string;
+  performCheckRemark?: string;
+  performCheckResult?: string;
+
+  hasNonCompete?: number;
+  hasNda?: number;
+  agreementRemark?: string;
+  hasDispute?: number;
+  disputeRemark?: string;
+
+  conclusion?: string;
+  conclusionReason?: string;
+  investigatorName?: string;
+  investigatorDate?: string;
+
+  interviews?: InterviewVO[];
+}

+ 6 - 4
src/api/system/withdraw/index.ts

@@ -16,12 +16,13 @@ export function listWithdraw(query: WithdrawQuery): AxiosPromise<WithdrawVO[]> {
 /**
  * 审核通过
  */
-export function auditPassWithdraw(id: string | number, remark?: string) {
+export function auditPassWithdraw(id: string | number, remark?: string, fileUrl?: string) {
   return request({
     url: '/system/withdraw/audit/pass/' + id,
     method: 'post',
     params: {
-      remark: remark || ''
+      remark: remark || '',
+      fileUrl: fileUrl || ''
     }
   });
 }
@@ -29,12 +30,13 @@ export function auditPassWithdraw(id: string | number, remark?: string) {
 /**
  * 审核驳回
  */
-export function auditRejectWithdraw(id: string | number, remark: string) {
+export function auditRejectWithdraw(id: string | number, remark: string, fileUrl?: string) {
   return request({
     url: '/system/withdraw/audit/reject/' + id,
     method: 'post',
     params: {
-      remark
+      remark,
+      fileUrl: fileUrl || ''
     }
   });
 }

+ 1 - 0
src/api/system/withdraw/types.ts

@@ -27,6 +27,7 @@ export interface WithdrawVO extends BaseEntity {
   auditTime?: string;
   auditRemark?: string;
   failReason?: string;
+  fileUrl?: string;
   createTime?: string;
   transferTime?: string;
   account?: WithdrawAccountVO;

+ 42 - 3
src/views/balanceReview.vue

@@ -70,6 +70,19 @@
           </template>
         </el-table-column>
         <el-table-column label="审核备注" prop="auditRemark" min-width="180" show-overflow-tooltip />
+        <el-table-column label="附件" min-width="100">
+          <template #default="scope">
+            <el-image
+              v-if="scope.row.fileUrl && ossUrlMap[scope.row.fileUrl]"
+              :src="ossUrlMap[scope.row.fileUrl]"
+              :preview-src-list="[ossUrlMap[scope.row.fileUrl]]"
+              style="width: 60px; height: 60px"
+              fit="cover"
+              preview-teleported
+            />
+            <span v-else>-</span>
+          </template>
+        </el-table-column>
         <el-table-column label="失败原因" prop="failReason" min-width="180" show-overflow-tooltip />
         <el-table-column label="申请时间" prop="createTime" width="170" />
         <el-table-column label="审核时间" prop="auditTime" width="170" />
@@ -123,6 +136,9 @@
             :placeholder="auditDialog.mode === 'pass' ? '请输入审核备注,选填' : '请输入驳回原因'"
           />
         </el-form-item>
+        <el-form-item label="上传文件">
+          <image-upload v-model="auditDialog.fileUrl" :limit="1" :fileSize="10" />
+        </el-form-item>
       </el-form>
       <template #footer>
         <div class="dialog-footer">
@@ -139,12 +155,15 @@ import { ref, reactive } from 'vue';
 import { ElMessage } from 'element-plus';
 import { listWithdraw, auditPassWithdraw, auditRejectWithdraw } from '@/api/system/withdraw';
 import type { WithdrawQuery, WithdrawVO } from '@/api/system/withdraw/types';
+import { listByIds } from '@/api/system/oss';
+import ImageUpload from '@/components/ImageUpload/index.vue';
 
 const loading = ref(false);
 const total = ref(0);
 const withdrawList = ref<WithdrawVO[]>([]);
 const queryRef = ref();
 const dateRange = ref<string[]>([]);
+const ossUrlMap = reactive<Record<string, string>>({});
 
 const queryParams = reactive<WithdrawQuery>({
   pageNum: 1,
@@ -168,11 +187,13 @@ const auditDialog = reactive<{
   mode: 'pass' | 'reject';
   row: WithdrawVO | null;
   remark: string;
+  fileUrl: string;
 }>({
   visible: false,
   mode: 'pass',
   row: null,
-  remark: ''
+  remark: '',
+  fileUrl: ''
 });
 
 const getList = async () => {
@@ -185,6 +206,21 @@ const getList = async () => {
     const res = await listWithdraw(queryParams);
     withdrawList.value = res.rows || [];
     total.value = res.total || 0;
+    // 解析 fileUrl 中的 ossId 为实际图片 URL
+    const ossIds = withdrawList.value
+      .map((item) => item.fileUrl)
+      .filter((id): id is string => !!id && !ossUrlMap[id]);
+    if (ossIds.length > 0) {
+      try {
+        const ossRes = await listByIds(ossIds.join(','));
+        for (const oss of ossRes.data || []) {
+          const fullUrl = oss.url.startsWith('http') ? oss.url : baseUrl + oss.url;
+          ossUrlMap[oss.ossId] = fullUrl;
+        }
+      } catch {
+        // oss 解析失败不影响主流程
+      }
+    }
   } finally {
     loading.value = false;
   }
@@ -207,6 +243,7 @@ const openAuditDialog = (row: WithdrawVO, mode: 'pass' | 'reject') => {
   auditDialog.mode = mode;
   auditDialog.row = row;
   auditDialog.remark = '';
+  auditDialog.fileUrl = '';
 };
 
 const submitAudit = async () => {
@@ -219,10 +256,10 @@ const submitAudit = async () => {
   }
 
   if (auditDialog.mode === 'pass') {
-    await auditPassWithdraw(auditDialog.row.id, auditDialog.remark);
+    await auditPassWithdraw(auditDialog.row.id, auditDialog.remark, auditDialog.fileUrl);
     ElMessage.success('审核通过成功');
   } else {
-    await auditRejectWithdraw(auditDialog.row.id, auditDialog.remark);
+    await auditRejectWithdraw(auditDialog.row.id, auditDialog.remark, auditDialog.fileUrl);
     ElMessage.success('驳回成功');
   }
 
@@ -252,6 +289,8 @@ const getAccountTypeLabel = (type?: number) => {
   return '-';
 };
 
+const baseUrl = import.meta.env.VITE_APP_BASE_API;
+
 getList();
 </script>
 

+ 240 - 54
src/views/system/backcheck/BackgroundCheckForm.vue

@@ -35,7 +35,7 @@
               <td><strong>候选人基本信息</strong></td>
               <td class="content-cell">
                 <div class="form-row"><span class="label">姓名:</span><el-input v-model="form.candidateName" disabled class="noline-input" /></div>
-                <div class="form-row"><span class="label">应聘职位:</span><el-input v-model="form.applyPosition" class="noline-input" /></div>
+                <div class="form-row"><span class="label">应聘职位:</span><el-input v-model="form.applyPosition" disabled class="noline-input" /></div>
                 <div class="form-row"><span class="label">调查时间:</span><el-date-picker v-model="form.checkTime" type="date" class="noline-input" /></div>
               </td>
               <td></td>
@@ -50,11 +50,13 @@
                 <div class="form-row"><span class="label">学信网核实:</span><el-input v-model="form.eduVerifyStatus" class="noline-input" /></div>
               </td>
               <td class="center-cell">
-                 <el-radio-group v-model="form.eduVerifyResult" class="vertical-radio">
-                  <el-radio label="属实">属实</el-radio>
-                  <el-radio label="不属实">不属实</el-radio>
-                  <el-radio label="无法核实">无法核实</el-radio>
-                </el-radio-group>
+                 <el-form-item prop="eduVerifyResult" style="margin-bottom:0">
+                   <el-radio-group v-model="form.eduVerifyResult" class="vertical-radio">
+                    <el-radio label="属实">属实</el-radio>
+                    <el-radio label="不属实">不属实</el-radio>
+                    <el-radio label="无法核实">无法核实</el-radio>
+                  </el-radio-group>
+                 </el-form-item>
               </td>
               <td></td>
             </tr>
@@ -69,12 +71,13 @@
                 <div class="form-row"><span class="label">离职原因:</span><el-input v-model="form.leaveReasonVerify" type="textarea" autosize class="noline-input flex-1" /></div>
               </td>
               <td class="center-cell">
-                 <!-- No specific property mapped originally, keep it for completeness of visual or bind if exist. -->
-                 <el-radio-group v-model="form.companyVerifyResult" class="vertical-radio">
-                  <el-radio label="属实">属实</el-radio>
-                  <el-radio label="部分属实">部分属实</el-radio>
-                  <el-radio label="不属实">不属实</el-radio>
-                </el-radio-group>
+                 <el-form-item prop="companyVerifyResult" style="margin-bottom:0">
+                   <el-radio-group v-model="form.companyVerifyResult" class="vertical-radio">
+                    <el-radio label="属实">属实</el-radio>
+                    <el-radio label="部分属实">部分属实</el-radio>
+                    <el-radio label="不属实">不属实</el-radio>
+                  </el-radio-group>
+                 </el-form-item>
               </td>
               <td class="content-cell">
                 <div></div>
@@ -100,11 +103,13 @@
                 <div class="form-row"><span class="label">离职交接情况:</span><el-input v-model="form.hrEvalTransfer" class="noline-input" /></div>
               </td>
               <td class="center-cell">
-                 <el-radio-group v-model="form.evalResult" class="vertical-radio">
-                  <el-radio label="良好">良好</el-radio>
-                  <el-radio label="一般">一般</el-radio>
-                  <el-radio label="需关注">需关注</el-radio>
-                </el-radio-group>
+                 <el-form-item prop="evalResult" style="margin-bottom:0">
+                   <el-radio-group v-model="form.evalResult" class="vertical-radio">
+                    <el-radio label="良好">良好</el-radio>
+                    <el-radio label="一般">一般</el-radio>
+                    <el-radio label="需关注">需关注</el-radio>
+                  </el-radio-group>
+                 </el-form-item>
               </td>
               <td class="content-cell">
                 <div></div>
@@ -199,7 +204,7 @@
           <div class="interview-meta">
             <span>调查表(一)</span>
             <span class="meta-item">姓名:<el-input v-model="form.interviewSupervisor.name" class="inline-input" /></span>
-            <span class="meta-item">关系:<el-input v-model="form.interviewSupervisor.relationship" class="inline-input" placeholder="输入关系或不填则为上级" /></span>
+            <span class="meta-item">关系:<el-input v-model="form.interviewSupervisor.relationship" class="inline-input" placeholder="不填则为上级" /></span>
             <span class="meta-item">联系方式:<el-input v-model="form.interviewSupervisor.contact" class="inline-input" /></span>
           </div>
           <table class="report-table" border="1" cellspacing="0" cellpadding="0">
@@ -227,7 +232,7 @@
           <div class="interview-meta">
             <span>调查表(二)</span>
             <span class="meta-item">姓名:<el-input v-model="form.interviewHR.name" class="inline-input" /></span>
-            <span class="meta-item">关系:<el-input v-model="form.interviewHR.relationship" class="inline-input" placeholder="输入关系或不填则为HR" /></span>
+            <span class="meta-item">关系:<el-input v-model="form.interviewHR.relationship" class="inline-input" placeholder="不填则为HR" /></span>
             <span class="meta-item">联系方式:<el-input v-model="form.interviewHR.contact" class="inline-input" /></span>
           </div>
           <table class="report-table" border="1" cellspacing="0" cellpadding="0">
@@ -255,7 +260,7 @@
           <div class="interview-meta">
             <span>调查表(三)</span>
             <span class="meta-item">姓名:<el-input v-model="form.interviewColleague.name" class="inline-input" /></span>
-            <span class="meta-item">关系:<el-input v-model="form.interviewColleague.relationship" class="inline-input" placeholder="输入关系或不填则为同事" /></span>
+            <span class="meta-item">关系:<el-input v-model="form.interviewColleague.relationship" class="inline-input" placeholder="不填则为同事" /></span>
             <span class="meta-item">联系方式:<el-input v-model="form.interviewColleague.contact" class="inline-input" /></span>
           </div>
           <table class="report-table" border="1" cellspacing="0" cellpadding="0">
@@ -278,10 +283,20 @@
           </table>
         </div>
 
-        <div class="form-actions">
-          <el-button type="primary" size="large" class="submit-btn" :loading="submitting" @click="handleSubmit">
-            确认并提交背调反馈
-          </el-button>
+        <div class="form-actions" v-if="!props.recordId || currentReportStatus < 2">
+          <template v-if="props.recordId">
+            <el-button type="primary" size="large" class="submit-btn" :loading="submitting" @click="handleSubmit(2)">
+              审核通过
+            </el-button>
+            <el-button type="danger" size="large" class="submit-btn" :loading="submitting" @click="handleSubmit(3)">
+              审核不通过
+            </el-button>
+          </template>
+          <template v-else>
+            <el-button type="primary" size="large" class="submit-btn" :loading="submitting" @click="handleSubmit()">
+              确认并提交背调反馈
+            </el-button>
+          </template>
         </div>
       </el-form>
     </div>
@@ -291,22 +306,42 @@
 
 <script setup lang="ts">
 
-import { ref, onMounted } from 'vue';
+import { ref, computed, onMounted } from 'vue';
 import { useRoute } from 'vue-router';
 import { ElMessage } from 'element-plus';
-import { User, Collection, OfficeBuilding, TrendCharts, Warning, Stamp, ChatDotRound, CircleCheckFilled, Loading } from '@element-plus/icons-vue';
-import request from '../api/request';
+import { CircleCheckFilled, Loading } from '@element-plus/icons-vue';
+import request from '@/utils/request';
+import { getBackCheckReport, submitBackCheckReport } from '@/api/system/backRecord';
+import type { BackCheckReportVO, InterviewVO } from '@/api/system/backRecord/types';
+
+const props = defineProps<{
+  recordId?: string | number;
+  reportStatus?: number;
+}>();
+
+const emit = defineEmits<{
+  submitted: [status: number];
+}>();
 
 const route = useRoute();
 const formRef = ref();
 const submitting = ref(false);
 const submitted = ref(false);
 const loading = ref(true);
+const formReportStatus = ref<number>(0);
+
+/** 当前报告状态:优先用 props,其次用内部值 */
+const currentReportStatus = computed(() => {
+  if (props.reportStatus !== undefined && props.reportStatus !== null) {
+    return props.reportStatus;
+  }
+  return formReportStatus.value;
+});
 
 const form = ref({
   candidateName: '',
   applyPosition: '',
-  checkTime: new Date(),
+  checkTime: '' as any,
   gradSchool: '',
   eduCertNo: '',
   eduVerifyStatus: '',
@@ -314,7 +349,7 @@ const form = ref({
   companyVerifyResult: '',
   evalResult: '',
   companyName: '',
-  workPeriod: [],
+  workPeriod: [] as string[],
   position: '',
   lastSalary: '',
   leaveReasonVerify: '',
@@ -341,67 +376,220 @@ const form = ref({
   conclusion: '',
   conclusionReason: '',
   investigatorName: '',
-  investigatorDate: new Date()
+  investigatorDate: '' as any
 });
 
 const rules = {
   candidateName: [{ required: true, message: '无法获取候选人信息', trigger: 'blur' }],
   investigatorName: [{ required: true, message: '请输入调查人姓名', trigger: 'blur' }],
   investigatorDate: [{ required: true, message: '请选择调查日期', trigger: 'change' }],
-  conclusion: [{ required: true, message: '请选择最终推荐结论', trigger: 'change' }]
+  conclusion: [{ required: true, message: '请选择最终推荐结论', trigger: 'change' }],
+  eduVerifyResult: [{ required: true, message: '请选择学历核实结果', trigger: 'change' }],
+  companyVerifyResult: [{ required: true, message: '请选择工作单位核实结果', trigger: 'change' }],
+  evalResult: [{ required: true, message: '请选择表现核实结果', trigger: 'change' }]
+};
+
+/** 将后端访谈记录映射到表单结构 */
+const mapInterviewToForm = (interviews?: InterviewVO[]) => {
+  const defaultInterview = { name: '', relationship: '', contact: '', q1: '', q2: '', q3: '', q4: '', q5: '' };
+  if (!interviews || interviews.length === 0) {
+    return {
+      interviewSupervisor: { ...defaultInterview },
+      interviewHR: { ...defaultInterview },
+      interviewColleague: { ...defaultInterview }
+    };
+  }
+  const mapOne = (iv?: InterviewVO) => iv ? {
+    name: iv.intervieweeName || '',
+    relationship: iv.intervieweeRelation || '',
+    contact: iv.intervieweeContact || '',
+    q1: iv.qa1 || '', q2: iv.qa2 || '', q3: iv.qa3 || '', q4: iv.qa4 || '', q5: iv.qa5 || ''
+  } : { ...defaultInterview };
+
+  return {
+    interviewSupervisor: mapOne(interviews[0]),
+    interviewHR: mapOne(interviews[1]),
+    interviewColleague: mapOne(interviews[2])
+  };
+};
+
+/** 从后端报告数据回填表单 */
+const fillForm = (data: BackCheckReportVO) => {
+  const f = form.value;
+  f.candidateName = data.candidateName || '';
+  f.applyPosition = data.applyPosition || '';
+  f.checkTime = data.checkTime || '';
+  f.gradSchool = data.gradSchool || '';
+  f.eduCertNo = data.eduCertNo || '';
+  f.eduVerifyStatus = data.eduVerifyStatus || '';
+  f.eduVerifyResult = data.eduCheckResult || '';
+  f.companyVerifyResult = data.companyCheckResult || '';
+  f.evalResult = data.performCheckResult || '';
+  f.companyName = data.companyName || '';
+  f.position = data.jobTitle || '';
+  f.lastSalary = data.lastSalary || '';
+  f.leaveReasonVerify = data.leaveReasonVerify || '';
+  f.attachment1Remark = data.companyCheckRemark || '';
+  f.leaderEvalAdvantage = data.leaderEvalAdvantage || '';
+  f.leaderEvalImprove = data.leaderEvalImprove || '';
+  f.leaderEvalProf = data.leaderEvalProf || '';
+  f.leaderEvalAttitude = data.leaderEvalAttitude || '';
+  f.leaderEvalTeam = data.leaderEvalTeam || '';
+  f.leaderEvalMorals = data.leaderEvalMorals || '';
+  f.colleagueEvalTeamwork = data.colleagueEvalTeamwork || '';
+  f.colleagueEvalProf = data.colleagueEvalProf || '';
+  f.hrEvalDispute = data.hrEvalDispute || '';
+  f.hrEvalTransfer = data.hrEvalTransfer || '';
+  f.attachment2Remark = data.performCheckRemark || '';
+  f.hasNonCompete = data.hasNonCompete === 1;
+  f.hasNda = data.hasNda === 1;
+  f.agreementRemark = data.agreementRemark || '';
+  f.hasDisputeStatus = data.hasDispute === 1;
+  f.disputeRemark = data.disputeRemark || '';
+  f.conclusion = data.conclusion || '';
+  f.conclusionReason = data.conclusionReason || '';
+  f.investigatorName = data.investigatorName || '';
+  f.investigatorDate = data.investigatorDate || '';
+
+  // 任职时间(后端存 workStartTime / workEndTime)
+  if (data.workStartTime || data.workEndTime) {
+    f.workPeriod = [data.workStartTime || '', data.workEndTime || ''];
+  }
+
+  // 访谈记录
+  const interviewMap = mapInterviewToForm(data.interviews);
+  f.interviewSupervisor = interviewMap.interviewSupervisor;
+  f.interviewHR = interviewMap.interviewHR;
+  f.interviewColleague = interviewMap.interviewColleague;
+
+  // 报告状态
+  if (data.reportStatus !== undefined && data.reportStatus !== null) {
+    formReportStatus.value = data.reportStatus;
+  }
 };
 
 onMounted(async () => {
-  const recordId = route.query.recordId;
-  if (recordId) {
+  const rid = props.recordId || route.query.recordId;
+
+  // 如果有 recordId,先尝试从管理端接口获取报告数据回显
+  if (rid) {
+    try {
+      const res = await getBackCheckReport(rid as string);
+      if (res.data) {
+        fillForm(res.data);
+        loading.value = false;
+        return;
+      }
+    } catch (e) {
+      // 管理端接口查询失败,回退到门户接口
+    }
+
+    // 回退:门户端查询表单提交状态
     try {
-      const res = await request.get('/portal/check/bgform/status', {
-        params: { recordId },
+      const statusRes = await request.get('/portal/check/bgform/status', {
+        params: { recordId: rid },
         skipAuth: true
       } as any);
-      if (res?.submitted) {
+      if (statusRes?.submitted) {
         submitted.value = true;
         loading.value = false;
         return;
       }
-      if (res?.candidateName) {
-        form.value.candidateName = res.candidateName;
+      if (statusRes?.candidateName) {
+        form.value.candidateName = statusRes.candidateName;
       }
     } catch (e) {
       // 查询失败不影响表单填写
     }
   }
+
   if (route.query.candidateName) {
     form.value.candidateName = route.query.candidateName as string;
   }
   loading.value = false;
 });
 
-const handleSubmit = () => {
+const handleSubmit = (reportStatus?: number) => {
   formRef.value?.validate(async (valid: boolean) => {
     if (!valid) {
       ElMessage.error('请完善必填信息!');
       return;
     }
 
-    const recordId = route.query.recordId;
-    if (!recordId) {
+    const rid = props.recordId || route.query.recordId;
+    if (!rid) {
       ElMessage.error('缺少背调记录ID,无法提交');
       return;
     }
 
     submitting.value = true;
     try {
-      await request.post('/portal/check/bgform/submit', {
-        recordId: recordId,
-        ...form.value
-      }, { skipAuth: true } as any);
-      submitted.value = true;
-      ElMessage.success('提交成功!感谢您的真实填写与支持。');
-      // 先尝试关闭窗口(仅对 window.open 打开的窗口有效)
-      setTimeout(() => {
-        window.close();
-      }, 1500);
+      if (props.recordId) {
+        // 管理端提交:使用新接口,带登录校验,含三个核实结果 + reportStatus
+        const f = form.value;
+        const mapInterview = (iv: typeof f.interviewSupervisor) => ({
+          name: iv.name,
+          relationship: iv.relationship,
+          contact: iv.contact,
+          q1: iv.q1, q2: iv.q2, q3: iv.q3, q4: iv.q4, q5: iv.q5
+        });
+        const submitData = {
+          reportStatus: reportStatus || 2,
+          candidateName: f.candidateName,
+          applyPosition: f.applyPosition,
+          checkTime: f.checkTime,
+          gradSchool: f.gradSchool,
+          eduCertNo: f.eduCertNo,
+          eduVerifyStatus: f.eduVerifyStatus,
+          eduCheckResult: f.eduVerifyResult,
+          companyName: f.companyName,
+          workPeriod: f.workPeriod?.length === 2 ? f.workPeriod : [],
+          position: f.position,
+          lastSalary: f.lastSalary,
+          leaveReasonVerify: f.leaveReasonVerify,
+          companyCheckRemark: f.attachment1Remark,
+          companyCheckResult: f.companyVerifyResult,
+          leaderEvalAdvantage: f.leaderEvalAdvantage,
+          leaderEvalImprove: f.leaderEvalImprove,
+          leaderEvalProf: f.leaderEvalProf,
+          leaderEvalAttitude: f.leaderEvalAttitude,
+          leaderEvalTeam: f.leaderEvalTeam,
+          leaderEvalMorals: f.leaderEvalMorals,
+          colleagueEvalTeamwork: f.colleagueEvalTeamwork,
+          colleagueEvalProf: f.colleagueEvalProf,
+          hrEvalDispute: f.hrEvalDispute,
+          hrEvalTransfer: f.hrEvalTransfer,
+          performCheckRemark: f.attachment2Remark,
+          performCheckResult: f.evalResult,
+          hasNonCompete: f.hasNonCompete,
+          hasNda: f.hasNda,
+          agreementRemark: f.agreementRemark,
+          hasDisputeStatus: f.hasDisputeStatus,
+          disputeRemark: f.disputeRemark,
+          conclusion: f.conclusion,
+          conclusionReason: f.conclusionReason,
+          investigatorName: f.investigatorName,
+          investigatorDate: f.investigatorDate,
+          interviewSupervisor: mapInterview(f.interviewSupervisor),
+          interviewHR: mapInterview(f.interviewHR),
+          interviewColleague: mapInterview(f.interviewColleague)
+        };
+        await submitBackCheckReport(rid, submitData);
+        ElMessage.success(reportStatus === 3 ? '已标记为审核不通过' : '审核通过');
+        formReportStatus.value = reportStatus || 2;
+        emit('submitted', reportStatus || 2);
+      } else {
+        // 门户端提交:使用原接口(免登录)
+        await request.post('/portal/check/bgform/submit', {
+          recordId: rid,
+          ...form.value
+        }, { skipAuth: true } as any);
+        submitted.value = true;
+        ElMessage.success('提交成功!感谢您的真实填写与支持。');
+        setTimeout(() => {
+          window.close();
+        }, 1500);
+      }
     } catch (e: any) {
       // error already handled by request interceptor
     } finally {
@@ -409,13 +597,11 @@ const handleSubmit = () => {
     }
   });
 };
-
 </script>
 <style scoped>
 .bg-check-container {
-  min-height: 100vh;
   background-color: #fff;
-  padding: 40px 20px;
+  padding: 16px 20px;
   font-family: SimSun, "宋体", serif;
 }
 

+ 2 - 2
src/views/system/backcheck/candidateList.vue

@@ -27,9 +27,9 @@
         @selection-change="handleSelectionChange"
       >
         <el-table-column type="selection" width="50" align="center" />
-        <el-table-column label="姓名" min-width="160">
+        <el-table-column label="姓名" min-width="160" header-align="left" :cell-style="{ textAlign: 'left' }">
           <template #default="scope">
-            <div class="flex flex-col">
+            <div class="flex flex-col items-start">
               <el-link type="primary" :underline="false" @click="handleDetail(scope.row)">
                 {{ scope.row.name }}
               </el-link>

+ 29 - 220
src/views/system/backcheck/report.vue

@@ -9,17 +9,13 @@
             <div class="info-item"><span class="label">手机号:</span><span class="value">{{ baseInfo.mobile }}</span></div>
             <div class="info-item"><span class="label">证件号:</span><span class="value">{{ baseInfo.idNo }}</span></div>
             <div class="info-item"><span class="label">报告状态:</span>
-              <el-tag v-if="baseInfo.reportStatus === '已完成'" type="success">已完成</el-tag>
-              <el-tag v-else type="danger">未完成</el-tag>
+              <el-tag v-if="reportStatus === 3" type="danger">已拒绝</el-tag>
+              <el-tag v-else-if="reportStatus === 2" type="success">已审核</el-tag>
+              <el-tag v-else-if="reportStatus === 1" type="warning">已出具</el-tag>
+              <el-tag v-else type="info">待填写</el-tag>
             </div>
           </div>
 
-          <div class="panel-title mt-4 shrink-0">报告目录</div>
-          <el-menu :default-active="activeKey" class="border-none flex-1 overflow-y-auto" @select="handleSelect">
-            <el-menu-item index="overview">报告概述</el-menu-item>
-            <el-menu-item index="detail">报告明细</el-menu-item>
-          </el-menu>
-
           <div class="mt-4 shrink-0">
             <el-button class="w-full" @click="handleBack">返回</el-button>
           </div>
@@ -27,91 +23,8 @@
       </el-col>
 
       <el-col :lg="18" :md="16" :sm="24" :xs="24" class="right-col h-full">
-        <div class="bg-white rounded p-4 right-panel h-full overflow-y-auto">
-          <div v-show="activeKey === 'overview'" class="report-overview">
-            <div class="report-title">报告概述</div>
-
-            <div class="report-card">
-              <div class="card-h">基本信息</div>
-              <el-descriptions :column="2" border>
-                <el-descriptions-item label="姓名">{{ baseInfo.name }}</el-descriptions-item>
-                <el-descriptions-item label="性别">{{ baseInfo.gender }}</el-descriptions-item>
-                <el-descriptions-item label="证件号码">{{ baseInfo.idNoMasked }}</el-descriptions-item>
-                <el-descriptions-item label="完成日期">{{ baseInfo.finishDate }}</el-descriptions-item>
-              </el-descriptions>
-            </div>
-
-            <div class="report-card mt-4">
-              <div class="card-h">核验概述</div>
-              <el-table :data="overviewRows" header-cell-class-name="custom-header" style="width: 100%">
-                <el-table-column prop="item" label="核验项目" min-width="120" />
-                <el-table-column prop="content" label="核验内容" min-width="220" />
-                <el-table-column prop="progress" label="进度" width="120" align="center" />
-                <el-table-column label="核验结果" width="120" align="center">
-                  <template #default="scope">
-                    <el-icon v-if="scope.row.ok" class="ok"><i-ep-circle-check /></el-icon>
-                    <el-icon v-else class="bad"><i-ep-circle-close /></el-icon>
-                  </template>
-                </el-table-column>
-              </el-table>
-            </div>
-
-            <div class="report-card mt-4">
-              <div class="card-h">风险等级说明</div>
-              <el-table :data="riskRows" header-cell-class-name="custom-header" style="width: 100%">
-                <el-table-column label="" width="60" align="center">
-                  <template #default="scope">
-                    <span class="risk-dot" :class="scope.row.level" />
-                  </template>
-                </el-table-column>
-                <el-table-column prop="desc" label="说明" />
-              </el-table>
-            </div>
-
-            <div class="report-card mt-4">
-              <div class="card-h">风险说明</div>
-              <div class="risk-desc">{{ riskDesc }}</div>
-            </div>
-
-            <div class="report-card mt-4">
-              <div class="card-h">基本信息</div>
-              <el-table :data="basicRows" header-cell-class-name="custom-header" style="width: 100%">
-                <el-table-column prop="label" label="字段" width="180" />
-                <el-table-column prop="value" label="内容" />
-                <el-table-column label="核验结果" width="120" align="center">
-                  <template #default="scope">
-                    <el-icon v-if="scope.row.ok" class="ok"><i-ep-circle-check /></el-icon>
-                    <el-icon v-else class="bad"><i-ep-circle-close /></el-icon>
-                  </template>
-                </el-table-column>
-                <el-table-column prop="remark" label="备注" width="220" />
-              </el-table>
-            </div>
-
-            <div class="mt-4 flex justify-center">
-              <el-button type="primary" @click="handleSave">保存</el-button>
-            </div>
-          </div>
-
-          <div v-show="activeKey === 'detail'" class="report-detail">
-            <div class="report-title">报告明细</div>
-
-            <el-table :data="detailRows" header-cell-class-name="custom-header" style="width: 100%" class="mt-4">
-              <el-table-column prop="label" label="字段" min-width="180" />
-              <el-table-column prop="value" label="内容" min-width="260" />
-              <el-table-column prop="result" label="核验结果" width="120" align="center">
-                <template #default="scope">
-                  <el-icon v-if="scope.row.ok" class="ok"><i-ep-circle-check /></el-icon>
-                  <el-icon v-else class="bad"><i-ep-circle-close /></el-icon>
-                </template>
-              </el-table-column>
-              <el-table-column prop="remark" label="备注" min-width="200" />
-            </el-table>
-
-            <div class="mt-4 flex justify-center">
-              <el-button type="primary" @click="handleSave">保存</el-button>
-            </div>
-          </div>
+        <div class="bg-white rounded right-panel h-full overflow-y-auto">
+          <BackgroundCheckForm :record-id="recordId" :report-status="reportStatus" @submitted="onSubmitted" />
         </div>
       </el-col>
     </el-row>
@@ -119,14 +32,17 @@
 </template>
 
 <script setup lang="ts">
-import { computed, reactive, ref } from 'vue';
+import { reactive, computed, ref, onMounted } from 'vue';
 import { useRoute, useRouter } from 'vue-router';
-import { ElMessage } from 'element-plus';
+import BackgroundCheckForm from './BackgroundCheckForm.vue';
+import { getBackCheckReport } from '@/api/system/backRecord';
 
 const route = useRoute();
 const router = useRouter();
 
-const activeKey = ref<'overview' | 'detail'>('overview');
+const recordId = computed(() => (route.query.id as string) || '');
+
+const reportStatus = ref<number>(0);
 
 const baseInfo = reactive({
   id: (route.query.id as string) || '',
@@ -136,66 +52,33 @@ const baseInfo = reactive({
   idNo: (route.query.idCard as string) || '4401***********0362',
   idNoMasked: (route.query.idCard as string) || '4401***********0362',
   gender: (route.query.gender === '0' ? '男' : (route.query.gender === '1' ? '女' : '未知')),
-  reportStatus: (route.query.status as string) || '已完成',
   finishDate: '2024.07.08'
 });
 
-const overviewRows = ref([
-  { item: '身份验证', content: '姓名-身份证号是否一致', progress: '已完成', ok: true },
-  { item: '学历验证', content: '经学信网毕业院校核实', progress: '已完成', ok: true },
-  { item: '不良记录', content: '经权威机构查询个人不良记录', progress: '已完成', ok: true },
-  { item: '涉诉风险', content: '经法院公开的民事诉讼记录查询', progress: '已完成', ok: true },
-  { item: '商业利益冲突', content: '经工商市场监管智慧查询', progress: '已完成', ok: true },
-  { item: '金融风险', content: '经权威金融机构查询', progress: '已完成', ok: true }
-]);
-
-const riskRows = ref([
-  { level: 'high', desc: '代表核验结果“存在严重失实或虚假情况,属于较高风险,建议务必关注”' },
-  { level: 'mid', desc: '代表核验结果“存在中等失实,属于中等风险,建议关注”' },
-  { level: 'low', desc: '代表核验结果“存在较小失实或暂无不良,属于低风险,建议适当关注”' },
-  { level: 'ok', desc: '代表核验结果“属实,无风险”' },
-  { level: 'none', desc: '代表核验结果“暂未核实完成,客观原因导致记录不完整或无法核实”' }
-]);
-
-const riskDesc = computed(() => {
-  return '自主寻找HR证明人成为的,报告整体无异常(简历对比:候选人简历中XXXXXXX为分公司)的离职时间为2024年3月,实际背调授权书的时间是2024年4月,也请你关注入职邀约时,放不放时间差。';
+onMounted(async () => {
+  if (recordId.value) {
+    try {
+      const res = await getBackCheckReport(recordId.value);
+      if (res.data?.reportStatus !== undefined && res.data?.reportStatus !== null) {
+        reportStatus.value = res.data.reportStatus;
+      } else if (res.data?.conclusion) {
+        reportStatus.value = 2;
+      } else if (res.data?.candidateName) {
+        reportStatus.value = 1;
+      }
+    } catch (e) {
+      // 查询失败保持默认
+    }
+  }
 });
 
-const detailRows = ref([
-  { label: '证明人来源', value: '才是背调项目主导方', ok: true, remark: '' },
-  { label: '证明人', value: '林某某', ok: true, remark: '' },
-  { label: '证明人职位', value: 'HR(广州分公司)', ok: true, remark: '' },
-  { label: '证明人联系方式', value: '135****8804', ok: true, remark: '' },
-  { label: '就职公司', value: 'XXXXXX公司(广州分公司)', ok: true, remark: '' },
-  { label: '就职时间', value: '2017-11-09 ~ 2024-04-16', ok: true, remark: '' },
-  { label: '担任职位', value: '大客户副理(销售助理)', ok: true, remark: '' }
-]);
-
-const basicRows = ref([
-  { label: '证明人来源', value: '才是背调项目主导方', ok: true, remark: '' },
-  { label: '证明人', value: '林某某', ok: true, remark: '' },
-  { label: '证明人职位', value: 'HR(广州分公司)', ok: true, remark: '' },
-  { label: '证明人联系方式', value: '135****8804', ok: true, remark: '' },
-  { label: '就职公司', value: 'XXXXXX公司(广州分公司)', ok: true, remark: 'XXXXXX公司(广州分公司)' },
-  { label: '就职时间', value: '2017-11-09 ~ 2024-04-16', ok: true, remark: '' },
-  { label: '担任职位', value: '大客户副理(销售助理)', ok: true, remark: '' },
-  { label: '工作地点', value: '广州分公司', ok: true, remark: '' },
-  { label: '雇佣类别', value: '合同制员工', ok: true, remark: '' }
-]);
-
-const handleSelect = (key: string) => {
-  if (key === 'overview' || key === 'detail') {
-    activeKey.value = key;
-  }
+const onSubmitted = (status: number) => {
+  reportStatus.value = status;
 };
 
 const handleBack = () => {
   router.back();
 };
-
-const handleSave = () => {
-  ElMessage.success('已保存');
-};
 </script>
 
 <style scoped lang="scss">
@@ -236,80 +119,6 @@ const handleSave = () => {
   }
 }
 
-.report-title {
-  text-align: center;
-  font-size: 22px;
-  font-weight: 700;
-  color: #1d2129;
-  margin: 6px 0 12px;
-}
-
-.report-card {
-  border: 1px solid #ebeef5;
-  border-radius: 8px;
-  overflow: hidden;
-
-  .card-h {
-    padding: 10px 14px;
-    background-color: #409eff;
-    color: #fff;
-    font-weight: 600;
-  }
-
-  :deep(.el-descriptions__body) {
-    background-color: #fff;
-  }
-}
-
-.risk-desc {
-  padding: 12px 14px;
-  line-height: 1.7;
-  font-size: 14px;
-  color: #4e5969;
-}
-
-.risk-dot {
-  display: inline-block;
-  width: 10px;
-  height: 10px;
-  border-radius: 50%;
-
-  &.high {
-    background: #f56c6c;
-  }
-
-  &.mid {
-    background: #e6a23c;
-  }
-
-  &.low {
-    background: #409eff;
-  }
-
-  &.ok {
-    background: #67c23a;
-  }
-
-  &.none {
-    background: #909399;
-  }
-}
-
-.ok {
-  color: #67c23a;
-}
-
-.bad {
-  color: #f56c6c;
-}
-
-:deep(.custom-header th) {
-  background-color: #f7f8fa !important;
-  color: #4e5969;
-  font-weight: 500;
-  padding: 12px 0;
-}
-
 @media screen and (max-width: 992px) {
   .left-panel {
     position: static;