521 lines
10 KiB
Vue
521 lines
10 KiB
Vue
<script setup>
|
||
/**
|
||
* 测评生成中页面
|
||
*
|
||
* 功能:
|
||
* - 显示加载动画
|
||
* - 显示提示文字
|
||
* - 轮询查询报告生成状态(3秒间隔)
|
||
* - 生成完成自动跳转结果页
|
||
*/
|
||
|
||
import { ref, onMounted, onUnmounted } from 'vue'
|
||
import { onLoad } from '@dcloudio/uni-app'
|
||
import { useUserStore } from '@/store/user.js'
|
||
import { getResultStatus } from '@/api/assessment.js'
|
||
import Navbar from '@/components/Navbar/index.vue'
|
||
|
||
const userStore = useUserStore()
|
||
|
||
// 页面参数
|
||
const recordId = ref('')
|
||
|
||
// 轮询定时器
|
||
let pollTimer = null
|
||
|
||
// 轮询间隔(毫秒)
|
||
const POLL_INTERVAL = 3000
|
||
|
||
// 最大轮询次数(防止无限轮询)
|
||
const MAX_POLL_COUNT = 100
|
||
|
||
// 当前轮询次数
|
||
const pollCount = ref(0)
|
||
|
||
// 加载提示文字列表
|
||
const loadingTips = [
|
||
'正在分析您的测评数据...',
|
||
'正在生成智能分析报告...',
|
||
'正在整理八大智能评估...',
|
||
'正在计算个人特质分析...',
|
||
'正在生成细分能力报告...',
|
||
'报告即将生成完成...'
|
||
]
|
||
|
||
// 当前提示文字索引
|
||
const currentTipIndex = ref(0)
|
||
|
||
// 提示文字切换定时器
|
||
let tipTimer = null
|
||
|
||
/**
|
||
* 开始轮询查询状态
|
||
*/
|
||
function startPolling() {
|
||
// 清除已有定时器
|
||
stopPolling()
|
||
|
||
// 立即查询一次
|
||
checkStatus()
|
||
|
||
// 设置轮询定时器
|
||
pollTimer = setInterval(() => {
|
||
checkStatus()
|
||
}, POLL_INTERVAL)
|
||
}
|
||
|
||
/**
|
||
* 停止轮询
|
||
*/
|
||
function stopPolling() {
|
||
if (pollTimer) {
|
||
clearInterval(pollTimer)
|
||
pollTimer = null
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 查询报告生成状态
|
||
*/
|
||
async function checkStatus() {
|
||
if (!recordId.value) {
|
||
console.error('缺少测评记录ID')
|
||
return
|
||
}
|
||
|
||
// 检查轮询次数
|
||
pollCount.value++
|
||
if (pollCount.value > MAX_POLL_COUNT) {
|
||
stopPolling()
|
||
uni.showModal({
|
||
title: '提示',
|
||
content: '报告生成时间较长,请稍后在"往期测评"中查看',
|
||
showCancel: false,
|
||
success: () => {
|
||
uni.switchTab({
|
||
url: '/pages/mine/index'
|
||
})
|
||
}
|
||
})
|
||
return
|
||
}
|
||
|
||
try {
|
||
const res = await getResultStatus(recordId.value)
|
||
|
||
if (res && res.code === 0 && res.data) {
|
||
const status = res.data.status
|
||
|
||
// 状态:1-生成中 2-已完成 3-失败
|
||
if (status === 2) {
|
||
// 生成完成,跳转结果页
|
||
stopPolling()
|
||
stopTipRotation()
|
||
|
||
uni.redirectTo({
|
||
url: `/pages/assessment/result/index?recordId=${recordId.value}`
|
||
})
|
||
} else if (status === 3) {
|
||
// 生成失败
|
||
stopPolling()
|
||
stopTipRotation()
|
||
|
||
uni.showModal({
|
||
title: '提示',
|
||
content: res.data.message || '报告生成失败,请重新测评',
|
||
showCancel: false,
|
||
success: () => {
|
||
uni.switchTab({
|
||
url: '/pages/index/index'
|
||
})
|
||
}
|
||
})
|
||
}
|
||
// status === 1 继续轮询
|
||
}
|
||
} catch (error) {
|
||
console.error('查询状态失败:', error)
|
||
// 网络错误不停止轮询,继续尝试
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 开始提示文字轮换
|
||
*/
|
||
function startTipRotation() {
|
||
tipTimer = setInterval(() => {
|
||
currentTipIndex.value = (currentTipIndex.value + 1) % loadingTips.length
|
||
}, 2500)
|
||
}
|
||
|
||
/**
|
||
* 停止提示文字轮换
|
||
*/
|
||
function stopTipRotation() {
|
||
if (tipTimer) {
|
||
clearInterval(tipTimer)
|
||
tipTimer = null
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 页面加载
|
||
*/
|
||
onLoad((options) => {
|
||
recordId.value = options.recordId || ''
|
||
|
||
// 恢复用户登录状态
|
||
userStore.restoreFromStorage()
|
||
|
||
// 开始轮询
|
||
startPolling()
|
||
|
||
// 开始提示文字轮换
|
||
startTipRotation()
|
||
})
|
||
|
||
/**
|
||
* 页面卸载时清理定时器
|
||
*/
|
||
onUnmounted(() => {
|
||
stopPolling()
|
||
stopTipRotation()
|
||
})
|
||
</script>
|
||
|
||
<template>
|
||
<view class="assessment-loading-page">
|
||
<!-- 导航栏 -->
|
||
<Navbar title="生成报告" :showBack="false" />
|
||
|
||
<!-- 加载内容区域 -->
|
||
<view class="loading-content">
|
||
<!-- 加载动画 -->
|
||
<view class="loading-animation">
|
||
<!-- 外圈旋转 -->
|
||
<view class="loading-circle loading-circle--outer">
|
||
<view class="circle-dot circle-dot--1"></view>
|
||
<view class="circle-dot circle-dot--2"></view>
|
||
<view class="circle-dot circle-dot--3"></view>
|
||
<view class="circle-dot circle-dot--4"></view>
|
||
</view>
|
||
|
||
<!-- 内圈旋转 -->
|
||
<view class="loading-circle loading-circle--inner">
|
||
<view class="circle-dot circle-dot--1"></view>
|
||
<view class="circle-dot circle-dot--2"></view>
|
||
<view class="circle-dot circle-dot--3"></view>
|
||
</view>
|
||
|
||
<!-- 中心图标 -->
|
||
<view class="loading-center">
|
||
<view class="center-icon">
|
||
<view class="icon-bar icon-bar--1"></view>
|
||
<view class="icon-bar icon-bar--2"></view>
|
||
<view class="icon-bar icon-bar--3"></view>
|
||
<view class="icon-bar icon-bar--4"></view>
|
||
<view class="icon-bar icon-bar--5"></view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 加载文字 -->
|
||
<view class="loading-text">
|
||
<text class="loading-title">报告生成中</text>
|
||
<text class="loading-tip">{{ loadingTips[currentTipIndex] }}</text>
|
||
</view>
|
||
|
||
<!-- 进度提示 -->
|
||
<view class="loading-progress">
|
||
<view class="progress-dots">
|
||
<view class="progress-dot progress-dot--1"></view>
|
||
<view class="progress-dot progress-dot--2"></view>
|
||
<view class="progress-dot progress-dot--3"></view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 底部提示 -->
|
||
<view class="loading-footer">
|
||
<text class="footer-text">请耐心等待,报告生成需要一点时间</text>
|
||
<text class="footer-subtext">生成完成后将自动跳转</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
|
||
<style lang="scss" scoped>
|
||
@import '@/styles/variables.scss';
|
||
|
||
.assessment-loading-page {
|
||
min-height: 100vh;
|
||
background: linear-gradient(180deg, #F8FAFF 0%, #EEF4FF 100%);
|
||
}
|
||
|
||
.loading-content {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
min-height: calc(100vh - 200rpx);
|
||
padding: $spacing-xl;
|
||
}
|
||
|
||
// 加载动画容器
|
||
.loading-animation {
|
||
position: relative;
|
||
width: 280rpx;
|
||
height: 280rpx;
|
||
margin-bottom: $spacing-xl;
|
||
}
|
||
|
||
// 旋转圆圈
|
||
.loading-circle {
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
border-radius: 50%;
|
||
|
||
&--outer {
|
||
width: 280rpx;
|
||
height: 280rpx;
|
||
animation: rotate 3s linear infinite;
|
||
}
|
||
|
||
&--inner {
|
||
width: 200rpx;
|
||
height: 200rpx;
|
||
animation: rotate-reverse 2s linear infinite;
|
||
}
|
||
}
|
||
|
||
// 圆点
|
||
.circle-dot {
|
||
position: absolute;
|
||
width: 20rpx;
|
||
height: 20rpx;
|
||
background: linear-gradient(135deg, $primary-color 0%, $primary-light 100%);
|
||
border-radius: 50%;
|
||
|
||
&--1 {
|
||
top: 0;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
}
|
||
|
||
&--2 {
|
||
top: 50%;
|
||
right: 0;
|
||
transform: translateY(-50%);
|
||
}
|
||
|
||
&--3 {
|
||
bottom: 0;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
}
|
||
|
||
&--4 {
|
||
top: 50%;
|
||
left: 0;
|
||
transform: translateY(-50%);
|
||
}
|
||
}
|
||
|
||
.loading-circle--inner .circle-dot {
|
||
width: 16rpx;
|
||
height: 16rpx;
|
||
background: linear-gradient(135deg, #FF6B6B 0%, #FF8E8E 100%);
|
||
|
||
&--1 {
|
||
top: 0;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
}
|
||
|
||
&--2 {
|
||
bottom: 25%;
|
||
right: 10%;
|
||
}
|
||
|
||
&--3 {
|
||
bottom: 25%;
|
||
left: 10%;
|
||
}
|
||
}
|
||
|
||
// 中心区域
|
||
.loading-center {
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
width: 120rpx;
|
||
height: 120rpx;
|
||
background-color: $bg-white;
|
||
border-radius: 50%;
|
||
box-shadow: 0 8rpx 32rpx rgba(74, 144, 226, 0.2);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
// 中心图标(音频波形效果)
|
||
.center-icon {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 6rpx;
|
||
height: 48rpx;
|
||
}
|
||
|
||
.icon-bar {
|
||
width: 8rpx;
|
||
background: linear-gradient(180deg, $primary-color 0%, $primary-light 100%);
|
||
border-radius: 4rpx;
|
||
animation: wave 1s ease-in-out infinite;
|
||
|
||
&--1 {
|
||
height: 24rpx;
|
||
animation-delay: 0s;
|
||
}
|
||
|
||
&--2 {
|
||
height: 36rpx;
|
||
animation-delay: 0.1s;
|
||
}
|
||
|
||
&--3 {
|
||
height: 48rpx;
|
||
animation-delay: 0.2s;
|
||
}
|
||
|
||
&--4 {
|
||
height: 36rpx;
|
||
animation-delay: 0.3s;
|
||
}
|
||
|
||
&--5 {
|
||
height: 24rpx;
|
||
animation-delay: 0.4s;
|
||
}
|
||
}
|
||
|
||
// 加载文字
|
||
.loading-text {
|
||
text-align: center;
|
||
margin-bottom: $spacing-xl;
|
||
}
|
||
|
||
.loading-title {
|
||
display: block;
|
||
font-size: $font-size-xxl;
|
||
font-weight: $font-weight-bold;
|
||
color: $text-color;
|
||
margin-bottom: $spacing-md;
|
||
}
|
||
|
||
.loading-tip {
|
||
display: block;
|
||
font-size: $font-size-md;
|
||
color: $text-secondary;
|
||
min-height: 40rpx;
|
||
transition: opacity 0.3s ease;
|
||
}
|
||
|
||
// 进度点
|
||
.loading-progress {
|
||
margin-bottom: $spacing-xl * 2;
|
||
}
|
||
|
||
.progress-dots {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: $spacing-sm;
|
||
}
|
||
|
||
.progress-dot {
|
||
width: 16rpx;
|
||
height: 16rpx;
|
||
background-color: $primary-color;
|
||
border-radius: 50%;
|
||
animation: bounce 1.4s ease-in-out infinite;
|
||
|
||
&--1 {
|
||
animation-delay: 0s;
|
||
}
|
||
|
||
&--2 {
|
||
animation-delay: 0.2s;
|
||
}
|
||
|
||
&--3 {
|
||
animation-delay: 0.4s;
|
||
}
|
||
}
|
||
|
||
// 底部提示
|
||
.loading-footer {
|
||
text-align: center;
|
||
position: absolute;
|
||
bottom: 120rpx;
|
||
left: 0;
|
||
right: 0;
|
||
padding: 0 $spacing-xl;
|
||
}
|
||
|
||
.footer-text {
|
||
display: block;
|
||
font-size: $font-size-sm;
|
||
color: $text-placeholder;
|
||
margin-bottom: $spacing-xs;
|
||
}
|
||
|
||
.footer-subtext {
|
||
display: block;
|
||
font-size: $font-size-xs;
|
||
color: $text-disabled;
|
||
}
|
||
|
||
// 动画定义
|
||
@keyframes rotate {
|
||
from {
|
||
transform: translate(-50%, -50%) rotate(0deg);
|
||
}
|
||
to {
|
||
transform: translate(-50%, -50%) rotate(360deg);
|
||
}
|
||
}
|
||
|
||
@keyframes rotate-reverse {
|
||
from {
|
||
transform: translate(-50%, -50%) rotate(360deg);
|
||
}
|
||
to {
|
||
transform: translate(-50%, -50%) rotate(0deg);
|
||
}
|
||
}
|
||
|
||
@keyframes wave {
|
||
0%, 100% {
|
||
transform: scaleY(0.5);
|
||
}
|
||
50% {
|
||
transform: scaleY(1);
|
||
}
|
||
}
|
||
|
||
@keyframes bounce {
|
||
0%, 80%, 100% {
|
||
transform: scale(0.6);
|
||
opacity: 0.5;
|
||
}
|
||
40% {
|
||
transform: scale(1);
|
||
opacity: 1;
|
||
}
|
||
}
|
||
</style>
|