All checks were successful
continuous-integration/drone/push Build is passing
422 lines
11 KiB
Vue
422 lines
11 KiB
Vue
<script setup>
|
||
/**
|
||
* 往期测评页面
|
||
* 展示用户的历史测评记录
|
||
*/
|
||
import { ref, computed, onMounted } from 'vue'
|
||
import { onShow, onPullDownRefresh, onReachBottom } from '@dcloudio/uni-app'
|
||
import { useUserStore } from '@/store/user.js'
|
||
import { useAuth } from '@/composables/useAuth.js'
|
||
import { getHistoryList, retestRecord } from '@/api/assessment.js'
|
||
import Empty from '@/components/Empty/index.vue'
|
||
import Loading from '@/components/Loading/index.vue'
|
||
|
||
const userStore = useUserStore()
|
||
const { checkLogin } = useAuth()
|
||
|
||
// 测评状态常量(与后端一致)
|
||
const ASSESSMENT_STATUS = {
|
||
PENDING: 1, // 待测评
|
||
TESTING: 2, // 测评中
|
||
GENERATING: 3, // 生成中
|
||
COMPLETED: 4, // 已完成
|
||
FAILED: 5, // 生成失败
|
||
DATA_READY: 6, // 数据已就绪(PDF生成中)
|
||
NEED_RETEST: 7 // 需重测(全同分)
|
||
}
|
||
|
||
// 状态
|
||
const loading = ref(false)
|
||
const refreshing = ref(false)
|
||
const historyList = ref([])
|
||
const page = ref(1)
|
||
const pageSize = ref(10)
|
||
const total = ref(0)
|
||
const noMore = ref(false)
|
||
|
||
// 计算属性
|
||
const isEmpty = computed(() => !loading.value && historyList.value.length === 0)
|
||
|
||
/**
|
||
* 获取测评状态样式类
|
||
*/
|
||
function getStatusClass(status) {
|
||
const classMap = {
|
||
[ASSESSMENT_STATUS.PENDING]: 'status-pending',
|
||
[ASSESSMENT_STATUS.TESTING]: 'status-testing',
|
||
[ASSESSMENT_STATUS.GENERATING]: 'status-generating',
|
||
[ASSESSMENT_STATUS.COMPLETED]: 'status-completed',
|
||
[ASSESSMENT_STATUS.FAILED]: 'status-failed',
|
||
[ASSESSMENT_STATUS.DATA_READY]: 'status-generating',
|
||
[ASSESSMENT_STATUS.NEED_RETEST]: 'status-retest'
|
||
}
|
||
return classMap[status] || ''
|
||
}
|
||
|
||
/**
|
||
* 加载测评历史列表
|
||
*/
|
||
async function loadHistoryList(isRefresh = false) {
|
||
if (loading.value) return
|
||
|
||
if (isRefresh) {
|
||
page.value = 1
|
||
noMore.value = false
|
||
}
|
||
|
||
loading.value = true
|
||
|
||
try {
|
||
const res = await getHistoryList({
|
||
page: page.value,
|
||
pageSize: pageSize.value
|
||
})
|
||
|
||
if (res.code === 0 && res.data) {
|
||
const list = res.data.list || []
|
||
total.value = res.data.total || 0
|
||
|
||
if (isRefresh) {
|
||
historyList.value = list
|
||
} else {
|
||
historyList.value = [...historyList.value, ...list]
|
||
}
|
||
|
||
// 判断是否还有更多
|
||
noMore.value = historyList.value.length >= total.value
|
||
} else {
|
||
uni.showToast({
|
||
title: res.message || '获取测评记录失败',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
} catch (error) {
|
||
console.error('获取测评历史失败:', error)
|
||
uni.showToast({
|
||
title: '网络错误,请重试',
|
||
icon: 'none'
|
||
})
|
||
} finally {
|
||
loading.value = false
|
||
refreshing.value = false
|
||
uni.stopPullDownRefresh()
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 下拉刷新
|
||
*/
|
||
onPullDownRefresh(() => {
|
||
refreshing.value = true
|
||
loadHistoryList(true)
|
||
})
|
||
|
||
/**
|
||
* 上拉加载更多
|
||
*/
|
||
onReachBottom(() => {
|
||
if (!noMore.value && !loading.value) {
|
||
page.value++
|
||
loadHistoryList()
|
||
}
|
||
})
|
||
|
||
/**
|
||
* 查看测评结果
|
||
*/
|
||
async function viewResult(record) {
|
||
// 已完成 - 查看报告
|
||
if (record.status === ASSESSMENT_STATUS.COMPLETED) {
|
||
uni.navigateTo({
|
||
url: `/pages/assessment/result/index?recordId=${record.id}`
|
||
})
|
||
return
|
||
}
|
||
|
||
// 数据已就绪(PDF生成中)- 可查看网页报告
|
||
if (record.status === ASSESSMENT_STATUS.DATA_READY) {
|
||
uni.navigateTo({
|
||
url: `/pages/assessment/result/index?recordId=${record.id}`
|
||
})
|
||
return
|
||
}
|
||
|
||
// 生成中 - 提示等待
|
||
if (record.status === ASSESSMENT_STATUS.GENERATING) {
|
||
uni.showToast({
|
||
title: '报告生成中,请稍后查看',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
|
||
// 待测评/测评中 - 跳转到答题页继续
|
||
if (record.status === ASSESSMENT_STATUS.PENDING || record.status === ASSESSMENT_STATUS.TESTING) {
|
||
uni.navigateTo({
|
||
url: `/pages/assessment/info/index?typeId=${record.assessmentTypeId || 1}`
|
||
})
|
||
return
|
||
}
|
||
|
||
// 需重测 - 调用重测接口后跳转答题页(免费重测)
|
||
if (record.status === ASSESSMENT_STATUS.NEED_RETEST) {
|
||
await handleRetestFromHistory(record)
|
||
return
|
||
}
|
||
|
||
// 生成失败 - 调用重测接口后跳转答题页(免费重测)
|
||
if (record.status === ASSESSMENT_STATUS.FAILED) {
|
||
await handleRetestFromHistory(record)
|
||
return
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 从往期测评列表发起重测(免费)
|
||
*/
|
||
async function handleRetestFromHistory(record) {
|
||
try {
|
||
uni.showLoading({ title: '重置中...' })
|
||
const res = await retestRecord(record.id)
|
||
uni.hideLoading()
|
||
|
||
if (res && res.code === 0) {
|
||
uni.navigateTo({
|
||
url: `/pages/assessment/questions/index?typeId=${record.assessmentTypeId || 1}&recordId=${record.id}`
|
||
})
|
||
} else {
|
||
uni.showToast({ title: res?.message || '重置失败,请重试', icon: 'none' })
|
||
}
|
||
} catch (error) {
|
||
uni.hideLoading()
|
||
console.error('重测重置失败:', error)
|
||
uni.showToast({ title: '重置失败,请重试', icon: 'none' })
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 页面显示时检查登录状态并加载数据
|
||
*/
|
||
onShow(() => {
|
||
userStore.restoreFromStorage()
|
||
if (checkLogin()) {
|
||
loadHistoryList(true)
|
||
}
|
||
})
|
||
|
||
/**
|
||
* 页面加载
|
||
*/
|
||
onMounted(() => {
|
||
userStore.restoreFromStorage()
|
||
})
|
||
</script>
|
||
|
||
<template>
|
||
<view class="history-page">
|
||
<!-- 页面加载中 -->
|
||
<Loading type="page" :loading="loading && historyList.length === 0" />
|
||
|
||
<!-- 测评记录列表 -->
|
||
<view class="history-list" v-if="!isEmpty">
|
||
<view
|
||
class="history-card"
|
||
v-for="record in historyList"
|
||
:key="record.id"
|
||
@click="viewResult(record)"
|
||
>
|
||
<!-- 卡片头部:测评名称 + 状态 -->
|
||
<view class="card-header">
|
||
<view class="assessment-name">{{ record.assessmentName || '多元智能测评' }}</view>
|
||
<view class="assessment-status" :class="getStatusClass(record.status)">
|
||
{{ record.statusText }}
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 卡片内容:测评人 + 测评日期 -->
|
||
<view class="card-content">
|
||
<view class="info-row">
|
||
<text class="info-label">测评人</text>
|
||
<text class="info-value">{{ record.name || '--' }}</text>
|
||
</view>
|
||
<view class="info-row">
|
||
<text class="info-label">测评日期</text>
|
||
<text class="info-value">{{ record.testDate || '--' }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 卡片底部:已完成显示查看报告,需重测显示重新测试 -->
|
||
<view class="card-footer" v-if="record.status === ASSESSMENT_STATUS.COMPLETED || record.status === ASSESSMENT_STATUS.DATA_READY">
|
||
<view class="view-btn">
|
||
<text>查看报告</text>
|
||
<view class="arrow-icon"></view>
|
||
</view>
|
||
</view>
|
||
<view class="card-footer" v-else-if="record.status === ASSESSMENT_STATUS.NEED_RETEST">
|
||
<view class="view-btn retest-btn">
|
||
<text>重新测试</text>
|
||
<view class="arrow-icon"></view>
|
||
</view>
|
||
</view>
|
||
<view class="card-footer" v-else-if="record.status === ASSESSMENT_STATUS.FAILED">
|
||
<view class="view-btn retest-btn">
|
||
<text>重新测试</text>
|
||
<view class="arrow-icon"></view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 加载更多 -->
|
||
<Loading
|
||
type="more"
|
||
:loading="loading && historyList.length > 0"
|
||
:noMore="noMore"
|
||
noMoreText="没有更多测评记录了"
|
||
/>
|
||
</view>
|
||
|
||
<!-- 空状态 -->
|
||
<Empty
|
||
v-if="isEmpty"
|
||
text="暂无测评记录"
|
||
:showButton="true"
|
||
buttonText="去测评"
|
||
buttonUrl="/pages/index/index"
|
||
/>
|
||
</view>
|
||
</template>
|
||
|
||
<style lang="scss" scoped>
|
||
@import '@/styles/variables.scss';
|
||
|
||
.history-page {
|
||
min-height: 100vh;
|
||
background-color: $bg-color;
|
||
padding: $spacing-lg;
|
||
padding-bottom: calc(#{$spacing-lg} + env(safe-area-inset-bottom));
|
||
}
|
||
|
||
.history-list {
|
||
.history-card {
|
||
background-color: $bg-white;
|
||
border-radius: $border-radius-lg;
|
||
margin-bottom: $spacing-lg;
|
||
overflow: hidden;
|
||
box-shadow: $shadow-sm;
|
||
|
||
.card-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: $spacing-md $spacing-lg;
|
||
border-bottom: 1rpx solid $border-light;
|
||
|
||
.assessment-name {
|
||
font-size: $font-size-lg;
|
||
font-weight: $font-weight-medium;
|
||
color: $text-color;
|
||
}
|
||
|
||
.assessment-status {
|
||
font-size: $font-size-sm;
|
||
padding: 4rpx 16rpx;
|
||
border-radius: $border-radius-sm;
|
||
|
||
// 待测评 - 橙色
|
||
&.status-pending {
|
||
color: $warning-color;
|
||
background-color: rgba(250, 173, 20, 0.1);
|
||
}
|
||
|
||
// 测评中 - 蓝色
|
||
&.status-testing {
|
||
color: $primary-color;
|
||
background-color: rgba(74, 144, 226, 0.1);
|
||
}
|
||
|
||
// 生成中 - 蓝色
|
||
&.status-generating {
|
||
color: $primary-color;
|
||
background-color: rgba(74, 144, 226, 0.1);
|
||
}
|
||
|
||
// 已完成 - 绿色
|
||
&.status-completed {
|
||
color: $success-color;
|
||
background-color: rgba(82, 196, 26, 0.1);
|
||
}
|
||
|
||
// 生成失败 - 红色
|
||
&.status-failed {
|
||
color: $error-color;
|
||
background-color: rgba(255, 77, 79, 0.1);
|
||
}
|
||
|
||
// 需重测 - 橙色
|
||
&.status-retest {
|
||
color: $warning-color;
|
||
background-color: rgba(250, 173, 20, 0.1);
|
||
}
|
||
}
|
||
}
|
||
|
||
.card-content {
|
||
padding: $spacing-md $spacing-lg;
|
||
|
||
.info-row {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: $spacing-xs 0;
|
||
|
||
.info-label {
|
||
font-size: $font-size-md;
|
||
color: $text-secondary;
|
||
}
|
||
|
||
.info-value {
|
||
font-size: $font-size-md;
|
||
color: $text-color;
|
||
}
|
||
}
|
||
}
|
||
|
||
.card-footer {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
padding: $spacing-md $spacing-lg;
|
||
border-top: 1rpx solid $border-light;
|
||
|
||
.view-btn {
|
||
display: flex;
|
||
align-items: center;
|
||
color: $primary-color;
|
||
font-size: $font-size-md;
|
||
|
||
&.retest-btn {
|
||
color: $warning-color;
|
||
|
||
.arrow-icon {
|
||
border-right-color: $warning-color;
|
||
border-bottom-color: $warning-color;
|
||
}
|
||
}
|
||
|
||
.arrow-icon {
|
||
width: 12rpx;
|
||
height: 12rpx;
|
||
border-right: 3rpx solid $primary-color;
|
||
border-bottom: 3rpx solid $primary-color;
|
||
transform: rotate(-45deg);
|
||
margin-left: 8rpx;
|
||
}
|
||
|
||
&:active {
|
||
opacity: 0.7;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</style>
|