Files
tra-app/src/pages/competition/result.vue
T

653 lines
18 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.
<template>
<view class="main_div max_page">
<!-- 顶部一些按钮 -->
<view class="top-button-div">
<view class="back_div" @click="common.navigateBack()">
<view class="back-icon"></view>
</view>
<view class="fl1"></view>
</view>
<view class="fl1"></view>
<view class="show-main-div">
<!-- 伸展出的吉祥物 -->
<view class="result-container-div" :class="{ victory: jieguo === 'Y', result: action === 'result' }">
<image class="img" src="@/static/images/competition/result_shengli.png"></image>
</view>
<view class="result-container-div" :class="{ defeat: jieguo === 'N', result: action === 'result' }">
<image class="img defeat" src="@/static/images/competition/result_shibai.png"></image>
</view>
<view class="mascot-div" :class="action">
<image class="img" src="@/static/images/competition/result_loading_mascot.png"></image>
</view>
<view class="mascot-msg-div" :class="action">
<image class="img" src="@/static/images/competition/result_loading_msg.png"></image>
</view>
<view class="bg-div">
<!-- 下方的pk结算信息 -->
<view class="pk-msg-div">
<view class="win-icon" :class="jieguo">
<image class="img_100" src="@/static/images/competition/result_huosheng.png"></image>
</view>
<view class="pk-msg-left-div" :class="myAction">
<view class="avatar-div">
<cached-avatar v-if="pkData.isAnony === 'N'" class="img"></cached-avatar>
<image v-else class="img" :src="defaultAnonymousAvatar" mode="aspectFill"></image>
</view>
<view class="name-div">
{{ pkData.isAnony === 'N' ? pkData.userName : formatName(pkData.userName) }}
</view>
<view class="title">正确/总题数</view>
<view class="topic-div number">
<text class="loadingNoText font-size-54">{{ pkData.correctNub }}</text>
<text class="loadingNoText">/{{ pkData.qnsNub }}</text>
<text class="loadingText">{{ resultStr }}</text>
</view>
<view class="title">总分</view>
<view class="score-div number">
<text class="font-size-54 loadingNoText">{{ pkData.totalScore }}</text>
<text class="loadingText">{{ resultStr }}</text>
</view>
<view class="title">用时</view>
<view class="time-div number">
<text class="loadingNoText">{{ formatSecondsToMS(pkData.timeTaken) }}</text>
<text class="loadingText">{{ resultStr }}</text>
</view>
</view>
<view class="pk-msg-right-div" :class="action">
<view class="avatar-div">
<image-preview v-if="pkData.pkIsAnony === 'N'" class="img" :src="pkData.imgPk"></image-preview>
<image v-else class="img" :src="defaultAnonymousAvatar" mode="aspectFill"></image>
</view>
<view class="name-div">
{{ pkData.pkIsAnony === 'N' ? pkData.userNamePk : formatName(pkData.userNamePk) }}
</view>
<view class="title">正确/总题数</view>
<view class="topic-div number">
<text class="loadingNoText font-size-54">{{ pkData.correctNubPk }}</text>
<text class="loadingNoText">/{{ pkData.qnsNubPk }}</text>
<text class="loadingText">{{ resultStr }}</text>
</view>
<view class="title">总分</view>
<view class="score-div number">
<text class="font-size-54 loadingNoText">{{ pkData.totalScorePk }}</text>
<text class="loadingText">{{ resultStr }}</text>
</view>
<view class="title">用时</view>
<view class="time-div number">
<text class="loadingNoText">{{ formatSecondsToMS(pkData.timeTakenPk) }}</text>
<text class="loadingText">{{ resultStr }}</text>
</view>
</view>
</view>
<!-- 查看排行榜按钮 -->
<view class="ranking-list-div" @click="buttonClick('toRanking')">
<text v-if="mode === 'pk_in'">查看排行榜</text>
</view>
<!-- 下方的两个按钮 -->
<view class="fl1"></view>
<view class="bottom-button-div" v-if="mode === 'pk_in'">
<uv-button class="button-1" text="退出挑战" shape="circle" @click="buttonClick('exit')"></uv-button>
<uv-button class="button-2" text="继续挑战" shape="circle" @click="buttonClick('continue')"></uv-button>
</view>
<view class="bottom-button-div" v-if="mode === 'pk_result'">
<uv-button class="button-3" text="返回" shape="circle" @click="buttonClick('continue')"></uv-button>
</view>
</view>
</view>
<badge ref="badgeRef"></badge>
<points ref="pointsRef"></points>
</view>
</template>
<script setup lang="ts">
import { ref, onMounted, reactive, computed, nextTick } from 'vue';
import common, { getUserInfo, formatName } from '@/common/common';
import { MatchTimeGenerator, formatTime } from './js/matching';
import { queryCompetitionAnswerResult } from '@/api/competition';
import { onLoad } from '@dcloudio/uni-app';
import { defaultAnonymousAvatar } from '@/enum';
import { pkAnonymousClose, pkAnonymousOpen, pkRankingList, pkRules } from '@/common/imgSvg';
import { queryTraUserAnonyByUserId, updateTraUserAnony } from '@/api/user';
import { formatSecondsToMS } from './js/tool';
import badge from '@/components/points-and-badge/badge';
import points from '@/components/points-and-badge/points';
import usePointsAndBadge from '@/components/points-and-badge/usePointsAndBadge';
const { pointAndBadgesRun, badgeRef, pointsRef } = usePointsAndBadge();
const resultStr = computed(() => (mode.value === 'pk_result' ? '加载中' : '结算中'));
const action = ref('');
const myAction = ref('');
const mode = ref(''); // 模式 PK中 pk_in Pk列表查询 pk_result
const autoSub = ref('00'); // 是否是自动提交 00 不是 01是
const pkData = reactive({
// 公共的
execId: '',
papersId: '',
orgId: '',
answerResult: '', // 答题结果 Y代表我赢,N代表我输
timeDifference: 30, // 时差 int 单位 s
// 自己的
userName: '',
isAnony: '', // 匿名 String
correctNub: 0, // 正确数 int
qnsNub: 0, // 总数 int
timeTaken: 0, // 用时 int
totalScore: 0, // 我方总分 int
// 对方的
userIdPk: '', // 对方用户Id String
userNamePk: '', // 对方用户姓名 String
imgPk: '', // 对方头像 String
pkIsAnony: '', // 匿名 String
correctNubPk: 0, // 正确数 int
qnsNubPk: 0, // 总数 int
timeTakenPk: 0, // 用时 int
totalScorePk: 0 // 对方总分 int
});
const jieguo = computed(() => pkData.answerResult);
const buttonClick = (type: String) => {
if (type === 'exit') {
common.navigateBack(2);
} else if (type === 'continue') {
common.navigateBack();
} else if (type === 'toRanking' && mode.value === 'pk_in') {
common.navigateTo(`/pages/competition/ranking?papersId=${pkData.papersId}&orgId=${pkData.orgId}`);
}
};
onLoad((e) => {
pkData.execId = e.execId || '';
pkData.papersId = e.papersId || '';
pkData.orgId = e.orgId || '';
mode.value = e.mode || 'pk_result';
autoSub.value = e.autoSub || '00';
const pkDataInit = common.getPageCache('competition_pk_to_resulf');
// const userInfo = getUserInfo();
// console.log('userInfo', userInfo);
console.log('pkDataInit', pkDataInit);
Object.assign(pkData, {
userName: pkDataInit.userName ?? '',
isAnony: pkDataInit.isAnony ?? '',
pkIsAnony: pkDataInit.pkIsAnony ?? '',
userNamePk: pkDataInit.pkUserName ?? '',
imgPk: pkDataInit.pkImgAddr ?? ''
});
});
onMounted(() => {
action.value = 'loading';
myAction.value = 'loading';
setTimeout(
() => {
queryCompetitionAnswerResult({ execId: pkData.execId, autoSub: autoSub.value }).then(({ body }) => {
if (mode.value === 'pk_in') {
console.log('调用它');
pointAndBadgesRun('11');
}
// 计算需要额外延时的时间
const currentTime = Math.floor(Date.now() / 1000);
// 2. 计算对手的答题结束时间(秒)
const opponentEndTime = body.endTime + body.timeDifference;
// 3. 计算当前时间到对手结束时间的延迟时间(秒),负数则取0
const delayTime = Math.max(0, opponentEndTime - currentTime);
console.log('计算需要额外延时的时间', delayTime);
const userResult = body.userResult;
Object.assign(pkData, {
// 自己的
isAnony: userResult.isAnony, // 匿名 String
userName: userResult.userName, // 匿名 String
correctNub: userResult.correctNub, // 正确数 int
qnsNub: userResult.qnsNub, // 总数 int
timeTaken: userResult.timeTaken, // 用时 int
totalScore: userResult.totalScore || 0, // 我方总分
// 对方的
userIdPk: userResult.userIdPk, // 用户Id String
userNamePk: userResult.userNamePk, // 用户姓名 String
imgPk: userResult.imgPk, // 用户姓名 String
pkIsAnony: userResult.isAnonyPk // 匿名 String
});
myAction.value = 'result';
setTimeout(() => {
pkData.answerResult = body.answerResult; // 答题结果 Y代表我赢,N代表我输
pkData.timeDifference = body.timeDifference; // 时差 int 单位 s
Object.assign(pkData, {
// 自己的
// isAnony: userResult.isAnony, // 匿名 String
// userName: userResult.userName, // 匿名 String
// correctNub: userResult.correctNub, // 正确数 int
// qnsNub: userResult.qnsNub, // 总数 int
// timeTaken: userResult.timeTaken, // 用时 int
// totalScore: userResult.totalScore || 0, // 我方总分
// 对方的
// userIdPk: userResult.userIdPk, // 用户Id String
// userNamePk: userResult.userNamePk, // 用户姓名 String
// imgPk: userResult.imgPk, // 用户姓名 String
// pkIsAnony: userResult.isAnonyPk, // 匿名 String
correctNubPk: userResult.correctNubPk, // 正确数 int
qnsNubPk: userResult.qnsNubPk || 0, // 总数 int
timeTakenPk: userResult.timeTakenPk || 0, // 用时 int
totalScorePk: userResult.totalScorePk || 0 // 对方总分
});
nextTick(() => {
action.value = 'result';
});
}, delayTime * 1000);
});
},
mode.value === 'pk_result' ? 0 : 1000
);
});
</script>
<style scoped lang="scss">
.pk-msg-div {
width: 570rpx;
height: 480rpx;
background-image: url('@/static/images/competition/result_pk_bg.png');
background-size: 100% auto; /* 宽度填满,高度自适应 */
background-position: center top; /* 图片顶部居中显示 */
background-repeat: no-repeat; /* 不重复平铺 */
position: relative;
display: flex;
justify-content: space-around;
font-size: 26rpx;
.pk-msg-left-div > .avatar-div {
// border: 3rpx solid #2b7af1;
border: 3rpx solid #fafafa;
}
.pk-msg-right-div > .avatar-div {
// border: 3rpx solid #f63c13;
border: 3rpx solid #fafafa;
}
.avatar-div {
width: 78rpx;
height: 78rpx;
border-radius: 50%;
overflow: hidden;
margin-top: 30rpx;
flex-shrink: 0;
.img {
width: 100%;
height: 100%;
}
}
.name-div {
margin-top: 10rpx;
font-weight: 700;
font-size: 30rpx;
color: #000000;
height: 42rpx;
}
.topic-div {
font-family: Arial, Arial;
font-style: italic;
height: 62rpx;
line-height: 62rpx;
}
.score-div {
font-family: Arial, Arial;
height: 62rpx;
line-height: 62rpx;
}
.win-icon {
width: 140rpx;
height: 130rpx;
position: absolute;
display: none;
&.Y {
display: block;
left: -20rpx;
top: -46rpx;
}
&.N {
display: block;
right: -30rpx;
top: -46rpx;
}
}
.pk-msg-left-div,
.pk-msg-right-div {
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
.title {
font-weight: 400;
margin-top: 10rpx;
}
}
.pk-msg-left-div {
.title {
color: #6a8db1;
}
.number {
color: #2b7af1;
}
}
.pk-msg-right-div {
.title {
color: #b16f6a;
}
.number {
color: #f63c13;
}
}
.font-size-54 {
font-size: 54rpx;
font-weight: 700;
}
}
.show-main-div {
width: 100%;
min-height: 500rpx;
background-color: #fff; /* 关键:背景图未覆盖的区域填充白色 */
position: relative;
display: flex;
flex-direction: column;
align-items: center;
border-radius: 31px 31px 0px 0px;
@keyframes victoryEnter {
0% {
transform: translateY(50rpx) scale(0.8);
opacity: 0;
}
30% {
transform: translateY(-10rpx) scale(1.05);
}
100% {
transform: translateY(0) scale(1);
opacity: 1;
}
}
@keyframes defeatEnter {
0% {
transform: translateY(-50rpx) scale(1.2);
opacity: 0;
}
70% {
transform: translateY(10rpx) scale(0.95);
}
100% {
transform: translateY(0) scale(1);
opacity: 1;
}
}
@keyframes defeatRotateSway {
0% {
transform: rotate(0deg); // 初始角度
}
25% {
transform: rotate(-2deg); // 向左微转2度
}
75% {
transform: rotate(2deg); // 向右微转2度
}
100% {
transform: rotate(0deg); // 回到初始角度
}
}
@keyframes victoryFloat {
0%,
100% {
transform: translateY(0);
// transform: translateY(0);
}
50% {
transform: translateY(-10rpx);
// transform: translateY(-10rpx);
}
}
.loadingText {
display: none;
}
// loading 状态样式(保持不变)
.loading {
&.mascot-div {
left: 50%;
transform: translateX(-50%);
}
&.mascot-msg-div {
opacity: 1;
pointer-events: auto;
}
.topic-div,
.score-div,
.time-div {
position: relative;
&::after {
content: '';
position: absolute;
top: 4rpx;
left: calc(100% + 3rpx);
animation: loading 2.5s infinite;
}
@keyframes loading {
0% {
content: '';
}
25% {
content: '.';
}
50% {
content: '..';
}
75% {
content: '...';
}
100% {
content: '';
}
}
}
.loadingText {
display: inline;
}
.loadingNoText {
display: none;
}
}
// 当 action=result 时,触发结果动画
.result {
// 胜利状态:入场 + 浮动 + 闪光
&.result-container-div.victory {
animation: victoryEnter 0.8s cubic-bezier(0.22, 1, 0.36, 1) forwards,
victoryFloat 2s ease-in-out infinite 0.8s;
}
// 失败状态:入场 + 抖动
&.result-container-div.defeat {
// defeatShake 1s ease-in-out infinite 0.6s
animation: defeatEnter 0.6s ease-out forwards, defeatRotateSway 1s ease-in-out 0.6s 1; // 入场后开始晃动,2秒结束,仅1次
}
}
// 结果容器基础样式
.result-container-div {
position: absolute;
top: -300rpx; // 初始位置(可根据需求调整)
z-index: 20;
opacity: 0; // 初始隐藏,动画中变为1
.img {
width: 512rpx;
height: 408rpx;
display: block; // 避免图片底部间隙
}
}
.mascot-div {
position: absolute;
top: -300rpx;
margin-left: 50rpx;
transition: top 0.6s ease-out;
z-index: 5;
left: 100%;
transform: translateX(0);
transition: left 0.5s ease-out, transform 0.5s ease-out;
.img {
width: 284rpx;
height: 400rpx;
}
}
.mascot-msg-div {
position: absolute;
top: -300rpx;
transition: top 0.6s ease-out;
z-index: 20;
margin-top: 240rpx;
transition: top 0.6s ease-out, opacity 0.5s ease-out; /* 新增透明度过渡 */
z-index: 20;
opacity: 0; /* 初始隐藏 */
pointer-events: none; /* 隐藏时不响应交互 */
.img {
width: 326rpx;
height: 198rpx;
}
}
.bg-div {
display: flex;
flex-direction: column;
align-items: center;
border-radius: 20rpx 20rpx 0 0;
padding-top: 140rpx;
position: relative;
width: 100%;
height: 100%;
.img {
// width: 100%;
// height: 100%;
}
z-index: 10;
background: linear-gradient(to bottom, transparent 0%, #ffffff 200rpx, #ffffff 100%),
linear-gradient(to right, #bfebff 0%, #fef1de 100%);
}
.ranking-list-div {
margin-top: 48rpx;
margin-bottom: 24rpx;
font-weight: 400;
font-size: 30rpx;
color: #0066ff;
text-align: center;
font-style: normal;
}
.bottom-button-div {
display: flex;
margin-top: 50rpx;
margin-bottom: 60rpx;
width: 550rpx;
justify-content: space-between;
.button-3 {
flex: 1;
}
:deep(.uv-button) {
/* 以下是所有 .uv-button 共用的公共样式 */
min-width: 246rpx;
height: 92rpx;
border-radius: 58px;
font-weight: 500;
text-align: center;
.uv-button__text {
font-size: 30rpx !important;
}
}
.button-1 :deep(.uv-button) {
background-color: #fff;
}
.button-2 :deep(.uv-button) {
background: linear-gradient(0deg, #009fff 0%, #0066ff 100%) !important;
color: #ffffff;
}
}
}
.top-button-div {
display: flex;
justify-content: space-between;
width: 100%;
height: 70rpx;
padding-top: 10rpx;
font-weight: 500;
position: relative;
margin-right: 10rpx;
.top-icon-div {
display: flex;
align-items: center;
margin-right: 30rpx;
font-weight: 400;
font-size: 26rpx;
color: #ffffff;
text-align: left;
.img1,
.img2,
.img3 {
margin-right: 6rpx;
}
.img1,
.img3 {
width: 40rpx;
height: 40rpx;
}
.img2 {
width: 30rpx;
height: 30rpx;
}
}
.back_div {
position: absolute;
left: 26rpx;
height: 34rpx;
top: 17rpx;
.back-icon {
position: relative;
width: 50rpx;
height: 50rpx;
cursor: pointer;
display: flex;
align-items: center;
}
.back-icon::before,
.back-icon::after {
content: '';
position: absolute;
transition: all 0.3s ease;
}
.back-icon::after {
top: 25%;
left: 25%;
width: 40%;
height: 40%;
border-top: 4rpx solid #ffffff;
border-left: 4rpx solid #ffffff;
transform: rotate(-45deg);
}
}
}
/* 容器样式 */
.max_page {
width: 100%;
min-height: 100vh;
background-image: url('@/static/images/competition/bg.png');
background-color: #000; /* 图片高度不足时的背景色 */
background-size: 100% auto; /* 宽度填满,高度自适应 */
background-position: center top; /* 图片顶部居中显示 */
background-repeat: no-repeat; /* 不重复平铺 */
overflow: hidden;
display: flex;
flex-direction: column;
align-items: center;
}
</style>