629 lines
17 KiB
Vue
629 lines
17 KiB
Vue
<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" :class="action">
|
||
<!-- 伸展出的吉祥物 -->
|
||
<view class="result-container-div" :class="{ victory: jieguo === 'Y' }">
|
||
<image class="img" src="@/static/images/competition/result_shengli.png"></image>
|
||
</view>
|
||
<view class="result-container-div" :class="{ defeat: jieguo === 'N' }">
|
||
<image class="img defeat" src="@/static/images/competition/result_shibai.png"></image>
|
||
</view>
|
||
|
||
<view class="mascot-div">
|
||
<image class="img" src="@/static/images/competition/result_loading_mascot.png"></image>
|
||
</view>
|
||
|
||
<view class="mascot-msg-div">
|
||
<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">
|
||
<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">结算中</text>
|
||
</view>
|
||
<view class="title">总分</view>
|
||
<view class="score-div number">
|
||
<text class="font-size-54 loadingNoText">{{ pkData.totalScore }}</text>
|
||
<text class="loadingText">结算中</text>
|
||
</view>
|
||
<view class="title">用时</view>
|
||
<view class="time-div number">
|
||
<text class="loadingNoText">{{ formatSecondsToMS(pkData.timeTaken) }}</text>
|
||
<text class="loadingText">结算中</text>
|
||
</view>
|
||
</view>
|
||
<view class="pk-msg-right-div">
|
||
<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">结算中</text>
|
||
</view>
|
||
<view class="title">总分</view>
|
||
<view class="score-div number">
|
||
<text class="font-size-54 loadingNoText">{{ pkData.totalScorePk }}</text>
|
||
<text class="loadingText">结算中</text>
|
||
</view>
|
||
<view class="title">用时</view>
|
||
<view class="time-div number">
|
||
<text class="loadingNoText">{{ formatSecondsToMS(pkData.timeTakenPk) }}</text>
|
||
<text class="loadingText">结算中</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 action = ref('');
|
||
const mode = ref(''); // 模式 PK中 pk_in, Pk列表查询 pk_result
|
||
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';
|
||
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 ?? ''
|
||
});
|
||
});
|
||
onMounted(() => {
|
||
action.value = 'loading';
|
||
|
||
setTimeout(() => {
|
||
queryCompetitionAnswerResult({ execId: pkData.execId }).then(({ body }) => {
|
||
// 计算需要额外延时的时间
|
||
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);
|
||
setTimeout(() => {
|
||
const userResult = body.userResult;
|
||
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';
|
||
});
|
||
if (mode.value === 'pk_in') {
|
||
console.log('调用它');
|
||
pointAndBadgesRun('11');
|
||
}
|
||
}, delayTime);
|
||
});
|
||
}, 2000);
|
||
});
|
||
</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>
|