Files
prop-ai-hr/mobile-uni/src/pages/candidate/progress/index.vue
T

321 lines
7.7 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="page candidate-record-page">
<view class="record-header">
<button class="back-button" aria-label="返回候选首页" @click="goBack">←</button>
<view>
<text class="page-title">面试记录</text>
<text class="page-subtitle">{{ latestRecord ? `${latestRecord.time} · ${latestRecord.position}` : '面试与资料审核状态' }}</text>
</view>
</view>
<view v-if="message" class="status-message">{{ message }}</view>
<view v-if="!loggedIn" class="panel login-panel">
<view>
<text class="panel-title">需要登录</text>
<text class="muted block">登录后查看本人面试记录。</text>
</view>
<button class="primary-button login-button" @click="goLogin">去登录</button>
</view>
<template v-else-if="latestRecord">
<view class="score-card">
<view class="score-card-head">
<view>
<text class="score-value">{{ latestRecord.score }} 分</text>
<text class="score-meta">AI 初评 · {{ latestRecord.result }}</text>
</view>
<text class="review-badge">{{ reviewStatusLabel }}</text>
</view>
<text class="score-summary">{{ latestRecord.summary }}</text>
<text class="score-evidence">{{ questionRecords.length }} 道题 · 已保存真实回答</text>
</view>
<view class="question-card">
<text class="section-title">问答记录</text>
<view v-for="item in questionRecords" :key="item.seq" class="question-record">
<view class="question-title-row">
<text class="question-index">{{ item.seq }}</text>
<text class="question-title">{{ item.question }}</text>
</view>
<text class="answer-text">回答:{{ item.answer }}</text>
<text class="answer-rating" :class="ratingTone(item.rating)">{{ item.rating }}</text>
</view>
<view v-if="!questionRecords.length" class="empty question-empty">暂无问答明细</view>
</view>
<view class="review-status-card">
<text class="review-status-title">人工复核状态</text>
<text class="review-status-desc">{{ reviewStatusDescription }}</text>
</view>
<button class="primary-button home-button" @click="goBack">返回候选人中心</button>
</template>
<view v-else class="panel empty-record-panel">
<text class="panel-title">暂无面试记录</text>
<text class="muted block">完成一次结构化面试后,这里会展示评分和问答明细。</text>
<button class="primary-button home-button" @click="openInterview">开始面试</button>
</view>
</view>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
import { onShow } from '@dcloudio/uni-app';
import type { CandidateMaterial, InterviewRecord } from '@/types/api';
import { isLoggedIn, rememberLoginRedirect } from '@/services/auth';
import { getCandidateInterviewRecords, getCandidateMaterials } from '@/services/candidate';
import { candidateRoutes } from '@/services/candidate-view';
import { resetPageScroll } from '@/services/navigation';
const currentPath = '/pages/candidate/progress/index';
const records = ref<InterviewRecord[]>([]);
const materials = ref<CandidateMaterial[]>([]);
const loggedIn = ref(false);
const message = ref('');
const latestRecord = computed(() => records.value[0] || null);
const latestMaterial = computed(() => materials.value[0] || null);
const questionRecords = computed(() => latestRecord.value?.questions || []);
const reviewStatusLabel = computed(() => latestMaterial.value?.status === '已通过' ? '人工复核完成' : '人工复核中');
const reviewStatusDescription = computed(() => latestMaterial.value?.status === '已通过'
? 'HR 已完成 AI 评分与补充资料复核,后续通知会同步更新。'
: 'HR 正在复核 AI 评分与补充资料,预计 1-2 个工作日更新。');
const clearData = () => {
records.value = [];
materials.value = [];
};
const loadPage = async () => {
loggedIn.value = isLoggedIn();
if (!loggedIn.value) {
records.value = [];
materials.value = [];
return;
}
try {
[materials.value, records.value] = await Promise.all([
getCandidateMaterials(),
getCandidateInterviewRecords()
]);
message.value = '';
} catch (error) {
clearData();
loggedIn.value = isLoggedIn();
message.value = loggedIn.value ? (error instanceof Error ? error.message : '面试记录加载失败') : '登录已过期,请重新登录';
}
};
const ratingTone = (rating: string) => rating === '优秀' ? 'excellent' : rating === '良好' ? 'good' : 'needs-work';
const goLogin = () => {
rememberLoginRedirect(currentPath);
uni.redirectTo({ url: '/pages/auth/login/index' });
};
const openInterview = () => uni.navigateTo({ url: candidateRoutes.interview });
const goBack = () => {
if (getCurrentPages().length <= 1) {
uni.redirectTo({ url: candidateRoutes.index });
return;
}
uni.navigateBack({ delta: 1, fail: () => uni.redirectTo({ url: candidateRoutes.index }) });
};
onShow(() => {
resetPageScroll();
void loadPage();
});
</script>
<style scoped>
.candidate-record-page {
background: #f9f9f9;
}
.record-header {
display: flex;
align-items: flex-start;
gap: 12px;
padding: 10px 4px 16px;
}
.back-button {
width: 28px;
min-width: 28px;
height: 28px;
padding: 0;
border: 0;
background: transparent;
color: #111;
font-size: 24px;
line-height: 28px;
}
.page-subtitle {
display: block;
margin-top: 5px;
color: #858991;
font-size: 13px;
}
.score-card,
.question-card,
.review-status-card {
border-radius: 18px;
background: #fff;
}
.score-card {
padding: 18px 16px;
}
.score-card-head,
.question-title-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.score-value {
display: block;
color: #e60012;
font-size: 28px;
font-weight: 800;
}
.score-meta,
.score-evidence {
display: block;
margin-top: 5px;
color: #858991;
font-size: 13px;
}
.score-evidence {
margin-top: 16px;
font-size: 12px;
}
.score-summary {
display: block;
margin-top: 12px;
color: #858991;
font-size: 13px;
line-height: 1.5;
}
.review-badge {
padding: 6px 10px;
border-radius: 16px;
background: #fff4df;
color: #d98200;
font-size: 12px;
white-space: nowrap;
}
.question-card {
margin-top: 14px;
padding: 18px 16px;
}
.section-title {
color: #111;
font-size: 18px;
font-weight: 700;
}
.question-record {
padding: 18px 0 0;
}
.question-index {
display: inline-flex;
width: 22px;
height: 22px;
flex: 0 0 22px;
align-items: center;
justify-content: center;
border-radius: 50%;
background: #fff0f1;
color: #e60012;
font-size: 12px;
}
.question-title {
flex: 1;
color: #111;
font-size: 14px;
line-height: 1.45;
}
.answer-text {
display: block;
margin: 8px 0 0 34px;
color: #858991;
font-size: 12px;
line-height: 1.5;
}
.answer-rating {
display: block;
margin: 6px 0 0 34px;
font-size: 12px;
font-weight: 700;
}
.answer-rating.excellent { color: #16a34a; }
.answer-rating.good { color: #e60012; }
.answer-rating.needs-work { color: #d98200; }
.review-status-card {
margin-top: 14px;
padding: 16px;
background: #eef6ff;
}
.review-status-title {
display: block;
color: #111;
font-size: 15px;
font-weight: 700;
}
.review-status-desc {
display: block;
margin-top: 6px;
color: #858991;
font-size: 12px;
line-height: 1.5;
}
.home-button {
width: 100%;
margin-top: 14px;
}
.login-panel,
.empty-record-panel {
border-radius: 18px;
}
.block {
display: block;
margin-top: 8px;
line-height: 1.5;
}
.login-button {
min-width: 96px;
margin-top: 14px;
}
.question-empty {
margin-top: 16px;
}
</style>