初始化:职引项目 v1.0
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
<template>
|
||||
<view class="page fade-in">
|
||||
<view class="hero">
|
||||
<text class="hero-title">面试记录</text>
|
||||
<text class="hero-sub">回顾你的成长轨迹</text>
|
||||
</view>
|
||||
|
||||
<view class="stats-bar card">
|
||||
<view class="stat">
|
||||
<text class="stat-val">{{ interviewList.length }}</text>
|
||||
<text class="stat-lbl">总次数</text>
|
||||
</view>
|
||||
<view class="stat-sep"></view>
|
||||
<view class="stat">
|
||||
<text class="stat-val">{{ avgScore }}</text>
|
||||
<text class="stat-lbl">平均分</text>
|
||||
</view>
|
||||
<view class="stat-sep"></view>
|
||||
<view class="stat">
|
||||
<text class="stat-val">{{ completedCount }}</text>
|
||||
<text class="stat-lbl">已完成</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="filter-wrap">
|
||||
<view class="filter-inner">
|
||||
<view class="filter-tab" :class="{ active: filter === 'all' }" @click="filter = 'all'">全部</view>
|
||||
<view class="filter-tab" :class="{ active: filter === 'completed' }" @click="filter = 'completed'">已完成</view>
|
||||
<view class="filter-tab" :class="{ active: filter === 'analyzing' }" @click="filter = 'analyzing'">进行中</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="list" v-if="filteredList.length > 0">
|
||||
<view class="record-card card" v-for="(item, idx) in filteredList" :key="idx">
|
||||
<view class="record-top" @click="goDetail(item)">
|
||||
<view class="record-icon">{{ item.score >= 80 ? '🌟' : item.score > 0 ? '📋' : '💬' }}</view>
|
||||
<view class="record-body">
|
||||
<view class="record-name">{{ item.position }}</view>
|
||||
<text class="record-meta">{{ item.time }} · {{ item.duration }}</text>
|
||||
</view>
|
||||
<view class="record-score" :class="scoreLevel(item.score)">
|
||||
{{ item.score ? item.score : '--' }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="record-actions" v-if="item.score > 0">
|
||||
<text class="rec-action" @click="goContribute(item)">💡 贡献面经</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="loading-tip" v-if="loading">加载中...</view>
|
||||
<view class="empty" v-else>
|
||||
<text class="empty-icon">{{ filter !== 'all' ? '🔍' : '📭' }}</text>
|
||||
<text class="empty-title">{{ emptyTitle }}</text>
|
||||
<text class="empty-desc">{{ emptyDesc }}</text>
|
||||
<button class="empty-btn btn-gradient" @click="goInterview" v-if="filter === 'all'">开始第一次面试</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { api } from '../../config'
|
||||
|
||||
const filter = ref('all')
|
||||
const interviewList = ref([])
|
||||
const loading = ref(true)
|
||||
|
||||
const completedCount = computed(() => interviewList.value.filter(i => i.score > 0).length)
|
||||
const avgScore = computed(() => {
|
||||
const scored = interviewList.value.filter(i => i.score > 0)
|
||||
if (scored.length === 0) return '--'
|
||||
return Math.round(scored.reduce((s, i) => s + i.score, 0) / scored.length)
|
||||
})
|
||||
const filteredList = computed(() => {
|
||||
if (filter.value === 'all') return interviewList.value
|
||||
if (filter.value === 'completed') return interviewList.value.filter(i => i.score > 0)
|
||||
return interviewList.value.filter(i => i.score === 0)
|
||||
})
|
||||
const emptyTitle = computed(() => {
|
||||
if (filter.value === 'all') return '暂无面试记录'
|
||||
if (filter.value === 'completed') return '暂无已完成面试'
|
||||
return '暂无进行中面试'
|
||||
})
|
||||
const emptyDesc = computed(() => {
|
||||
if (filter.value === 'all') return '完成你的第一场模拟面试吧'
|
||||
if (filter.value === 'completed') return '继续面试练习'
|
||||
return '所有面试都已评价完成'
|
||||
})
|
||||
|
||||
const formatDate = (d) => {
|
||||
if (!d) return '--'
|
||||
const date = new Date(d)
|
||||
return `${String(date.getMonth()+1).padStart(2,'0')}-${String(date.getDate()).padStart(2,'0')} ${String(date.getHours()).padStart(2,'0')}:${String(date.getMinutes()).padStart(2,'0')}`
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
const token = uni.getStorageSync('token') || ''
|
||||
if (!token) { loading.value = false; return }
|
||||
try {
|
||||
const res = await uni.request({ url: api('/interview/list/all'), method: 'GET', header: { 'Authorization': `Bearer ${token}` } })
|
||||
if (res.statusCode === 200 && Array.isArray(res.data)) {
|
||||
interviewList.value = res.data.map(i => ({
|
||||
position: i.position || '通用岗位', time: formatDate(i.createdAt || i.time),
|
||||
score: i.totalScore || 0, duration: `${i.questionCount || 0}题`, id: i.id,
|
||||
}))
|
||||
}
|
||||
} catch(e) { console.error(e) }
|
||||
finally { loading.value = false }
|
||||
})
|
||||
|
||||
const scoreLevel = (s) => { if (!s) return 'pending'; if (s >= 80) return 'good'; if (s >= 60) return 'medium'; return 'poor' }
|
||||
const goDetail = (item) => { if (item.id) uni.navigateTo({ url: `/pages/report/report?interviewId=${item.id}` }) }
|
||||
const goContribute = (item) => {
|
||||
uni.navigateTo({ url: `/pages/contribute/contribute?interviewId=${item.id}&position=${encodeURIComponent(item.position)}` })
|
||||
}
|
||||
const goInterview = () => uni.navigateTo({ url: '/pages/interview/interview' })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page { height: 100%; overflow-y: auto; background: var(--color-bg); }
|
||||
.hero { background: linear-gradient(135deg, var(--color-gradient-start) 0%, var(--color-gradient-mid) 50%, var(--color-gradient-end) 100%); padding: 48rpx 32rpx 72rpx; border-radius: 0 0 48rpx 48rpx; }
|
||||
.hero-title { font-size: 40rpx; font-weight: 700; color: #FFF; display: block; }
|
||||
.hero-sub { font-size: 22rpx; color: rgba(255,255,255,0.7); margin-top: 8rpx; display: block; }
|
||||
|
||||
.stats-bar { display: flex; align-items: center; padding: 24rpx; margin: -40rpx 32rpx 0; position: relative; z-index: 1; border-radius: var(--radius-lg); }
|
||||
.stat { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 6rpx; }
|
||||
.stat-val { font-size: 36rpx; font-weight: 700; color: var(--color-primary); }
|
||||
.stat-lbl { font-size: 20rpx; color: var(--color-text-tertiary); }
|
||||
.stat-sep { width: 1rpx; height: 40rpx; background: var(--color-border); }
|
||||
|
||||
.filter-wrap { padding: 24rpx 32rpx 0; }
|
||||
.filter-inner { display: flex; gap: 8rpx; background: #FFF; border-radius: var(--radius-md); padding: 6rpx; }
|
||||
.filter-tab { flex: 1; text-align: center; font-size: 24rpx; color: var(--color-text-secondary); padding: 14rpx 0; border-radius: var(--radius-sm); }
|
||||
.filter-tab.active { background: var(--color-primary); color: #FFF; font-weight: 600; }
|
||||
|
||||
.list { padding: 20rpx 32rpx 48rpx; }
|
||||
.record-card { padding: 24rpx 28rpx; margin-bottom: 16rpx; border-radius: var(--radius-lg); }
|
||||
.record-top { display: flex; align-items: center; gap: 16rpx; }
|
||||
.record-icon { font-size: 40rpx; width: 64rpx; height: 64rpx; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
|
||||
.record-body { flex: 1; min-width: 0; }
|
||||
.record-name { font-size: 28rpx; font-weight: 600; color: var(--color-text); }
|
||||
.record-meta { font-size: 20rpx; color: var(--color-text-tertiary); margin-top: 6rpx; display: block; }
|
||||
.record-score { font-size: 28rpx; font-weight: 700; width: 72rpx; text-align: right; flex-shrink: 0; }
|
||||
.record-score.good { color: var(--color-success); }
|
||||
.record-score.medium { color: var(--color-warning); }
|
||||
.record-score.poor { color: var(--color-error); }
|
||||
.record-score.pending { color: var(--color-text-tertiary); }
|
||||
|
||||
.record-actions { margin-top: 12rpx; padding-top: 12rpx; border-top: 1rpx solid var(--color-border); display: flex; gap: 20rpx; }
|
||||
.rec-action { font-size: 22rpx; color: var(--color-primary); font-weight: 500; }
|
||||
|
||||
.empty { display: flex; flex-direction: column; align-items: center; padding: 120rpx 32rpx 0; }
|
||||
.empty-icon { font-size: 80rpx; margin-bottom: 20rpx; }
|
||||
.empty-title { font-size: 28rpx; font-weight: 600; color: var(--color-text); }
|
||||
.empty-desc { font-size: 22rpx; color: var(--color-text-tertiary); margin-top: 8rpx; margin-bottom: 36rpx; }
|
||||
.empty-btn { padding: 18rpx 48rpx; border-radius: var(--radius-round); font-size: 26rpx; }
|
||||
.loading-tip { text-align: center; padding: 80rpx; font-size: 24rpx; color: var(--color-text-tertiary); }
|
||||
</style>
|
||||
Reference in New Issue
Block a user