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

338 lines
12 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">
<template v-if="!isMatchingSuccess">
<!-- 顶部一些按钮 -->
<view class="top-button-div">
<view class="back_div" @click="common.navigateBack()">
<view class="back-icon"></view>
</view>
<view class="fl1"></view>
<view class="top-icon-div" @click="click_top_icon(1)">
<image class="img1" :src="data_info.isAnony === 'N' ? pkAnonymousClose() : pkAnonymousOpen()"></image>
<view class="text">{{ data_info.isAnony === 'N' ? '不匿名' : '匿名' }}</view>
</view>
<view class="top-icon-div" @click="click_top_icon(2)">
<image class="img2" :src="pkRules()"></image>
<view class="text">规则说明</view>
</view>
<view class="top-icon-div" @click="click_top_icon(3)">
<image class="img3" :src="pkRankingList()"></image>
<view class="text">排行榜</view>
</view>
</view>
<view class="title-div" :class="{ hidden: challengeLoading }">
{{ data_info.comName }}
</view>
<view class="match-msg-div" :class="{ hidden: !challengeLoading }">匹配中...</view>
<view class="avatar-div">
<view class="avatar-surround-div">
<view class="avatar-img-div" :class="{ hidden: challengeLoading }">
<cached-avatar v-show="data_info.isAnony === 'N'" class="img"></cached-avatar>
<image v-show="data_info.isAnony === 'Y'" class="img" :src="defaultAnonymousAvatar"></image>
</view>
</view>
<view class="avatar-name-div">{{ data_info.isAnony === 'N' ? data_info.userName : '匿名' }}</view>
</view>
<view class="select-div" :class="{ hidden: challengeLoading }" @click="selectionInstitutionsRef.open(true)">
<view class="select-institution-div">
{{ data_info.orgName }}
<image class="img" :src="switchIcon()"></image>
</view>
<view class="select-institution-msg-div">*选择机构后会根据机构进行人员随机匹配挑战</view>
</view>
<view class="fl1"></view>
<!-- 匹配时间 -->
<view class="match-time-div" :class="{ hidden: !challengeLoading }">
{{ formatTime(practiceTime) }}
</view>
<view class="button-div" v-show="challengeButtonStatus">
<view
:class="{ start_button: !challengeLoading, cancel_button: challengeLoading, 'button-transition': true }"
@click="start_challenge"
>
{{ challengeLoading ? '取消匹配' : '开始挑战' }}
</view>
</view>
<view class="fl1"></view>
<view class="fl1"></view>
<view class="fl1"></view>
</template>
<template v-else>
<view class="matching-success-msg">匹配成功</view>
<view class="matching-success-animation">
<view class="left animate-left">
<image class="img" src="@/static/images/competition/matching-success-left.png" mode="widthFix"></image>
<view class="information-div-left">
<view class="avatar-img-div">
<cached-avatar v-show="data_info.isAnony === 'N'" class="img"></cached-avatar>
<image v-show="data_info.isAnony === 'Y'" class="img" :src="defaultAnonymousAvatar"></image>
</view>
<view class="name">
{{ data_info.isAnony === 'N' ? data_info.userName : '匿名' }}
</view>
<view class="org">
{{ data_info.isAnony === 'N' ? data_info.orgIdName : '机构不显示' }}
</view>
</view>
</view>
<view class="right animate-right">
<image class="img" src="@/static/images/competition/matching-success-right.png" mode="widthFix"></image>
<view class="information-div-right">
<view class="avatar-img-div">
<image-preview
v-if="data_info.pkIsAnony === 'N'"
class="img"
:src="data_info.pkimgAddr"
></image-preview>
<image v-else class="img" :src="defaultAnonymousAvatar" mode="aspectFill"></image>
</view>
<view class="name">
{{ data_info.pkIsAnony === 'N' ? data_info.pkUserName : '匿名' }}
</view>
<view class="org">
{{ data_info.pkIsAnony === 'N' ? data_info.pkOrgIdName : '机构不显示' }}
</view>
</view>
</view>
</view>
<view class="matching-success-text">准备</view>
</template>
<uni-popup ref="popupRef" class="popup">
<view class="sheet_popup_div">
<view class="title_div">双人对战规则说明</view>
<view class="note_div">
{{ data_info.ruleDesc }}
</view>
</view>
</uni-popup>
<selectionInstitutions ref="selectionInstitutionsRef" @submit="selectionInstitutionsSubmit"></selectionInstitutions>
<uv-picker ref="anonyPickerRef" :columns="[anonyOptions]" keyName="label" @confirm="anonyConfirm"></uv-picker>
</view>
</template>
<script setup lang="ts">
import {ref, onMounted, reactive} from 'vue';
import common, {getUserInfo} from '@/common/common';
import {MatchTimeGenerator, formatTime} from './js/matching';
import {queryTraCompetitionInfoByPkUser} from '@/api/competition';
import {onLoad} from '@dcloudio/uni-app';
import {defaultAnonymousAvatar} from '@/enum';
import {pkAnonymousClose, pkAnonymousOpen, pkRankingList, pkRules, switchIcon} from '@/common/imgSvg';
import {queryTraUserAnonyByUserId, updateTraUserAnony} from '@/api/user';
import selectionInstitutions from '@/components/selection-institutions/index.vue';
const data_info = reactive({
userId: '',
userName: '',
orgName: '全部', // 机构名称
orgId: 'A', // 机构的ID,全行是A
papersId: '', // 试卷id
isAnony: '', // 是否匿名 Y是N否
ruleDesc: '', // 规则说明
comName: '', // 标题
comId: '',
orgIdName: '',
pkIsAnony: '',
pkimgAddr: '',
pkUserName: '',
pkOrgIdName: ''
});
const popupRef = ref(null);
const anonyPickerRef = ref(null);
const selectionInstitutionsRef = ref(null);
const challengeLoading = ref(false);
const isMatchingSuccess = ref(false); // 匹配成功状态
const challengeButtonStatus = ref(true); // 匹配按钮显示
const practiceTime = ref(0); // 匹配时间
let practiceTimeTimer: number | null = null; // 匹配时间计时器
let matchTimer: number | null = null; // 匹配成功定时器
const anonyOptions = [
{
value: 'Y',
label: '匿名'
},
{
value: 'N',
label: '不匿名'
}
];
// 开始挑战
const start_challenge = () => {
if (!challengeLoading.value) {
challengeLoading.value = true;
// 清除旧计时器
if (practiceTimeTimer) {
clearInterval(practiceTimeTimer);
}
// 开始计时
const practiceStartTime = new Date().getTime();
practiceTimeTimer = setInterval(() => {
const currentTime = Date.now();
practiceTime.value = Math.floor((currentTime - practiceStartTime) / 1000);
}, 1000);
// 生成匹配延迟时间
// const delayTime = 0;
const delayTime = MatchTimeGenerator.generateInSeconds();
// 匹配定时器
matchTimer = setTimeout(async () => {
try {
challengeButtonStatus.value = false;
const orgId = data_info.orgId
const papersId = data_info.papersId
const comId = data_info.comId
const pkDataBody = await queryTraCompetitionInfoByPkUser({
// orgId:510121,
// papersId:'COMPETITION_PAPERS0250640321542025101615442400000003107',
// comId:'TRA_COMPETITION_INFO0250640321542025101615442400000003099'
orgId, papersId, comId
});
const pkData = pkDataBody.body;
// 下面两行是给后端擦屁股,后端传回的数值为空
pkData.orgId = orgId
pkData.papersId = papersId
common.setPageCache('competition_pk', pkData);
// 1. 显示匹配成功状态
isMatchingSuccess.value = true;
uni.vibrateLong({
success: function () {
}
});
// 2. 执行匹配成功动画(等待1.5秒)
await new Promise((resolve) => setTimeout(resolve, 600));
// 3. 清除计时器
if (practiceTimeTimer) {
clearInterval(practiceTimeTimer);
practiceTimeTimer = null;
}
// 5. 跳转页面
common.navigateTo('/pages/competition/pk');
challengeLoading.value = true;
} catch (error) {
common.msg('队列繁忙,请稍后再试!');
// 出错处理
} finally {
resetChallengeState();
}
}, delayTime * 1000);
} else {
// 取消挑战
resetChallengeState();
}
};
interface InstitutionItem {
orgName: string; // 机构名称,类型为字符串
orgId: string; // 机构ID,类型为字符串
parentId: string; // 父级ID,类型为字符串
children?: InstitutionItem[];
}
const selectionInstitutionsSubmit = (options: [InstitutionItem, ...InstitutionItem[]]) => {
// 缓存起来
common.setValue('cache_selection_institutions' + data_info.userId, options);
const lastItem = options.at(-1);
data_info.orgName = lastItem.orgName;
data_info.orgId = lastItem.orgId;
selectionInstitutionsRef.value.close();
};
const anonyConfirm = (e) => {
const originalIsAnony = data_info.isAnony;
const isAnony = e.value[0].value;
if (isAnony === data_info.isAnony) return;
data_info.isAnony = isAnony;
common.loading('修改中');
updateTraUserAnony({isAnony})
.then(() => {
setTimeout(() => common.msg('切换成功'), 400);
})
.catch(() => {
// 4. 请求失败:用初始值回滚
data_info.isAnony = originalIsAnony;
common.msg('切换失败,请重试'); // 可选:提示失败
})
.finally(() => {
common.hideLoading();
});
};
const click_top_icon = (type: number) => {
// 匿名切换
if (type === 1) {
anonyPickerRef.value.setIndexs([data_info.isAnony === 'Y' ? 0 : 1], true);
anonyPickerRef.value.open();
} else if (type === 2) {
// 打开规则说明
popupRef.value.open('bottom');
} else if (type === 3) {
// 去排行榜
common.navigateTo(`/pages/competition/ranking?papersId=${data_info.papersId}&orgId=${data_info.orgId}`)
}
};
// 统一重置状态的函数
const resetChallengeState = () => {
if (matchTimer) {
clearTimeout(matchTimer);
matchTimer = null;
}
if (practiceTimeTimer) {
clearInterval(practiceTimeTimer);
practiceTimeTimer = null;
}
challengeButtonStatus.value = true;
challengeLoading.value = false;
isMatchingSuccess.value = false;
practiceTime.value = 0;
};
onLoad((e) => {
const lastData = common.getPageCache('competition_list');
console.log('lastData', lastData);
const userInfo = getUserInfo();
Object.assign(data_info, {
...lastData,
userId: userInfo.userId,
userName: userInfo.userName,
gender: userInfo.gender,
papersId: e.papersId,
comId: e.comId
});
const selectOptions = common.getValue('cache_selection_institutions' + data_info.userId);
if (Array.isArray(selectOptions) && selectOptions.length > 0) {
const lastItem = selectOptions.at(-1);
data_info.orgName = lastItem.orgName;
data_info.orgId = lastItem.orgId;
// const orgTreeInit = common.getValue('orgTree');
// if (Array.isArray(orgTreeInit) && orgTreeInit.length > 0) {
// console.log('selectOptions', selectOptions);
// }
}
});
</script>
<style scoped lang="scss">
// 匹配成功样式动画
@import './style/matching-success.scss';
@import './style/matching-loading.scss';
@import './style/matching-prelude.scss';
/* 容器样式 */
.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>