2026-02-06 17:45:44 +08:00
|
|
|
<script setup lang="ts">
|
2026-02-10 08:45:26 +08:00
|
|
|
import { computed, onMounted, ref } from 'vue'
|
|
|
|
|
import { useRoute } from 'vue-router'
|
2026-02-06 17:45:44 +08:00
|
|
|
import CompetitionLayout from '@/components/custom/user/CompetitionLayout.vue'
|
|
|
|
|
import { useRouterPush } from '@/hooks/common/router'
|
2026-02-10 08:45:26 +08:00
|
|
|
import { fetchGetQuestionOutline } from '@/service/api/game'
|
|
|
|
|
import { useActivityInfoStore } from '@/store/modules/activityinfo'
|
2026-02-06 17:45:44 +08:00
|
|
|
import { useCompetitionStore } from '@/store/modules/competition'
|
2026-02-10 08:45:26 +08:00
|
|
|
import { AudioController } from '@/utils/audio'
|
2026-02-06 17:45:44 +08:00
|
|
|
|
2026-02-10 08:45:26 +08:00
|
|
|
const { routerPushByKey, routerBack } = useRouterPush()
|
|
|
|
|
|
|
|
|
|
// 获取路由参数
|
|
|
|
|
const route = useRoute()
|
|
|
|
|
const activityInfoStore = useActivityInfoStore()
|
|
|
|
|
const activityId = computed(() => activityInfoStore.activityId || route.query?.activityId as string)
|
2026-02-06 17:45:44 +08:00
|
|
|
|
|
|
|
|
const store = useCompetitionStore()
|
|
|
|
|
|
2026-02-10 08:45:26 +08:00
|
|
|
const loading = ref(false)
|
|
|
|
|
const question = ref<Api.Competition.QuestionListRecord | null>(null)
|
|
|
|
|
const flippedId = ref<number | null>(null)
|
|
|
|
|
const audioController = new AudioController()
|
|
|
|
|
|
|
|
|
|
// eslint-disable-next-line unused-imports/no-unused-vars
|
|
|
|
|
const pageDescription = computed(() => {
|
|
|
|
|
if (question.value) {
|
|
|
|
|
return question.value.QuestionSubTitle || ''
|
|
|
|
|
}
|
|
|
|
|
return ''
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
async function initQuestions() {
|
|
|
|
|
if (!activityId.value)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
loading.value = true
|
|
|
|
|
try {
|
|
|
|
|
const { data: outlineData, error } = await fetchGetQuestionOutline(Number(activityId.value), 0)
|
|
|
|
|
// 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.QuestionListRecord
|
|
|
|
|
store.setQuestionList([question.value]) // Store expects array, so wrap it
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (error) {
|
|
|
|
|
window.$message?.error('获取题目失败,请重试')
|
|
|
|
|
console.error(error)
|
|
|
|
|
}
|
|
|
|
|
finally {
|
|
|
|
|
loading.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
initQuestions()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
async function handleCardClick(item: Api.Competition.QuestionListRecord) {
|
|
|
|
|
if (flippedId.value)
|
|
|
|
|
return // 防止重复点击
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// 1. 播放翻牌音效 (忽略音频错误)
|
|
|
|
|
try {
|
|
|
|
|
audioController.play('flip')
|
|
|
|
|
}
|
|
|
|
|
catch (e) {
|
|
|
|
|
console.error('Audio play failed', e)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 2. 触发翻转动画
|
|
|
|
|
flippedId.value = item.ID
|
|
|
|
|
|
|
|
|
|
// 3. 延迟1s后跳转
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
try {
|
|
|
|
|
store.setCurrentQuestionInfo(item)
|
|
|
|
|
routerPushByKey('user_game')
|
|
|
|
|
}
|
|
|
|
|
catch (error) {
|
|
|
|
|
console.error('Navigation failed', error)
|
|
|
|
|
flippedId.value = null // 跳转失败则重置状态
|
|
|
|
|
window.$message?.error('跳转失败,请重试')
|
|
|
|
|
}
|
|
|
|
|
}, 1000)
|
|
|
|
|
}
|
|
|
|
|
catch (error) {
|
|
|
|
|
console.error(error)
|
|
|
|
|
flippedId.value = null
|
|
|
|
|
}
|
2026-02-06 17:45:44 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleBack() {
|
2026-02-10 08:45:26 +08:00
|
|
|
routerBack()
|
2026-02-06 17:45:44 +08:00
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<template>
|
2026-02-10 08:45:26 +08:00
|
|
|
<CompetitionLayout :show-back="true" :show-next="false" :title="question?.ActitvityQuestionName || '抽题'" @back="handleBack">
|
2026-02-06 17:45:44 +08:00
|
|
|
<div class="h-full w-full flex flex-col items-center pt-10 font-sans">
|
2026-02-10 08:45:26 +08:00
|
|
|
<div v-if="question" class="mb-10 max-w-4xl text-center">
|
2026-02-06 17:45:44 +08:00
|
|
|
<div class="text-2xl text-gray-800 font-medium leading-relaxed tracking-wide">
|
2026-02-10 08:45:26 +08:00
|
|
|
{{ question?.ActitvityQuestionName || '' }}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 题目卡片列表 -->
|
|
|
|
|
<div v-if="loading" class="text-xl text-gray-500">
|
|
|
|
|
加载中...
|
|
|
|
|
</div>
|
|
|
|
|
<div v-else class="w-full flex flex-1 items-center justify-center pb-10">
|
|
|
|
|
<div v-if="question" class="lottery-stick-container">
|
|
|
|
|
<div
|
|
|
|
|
class="lottery-stick"
|
|
|
|
|
:class="{ 'is-selected': flippedId === 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">{{ question.ActitvityQuestionName }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-02-06 17:45:44 +08:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-02-10 08:45:26 +08:00
|
|
|
<div v-if="!loading && !question" class="text-xl text-gray-400">
|
|
|
|
|
暂无题目
|
2026-02-06 17:45:44 +08:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</CompetitionLayout>
|
|
|
|
|
</template>
|
2026-02-10 08:45:26 +08:00
|
|
|
|
|
|
|
|
<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(2) rotateY(180deg);
|
|
|
|
|
z-index: 50;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 竖排文字 */
|
|
|
|
|
.writing-mode-vertical-rl {
|
|
|
|
|
writing-mode: vertical-rl;
|
|
|
|
|
text-orientation: upright;
|
|
|
|
|
}
|
|
|
|
|
</style>
|