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

521 lines
10 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>