Files
vueapp/pages/teaching/teaching.vue
T
2026-06-11 12:12:55 +08:00

1001 lines
27 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="teaching-page">
<view class="teaching-shell">
<view class="top-nav">
<button class="icon-button" aria-label="设置" @click="openSettings">
<view class="settings-icon"></view>
</button>
<button class="icon-button home-button" aria-label="首页" @click="goHome">
<view class="home-icon"></view>
</button>
<view class="nav-spacer"></view>
<button class="icon-button" aria-label="个人中心" @click="openProfile">
<view class="account-icon"></view>
</button>
</view>
<view class="patient-header">
<text class="case-heading">患者{{ patient.name }} ({{ complaintShort }})</text>
<view class="patient-meta">
<text>姓名{{ patient.name }}</text>
<text>性别{{ patient.gender }}</text>
<text>年龄{{ patient.age }}</text>
<text>科室{{ patient.department }}</text>
</view>
</view>
<scroll-view class="teaching-body" scroll-y>
<view class="mentor-section">
<view class="mentor-profile">
<view class="mentor-avatar">
<image src="/static/config-doctor.png" mode="aspectFill"></image>
</view>
<view class="online-dot"></view>
<text class="mentor-name">王主任</text>
</view>
<view class="question-bubble">
<text>{{ currentQuestion?.question || questionStatusText }}</text>
</view>
</view>
<view v-if="showVideoView && currentQuestion" class="video-section">
<video
v-if="currentQuestion.videoUrl"
class="video-player real-video"
:src="currentQuestion.videoUrl"
controls
autoplay
@play="videoPlaying = true"
@pause="videoPlaying = false"
@ended="videoPlaying = false"
@error="handleVideoError"
></video>
<view v-else class="video-player" @click="toggleVideoPlay">
<view class="video-poster" :class="{ playing: videoPlaying }">
<view class="heart-visual">
<view class="heart-core"></view>
<view class="heart-pulse pulse-one"></view>
<view class="heart-pulse pulse-two"></view>
</view>
</view>
<view class="video-overlay">
<view class="play-button" :class="{ playing: videoPlaying }">
<view class="play-icon"></view>
</view>
</view>
<view class="video-progress">
<view class="video-progress-fill" :style="{ width: videoPlaying ? '62%' : '33%' }"></view>
</view>
</view>
<view class="video-copy">
<text class="video-title">{{ currentQuestion.videoTitle || '讲解视频' }}</text>
<text v-if="currentQuestion.videoDesc" class="video-desc">{{ currentQuestion.videoDesc }}</text>
</view>
</view>
<view v-else-if="currentQuestion" class="option-list">
<button
v-for="option in currentQuestion.options"
:key="option.value"
class="option-card"
:class="getOptionClass(option.value)"
@click="selectOption(option.value)"
>
<text class="option-key">{{ option.key }}</text>
<text class="option-text">{{ option.text }}</text>
<view v-if="hasCorrectAnswer && selectedOption === option.value && option.value !== currentQuestion.correctAnswer" class="wrong-icon"></view>
<view v-if="hasCorrectAnswer && selectedOption === option.value && option.value === currentQuestion.correctAnswer" class="right-icon"></view>
</button>
</view>
<view v-else class="empty-state">
<text>{{ questionStatusText }}</text>
</view>
<view v-if="!showVideoView && selectedOption && hasAnalysis" class="analysis-card">
<view class="analysis-title">
<view class="bulb-icon"></view>
<text>答案解析</text>
</view>
<view class="analysis-content">
<text v-if="currentQuestion?.analysis" class="analysis-main">{{ currentQuestion.analysis }}</text>
<view v-if="currentQuestion?.analysis && currentQuestion?.note" class="analysis-divider"></view>
<text v-if="currentQuestion?.note" class="analysis-note">{{ currentQuestion.note }}</text>
</view>
</view>
<view class="bottom-actions">
<button v-if="!showVideoView && hasVideo" class="video-button" @click="handleWatchVideo">
<view class="video-icon"></view>
<text>查看知识点视频</text>
</button>
<button
class="next-button"
:class="{ disabled: !currentQuestion || submittingEvaluation || loadingQuestions }"
:disabled="!currentQuestion || submittingEvaluation || loadingQuestions"
@click="handleNextQuestion"
>
<text>{{ nextButtonText }}</text>
<view class="next-icon"></view>
</button>
</view>
</scroll-view>
</view>
<view class="toast" :class="{ visible: toastVisible }">{{ toastMessage }}</view>
</view>
</template>
<script setup lang="ts">
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { readStoredClinicalCase, type ClinicalCase } from '../../api/cases'
import { createHomeNavigator, createProfileOpener, createSettingsOpener } from '../../api/navigation'
import {
fetchTeachingCaseItems,
generateTeachingEvaluation,
type TeachingAnswer,
type TeachingCaseQuestion
} from '../../api/teaching'
const props = defineProps<{
caseItem?: ClinicalCase | null
}>()
const emit = defineEmits<{
(event: 'open-settings'): void
(event: 'open-profile'): void
(event: 'go-home'): void
}>()
const openProfile = createProfileOpener(emit)
const openSettings = createSettingsOpener(emit)
const goHome = createHomeNavigator(emit)
const TEACHING_CASE_ID = 1
const questionIndex = ref(0)
const selectedOption = ref('')
const showVideoView = ref(false)
const videoPlaying = ref(false)
const storedCase = ref<ClinicalCase | null>(null)
const questions = ref<TeachingCaseQuestion[]>([])
const answerMap = ref<Record<string, string>>({})
const loadingQuestions = ref(false)
const submittingEvaluation = ref(false)
const questionLoadFailed = ref(false)
const toastMessage = ref('')
const toastVisible = ref(false)
let toastTimer: ReturnType<typeof setTimeout> | null = null
const activeCase = computed(() => props.caseItem || storedCase.value)
const activeCaseId = computed(() => TEACHING_CASE_ID)
const patient = computed(() => ({
name: activeCase.value?.patientName || '未选择病例',
gender: activeCase.value?.gender || '-',
age: activeCase.value?.age || '-',
department: activeCase.value?.department || '-',
chiefComplaint: activeCase.value?.title || '暂无病例信息'
}))
const complaintShort = computed(() => {
if (patient.value.chiefComplaint.includes('胸痛')) return '胸痛'
return patient.value.chiefComplaint.slice(0, 6)
})
const currentQuestion = computed(() => questions.value[questionIndex.value] || null)
const hasCorrectAnswer = computed(() => Boolean(currentQuestion.value?.correctAnswer))
const hasAnalysis = computed(() => Boolean(currentQuestion.value?.analysis || currentQuestion.value?.note))
const hasVideo = computed(() => Boolean(currentQuestion.value?.videoUrl))
const isLastQuestion = computed(() => questionIndex.value >= questions.value.length - 1)
const nextButtonText = computed(() => {
if (submittingEvaluation.value) return '提交中...'
return isLastQuestion.value ? '提交评价' : '下一题'
})
const questionStatusText = computed(() => {
if (loadingQuestions.value) return '题目加载中...'
if (questionLoadFailed.value) return '题目加载失败,请稍后重试。'
return '暂无教学题目'
})
function selectOption(value: string) {
selectedOption.value = value
if (currentQuestion.value) {
answerMap.value[String(currentQuestion.value.id)] = value
}
}
function getOptionClass(value: string) {
if (selectedOption.value !== value) return ''
if (!currentQuestion.value?.correctAnswer) return 'selected-correct'
return value === currentQuestion.value.correctAnswer ? 'selected-correct' : 'selected-wrong'
}
function handleWatchVideo() {
if (!currentQuestion.value?.videoUrl) {
showToast('当前题目暂无讲解视频')
return
}
showVideoView.value = true
videoPlaying.value = false
}
async function handleNextQuestion() {
if (!currentQuestion.value || submittingEvaluation.value || loadingQuestions.value) return
if (!selectedOption.value) {
showToast('请先选择答案')
return
}
if (isLastQuestion.value) {
await submitTeachingEvaluation()
return
}
const nextIndex = questionIndex.value + 1
questionIndex.value = nextIndex
selectedOption.value = answerMap.value[String(questions.value[nextIndex].id)] || ''
showVideoView.value = false
videoPlaying.value = false
uni.setStorageSync('clinical-thinking-teaching-question', {
caseId: activeCaseId.value,
questionId: questions.value[nextIndex].id,
index: nextIndex
})
}
function toggleVideoPlay() {
videoPlaying.value = !videoPlaying.value
}
function handleVideoError() {
showToast('讲解视频加载失败')
}
async function loadTeachingQuestions() {
if (!activeCaseId.value) {
questionLoadFailed.value = true
showToast('未找到当前教学病例')
return
}
loadingQuestions.value = true
questionLoadFailed.value = false
try {
const result = await fetchTeachingCaseItems(activeCaseId.value)
questions.value = result
questionIndex.value = 0
selectedOption.value = ''
answerMap.value = {}
showVideoView.value = false
videoPlaying.value = false
if (result.length === 0) {
questionLoadFailed.value = true
showToast('暂无教学题目')
}
} catch (error) {
questionLoadFailed.value = true
showToast(error instanceof Error ? error.message : '题目列表加载失败')
} finally {
loadingQuestions.value = false
}
}
async function submitTeachingEvaluation() {
if (!activeCaseId.value) {
showToast('未找到当前教学病例')
return
}
const answers = buildTeachingAnswers()
if (answers.length !== questions.value.length) {
showToast('请完成全部题目后再提交')
return
}
submittingEvaluation.value = true
try {
const result = await generateTeachingEvaluation({
case_id: activeCaseId.value,
answers
})
uni.setStorageSync('clinical-thinking-case-mode', 'teaching')
uni.setStorageSync('clinical-thinking-teaching-evaluation', result)
uni.setStorageSync('clinical-thinking-teaching-evaluation-id', result.evaluation_id)
uni.navigateTo({
url: '/pages/assessment/assessment'
})
} catch (error) {
showToast(error instanceof Error ? error.message : '教学评价生成失败')
} finally {
submittingEvaluation.value = false
}
}
function buildTeachingAnswers(): TeachingAnswer[] {
return questions.value
.map(item => ({
question_id: item.id,
selected_answer: answerMap.value[String(item.id)] || ''
}))
.filter(item => item.selected_answer)
}
function showToast(message: string) {
if (toastTimer) clearTimeout(toastTimer)
toastMessage.value = message
toastVisible.value = true
toastTimer = setTimeout(() => {
toastVisible.value = false
}, 2200)
}
onMounted(() => {
storedCase.value = readStoredClinicalCase()
void loadTeachingQuestions()
})
onUnmounted(() => {
if (toastTimer) clearTimeout(toastTimer)
})
</script>
<style>
page {
min-height: 100%;
background: #e7e8f0;
}
.teaching-page {
min-height: 100vh;
background: #e7e8f0;
color: #191c21;
font-family: Inter, -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', Arial, sans-serif;
-webkit-tap-highlight-color: transparent;
}
.teaching-shell {
position: relative;
height: 100vh;
overflow: hidden;
background: #f2f3fb;
display: flex;
flex-direction: column;
}
.top-nav {
position: relative;
z-index: 20;
box-sizing: border-box;
height: 56px;
padding: 0 20px;
border-bottom: 1px solid rgba(194, 198, 212, 0.3);
background: rgba(255, 255, 255, 0.82);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
display: flex;
align-items: center;
}
.nav-spacer {
flex: 1;
}
.icon-button {
width: 40px;
height: 40px;
padding: 0;
border-radius: 50%;
background: transparent;
display: flex;
align-items: center;
justify-content: center;
}
.icon-button::after {
border: 0;
}
.icon-button:active {
background: rgba(25, 28, 33, 0.05);
}
.home-button {
margin-left: 4px;
}
.settings-icon,
.home-icon,
.account-icon {
background: #424752;
}
.settings-icon {
width: 22px;
height: 22px;
-webkit-mask: url("data:image/svg+xml,%3Csvg%20viewBox='0%200%2024%2024'%20xmlns='http://www.w3.org/2000/svg'%3E%3Cpath%20d='M19.43%2012.98c.04-.32.07-.65.07-.98s-.02-.66-.07-.98l2.11-1.65c.19-.15.24-.42.12-.64l-2-3.46c-.12-.22-.37-.31-.6-.22l-2.49%201c-.52-.4-1.08-.73-1.69-.98L14.5%202.42C14.47%202.18%2014.25%202%2014%202h-4c-.25%200-.46.18-.5.42l-.38%202.65c-.61.25-1.17.59-1.69.98l-2.49-1c-.23-.08-.48%200-.6.22l-2%203.46c-.13.22-.07.49.12.64l2.11%201.65c-.04.32-.08.65-.08.98s.03.66.08.98l-2.11%201.65c-.19.15-.24.42-.12.64l2%203.46c.12.22.37.31.6.22l2.49-1c.52.4%201.08.73%201.69.98l.38%202.65c.04.24.25.42.5.42h4c.25%200%20.46-.18.5-.42l.38-2.65c.61-.25%201.17-.58%201.69-.98l2.49%201c.23.08.48%200%20.6-.22l2-3.46c.12-.22.07-.49-.12-.64l-2.11-1.65zM12%2015.5A3.5%203.5%200%201%201%2012%208a3.5%203.5%200%200%201%200%207.5z'/%3E%3C/svg%3E") center / contain no-repeat;
mask: url("data:image/svg+xml,%3Csvg%20viewBox='0%200%2024%2024'%20xmlns='http://www.w3.org/2000/svg'%3E%3Cpath%20d='M19.43%2012.98c.04-.32.07-.65.07-.98s-.02-.66-.07-.98l2.11-1.65c.19-.15.24-.42.12-.64l-2-3.46c-.12-.22-.37-.31-.6-.22l-2.49%201c-.52-.4-1.08-.73-1.69-.98L14.5%202.42C14.47%202.18%2014.25%202%2014%202h-4c-.25%200-.46.18-.5.42l-.38%202.65c-.61.25-1.17.59-1.69.98l-2.49-1c-.23-.08-.48%200-.6.22l-2%203.46c-.13.22-.07.49.12.64l2.11%201.65c-.04.32-.08.65-.08.98s.03.66.08.98l-2.11%201.65c-.19.15-.24.42-.12.64l2%203.46c.12.22.37.31.6.22l2.49-1c.52.4%201.08.73%201.69.98l.38%202.65c.04.24.25.42.5.42h4c.25%200%20.46-.18.5-.42l.38-2.65c.61-.25%201.17-.58%201.69-.98l2.49%201c.23.08.48%200%20.6-.22l2-3.46c.12-.22.07-.49-.12-.64l-2.11-1.65zM12%2015.5A3.5%203.5%200%201%201%2012%208a3.5%203.5%200%200%201%200%207.5z'/%3E%3C/svg%3E") center / contain no-repeat;
}
.home-icon {
width: 23px;
height: 23px;
-webkit-mask: url("data:image/svg+xml,%3Csvg%20viewBox='0%200%2024%2024'%20xmlns='http://www.w3.org/2000/svg'%3E%3Cpath%20d='M10%2020v-6h4v6h5v-8h3L12%203%202%2012h3v8h5z'/%3E%3C/svg%3E") center / contain no-repeat;
mask: url("data:image/svg+xml,%3Csvg%20viewBox='0%200%2024%2024'%20xmlns='http://www.w3.org/2000/svg'%3E%3Cpath%20d='M10%2020v-6h4v6h5v-8h3L12%203%202%2012h3v8h5z'/%3E%3C/svg%3E") center / contain no-repeat;
}
.account-icon {
width: 24px;
height: 24px;
-webkit-mask: url("data:image/svg+xml,%3Csvg%20viewBox='0%200%2024%2024'%20xmlns='http://www.w3.org/2000/svg'%3E%3Cpath%20d='M12%202a10%2010%200%201%200%200%2020%2010%2010%200%200%200%200-20zm0%203a3.5%203.5%200%201%201%200%207%203.5%203.5%200%200%201%200-7zm0%2015a8%208%200%200%201-6.4-3.2c1.18-2.02%203.57-3.3%206.4-3.3s5.22%201.28%206.4%203.3A8%208%200%200%201%2012%2020z'/%3E%3C/svg%3E") center / contain no-repeat;
mask: url("data:image/svg+xml,%3Csvg%20viewBox='0%200%2024%2024'%20xmlns='http://www.w3.org/2000/svg'%3E%3Cpath%20d='M12%202a10%2010%200%201%200%200%2020%2010%2010%200%200%200%200-20zm0%203a3.5%203.5%200%201%201%200%207%203.5%203.5%200%200%201%200-7zm0%2015a8%208%200%200%201-6.4-3.2c1.18-2.02%203.57-3.3%206.4-3.3s5.22%201.28%206.4%203.3A8%208%200%200%201%2012%2020z'/%3E%3C/svg%3E") center / contain no-repeat;
}
.patient-header {
position: relative;
z-index: 10;
padding: 16px 20px;
border-bottom: 1px solid rgba(194, 198, 212, 0.2);
background: #ffffff;
box-shadow: 0 2px 8px rgba(25, 28, 33, 0.04);
}
.case-heading {
display: block;
color: #191c21;
font-size: 20px;
line-height: 28px;
font-weight: 600;
}
.patient-meta {
margin-top: 8px;
padding-top: 8px;
border-top: 1px solid rgba(194, 198, 212, 0.3);
display: flex;
flex-wrap: wrap;
gap: 4px 24px;
color: #424752;
font-size: 13px;
line-height: 20px;
}
.teaching-body {
flex: 1;
min-height: 0;
padding-bottom: 40px;
}
.mentor-section {
padding: 24px 20px 0;
display: flex;
align-items: flex-start;
gap: 16px;
}
.mentor-profile {
position: relative;
flex: 0 0 auto;
width: 64px;
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
}
.mentor-avatar {
width: 64px;
height: 64px;
border: 2px solid #ffffff;
border-radius: 16px;
background: #ffffff;
box-shadow: 0 4px 12px rgba(0, 71, 141, 0.16);
overflow: hidden;
animation: pulse-border 2s infinite;
}
.mentor-avatar image {
width: 100%;
height: 100%;
}
.online-dot {
position: absolute;
right: 2px;
top: 50px;
width: 16px;
height: 16px;
border: 2px solid #ffffff;
border-radius: 50%;
background: #22c55e;
box-shadow: 0 2px 4px rgba(25, 28, 33, 0.16);
}
.mentor-name {
padding: 2px 8px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.6);
color: #191c21;
font-size: 12px;
line-height: 18px;
font-weight: 700;
}
.question-bubble {
position: relative;
flex: 1;
padding: 16px;
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 16px;
background: rgba(255, 255, 255, 0.72);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
box-shadow: 0 2px 8px rgba(25, 28, 33, 0.06);
color: #191c21;
font-size: 16px;
line-height: 24px;
}
.question-bubble::after {
content: '';
position: absolute;
left: -8px;
top: 24px;
width: 0;
height: 0;
border-top: 8px solid transparent;
border-right: 8px solid rgba(255, 255, 255, 0.72);
border-bottom: 8px solid transparent;
}
.video-section {
padding: 24px 20px 0;
display: flex;
flex-direction: column;
gap: 16px;
}
.video-player {
position: relative;
width: 100%;
aspect-ratio: 16 / 9;
border-radius: 16px;
background: #050816;
box-shadow: 0 8px 20px rgba(25, 28, 33, 0.18);
overflow: hidden;
}
.real-video {
display: block;
object-fit: contain;
}
.video-poster {
position: absolute;
inset: 0;
background:
radial-gradient(circle at 58% 42%, rgba(169, 199, 255, 0.35), transparent 28%),
radial-gradient(circle at 38% 58%, rgba(125, 244, 255, 0.22), transparent 34%),
linear-gradient(135deg, #081426 0%, #12345f 52%, #07111f 100%);
}
.video-poster.playing .heart-pulse {
animation-play-state: running;
}
.heart-visual {
position: absolute;
left: 50%;
top: 50%;
width: 116px;
height: 116px;
transform: translate(-50%, -50%);
}
.heart-core {
position: absolute;
left: 50%;
top: 50%;
width: 72px;
height: 72px;
border-radius: 48% 52% 48% 52%;
background: linear-gradient(135deg, #ff6b6b, #9f4300);
box-shadow: 0 0 32px rgba(255, 182, 145, 0.55);
transform: translate(-50%, -50%) rotate(45deg);
}
.heart-pulse {
position: absolute;
inset: 0;
border: 2px solid rgba(169, 199, 255, 0.62);
border-radius: 50%;
animation: video-pulse 1.8s ease-out infinite paused;
}
.pulse-two {
animation-delay: 0.75s;
}
.video-overlay {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.2);
display: flex;
align-items: center;
justify-content: center;
}
.play-button {
width: 64px;
height: 64px;
border-radius: 50%;
background: rgba(0, 71, 141, 0.92);
box-shadow: 0 8px 18px rgba(0, 0, 0, 0.26);
display: flex;
align-items: center;
justify-content: center;
}
.play-button.playing {
opacity: 0.78;
}
.play-icon {
width: 34px;
height: 34px;
margin-left: 4px;
background: #ffffff;
-webkit-mask: url("data:image/svg+xml,%3Csvg%20viewBox='0%200%2024%2024'%20xmlns='http://www.w3.org/2000/svg'%3E%3Cpath%20d='M8%205v14l11-7z'/%3E%3C/svg%3E") center / contain no-repeat;
mask: url("data:image/svg+xml,%3Csvg%20viewBox='0%200%2024%2024'%20xmlns='http://www.w3.org/2000/svg'%3E%3Cpath%20d='M8%205v14l11-7z'/%3E%3C/svg%3E") center / contain no-repeat;
}
.play-button.playing .play-icon {
margin-left: 0;
-webkit-mask: url("data:image/svg+xml,%3Csvg%20viewBox='0%200%2024%2024'%20xmlns='http://www.w3.org/2000/svg'%3E%3Cpath%20d='M6%205h4v14H6V5zm8%200h4v14h-4V5z'/%3E%3C/svg%3E") center / contain no-repeat;
mask: url("data:image/svg+xml,%3Csvg%20viewBox='0%200%2024%2024'%20xmlns='http://www.w3.org/2000/svg'%3E%3Cpath%20d='M6%205h4v14H6V5zm8%200h4v14h-4V5z'/%3E%3C/svg%3E") center / contain no-repeat;
}
.video-progress {
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 4px;
background: rgba(194, 198, 212, 0.3);
}
.video-progress-fill {
height: 100%;
background: #00478d;
transition: width 0.24s ease;
}
.video-copy {
display: flex;
flex-direction: column;
gap: 8px;
}
.video-title {
color: #191c21;
font-size: 20px;
line-height: 28px;
font-weight: 600;
}
.video-desc {
color: #424752;
font-size: 14px;
line-height: 22px;
}
.option-list {
padding: 24px 20px 0;
display: flex;
flex-direction: column;
gap: 12px;
}
.option-card {
box-sizing: border-box;
width: 100%;
min-height: 72px;
padding: 16px;
border: 1px solid rgba(194, 198, 212, 0.3);
border-radius: 16px;
background: rgba(255, 255, 255, 0.72);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
box-shadow: 0 2px 8px rgba(25, 28, 33, 0.06);
display: flex;
align-items: center;
text-align: left;
}
.option-card::after {
border: 0;
}
.option-key {
flex: 0 0 auto;
width: 40px;
height: 40px;
margin-right: 16px;
border-radius: 12px;
background: #e1e2ea;
color: #424752;
font-size: 16px;
line-height: 40px;
font-weight: 700;
text-align: center;
}
.option-text {
flex: 1;
min-width: 0;
color: #191c21;
font-size: 16px;
line-height: 24px;
font-weight: 500;
}
.option-card.selected-wrong {
border: 2px solid #ba1a1a;
background: rgba(186, 26, 26, 0.05);
}
.option-card.selected-wrong .option-key {
background: #ba1a1a;
color: #ffffff;
}
.option-card.selected-wrong .option-text {
color: #ba1a1a;
}
.option-card.selected-correct {
border: 2px solid #00478d;
background: rgba(0, 71, 141, 0.07);
}
.option-card.selected-correct .option-key {
background: #00478d;
color: #ffffff;
}
.empty-state {
margin: 24px 20px 0;
min-height: 160px;
padding: 24px;
border: 1px dashed rgba(194, 198, 212, 0.8);
border-radius: 16px;
background: rgba(255, 255, 255, 0.64);
color: rgba(66, 71, 82, 0.82);
font-size: 15px;
line-height: 24px;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
}
.wrong-icon,
.right-icon {
position: relative;
flex: 0 0 auto;
width: 28px;
height: 28px;
margin-left: 12px;
border-radius: 50%;
}
.wrong-icon {
background: #ba1a1a;
}
.wrong-icon::before,
.wrong-icon::after {
content: '';
position: absolute;
left: 7px;
top: 13px;
width: 14px;
height: 2px;
border-radius: 999px;
background: #ffffff;
}
.wrong-icon::before {
transform: rotate(45deg);
}
.wrong-icon::after {
transform: rotate(-45deg);
}
.right-icon {
background: #00478d;
}
.right-icon::after {
content: '';
position: absolute;
left: 9px;
top: 6px;
width: 7px;
height: 13px;
border-right: 3px solid #ffffff;
border-bottom: 3px solid #ffffff;
transform: rotate(45deg);
}
.analysis-card {
margin: 24px 20px 16px;
padding: 24px;
border-left: 6px solid #00478d;
border-radius: 16px;
background: rgba(255, 255, 255, 0.82);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
box-shadow: 0 4px 12px rgba(25, 28, 33, 0.1);
}
.analysis-title {
margin-bottom: 16px;
display: flex;
align-items: center;
gap: 8px;
color: #00478d;
font-size: 20px;
line-height: 28px;
font-weight: 600;
}
.bulb-icon {
width: 22px;
height: 22px;
background: currentColor;
-webkit-mask: url("data:image/svg+xml,%3Csvg%20viewBox='0%200%2024%2024'%20xmlns='http://www.w3.org/2000/svg'%3E%3Cpath%20d='M9%2021h6v-1.5H9V21zm3-19a7%207%200%200%200-4%2012.74V17c0%20.55.45%201%201%201h6c.55%200%201-.45%201-1v-2.26A7%207%200%200%200%2012%202zm2.85%2011.1-.85.6V16h-4v-2.3l-.85-.6A5%205%200%201%201%2014.85%2013.1z'/%3E%3C/svg%3E") center / contain no-repeat;
mask: url("data:image/svg+xml,%3Csvg%20viewBox='0%200%2024%2024'%20xmlns='http://www.w3.org/2000/svg'%3E%3Cpath%20d='M9%2021h6v-1.5H9V21zm3-19a7%207%200%200%200-4%2012.74V17c0%20.55.45%201%201%201h6c.55%200%201-.45%201-1v-2.26A7%207%200%200%200%2012%202zm2.85%2011.1-.85.6V16h-4v-2.3l-.85-.6A5%205%200%201%201%2014.85%2013.1z'/%3E%3C/svg%3E") center / contain no-repeat;
}
.analysis-content {
display: flex;
flex-direction: column;
gap: 16px;
}
.analysis-main {
color: #191c21;
font-size: 16px;
line-height: 25px;
}
.analysis-divider {
height: 1px;
background: rgba(194, 198, 212, 0.2);
}
.analysis-note {
color: #424752;
font-size: 14px;
line-height: 22px;
font-style: italic;
}
.bottom-actions {
padding: 24px 20px 40px;
display: flex;
flex-direction: column;
gap: 12px;
}
.video-button,
.next-button {
box-sizing: border-box;
width: 100%;
height: 56px;
border-radius: 16px;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
font-size: 16px;
line-height: 24px;
font-weight: 700;
transition: transform 0.18s ease, opacity 0.18s ease;
}
.video-button::after,
.next-button::after {
border: 0;
}
.video-button:active,
.next-button:active {
transform: scale(0.98);
}
.video-button {
border: 2px solid rgba(0, 71, 141, 0.2);
background: rgba(255, 255, 255, 0.86);
color: #00478d;
}
.next-button {
border: 0;
background: #00478d;
box-shadow: 0 4px 12px rgba(0, 71, 141, 0.22);
color: #ffffff;
}
.next-button.disabled {
opacity: 0.5;
}
.video-icon,
.next-icon {
flex: 0 0 auto;
width: 22px;
height: 22px;
background: currentColor;
}
.video-icon {
-webkit-mask: url("data:image/svg+xml,%3Csvg%20viewBox='0%200%2024%2024'%20xmlns='http://www.w3.org/2000/svg'%3E%3Cpath%20d='M17%2010.5V6c0-.55-.45-1-1-1H4c-.55%200-1%20.45-1%201v12c0%20.55.45%201%201%201h12c.55%200%201-.45%201-1v-4.5l4%204v-11l-4%204zM15%2017H5V7h10v10z'/%3E%3C/svg%3E") center / contain no-repeat;
mask: url("data:image/svg+xml,%3Csvg%20viewBox='0%200%2024%2024'%20xmlns='http://www.w3.org/2000/svg'%3E%3Cpath%20d='M17%2010.5V6c0-.55-.45-1-1-1H4c-.55%200-1%20.45-1%201v12c0%20.55.45%201%201%201h12c.55%200%201-.45%201-1v-4.5l4%204v-11l-4%204zM15%2017H5V7h10v10z'/%3E%3C/svg%3E") center / contain no-repeat;
}
.next-icon {
-webkit-mask: url("data:image/svg+xml,%3Csvg%20viewBox='0%200%2024%2024'%20xmlns='http://www.w3.org/2000/svg'%3E%3Cpath%20d='M12%204l1.41%201.41L8.83%2010H20v2H8.83l4.58%204.59L12%2018l-7-7%207-7z'/%3E%3C/svg%3E") center / contain no-repeat;
mask: url("data:image/svg+xml,%3Csvg%20viewBox='0%200%2024%2024'%20xmlns='http://www.w3.org/2000/svg'%3E%3Cpath%20d='M12%204l1.41%201.41L8.83%2010H20v2H8.83l4.58%204.59L12%2018l-7-7%207-7z'/%3E%3C/svg%3E") center / contain no-repeat;
transform: rotate(180deg);
}
.toast {
position: fixed;
left: 50%;
bottom: 32px;
z-index: 100;
max-width: 320px;
padding: 12px 20px;
border-radius: 12px;
background: #2e3037;
color: #eff0f8;
font-size: 14px;
line-height: 20px;
font-weight: 600;
text-align: center;
pointer-events: none;
opacity: 0;
transform: translate(-50%, 16px);
transition: opacity 0.3s ease, transform 0.3s ease;
}
.toast.visible {
opacity: 1;
transform: translate(-50%, 0);
}
@keyframes pulse-border {
0% {
box-shadow: 0 0 0 0 rgba(0, 71, 141, 0.4);
}
70% {
box-shadow: 0 0 0 10px rgba(0, 71, 141, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(0, 71, 141, 0);
}
}
@keyframes video-pulse {
0% {
opacity: 0.78;
transform: scale(0.64);
}
100% {
opacity: 0;
transform: scale(1.16);
}
}
</style>