diff --git a/apps/admin/src/assets/audio/boy-10s.mp3 b/apps/admin/src/assets/audio/boy-10s.mp3 new file mode 100644 index 0000000..804ef64 Binary files /dev/null and b/apps/admin/src/assets/audio/boy-10s.mp3 differ diff --git a/apps/admin/src/assets/audio/girl-10.mp3 b/apps/admin/src/assets/audio/girl-10.mp3 new file mode 100644 index 0000000..5a6c96f Binary files /dev/null and b/apps/admin/src/assets/audio/girl-10.mp3 differ diff --git a/apps/admin/src/assets/audio/start3.mp3 b/apps/admin/src/assets/audio/start3.mp3 new file mode 100644 index 0000000..4f7a52b Binary files /dev/null and b/apps/admin/src/assets/audio/start3.mp3 differ diff --git a/apps/admin/src/assets/audio/system-10.mp3 b/apps/admin/src/assets/audio/system-10.mp3 new file mode 100644 index 0000000..e96f1de Binary files /dev/null and b/apps/admin/src/assets/audio/system-10.mp3 differ diff --git a/apps/admin/src/assets/imgs/user/draw.svg b/apps/admin/src/assets/imgs/user/draw.svg new file mode 100644 index 0000000..521736e --- /dev/null +++ b/apps/admin/src/assets/imgs/user/draw.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/admin/src/assets/imgs/user/g1.svg b/apps/admin/src/assets/imgs/user/g1.svg new file mode 100644 index 0000000..a6deb83 --- /dev/null +++ b/apps/admin/src/assets/imgs/user/g1.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/admin/src/assets/imgs/user/g2.svg b/apps/admin/src/assets/imgs/user/g2.svg new file mode 100644 index 0000000..5e896a5 --- /dev/null +++ b/apps/admin/src/assets/imgs/user/g2.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/admin/src/assets/imgs/user/star-icon.svg b/apps/admin/src/assets/imgs/user/star-icon.svg index 54840ed..12375ef 100644 --- a/apps/admin/src/assets/imgs/user/star-icon.svg +++ b/apps/admin/src/assets/imgs/user/star-icon.svg @@ -1,48 +1,66 @@ - - - - + + + - - + + + + - - - - - - + + + + + + - + + + + - + - - - + + + - - + + - + + + + + + - - - + + + - - + + - - + + + + + + + + + + + diff --git a/apps/admin/src/assets/imgs/user/team-title-bg.svg b/apps/admin/src/assets/imgs/user/team-title-bg.svg new file mode 100644 index 0000000..a900329 --- /dev/null +++ b/apps/admin/src/assets/imgs/user/team-title-bg.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/apps/admin/src/assets/imgs/user/team-title-icon.svg b/apps/admin/src/assets/imgs/user/team-title-icon.svg new file mode 100644 index 0000000..a345919 --- /dev/null +++ b/apps/admin/src/assets/imgs/user/team-title-icon.svg @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/admin/src/assets/imgs/user/title-bg2.svg b/apps/admin/src/assets/imgs/user/title-bg2.svg index 5abffb8..de6b2cc 100644 --- a/apps/admin/src/assets/imgs/user/title-bg2.svg +++ b/apps/admin/src/assets/imgs/user/title-bg2.svg @@ -1,21 +1,9 @@ - - - - + + - - - - - - - - - - - - + + - + diff --git a/apps/admin/src/service/api/question.ts b/apps/admin/src/service/api/question.ts index 5e394d1..78c0f64 100644 --- a/apps/admin/src/service/api/question.ts +++ b/apps/admin/src/service/api/question.ts @@ -112,6 +112,7 @@ export function fetchUpdateQuestionLibrary(params: Api.Question.AddQuestionLibra Type: params.type, IsGood: params.IsGood, ImageUrl: params.imageUrl, + IsDisabled: params.IsDisabled, }, }) } diff --git a/apps/admin/src/store/modules/competition/index.ts b/apps/admin/src/store/modules/competition/index.ts index 96bfc53..7647134 100644 --- a/apps/admin/src/store/modules/competition/index.ts +++ b/apps/admin/src/store/modules/competition/index.ts @@ -93,9 +93,9 @@ export const useCompetitionStore = defineStore('competition', () => { timerInterval.value = window.setInterval(() => { if (timeLeft.value > 0) { timeLeft.value-- - // 剩余5秒播放倒计时音效 - if (timeLeft.value <= 5) { - audioController.play('tick') + // 剩余10秒播放倒计时音效 + if (timeLeft.value <= 10) { + audioController.play('tick10') } } else { diff --git a/apps/admin/src/typings/api/question.d.ts b/apps/admin/src/typings/api/question.d.ts index 15a95ca..965b5d2 100644 --- a/apps/admin/src/typings/api/question.d.ts +++ b/apps/admin/src/typings/api/question.d.ts @@ -68,8 +68,8 @@ declare namespace Api { type: QuestionScoreType /** is priority 0: no 1: yes */ IsGood: number - /** is disabled 0: no 1: yes */ - IsDisabled: number + /** is disabled */ + IsDisabled: boolean } /** common search params of table */ diff --git a/apps/admin/src/utils/audio.ts b/apps/admin/src/utils/audio.ts index 580fa4c..6b0f34a 100644 --- a/apps/admin/src/utils/audio.ts +++ b/apps/admin/src/utils/audio.ts @@ -1,10 +1,15 @@ +import system10 from '@/assets/audio/boy-10s.mp3' + export class AudioController { private audioContext: AudioContext | null = null private soundFiles: Record = {} private audioCache: Record = {} constructor(soundFiles: Record = {}) { - this.soundFiles = soundFiles + this.soundFiles = { + tick10: system10, + ...soundFiles, + } } private initAudioContext() { @@ -17,7 +22,7 @@ export class AudioController { * 播放指定类型的音效 * 优先使用预设的音频文件,如果没有配置或加载失败,则使用 Web Audio API 合成音效 */ - async play(type: 'start' | 'tick' | 'flip') { + async play(type: 'start' | 'tick10' | 'flip') { // 尝试播放音频文件 if (this.soundFiles[type]) { try { @@ -54,7 +59,7 @@ export class AudioController { }) } - private playSynth(type: 'start' | 'tick' | 'flip') { + private playSynth(type: 'start' | 'tick10' | 'flip') { this.initAudioContext() if (!this.audioContext) return @@ -75,7 +80,7 @@ export class AudioController { osc.start(ctxTime) osc.stop(ctxTime + 0.3) } - else if (type === 'tick') { + else if (type === 'tick10') { const osc = this.audioContext.createOscillator() osc.connect(gain) // 倒计时音效:急促的滴答声 diff --git a/apps/admin/src/views/question-store/modules/QuestionList.vue b/apps/admin/src/views/question-store/modules/QuestionList.vue index 0eebf28..516865f 100644 --- a/apps/admin/src/views/question-store/modules/QuestionList.vue +++ b/apps/admin/src/views/question-store/modules/QuestionList.vue @@ -113,7 +113,7 @@ const questionForm = ref({ type: QuestionType.SingleChoice as QuestionType | number, // 题目类型 imageUrl: '', IsGood: 0, - IsDisabled: 0, + IsDisabled: false, }) const fileList = ref([]) @@ -185,6 +185,7 @@ async function handleBatchDisable(disabled: boolean) { } const payload = selectedQuestionIds.value.map(id => ({ + id, Id: id, IsDisabled: disabled, })) @@ -266,7 +267,7 @@ function handleAddQuestion() { type: props.currentCategory?.QuestionType || QuestionType.SingleChoice, imageUrl: '', IsGood: 0, - IsDisabled: 0, + IsDisabled: false, } fileList.value = [] showQuestionModal.value = true @@ -291,7 +292,7 @@ function handleEditQuestion(id: number) { type: question.Type, imageUrl: question.ImageUrl || '', IsGood: question.IsPriority || 0, - IsDisabled: question.IsDisabled || 0, + IsDisabled: !!question.IsDisabled, } fileList.value = [] if (question.ImageUrl) { @@ -344,6 +345,7 @@ async function submitQuestion() { type: questionForm.value.type as any, questionId: props.currentCategory.Id, IsGood: questionForm.value.IsGood, + // 传给后端为 boolean IsDisabled: questionForm.value.IsDisabled, } @@ -616,10 +618,10 @@ async function submitQuestion() { - + - + diff --git a/apps/admin/src/views/results/results-detail/index.vue b/apps/admin/src/views/results/results-detail/index.vue index f2e4d6c..8f8df0c 100644 --- a/apps/admin/src/views/results/results-detail/index.vue +++ b/apps/admin/src/views/results/results-detail/index.vue @@ -282,7 +282,7 @@ async function fetchAnswerData() { // 假设返回的数据结构是数组,取第一条或者根据逻辑取特定的一条 const result = data?.data || [] - console.log('接口返回的游戏统计数据:', result) + // console.log('接口返回的游戏统计数据:', result) // 获取题目详情 ID let detailId = 0 @@ -364,24 +364,11 @@ async function fetchAnswerData() { // 优先使用 ans 中的图片,如果没有则回退到 answerData 中的图片 const imageUrl = ans.AnswerValuePicture || (parsedAnswers.length === 1 && parsedAnswers[0] === ans ? answerData.UserAnswerPicture : '') let finalImageUrl = imageUrl - let cleanBaseUrl = '' - let timestamp = new Date().getTime() + const cleanBaseUrl = '' + const timestamp = new Date().getTime() - if (imageUrl) { - // 清理 URL 中的反引号、引号和空格 - cleanBaseUrl = imageUrl.replace(/[`'"]/g, '').trim() - - // 尝试复用旧的时间戳以防止闪烁 - // 只有当 URL 发生变化,或者 AI 状态/分数发生变化时才更新时间戳 - // 注意:如果 ans.AnswerValuePicture 发生了变化(即使只是参数变化),cleanBaseUrl 会变 - if (oldAns && oldAns._baseImgUrl === cleanBaseUrl && oldAns._aiStatus === aiStatus && oldAns._aiScore === aiScore && oldAns._t) { - timestamp = oldAns._t - } - - // 强制刷新:每次都添加新的时间戳 - const separator = cleanBaseUrl.includes('?') ? '&' : '?' - finalImageUrl = `${cleanBaseUrl}${separator}_t=${timestamp}` - } + finalImageUrl = `${finalImageUrl}?_t=${timestamp}` + console.log(finalImageUrl, 'finalImageUrl') // 判断是否手动干预: // 1. 本地存储有状态 (hasStoredStatus) 或有分数 (hasStoredScore) @@ -479,6 +466,11 @@ async function handleNexCurrentQuestion() { } function handlePublish() { + const endTimeStr = (currentQuestion.value as any)?.EndTime + if (endTimeStr && Date.now() < new Date(endTimeStr).getTime()) { + window.$message?.warning('倒计时未结束,暂不能发布') + return + } if (currentQuestion.value?.TeamGroupQuestionID) { // 检查是否有未评分的题目 let hasUnrated = false @@ -586,6 +578,14 @@ function handlePublish() { * 消费并发布当前题目 */ const pollingTimer = ref(null) +const nowTs = ref(Date.now()) +const canPublish = computed(() => { + const endTime = (currentQuestion.value as any)?.EndTime + if (!endTime) + return false + const end = new Date(endTime).getTime() + return nowTs.value >= end +}) function startPolling() { stopPolling() @@ -611,6 +611,7 @@ function startPolling() { } fetchAnswerData() // 立即获取 + nowTs.value = Date.now() pollingTimer.value = setInterval(() => { // 每次轮询前再次检查是否过期 const currentEndTime = (currentQuestion.value as any)?.EndTime @@ -623,6 +624,7 @@ function startPolling() { return } } + nowTs.value = Date.now() fetchAnswerData() }, 3000) } @@ -714,7 +716,7 @@ onUnmounted(() => { 下一题 - + 发布结果 @@ -776,7 +778,7 @@ onUnmounted(() => { !item._isManual ? 'border-gray-300 bg-gray-50/30' : (item.Status === 1 ? 'border-green-500 bg-green-50/30' : 'border-red-500 bg-red-50/30'), ]" > -
+
由 ai识别出的答题文本:
diff --git a/apps/admin/src/views/results/results-detail/modules/Typewriter.vue b/apps/admin/src/views/results/results-detail/modules/Typewriter.vue index 417d1dd..a744072 100644 --- a/apps/admin/src/views/results/results-detail/modules/Typewriter.vue +++ b/apps/admin/src/views/results/results-detail/modules/Typewriter.vue @@ -17,6 +17,11 @@ function init() { typeItInstance.destroy() } + // 清空上一次的内容,避免重复累加显示 + if (textRef.value) { + textRef.value.textContent = '' + } + const options: Options = { strings: props.text || '', lifeLike: true, diff --git a/apps/admin/src/views/user/cover/index.vue b/apps/admin/src/views/user/cover/index.vue index 805f102..b3cba15 100644 --- a/apps/admin/src/views/user/cover/index.vue +++ b/apps/admin/src/views/user/cover/index.vue @@ -128,8 +128,10 @@ function toggleFlip(index: number) { } else { flippedCards.value.add(index) - audioController.play('flip') // 播放翻牌音效 + audioController.play('flip') // 翻牌动效 } + // 每次点击都播放翻牌音效(无论翻开还是翻回去) + // audioController.play('flip') } async function initSyncRouteParams() { @@ -148,6 +150,39 @@ async function initSyncRouteParams() { const showCards = ref(false) +// 3D 悬停效果逻辑 +const cardStyles = ref>({}) + +function handleMouseMove(e: MouseEvent, index: number) { + const card = e.currentTarget as HTMLElement + const rect = card.getBoundingClientRect() + const x = e.clientX - rect.left + const y = e.clientY - rect.top + const centerX = rect.width / 2 + const centerY = rect.height / 2 + + // 计算旋转角度 + // 鼠标在上方(y < centerY) -> rotateX > 0 (上半部分凸起) + // 鼠标在下方(y > centerY) -> rotateX < 0 (下半部分凸起) + const rotateX = ((centerY - y) / centerY) * 10 + + // 鼠标在左方(x < centerX) -> rotateY < 0 (左半部分凸起) + // 鼠标在右方(x > centerX) -> rotateY > 0 (右半部分凸起) + const rotateY = ((x - centerX) / centerX) * 10 + + cardStyles.value[index] = { + transform: `perspective(1000px) rotateX(${rotateX}deg) rotateY(${rotateY}deg) scale3d(1.02, 1.02, 1.02)`, + transition: 'transform 0.1s ease-out', // 移动时响应要快 + } +} + +function handleMouseLeave(index: number) { + cardStyles.value[index] = { + transform: 'perspective(1000px) rotateX(0) rotateY(0) scale3d(1, 1, 1)', + transition: 'transform 0.5s ease', // 离开时平滑复位 + } +} + onMounted(async () => { await initSyncRouteParams() await getQuestions() @@ -160,8 +195,8 @@ onMounted(async () => {