1001 lines
27 KiB
Vue
1001 lines
27 KiB
Vue
<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>
|