mi-assessment/uniapp/pages/assessment/history/index.vue
2026-02-20 14:57:43 +08:00

363 lines
8.4 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 } 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 = {
GENERATING: 1, // 生成中
COMPLETED: 2, // 已完成
FAILED: 3 // 生成失败
}
// 状态
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)
const hasMore = computed(() => historyList.value.length < total.value)
/**
* 获取测评状态文本
*/
function getStatusText(status) {
const statusMap = {
[ASSESSMENT_STATUS.GENERATING]: '生成中',
[ASSESSMENT_STATUS.COMPLETED]: '已完成',
[ASSESSMENT_STATUS.FAILED]: '生成失败'
}
return statusMap[status] || '未知状态'
}
/**
* 获取测评状态样式类
*/
function getStatusClass(status) {
const classMap = {
[ASSESSMENT_STATUS.GENERATING]: 'status-generating',
[ASSESSMENT_STATUS.COMPLETED]: 'status-completed',
[ASSESSMENT_STATUS.FAILED]: 'status-failed'
}
return classMap[status] || ''
}
/**
* 格式化日期
*/
function formatDate(dateStr) {
if (!dateStr) return ''
const date = new Date(dateStr)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return `${year}-${month}-${day}`
}
/**
* 加载测评历史列表
*/
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()
}
})
/**
* 查看测评结果
*/
function viewResult(record) {
if (record.status === ASSESSMENT_STATUS.GENERATING) {
uni.showToast({
title: '报告生成中,请稍后查看',
icon: 'none'
})
return
}
if (record.status === ASSESSMENT_STATUS.FAILED) {
uni.showToast({
title: '报告生成失败,请联系客服',
icon: 'none'
})
return
}
uni.navigateTo({
url: `/pages/assessment/result/index?recordId=${record.id}`
})
}
/**
* 判断是否显示查看结果按钮
*/
function showViewResultBtn(status) {
return status === ASSESSMENT_STATUS.COMPLETED
}
/**
* 页面显示时检查登录状态并加载数据
*/
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)">
{{ getStatusText(record.status) }}
</view>
</view>
<!-- 卡片内容 -->
<view class="card-content">
<view class="info-row">
<text class="info-label">测评人</text>
<text class="info-value">{{ record.userName || '--' }}</text>
</view>
<view class="info-row">
<text class="info-label">测评日期</text>
<text class="info-value">{{ formatDate(record.createTime) }}</text>
</view>
</view>
<!-- 卡片底部操作 -->
<view class="card-footer" v-if="showViewResultBtn(record.status)">
<view class="view-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-completed {
color: $success-color;
background-color: rgba(82, 196, 26, 0.1);
}
// 生成中 - 蓝色
&.status-generating {
color: $primary-color;
background-color: rgba(74, 144, 226, 0.1);
}
// 生成失败 - 红色
&.status-failed {
color: $error-color;
background-color: rgba(255, 77, 79, 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;
.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>