feat(aihr): persist interview human review

This commit is contained in:
2026-07-14 09:44:03 +08:00
parent ccb164a45a
commit e55abec630
10 changed files with 271 additions and 14 deletions
+20
View File
@@ -64,14 +64,25 @@ export type InterviewFinishResponse = {
};
export type InterviewRecord = {
sessionId?: string;
time: string;
candidate: string;
position: string;
score: number;
reviewedScore?: number | null;
finalScore?: number;
reviewStatus?: string;
reviewer?: string;
reviewNote?: string;
result: string;
summary: string;
};
export type InterviewReviewRequest = {
reviewedScore: number;
reviewNote?: string;
};
export type CandidateLinkResponse = {
candidateId: string;
extPartyId: string;
@@ -126,6 +137,15 @@ export function listInterviewRecords(limit = 20): Promise<ApiResult<InterviewRec
});
}
export function reviewInterviewRecord(sessionId: string, data: InterviewReviewRequest): Promise<ApiResult<null>> {
return request({
url: `/api/recruit/interview/records/${encodeURIComponent(sessionId)}/review`,
method: 'post',
data,
headers: { repeatSubmit: false }
});
}
export function getCandidateEmployeeLink(candidateId: string): Promise<ApiResult<CandidateLinkResponse | null>> {
return request({
url: '/api/recruit/interview/candidate-links',
+107 -1
View File
@@ -174,12 +174,56 @@
<el-table-column prop="time" label="时间" width="128" />
<el-table-column prop="candidate" label="候选人" width="120" />
<el-table-column prop="position" label="岗位" min-width="150" />
<el-table-column prop="score" label="得分" width="80" />
<el-table-column prop="score" label="AI得分" width="90" />
<el-table-column label="人工复核分" width="110">
<template #default="{ row }">{{ row.reviewedScore ?? '—' }}</template>
</el-table-column>
<el-table-column label="最终采用分" width="110">
<template #default="{ row }">{{ row.finalScore ?? row.score }}</template>
</el-table-column>
<el-table-column prop="result" label="结论" width="120" />
<el-table-column label="复核状态" width="100">
<template #default="{ row }">
<el-tag :type="row.reviewStatus === '已复核' ? 'success' : 'warning'" size="small">
{{ row.reviewStatus || '待复核' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="summary" label="AI 摘要" min-width="260" />
<el-table-column label="操作" width="100" fixed="right">
<template #default="{ row }">
<el-button link type="primary" :disabled="!row.sessionId" @click="openInterviewReview(row)">人工复核</el-button>
</template>
</el-table-column>
</el-table>
</section>
<el-dialog v-model="interviewReviewVisible" title="人工复核 AI 面试结果" width="520px">
<div v-if="reviewingRecord" class="interview-review-context">
<strong>{{ reviewingRecord.candidate }} · {{ reviewingRecord.position }}</strong>
<span>AI 得分 {{ reviewingRecord.score }},当前最终采用分 {{ reviewingRecord.finalScore ?? reviewingRecord.score }}</span>
</div>
<el-form label-width="104px" class="interview-review-form">
<el-form-item label="人工复核分" required>
<el-input-number v-model="reviewScore" :min="0" :max="100" :precision="0" controls-position="right" />
</el-form-item>
<el-form-item label="复核意见">
<el-input
v-model="reviewNote"
type="textarea"
:rows="4"
maxlength="1000"
show-word-limit
placeholder="记录人工判断依据,可说明是否覆盖 AI 建议"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="interviewReviewVisible = false">取消</el-button>
<el-button type="danger" :loading="interviewReviewSaving" @click="submitInterviewReview">保存复核</el-button>
</template>
</el-dialog>
<section class="panel record-panel material-review-panel">
<div class="panel-title title-with-extra">
<div>
@@ -232,6 +276,7 @@ import {
linkCandidateEmployee,
listCandidateMaterialsForReview,
listInterviewRecords,
reviewInterviewRecord,
reviewCandidateMaterial,
startInterview,
submitInterviewAnswer,
@@ -426,6 +471,11 @@ const apiQuestions = ref<InterviewQuestion[]>([]);
const apiScoreResult = ref<CandidateSeed['scoreResult'] | null>(null);
const answers = reactive<Record<string, string>>({});
const records = ref<InterviewRecord[]>([...baseRecords]);
const interviewReviewVisible = ref(false);
const interviewReviewSaving = ref(false);
const reviewingRecord = ref<InterviewRecord | null>(null);
const reviewScore = ref<number | undefined>(undefined);
const reviewNote = ref('');
const materialReviews = ref<CandidateMaterialReview[]>([]);
const materialStatus = ref('待审核');
const materialLoading = ref(false);
@@ -626,6 +676,38 @@ const loadInterviewRecords = async () => {
}
};
const openInterviewReview = (row: InterviewRecord) => {
if (!row.sessionId) {
ElMessage.warning('当前记录没有可复核的后端会话');
return;
}
reviewingRecord.value = row;
reviewScore.value = row.reviewedScore ?? row.finalScore ?? row.score;
reviewNote.value = row.reviewNote ?? '';
interviewReviewVisible.value = true;
};
const submitInterviewReview = async () => {
if (!reviewingRecord.value?.sessionId || reviewScore.value === undefined) {
ElMessage.warning('请输入人工复核分');
return;
}
interviewReviewSaving.value = true;
try {
await reviewInterviewRecord(reviewingRecord.value.sessionId, {
reviewedScore: reviewScore.value,
reviewNote: reviewNote.value.trim()
});
interviewReviewVisible.value = false;
await loadInterviewRecords();
ElMessage.success('面试结果人工复核已保存');
} catch {
ElMessage.error('面试结果人工复核失败');
} finally {
interviewReviewSaving.value = false;
}
};
const loadCandidateLink = async () => {
candidateLink.value = null;
linkExtPartyId.value = '';
@@ -1196,6 +1278,30 @@ watch(candidateId, loadCandidateLink);
width: 110px;
}
.interview-review-context {
display: grid;
gap: 6px;
margin-bottom: 18px;
padding: 12px;
border-radius: 8px;
background: #fff8ed;
color: #46566e;
strong {
color: #26354c;
}
span {
font-size: 12px;
}
}
.interview-review-form {
:deep(.el-input-number) {
width: 180px;
}
}
.material-name {
display: grid;
gap: 2px;