2026-05-29 17:40:10 +08:00
|
|
|
|
<template>
|
2026-06-09 17:00:23 +08:00
|
|
|
|
<view class="cases-page">
|
2026-05-29 17:40:10 +08:00
|
|
|
|
<view class="case-shell">
|
|
|
|
|
|
<view class="case-header">
|
2026-06-09 17:00:23 +08:00
|
|
|
|
<button class="icon-button" aria-label="设置" @click="openSettings">
|
2026-05-29 17:40:10 +08:00
|
|
|
|
<view class="settings-icon"></view>
|
|
|
|
|
|
</button>
|
2026-06-09 17:00:23 +08:00
|
|
|
|
<button class="icon-button home-button" aria-label="首页" @click="goHome">
|
2026-05-29 17:40:10 +08:00
|
|
|
|
<view class="home-icon"></view>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<view class="header-spacer"></view>
|
2026-06-05 15:27:29 +08:00
|
|
|
|
<button class="icon-button" aria-label="个人中心" @click="openProfile">
|
2026-05-29 17:40:10 +08:00
|
|
|
|
<view class="account-icon"></view>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
2026-06-13 06:05:37 +08:00
|
|
|
|
<scroll-view class="case-content" scroll-y @scrolltolower="loadMoreCases">
|
|
|
|
|
|
<view class="list-hero">
|
|
|
|
|
|
<text class="list-title">{{ currentSourceConfig.title }}</text>
|
|
|
|
|
|
<text class="list-subtitle">{{ currentSourceConfig.subtitle }}</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
2026-05-29 17:40:10 +08:00
|
|
|
|
<view class="search-row">
|
|
|
|
|
|
<view class="search-box">
|
|
|
|
|
|
<view class="search-icon"></view>
|
|
|
|
|
|
<input
|
|
|
|
|
|
class="search-input"
|
|
|
|
|
|
v-model="keyword"
|
|
|
|
|
|
type="text"
|
2026-06-13 06:05:37 +08:00
|
|
|
|
:placeholder="currentSourceConfig.placeholder"
|
2026-05-29 17:40:10 +08:00
|
|
|
|
placeholder-class="search-placeholder"
|
2026-06-13 06:05:37 +08:00
|
|
|
|
confirm-type="search"
|
|
|
|
|
|
@confirm="searchCases"
|
2026-05-29 17:40:10 +08:00
|
|
|
|
/>
|
|
|
|
|
|
</view>
|
2026-06-13 06:05:37 +08:00
|
|
|
|
<button class="search-button" :disabled="loading" @click="searchCases">搜索</button>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<view v-if="caseTypeFilters.length" class="filter-row">
|
|
|
|
|
|
<button
|
|
|
|
|
|
v-for="filter in caseTypeFilters"
|
|
|
|
|
|
:key="filter.value"
|
|
|
|
|
|
class="filter-chip"
|
|
|
|
|
|
:class="{ active: filters.case_type === filter.value }"
|
|
|
|
|
|
@click="chooseCaseType(filter.value)"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ filter.label }}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<view class="filter-row compact">
|
|
|
|
|
|
<button
|
|
|
|
|
|
v-for="filter in difficultyFilters"
|
|
|
|
|
|
:key="filter.value"
|
|
|
|
|
|
class="filter-chip"
|
|
|
|
|
|
:class="{ active: filters.difficulty === filter.value }"
|
|
|
|
|
|
@click="chooseDifficulty(filter.value)"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ filter.label }}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<view class="summary-row">
|
|
|
|
|
|
<text>{{ loading && cases.length === 0 ? '病例加载中...' : `共 ${totalCount} 个病例` }}</text>
|
|
|
|
|
|
<text v-if="departmentName">科室:{{ departmentName }}</text>
|
2026-05-29 17:40:10 +08:00
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<view class="case-list">
|
|
|
|
|
|
<view
|
2026-06-13 06:05:37 +08:00
|
|
|
|
v-for="item in cases"
|
2026-05-29 17:40:10 +08:00
|
|
|
|
:key="item.id"
|
|
|
|
|
|
class="case-card"
|
2026-06-01 15:35:17 +08:00
|
|
|
|
:class="`mode-${item.mode}`"
|
2026-05-29 17:40:10 +08:00
|
|
|
|
@click="selectCase(item)"
|
|
|
|
|
|
>
|
|
|
|
|
|
<view class="case-main">
|
|
|
|
|
|
<view class="patient-avatar" :class="`avatar-${item.tone}`">
|
|
|
|
|
|
<text>{{ item.patientName.slice(0, 1) }}</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="case-info">
|
|
|
|
|
|
<text class="case-title">{{ item.title }}</text>
|
|
|
|
|
|
<text class="case-meta">
|
2026-06-13 06:05:37 +08:00
|
|
|
|
{{ item.gender }},{{ item.age || '-' }}岁,{{ item.department }},{{ item.caseTypeDisplay || item.scene }}
|
2026-05-29 17:40:10 +08:00
|
|
|
|
</text>
|
2026-06-13 06:05:37 +08:00
|
|
|
|
<text v-if="item.description" class="case-desc">{{ item.description }}</text>
|
|
|
|
|
|
<view v-if="item.competencyTags?.length" class="tag-row">
|
|
|
|
|
|
<text
|
|
|
|
|
|
v-for="tag in item.competencyTags.slice(0, 3)"
|
|
|
|
|
|
:key="tag"
|
|
|
|
|
|
class="case-tag"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ tag }}
|
|
|
|
|
|
</text>
|
|
|
|
|
|
</view>
|
2026-05-29 17:40:10 +08:00
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="case-footer">
|
|
|
|
|
|
<text class="case-no">病例编号: {{ item.caseNo }}</text>
|
2026-06-01 15:35:17 +08:00
|
|
|
|
<view class="mode-badge" :class="`mode-badge-${item.mode}`">
|
|
|
|
|
|
<view class="mode-icon" :class="`mode-icon-${item.mode}`"></view>
|
|
|
|
|
|
<text>{{ getModeLabel(item.mode) }}</text>
|
|
|
|
|
|
</view>
|
2026-05-29 17:40:10 +08:00
|
|
|
|
</view>
|
2026-06-13 06:05:37 +08:00
|
|
|
|
<view class="stat-row">
|
|
|
|
|
|
<text>{{ getDifficultyLabel(item.difficulty, item.difficultyScore) }}</text>
|
|
|
|
|
|
<text v-if="item.estimatedMinutes">{{ item.estimatedMinutes }}分钟</text>
|
|
|
|
|
|
<text v-if="item.myBestScore !== null && item.myBestScore !== undefined">最高分 {{ item.myBestScore }}</text>
|
|
|
|
|
|
<text v-if="item.myTrainCount !== null && item.myTrainCount !== undefined">已练 {{ item.myTrainCount }} 次</text>
|
|
|
|
|
|
</view>
|
2026-05-29 17:40:10 +08:00
|
|
|
|
</view>
|
|
|
|
|
|
|
2026-06-13 06:05:37 +08:00
|
|
|
|
<view v-if="loading && cases.length === 0" class="empty-state">
|
|
|
|
|
|
<view class="spinner"></view>
|
|
|
|
|
|
<text>正在获取病例列表...</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<view v-else-if="loadFailed" class="empty-state">
|
|
|
|
|
|
<text>{{ errorMessage || '病例列表加载失败' }}</text>
|
|
|
|
|
|
<button class="retry-button" @click="reloadCases">重新加载</button>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<view v-else-if="cases.length === 0" class="empty-state">
|
2026-05-29 17:40:10 +08:00
|
|
|
|
<text>暂无匹配病例</text>
|
|
|
|
|
|
</view>
|
2026-06-13 06:05:37 +08:00
|
|
|
|
|
|
|
|
|
|
<view v-else-if="loadingMore" class="load-more-state">
|
|
|
|
|
|
<text>继续加载中...</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<view v-else-if="!hasMore" class="load-more-state">
|
|
|
|
|
|
<text>已显示全部病例</text>
|
|
|
|
|
|
</view>
|
2026-05-29 17:40:10 +08:00
|
|
|
|
</view>
|
|
|
|
|
|
</scroll-view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<view class="toast" :class="{ visible: toastVisible }">{{ toastMessage }}</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
2026-06-13 06:05:37 +08:00
|
|
|
|
import { computed, onMounted, onUnmounted, reactive, ref, watch } from 'vue'
|
2026-06-01 15:35:17 +08:00
|
|
|
|
import { onLoad } from '@dcloudio/uni-app'
|
2026-06-15 17:55:07 +08:00
|
|
|
|
import {
|
|
|
|
|
|
fetchCaseListPage,
|
|
|
|
|
|
takePrefetchedCaseList,
|
|
|
|
|
|
type CaseListSource,
|
|
|
|
|
|
type CaseMode,
|
|
|
|
|
|
type ClinicalCase
|
|
|
|
|
|
} from '../../api/cases'
|
2026-06-09 17:00:23 +08:00
|
|
|
|
import { createHomeNavigator, createProfileOpener, createSettingsOpener } from '../../api/navigation'
|
2026-05-29 17:40:10 +08:00
|
|
|
|
|
|
|
|
|
|
const emit = defineEmits<{
|
|
|
|
|
|
(event: 'open-settings'): void
|
|
|
|
|
|
(event: 'open-profile'): void
|
|
|
|
|
|
(event: 'go-home'): void
|
|
|
|
|
|
}>()
|
|
|
|
|
|
|
2026-06-05 15:27:29 +08:00
|
|
|
|
const openProfile = createProfileOpener(emit)
|
2026-06-09 17:00:23 +08:00
|
|
|
|
const openSettings = createSettingsOpener(emit)
|
|
|
|
|
|
const goHome = createHomeNavigator(emit)
|
2026-06-05 15:27:29 +08:00
|
|
|
|
|
2026-05-29 17:40:10 +08:00
|
|
|
|
const cases = ref<ClinicalCase[]>([])
|
|
|
|
|
|
const keyword = ref('')
|
2026-06-13 06:05:37 +08:00
|
|
|
|
const loading = ref(false)
|
|
|
|
|
|
const loadingMore = ref(false)
|
|
|
|
|
|
const loadFailed = ref(false)
|
|
|
|
|
|
const errorMessage = ref('')
|
|
|
|
|
|
const totalCount = ref(0)
|
|
|
|
|
|
const currentPage = ref(1)
|
|
|
|
|
|
const pageSize = ref(10)
|
|
|
|
|
|
const source = ref<CaseListSource>('recommended')
|
|
|
|
|
|
const departmentId = ref('')
|
|
|
|
|
|
const departmentName = ref('')
|
2026-05-29 17:40:10 +08:00
|
|
|
|
const toastMessage = ref('')
|
|
|
|
|
|
const toastVisible = ref(false)
|
2026-06-13 06:05:37 +08:00
|
|
|
|
|
|
|
|
|
|
const filters = reactive({
|
|
|
|
|
|
case_type: '',
|
|
|
|
|
|
difficulty: ''
|
|
|
|
|
|
})
|
2026-05-29 17:40:10 +08:00
|
|
|
|
|
|
|
|
|
|
let toastTimer: ReturnType<typeof setTimeout> | null = null
|
2026-06-13 06:05:37 +08:00
|
|
|
|
let searchTimer: ReturnType<typeof setTimeout> | null = null
|
|
|
|
|
|
let requestSeq = 0
|
|
|
|
|
|
|
|
|
|
|
|
const sourceConfigs: Record<CaseListSource, { title: string; subtitle: string; placeholder: string }> = {
|
|
|
|
|
|
recommended: {
|
|
|
|
|
|
title: '开始训练',
|
|
|
|
|
|
subtitle: '基于您的训练画像智能推荐病例',
|
|
|
|
|
|
placeholder: '搜索病例标题、科室、主诉'
|
|
|
|
|
|
},
|
|
|
|
|
|
specialty: {
|
|
|
|
|
|
title: '专项强化',
|
|
|
|
|
|
subtitle: '按配置科室聚焦专项病例',
|
|
|
|
|
|
placeholder: '搜索专项病例'
|
|
|
|
|
|
},
|
|
|
|
|
|
weak: {
|
|
|
|
|
|
title: '薄弱环节',
|
|
|
|
|
|
subtitle: '优先呈现训练分数低于 70 分的病例',
|
|
|
|
|
|
placeholder: '搜索待补强病例'
|
|
|
|
|
|
},
|
|
|
|
|
|
teaching: {
|
|
|
|
|
|
title: '教学互动',
|
|
|
|
|
|
subtitle: '练习模式与教学互动模式病例',
|
|
|
|
|
|
placeholder: '搜索教学互动病例'
|
|
|
|
|
|
},
|
|
|
|
|
|
'teacher-task': {
|
|
|
|
|
|
title: '教师任务',
|
|
|
|
|
|
subtitle: '老师配置的针对性任务训练',
|
|
|
|
|
|
placeholder: '搜索教师任务病例'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-05-29 17:40:10 +08:00
|
|
|
|
|
2026-06-13 06:05:37 +08:00
|
|
|
|
const caseTypeFilters = computed(() => {
|
|
|
|
|
|
if (source.value === 'teaching' || source.value === 'teacher-task') return []
|
|
|
|
|
|
return [
|
|
|
|
|
|
{ label: '全部类型', value: '' },
|
|
|
|
|
|
{ label: '练习模式', value: 'practice' },
|
|
|
|
|
|
{ label: '教学互动', value: 'teaching' }
|
|
|
|
|
|
]
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const difficultyFilters = [
|
|
|
|
|
|
{ label: '全部难度', value: '' },
|
|
|
|
|
|
{ label: '简单', value: 'easy' },
|
|
|
|
|
|
{ label: '中等', value: 'medium' },
|
|
|
|
|
|
{ label: '困难', value: 'hard' }
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
const currentSourceConfig = computed(() => sourceConfigs[source.value])
|
|
|
|
|
|
|
|
|
|
|
|
const hasMore = computed(() => cases.value.length < totalCount.value)
|
|
|
|
|
|
|
|
|
|
|
|
watch(keyword, () => {
|
|
|
|
|
|
if (searchTimer) clearTimeout(searchTimer)
|
|
|
|
|
|
searchTimer = setTimeout(() => {
|
|
|
|
|
|
reloadCases()
|
|
|
|
|
|
}, 500)
|
2026-05-29 17:40:10 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
2026-06-01 15:35:17 +08:00
|
|
|
|
function getModeLabel(mode: CaseMode) {
|
|
|
|
|
|
return mode === 'teaching' ? '教学模式' : '训练模式'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-13 06:05:37 +08:00
|
|
|
|
function getDifficultyLabel(difficulty?: string, score?: number) {
|
|
|
|
|
|
const labelMap: Record<string, string> = {
|
|
|
|
|
|
easy: '简单',
|
|
|
|
|
|
medium: '中等',
|
|
|
|
|
|
hard: '困难'
|
|
|
|
|
|
}
|
|
|
|
|
|
const label = difficulty ? labelMap[difficulty] || difficulty : '难度未设置'
|
|
|
|
|
|
return score ? `${label} · ${score}分` : label
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function chooseCaseType(value: string) {
|
|
|
|
|
|
if (filters.case_type === value) return
|
|
|
|
|
|
filters.case_type = value
|
|
|
|
|
|
reloadCases()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function chooseDifficulty(value: string) {
|
|
|
|
|
|
if (filters.difficulty === value) return
|
|
|
|
|
|
filters.difficulty = value
|
|
|
|
|
|
reloadCases()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function searchCases() {
|
|
|
|
|
|
if (searchTimer) clearTimeout(searchTimer)
|
|
|
|
|
|
reloadCases()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function buildQuery(page: number) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
search: keyword.value.trim(),
|
|
|
|
|
|
case_type: source.value === 'teaching' || source.value === 'teacher-task' ? undefined : filters.case_type,
|
|
|
|
|
|
difficulty: filters.difficulty,
|
|
|
|
|
|
department: departmentId.value,
|
|
|
|
|
|
page,
|
|
|
|
|
|
page_size: pageSize.value
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function loadCases(page = 1) {
|
|
|
|
|
|
const isFirstPage = page === 1
|
|
|
|
|
|
if ((loading.value && isFirstPage) || loadingMore.value) return
|
|
|
|
|
|
|
|
|
|
|
|
const seq = ++requestSeq
|
|
|
|
|
|
loading.value = isFirstPage
|
|
|
|
|
|
loadingMore.value = !isFirstPage
|
|
|
|
|
|
loadFailed.value = false
|
|
|
|
|
|
errorMessage.value = ''
|
2026-06-15 17:55:07 +08:00
|
|
|
|
const query = buildQuery(page)
|
2026-06-13 06:05:37 +08:00
|
|
|
|
|
|
|
|
|
|
try {
|
2026-06-15 17:55:07 +08:00
|
|
|
|
const result = isFirstPage
|
|
|
|
|
|
? takePrefetchedCaseList(source.value, query) || await fetchCaseListPage(source.value, query)
|
|
|
|
|
|
: await fetchCaseListPage(source.value, query)
|
2026-06-13 06:05:37 +08:00
|
|
|
|
if (seq !== requestSeq) return
|
|
|
|
|
|
|
|
|
|
|
|
cases.value = isFirstPage ? result.results : [...cases.value, ...result.results]
|
|
|
|
|
|
totalCount.value = result.count
|
|
|
|
|
|
currentPage.value = page
|
|
|
|
|
|
departmentName.value = source.value === 'specialty'
|
|
|
|
|
|
? cases.value.find(item => item.department)?.department || departmentName.value
|
|
|
|
|
|
: ''
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
if (seq !== requestSeq) return
|
|
|
|
|
|
loadFailed.value = isFirstPage
|
|
|
|
|
|
errorMessage.value = error instanceof Error ? error.message : '病例列表加载失败'
|
|
|
|
|
|
showToast(errorMessage.value)
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
if (seq === requestSeq) {
|
|
|
|
|
|
loading.value = false
|
|
|
|
|
|
loadingMore.value = false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function reloadCases() {
|
|
|
|
|
|
currentPage.value = 1
|
|
|
|
|
|
cases.value = []
|
|
|
|
|
|
totalCount.value = 0
|
|
|
|
|
|
loadCases(1)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function loadMoreCases() {
|
|
|
|
|
|
if (loading.value || loadingMore.value || !hasMore.value) return
|
|
|
|
|
|
loadCases(currentPage.value + 1)
|
2026-05-29 17:40:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function selectCase(item: ClinicalCase) {
|
|
|
|
|
|
uni.setStorageSync('clinical-thinking-selected-case', item)
|
2026-06-01 15:35:17 +08:00
|
|
|
|
uni.setStorageSync('clinical-thinking-case-mode', item.mode)
|
2026-06-09 17:00:23 +08:00
|
|
|
|
uni.navigateTo({
|
|
|
|
|
|
url: item.mode === 'teaching' ? '/pages/teaching/teaching' : '/pages/scenario/scenario'
|
|
|
|
|
|
})
|
2026-05-29 17:40:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function showToast(message: string) {
|
|
|
|
|
|
if (toastTimer) clearTimeout(toastTimer)
|
|
|
|
|
|
toastMessage.value = message
|
|
|
|
|
|
toastVisible.value = true
|
|
|
|
|
|
toastTimer = setTimeout(() => {
|
|
|
|
|
|
toastVisible.value = false
|
|
|
|
|
|
}, 2200)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-01 15:35:17 +08:00
|
|
|
|
onLoad(query => {
|
2026-06-13 06:05:37 +08:00
|
|
|
|
const querySource = query?.source
|
|
|
|
|
|
const queryMode = query?.mode
|
|
|
|
|
|
const queryDepartment = query?.department
|
|
|
|
|
|
const queryPageSize = Number(query?.page_size)
|
|
|
|
|
|
|
|
|
|
|
|
if (isCaseListSource(querySource)) {
|
|
|
|
|
|
source.value = querySource
|
|
|
|
|
|
} else if (queryMode === 'teaching') {
|
|
|
|
|
|
source.value = 'teaching'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (typeof queryDepartment === 'string' && queryDepartment.trim()) {
|
|
|
|
|
|
departmentId.value = queryDepartment
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (Number.isInteger(queryPageSize) && queryPageSize > 0) {
|
|
|
|
|
|
pageSize.value = queryPageSize
|
2026-06-01 15:35:17 +08:00
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2026-05-29 17:40:10 +08:00
|
|
|
|
onMounted(loadCases)
|
|
|
|
|
|
|
|
|
|
|
|
onUnmounted(() => {
|
|
|
|
|
|
if (toastTimer) clearTimeout(toastTimer)
|
2026-06-13 06:05:37 +08:00
|
|
|
|
if (searchTimer) clearTimeout(searchTimer)
|
2026-05-29 17:40:10 +08:00
|
|
|
|
})
|
2026-06-13 06:05:37 +08:00
|
|
|
|
|
|
|
|
|
|
function isCaseListSource(value: unknown): value is CaseListSource {
|
|
|
|
|
|
return value === 'recommended' ||
|
|
|
|
|
|
value === 'specialty' ||
|
|
|
|
|
|
value === 'weak' ||
|
|
|
|
|
|
value === 'teaching' ||
|
|
|
|
|
|
value === 'teacher-task'
|
|
|
|
|
|
}
|
2026-05-29 17:40:10 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style>
|
|
|
|
|
|
page {
|
|
|
|
|
|
min-height: 100%;
|
|
|
|
|
|
background: #e7e8f0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cases-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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.case-shell {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
min-height: 100vh;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
background: #f2f3fb;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.case-header {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
left: 0;
|
|
|
|
|
|
right: 0;
|
|
|
|
|
|
top: 0;
|
|
|
|
|
|
z-index: 20;
|
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
height: 56px;
|
|
|
|
|
|
padding: 0 20px;
|
|
|
|
|
|
border-bottom: 1px solid rgba(194, 198, 212, 0.3);
|
|
|
|
|
|
background: #ffffff;
|
|
|
|
|
|
box-shadow: 0 2px 8px rgba(25, 28, 33, 0.04);
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.header-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,
|
|
|
|
|
|
.search-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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.case-content {
|
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
height: 100vh;
|
|
|
|
|
|
padding: 76px 20px 24px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-13 06:05:37 +08:00
|
|
|
|
.list-hero {
|
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 4px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.list-title {
|
|
|
|
|
|
color: #191c21;
|
|
|
|
|
|
font-size: 22px;
|
|
|
|
|
|
line-height: 30px;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.list-subtitle {
|
|
|
|
|
|
color: #727783;
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
line-height: 20px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-29 17:40:10 +08:00
|
|
|
|
.search-row {
|
|
|
|
|
|
margin-bottom: 12px;
|
2026-06-13 06:05:37 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 8px;
|
2026-05-29 17:40:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.search-box {
|
|
|
|
|
|
position: relative;
|
2026-06-13 06:05:37 +08:00
|
|
|
|
flex: 1;
|
|
|
|
|
|
min-width: 0;
|
2026-05-29 17:40:10 +08:00
|
|
|
|
height: 40px;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
background: #ffffff;
|
|
|
|
|
|
box-shadow: 0 2px 8px rgba(25, 28, 33, 0.04);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-13 06:05:37 +08:00
|
|
|
|
.search-button {
|
|
|
|
|
|
flex: 0 0 auto;
|
|
|
|
|
|
width: 64px;
|
|
|
|
|
|
height: 40px;
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
background: #00478d;
|
|
|
|
|
|
color: #ffffff;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
line-height: 40px;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.search-button::after,
|
|
|
|
|
|
.filter-chip::after,
|
|
|
|
|
|
.retry-button::after {
|
|
|
|
|
|
border: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.search-button[disabled] {
|
|
|
|
|
|
background: #c2c6d4;
|
|
|
|
|
|
color: #ffffff;
|
|
|
|
|
|
opacity: 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-29 17:40:10 +08:00
|
|
|
|
.search-icon {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
left: 12px;
|
|
|
|
|
|
top: 50%;
|
|
|
|
|
|
width: 20px;
|
|
|
|
|
|
height: 20px;
|
|
|
|
|
|
background: #727783;
|
|
|
|
|
|
transform: translateY(-50%);
|
|
|
|
|
|
-webkit-mask: url("data:image/svg+xml,%3Csvg%20viewBox='0%200%2024%2024'%20xmlns='http://www.w3.org/2000/svg'%3E%3Cpath%20d='M9.5%203a6.5%206.5%200%200%200%200%2013c1.61%200%203.09-.59%204.23-1.57L19.29%2020%2020.7%2018.59l-5.56-5.56A6.47%206.47%200%200%200%2016%209.5%206.5%206.5%200%200%200%209.5%203zm0%202a4.5%204.5%200%201%201%200%209%204.5%204.5%200%200%201%200-9z'/%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.5%203a6.5%206.5%200%200%200%200%2013c1.61%200%203.09-.59%204.23-1.57L19.29%2020%2020.7%2018.59l-5.56-5.56A6.47%206.47%200%200%200%2016%209.5%206.5%206.5%200%200%200%209.5%203zm0%202a4.5%204.5%200%201%201%200%209%204.5%204.5%200%200%201%200-9z'/%3E%3C/svg%3E") center / contain no-repeat;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.search-input {
|
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 40px;
|
|
|
|
|
|
padding: 0 16px 0 40px;
|
|
|
|
|
|
border: 0;
|
|
|
|
|
|
background: transparent;
|
|
|
|
|
|
color: #191c21;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
line-height: 20px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.search-placeholder {
|
|
|
|
|
|
color: #c2c6d4;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-13 06:05:37 +08:00
|
|
|
|
.filter-row {
|
|
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
overflow-x: auto;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.filter-row.compact {
|
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.filter-chip {
|
|
|
|
|
|
flex: 0 0 auto;
|
|
|
|
|
|
min-width: 64px;
|
|
|
|
|
|
height: 32px;
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
padding: 0 12px;
|
|
|
|
|
|
border: 1px solid rgba(194, 198, 212, 0.5);
|
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
|
background: #ffffff;
|
|
|
|
|
|
color: #424752;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
line-height: 18px;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.filter-chip.active {
|
|
|
|
|
|
border-color: rgba(0, 71, 141, 0.28);
|
|
|
|
|
|
background: #d6e3ff;
|
|
|
|
|
|
color: #00478d;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.summary-row {
|
|
|
|
|
|
margin: 0 0 12px;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
color: #727783;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
line-height: 18px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-29 17:40:10 +08:00
|
|
|
|
.case-list {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
padding-bottom: 20px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.case-card {
|
|
|
|
|
|
padding: 12px;
|
|
|
|
|
|
border: 1px solid rgba(194, 198, 212, 0.3);
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
background: #ffffff;
|
|
|
|
|
|
box-shadow: 0 2px 8px rgba(25, 28, 33, 0.04);
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-01 15:35:17 +08:00
|
|
|
|
.case-card.mode-teaching {
|
|
|
|
|
|
border-color: rgba(0, 71, 141, 0.22);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-29 17:40:10 +08:00
|
|
|
|
.case-card:active {
|
|
|
|
|
|
transform: scale(0.99);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.case-main {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.patient-avatar {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
flex: 0 0 auto;
|
|
|
|
|
|
width: 70px;
|
|
|
|
|
|
height: 70px;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
color: #ffffff;
|
|
|
|
|
|
font-size: 28px;
|
|
|
|
|
|
line-height: 1;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.patient-avatar::before {
|
|
|
|
|
|
content: '';
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
left: 50%;
|
|
|
|
|
|
top: 15px;
|
|
|
|
|
|
width: 20px;
|
|
|
|
|
|
height: 20px;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
background: rgba(255, 255, 255, 0.42);
|
|
|
|
|
|
transform: translateX(-50%);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.patient-avatar::after {
|
|
|
|
|
|
content: '';
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
left: 50%;
|
|
|
|
|
|
bottom: -7px;
|
|
|
|
|
|
width: 50px;
|
|
|
|
|
|
height: 34px;
|
|
|
|
|
|
border-radius: 50% 50% 0 0;
|
|
|
|
|
|
background: rgba(255, 255, 255, 0.28);
|
|
|
|
|
|
transform: translateX(-50%);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.patient-avatar text {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
z-index: 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.avatar-blue {
|
|
|
|
|
|
background: linear-gradient(135deg, #7da6d9, #00478d);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.avatar-teal {
|
|
|
|
|
|
background: linear-gradient(135deg, #5dd8e2, #006970);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.avatar-pink {
|
|
|
|
|
|
background: linear-gradient(135deg, #ffb3c7, #a63a5f);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.avatar-orange {
|
|
|
|
|
|
background: linear-gradient(135deg, #ffb691, #9f4300);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.avatar-purple {
|
|
|
|
|
|
background: linear-gradient(135deg, #b5a6ff, #5f4bb6);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.avatar-green {
|
|
|
|
|
|
background: linear-gradient(135deg, #9edc9a, #347a35);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.case-info {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.case-title {
|
|
|
|
|
|
display: -webkit-box;
|
|
|
|
|
|
margin-bottom: 4px;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
color: #191c21;
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
line-height: 20px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
|
-webkit-box-orient: vertical;
|
|
|
|
|
|
-webkit-line-clamp: 2;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.case-meta {
|
2026-06-13 06:05:37 +08:00
|
|
|
|
display: block;
|
|
|
|
|
|
margin-bottom: 4px;
|
2026-05-29 17:40:10 +08:00
|
|
|
|
color: #424752;
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
line-height: 20px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-13 06:05:37 +08:00
|
|
|
|
.case-desc {
|
|
|
|
|
|
display: -webkit-box;
|
|
|
|
|
|
margin-bottom: 6px;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
color: #727783;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
line-height: 18px;
|
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
|
-webkit-box-orient: vertical;
|
|
|
|
|
|
-webkit-line-clamp: 2;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.tag-row {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
gap: 6px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.case-tag {
|
|
|
|
|
|
max-width: 96px;
|
|
|
|
|
|
padding: 2px 8px;
|
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
|
background: #ecedf6;
|
|
|
|
|
|
color: #424752;
|
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
|
line-height: 16px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-29 17:40:10 +08:00
|
|
|
|
.case-footer {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: flex-end;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.case-no {
|
|
|
|
|
|
color: #c2c6d4;
|
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
|
line-height: 16px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-01 15:35:17 +08:00
|
|
|
|
.mode-badge {
|
|
|
|
|
|
flex: 0 0 auto;
|
|
|
|
|
|
height: 24px;
|
|
|
|
|
|
padding: 0 8px;
|
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 4px;
|
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
|
line-height: 16px;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.mode-badge-training {
|
|
|
|
|
|
background: #ecedf6;
|
|
|
|
|
|
color: #424752;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.mode-badge-teaching {
|
|
|
|
|
|
background: #d6e3ff;
|
|
|
|
|
|
color: #00478d;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.mode-icon {
|
|
|
|
|
|
width: 14px;
|
|
|
|
|
|
height: 14px;
|
|
|
|
|
|
background: currentColor;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.mode-icon-training {
|
|
|
|
|
|
-webkit-mask: url("data:image/svg+xml,%3Csvg%20viewBox='0%200%2024%2024'%20xmlns='http://www.w3.org/2000/svg'%3E%3Cpath%20d='M9%2016.2l-3.5-3.5L4%2014.2%209%2019%2020%208l-1.5-1.5L9%2016.2z'/%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%2016.2l-3.5-3.5L4%2014.2%209%2019%2020%208l-1.5-1.5L9%2016.2z'/%3E%3C/svg%3E") center / contain no-repeat;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.mode-icon-teaching {
|
|
|
|
|
|
-webkit-mask: url("data:image/svg+xml,%3Csvg%20viewBox='0%200%2024%2024'%20xmlns='http://www.w3.org/2000/svg'%3E%3Cpath%20d='M12%203L1%209l11%206%209-4.91V17h2V9L12%203zm0%202.28L18.85%209%2012%2012.72%205.15%209%2012%205.28zM5%2013.18v3.2C6.63%2018.24%209.26%2019%2012%2019s5.37-.76%207-2.62v-3.2l-7%203.82-7-3.82z'/%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%203L1%209l11%206%209-4.91V17h2V9L12%203zm0%202.28L18.85%209%2012%2012.72%205.15%209%2012%205.28zM5%2013.18v3.2C6.63%2018.24%209.26%2019%2012%2019s5.37-.76%207-2.62v-3.2l-7%203.82-7-3.82z'/%3E%3C/svg%3E") center / contain no-repeat;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-13 06:05:37 +08:00
|
|
|
|
.stat-row {
|
|
|
|
|
|
padding-top: 8px;
|
|
|
|
|
|
border-top: 1px solid rgba(194, 198, 212, 0.22);
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
color: #727783;
|
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
|
line-height: 16px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-29 17:40:10 +08:00
|
|
|
|
.empty-state {
|
2026-06-13 06:05:37 +08:00
|
|
|
|
min-height: 160px;
|
2026-05-29 17:40:10 +08:00
|
|
|
|
padding: 40px 0;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
color: #727783;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
line-height: 20px;
|
2026-06-13 06:05:37 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.retry-button {
|
|
|
|
|
|
width: 96px;
|
|
|
|
|
|
height: 36px;
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
background: #00478d;
|
|
|
|
|
|
color: #ffffff;
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
line-height: 36px;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.load-more-state {
|
|
|
|
|
|
padding: 8px 0 16px;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
color: #a0a5b2;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
line-height: 18px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.spinner {
|
|
|
|
|
|
width: 22px;
|
|
|
|
|
|
height: 22px;
|
|
|
|
|
|
border: 2px solid rgba(0, 71, 141, 0.18);
|
|
|
|
|
|
border-top-color: #00478d;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
animation: spin 1s linear infinite;
|
2026-05-29 17:40:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.toast {
|
|
|
|
|
|
position: fixed;
|
|
|
|
|
|
left: 50%;
|
|
|
|
|
|
bottom: 96px;
|
|
|
|
|
|
z-index: 100;
|
|
|
|
|
|
max-width: 320px;
|
|
|
|
|
|
padding: 12px 24px;
|
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|
2026-06-13 06:05:37 +08:00
|
|
|
|
|
|
|
|
|
|
@keyframes spin {
|
|
|
|
|
|
from {
|
|
|
|
|
|
transform: rotate(0deg);
|
|
|
|
|
|
}
|
|
|
|
|
|
to {
|
|
|
|
|
|
transform: rotate(360deg);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-05-29 17:40:10 +08:00
|
|
|
|
</style>
|