feat(aihr): align figma pages for release
This commit is contained in:
@@ -1,14 +1,11 @@
|
||||
<template>
|
||||
<view class="page candidate-subpage progress-page">
|
||||
<view class="subpage-header">
|
||||
<button class="subpage-back-button" aria-label="返回候选首页" title="返回候选首页" @click="goBack">←</button>
|
||||
<view class="subpage-heading">
|
||||
<text class="page-title">进度通知</text>
|
||||
<text class="page-subtitle">面试与资料审核状态</text>
|
||||
<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>
|
||||
<button v-if="loggedIn" class="ghost-button refresh-button" :disabled="loading" @click="loadPage">
|
||||
{{ loading ? '刷新中' : '刷新' }}
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<view v-if="message" class="status-message">{{ message }}</view>
|
||||
@@ -16,66 +13,50 @@
|
||||
<view v-if="!loggedIn" class="panel login-panel">
|
||||
<view>
|
||||
<text class="panel-title">需要登录</text>
|
||||
<text class="muted block">登录后查看本人面试和资料审核进度。</text>
|
||||
<text class="muted block">登录后查看本人面试记录。</text>
|
||||
</view>
|
||||
<button class="primary-button login-button" @click="goLogin">去登录</button>
|
||||
</view>
|
||||
|
||||
<template v-else>
|
||||
<view class="panel status-panel">
|
||||
<view class="status-head">
|
||||
<text class="status-badge">{{ flow.badge }}</text>
|
||||
<text class="status-progress">{{ flow.progressText }}</text>
|
||||
<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="status-title">{{ flow.notice }}</text>
|
||||
<text class="status-desc">{{ flow.desc }}</text>
|
||||
<view class="progress-bar">
|
||||
<view class="progress-fill" :style="{ width: `${flow.progress}%` }" />
|
||||
</view>
|
||||
<button class="primary-button next-button" :disabled="loading" @click="runCurrentAction">
|
||||
{{ flow.stage === 'review' || flow.stage === 'complete' ? '刷新进度' : flow.cta }}
|
||||
</button>
|
||||
<text class="score-summary">{{ latestRecord.summary }}</text>
|
||||
<text class="score-evidence">{{ questionRecords.length }} 道题 · 已保存真实回答</text>
|
||||
</view>
|
||||
|
||||
<view class="panel steps-panel">
|
||||
<text class="panel-title">办理流程</text>
|
||||
<view class="step-list">
|
||||
<view v-for="step in steps" :key="step.index" class="step-row" :class="step.status">
|
||||
<view class="step-rail">
|
||||
<text class="step-dot">{{ step.status === 'complete' ? '✓' : step.index }}</text>
|
||||
<view v-if="step.index < steps.length" class="step-line" />
|
||||
</view>
|
||||
<view class="step-copy">
|
||||
<text class="step-title">{{ step.title }}</text>
|
||||
<text class="step-desc">{{ step.desc }}</text>
|
||||
</view>
|
||||
<text class="step-status">{{ stepStatusLabel(step.status) }}</text>
|
||||
<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="panel evidence-panel">
|
||||
<text class="panel-title">办理依据</text>
|
||||
<view v-if="latestRecord" class="evidence-row">
|
||||
<text class="evidence-label">面试记录</text>
|
||||
<view class="evidence-copy">
|
||||
<text class="evidence-title">{{ latestRecord.position }} · {{ latestRecord.score }}分</text>
|
||||
<text class="evidence-desc">{{ latestRecord.time }} · {{ latestRecord.result }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="latestMaterial" class="evidence-row">
|
||||
<text class="evidence-label">最新资料</text>
|
||||
<view class="evidence-copy">
|
||||
<view class="material-title-row">
|
||||
<text class="evidence-title">{{ latestMaterial.fileName }}</text>
|
||||
<text class="status-pill" :class="materialStatusTone(latestMaterial.status)">{{ latestMaterial.status }}</text>
|
||||
</view>
|
||||
<text class="evidence-desc">{{ latestMaterial.time }} · {{ formatCandidateFileSize(latestMaterial.fileSize) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="!latestRecord && !latestMaterial && !loading" class="empty compact-empty">暂无办理记录</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>
|
||||
|
||||
@@ -85,84 +66,51 @@ 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 {
|
||||
buildCandidateFlow,
|
||||
buildCandidateSteps,
|
||||
candidateRoutes,
|
||||
formatCandidateFileSize,
|
||||
materialStatusTone
|
||||
} from '@/services/candidate-view';
|
||||
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 loading = ref(false);
|
||||
const message = ref('');
|
||||
|
||||
const flow = computed(() => buildCandidateFlow(records.value, materials.value));
|
||||
const steps = computed(() => buildCandidateSteps(flow.value));
|
||||
const latestRecord = computed(() => records.value[0] || null);
|
||||
const latestMaterial = computed(() => materials.value[0] || null);
|
||||
|
||||
const refreshAuthState = () => {
|
||||
loggedIn.value = isLoggedIn();
|
||||
};
|
||||
|
||||
const clearData = () => {
|
||||
records.value = [];
|
||||
materials.value = [];
|
||||
};
|
||||
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 loadPage = async () => {
|
||||
refreshAuthState();
|
||||
message.value = '';
|
||||
loggedIn.value = isLoggedIn();
|
||||
if (!loggedIn.value) {
|
||||
clearData();
|
||||
records.value = [];
|
||||
materials.value = [];
|
||||
return;
|
||||
}
|
||||
if (loading.value) return;
|
||||
loading.value = true;
|
||||
try {
|
||||
const [nextMaterials, nextRecords] = await Promise.all([
|
||||
[materials.value, records.value] = await Promise.all([
|
||||
getCandidateMaterials(),
|
||||
getCandidateInterviewRecords()
|
||||
]);
|
||||
materials.value = nextMaterials;
|
||||
records.value = nextRecords;
|
||||
message.value = '';
|
||||
} catch (error) {
|
||||
refreshAuthState();
|
||||
if (!loggedIn.value) {
|
||||
clearData();
|
||||
message.value = '登录已过期,请重新登录';
|
||||
} else {
|
||||
message.value = error instanceof Error ? error.message : '办理进度加载失败';
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
loggedIn.value = isLoggedIn();
|
||||
message.value = loggedIn.value ? (error instanceof Error ? error.message : '面试记录加载失败') : '登录已过期,请重新登录';
|
||||
}
|
||||
};
|
||||
|
||||
const runCurrentAction = () => {
|
||||
if (flow.value.stage === 'review' || flow.value.stage === 'complete') {
|
||||
void loadPage();
|
||||
return;
|
||||
}
|
||||
uni.navigateTo({ url: flow.value.route });
|
||||
};
|
||||
|
||||
const stepStatusLabel = (status: 'complete' | 'current' | 'pending') => {
|
||||
if (status === 'complete') return '已完成';
|
||||
if (status === 'current') return '当前';
|
||||
return '待办理';
|
||||
};
|
||||
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 });
|
||||
@@ -178,228 +126,189 @@ onShow(() => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.candidate-subpage {
|
||||
background: #f4f7fb;
|
||||
.candidate-record-page {
|
||||
background: #f9f9f9;
|
||||
}
|
||||
|
||||
.candidate-subpage .panel,
|
||||
.candidate-subpage button {
|
||||
border-radius: 8px;
|
||||
.record-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
padding: 10px 4px 16px;
|
||||
}
|
||||
|
||||
.login-panel,
|
||||
.status-head,
|
||||
.evidence-row,
|
||||
.material-title-row {
|
||||
.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;
|
||||
}
|
||||
|
||||
.refresh-button {
|
||||
min-width: 76px;
|
||||
padding: 0 10px;
|
||||
.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;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.login-button {
|
||||
min-width: 96px;
|
||||
}
|
||||
|
||||
.status-panel {
|
||||
border-left: 4px solid #e60012;
|
||||
}
|
||||
|
||||
.status-badge,
|
||||
.status-progress {
|
||||
color: #b91c1c;
|
||||
font-size: 13px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.status-title {
|
||||
display: block;
|
||||
margin-top: 14px;
|
||||
color: #172033;
|
||||
font-size: 21px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.status-desc {
|
||||
display: block;
|
||||
margin-top: 8px;
|
||||
color: #64748b;
|
||||
font-size: 14px;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 8px;
|
||||
.question-empty {
|
||||
margin-top: 16px;
|
||||
overflow: hidden;
|
||||
border-radius: 4px;
|
||||
background: #fee2e2;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
border-radius: inherit;
|
||||
background: #e60012;
|
||||
}
|
||||
|
||||
.next-button {
|
||||
width: 100%;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.step-list {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.step-row {
|
||||
display: grid;
|
||||
min-height: 76px;
|
||||
grid-template-columns: 30px minmax(0, 1fr) auto;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.step-rail {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.step-dot {
|
||||
display: flex;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
background: #e2e8f0;
|
||||
color: #475569;
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.step-row.complete .step-dot {
|
||||
background: #dcfce7;
|
||||
color: #166534;
|
||||
}
|
||||
|
||||
.step-row.current .step-dot {
|
||||
background: #fee2e2;
|
||||
color: #b91c1c;
|
||||
}
|
||||
|
||||
.step-line {
|
||||
width: 2px;
|
||||
flex: 1;
|
||||
background: #e2e8f0;
|
||||
}
|
||||
|
||||
.step-row.complete .step-line {
|
||||
background: #22c55e;
|
||||
}
|
||||
|
||||
.step-title {
|
||||
display: block;
|
||||
color: #172033;
|
||||
font-size: 15px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.step-desc {
|
||||
display: block;
|
||||
margin-top: 5px;
|
||||
color: #64748b;
|
||||
font-size: 12px;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.step-status {
|
||||
color: #64748b;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.step-row.current .step-status {
|
||||
color: #b91c1c;
|
||||
}
|
||||
|
||||
.step-row.complete .step-status {
|
||||
color: #15803d;
|
||||
}
|
||||
|
||||
.evidence-row {
|
||||
justify-content: flex-start;
|
||||
margin-top: 14px;
|
||||
padding-top: 14px;
|
||||
border-top: 1px solid #e2e8f0;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.evidence-label {
|
||||
flex: 0 0 64px;
|
||||
color: #64748b;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.evidence-copy {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.evidence-title {
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
color: #172033;
|
||||
font-size: 14px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.evidence-desc {
|
||||
display: block;
|
||||
margin-top: 6px;
|
||||
color: #64748b;
|
||||
font-size: 12px;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.status-pill {
|
||||
flex: 0 0 auto;
|
||||
padding: 5px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.status-pill.pending {
|
||||
background: #fef3c7;
|
||||
color: #92400e;
|
||||
}
|
||||
|
||||
.status-pill.passed {
|
||||
background: #dcfce7;
|
||||
color: #166534;
|
||||
}
|
||||
|
||||
.status-pill.rejected {
|
||||
background: #fee2e2;
|
||||
color: #991b1b;
|
||||
}
|
||||
|
||||
.compact-empty {
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user