- 新增大屏开发调试指南文档,包含设计基准、调试方法和字体配置说明 - 添加字体大小预览工具,支持多分辨率视口切换(2048x1080/1920x1080/3840x2160) - 新增字体资源 README 文档和团队标题背景图片 - 优化用户界面背景图片(title-bg3.png、title-bg4.png) - 更新 ActionButton 和 CompetitionLayout 组件样式 - 完善用户模块各页面布局(分析、封面、抽签、游戏、规则、团队) - 优化题目渲染器组件和成绩详情页面 - 更新 UnoCSS 配置以支持大屏字体缩放方案
260 lines
7.4 KiB
Vue
260 lines
7.4 KiB
Vue
<script setup lang="ts">
|
|
import { computed, onMounted, ref } from 'vue'
|
|
import { useRoute } from 'vue-router'
|
|
import drawImg from '@/assets/imgs/user/draw.webp'
|
|
import CompetitionLayout from '@/components/custom/user/CompetitionLayout.vue'
|
|
import { useRouterPush } from '@/hooks/common/router'
|
|
import { fetchGetCurrentQuestion } from '@/service/api/game'
|
|
import { useActivityInfoStore } from '@/store/modules/activityinfo'
|
|
import { useCompetitionStore } from '@/store/modules/competition'
|
|
import { AudioController } from '@/utils/audio'
|
|
|
|
const { routerPushByKey, routerBack } = useRouterPush()
|
|
|
|
// 获取路由参数
|
|
const route = useRoute()
|
|
const activityInfoStore = useActivityInfoStore()
|
|
const activityId = computed(() => activityInfoStore.activityId || route.query?.activityId as string)
|
|
const groupsId = computed(() => activityInfoStore.groupId || route.query?.groupsId as string)
|
|
const teamId = computed(() => activityInfoStore.teamId || route.query?.teamId as string)
|
|
const teamIds = computed(() => route.query?.teamIds as string)
|
|
const RoundType = computed(() => Number(route.query?.RoundType || 0) as Api.Competition.CompetitionRoundType)
|
|
|
|
const store = useCompetitionStore()
|
|
|
|
const loading = ref(false)
|
|
const question = ref<Api.Competition.CurrentQuestionResponse | null>(null)
|
|
const flippedId = ref<number | null>(null)
|
|
const audioController = new AudioController()
|
|
|
|
/**
|
|
* 初始化题目
|
|
* 1. 从服务器获取当前题目
|
|
* 2. 存储题目信息到全局状态
|
|
*/
|
|
async function initQuestions() {
|
|
if (!activityId.value)
|
|
return
|
|
|
|
loading.value = true
|
|
try {
|
|
const { data: outlineData, error } = await fetchGetCurrentQuestion({
|
|
ActivityID: Number(activityId.value),
|
|
GroupID: Number(groupsId.value),
|
|
RoundType: RoundType.value,
|
|
})
|
|
// eslint-disable-next-line no-console
|
|
console.log(outlineData, 'outlineData')
|
|
if (error) {
|
|
window?.$message?.error(error.message)
|
|
return
|
|
}
|
|
if (outlineData && outlineData.data) {
|
|
question.value = outlineData.data as unknown as Api.Competition.CurrentQuestionResponse
|
|
|
|
store.setCurrentQuestionMainInfo(question.value)
|
|
}
|
|
}
|
|
catch (error) {
|
|
window.$message?.error('获取题目失败,请重试')
|
|
console.error(error)
|
|
}
|
|
finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
onMounted(() => {
|
|
initQuestions()
|
|
})
|
|
|
|
/**
|
|
* 根据抽题页的大纲 id 随机获取的当前题目基础提纲信息下面的题目信息,跳转至游戏页面
|
|
*/
|
|
async function handleCardClick(item: Api.Competition.CurrentQuestionResponse) {
|
|
const { ID } = item?.Activity_Question || {}
|
|
|
|
if (!ID) {
|
|
window.$message?.error('题目ID不存在')
|
|
// 回到选组页面
|
|
routerPushByKey('user_groups', {
|
|
query: {
|
|
activityId: activityId.value,
|
|
RoundType: RoundType.value,
|
|
},
|
|
})
|
|
return
|
|
}
|
|
|
|
if (flippedId.value)
|
|
return // 防止重复点击
|
|
|
|
try {
|
|
// 1. 播放翻牌音效 (忽略音频错误)
|
|
try {
|
|
audioController.play('flip')
|
|
}
|
|
catch (e) {
|
|
console.error('Audio play failed', e)
|
|
}
|
|
// 2. 触发翻转动画
|
|
flippedId.value = ID
|
|
|
|
// 3. 延迟1s后跳转
|
|
setTimeout(() => {
|
|
try {
|
|
store.setCurrentQuestionInfo(item.Activity_Question || {})
|
|
routerPushByKey('user_game', {
|
|
query: {
|
|
activityId: activityId.value,
|
|
groupsId: groupsId.value,
|
|
teamId: teamId.value,
|
|
teamIds: teamIds.value,
|
|
},
|
|
})
|
|
}
|
|
catch (error) {
|
|
console.error('Navigation failed', error)
|
|
flippedId.value = null // 跳转失败则重置状态
|
|
window.$message?.error('跳转失败,请重试')
|
|
}
|
|
}, 1000)
|
|
}
|
|
catch (error) {
|
|
console.error(error)
|
|
flippedId.value = null
|
|
}
|
|
}
|
|
|
|
function handleBack() {
|
|
routerBack()
|
|
}
|
|
|
|
const questionMainTitle = computed(() => question.value?.QuestionList?.Name || '')
|
|
const questionTitle = computed(() => question.value?.QuestionList?.QuestionContent || question.value?.QuestionList?.Name || '')
|
|
|
|
const titleBgTypeNum = computed(() => RoundType.value === 0 ? 3 : 4)
|
|
</script>
|
|
|
|
<template>
|
|
<CompetitionLayout
|
|
:show-back="true" :show-next="true" back-btn-text="返回" :title="questionMainTitle || '抽题'" next-btn-text="开始抽题"
|
|
:title-bg-type="titleBgTypeNum || 3" title-text-color="#ffffff"
|
|
@next="handleCardClick(question as Api.Competition.CurrentQuestionResponse)" @back="handleBack"
|
|
>
|
|
<div class="h-full w-full flex flex-col items-center pt-10 font-sans">
|
|
<div v-if="question" class="mb-10 max-w-85% text-center">
|
|
<div class="text-4xl text-gray-800 font-medium leading-relaxed tracking-wide">
|
|
{{ questionTitle || '' }}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 题目卡片列表 -->
|
|
<div v-if="loading" class="text-xl text-gray-500">
|
|
加载中...
|
|
</div>
|
|
<div v-else class="w-full flex items-center justify-center pb-10">
|
|
<div v-if="question" class="lottery-stick-container">
|
|
<div
|
|
class="lottery-stick" :class="{ 'is-selected': flippedId === question?.Activity_Question?.ID }"
|
|
@click="handleCardClick(question)"
|
|
>
|
|
<!-- <div class="stick-face stick-body">
|
|
<div class="stick-top-mark" />
|
|
<div
|
|
class="writing-mode-vertical-rl h-full flex items-center justify-center text-xl text-yellow-900 font-bold tracking-widest opacity-80"
|
|
>
|
|
抽 题
|
|
</div>
|
|
</div>
|
|
<div class="stick-face stick-content">
|
|
<span class="writing-mode-vertical-rl text-2xl text-red-600 font-bold">{{ questionMainTitle
|
|
}}</span>
|
|
</div> -->
|
|
<!-- 禁用放大 -->
|
|
<NImage
|
|
:src="drawImg" class="h-full w-full object-cover" width="300" height="300"
|
|
:preview-disabled="true"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="!loading && !question" class="text-xl text-gray-400">
|
|
暂无题目
|
|
</div>
|
|
</div>
|
|
</CompetitionLayout>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.lottery-stick-container {
|
|
/* width: 50px;
|
|
height: 240px; */
|
|
perspective: 1000px;
|
|
cursor: pointer;
|
|
transition: transform 0.3s;
|
|
}
|
|
|
|
.lottery-stick-container:hover {
|
|
transform: translateY(-20px);
|
|
}
|
|
|
|
.lottery-stick {
|
|
width: 100%;
|
|
height: 100%;
|
|
position: relative;
|
|
transform-style: preserve-3d;
|
|
transition: all 0.8s cubic-bezier(0.4, 0, 0.2, 1);
|
|
}
|
|
|
|
.stick-face {
|
|
position: absolute;
|
|
width: 100%;
|
|
height: 100%;
|
|
backface-visibility: hidden;
|
|
border-radius: 8px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.2);
|
|
}
|
|
|
|
/* 竹签样式 */
|
|
.stick-body {
|
|
background: linear-gradient(to right, #e6cba5, #d4b081, #c69c6d);
|
|
border: 1px solid #b08d55;
|
|
}
|
|
|
|
.stick-top-mark {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 20px;
|
|
background-color: #ef4444;
|
|
/* red-500 */
|
|
border-radius: 8px 8px 0 0;
|
|
}
|
|
|
|
/* 翻转后的内容面 - 做成类似令牌的样子 */
|
|
.stick-content {
|
|
background: linear-gradient(to bottom, #fff9f0, #fff);
|
|
border: 2px solid #b08d55;
|
|
transform: rotateY(180deg);
|
|
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
|
|
}
|
|
|
|
/* 选中(抽出)动画状态 */
|
|
.lottery-stick.is-selected {
|
|
transform: translateY(-100px) scale(1.4) rotateY(180deg);
|
|
z-index: 50;
|
|
}
|
|
|
|
/* 竖排文字 */
|
|
.writing-mode-vertical-rl {
|
|
writing-mode: vertical-rl;
|
|
text-orientation: upright;
|
|
}
|
|
</style>
|