Files
vueapp/pages/cases/cases.vue
T
2026-05-29 17:40:10 +08:00

447 lines
12 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>
<ScenarioPage
v-if="showScenarioPage"
:case-item="selectedCase"
@open-settings="emit('open-settings')"
@open-profile="emit('open-profile')"
@go-home="emit('go-home')"
/>
<view v-else class="cases-page">
<view class="case-shell">
<view class="case-header">
<button class="icon-button" aria-label="设置" @click="emit('open-settings')">
<view class="settings-icon"></view>
</button>
<button class="icon-button home-button" aria-label="首页" @click="emit('go-home')">
<view class="home-icon"></view>
</button>
<view class="header-spacer"></view>
<button class="icon-button" aria-label="个人中心" @click="emit('open-profile')">
<view class="account-icon"></view>
</button>
</view>
<scroll-view class="case-content" scroll-y>
<view class="search-row">
<view class="search-box">
<view class="search-icon"></view>
<input
class="search-input"
v-model="keyword"
type="text"
placeholder="科室、主诉模糊搜索"
placeholder-class="search-placeholder"
/>
</view>
</view>
<view class="case-list">
<view
v-for="item in filteredCases"
:key="item.id"
class="case-card"
@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">
{{ item.patientName }}{{ item.gender }}{{ item.age }}{{ item.department }}{{ item.scene }}
</text>
</view>
</view>
<view class="case-footer">
<text class="case-no">病例编号: {{ item.caseNo }}</text>
</view>
</view>
<view v-if="filteredCases.length === 0" class="empty-state">
<text>暂无匹配病例</text>
</view>
</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 { fetchCaseList, type ClinicalCase } from '../../api/cases'
import ScenarioPage from '../scenario/scenario.vue'
const emit = defineEmits<{
(event: 'open-settings'): void
(event: 'open-profile'): void
(event: 'go-home'): void
}>()
const cases = ref<ClinicalCase[]>([])
const keyword = ref('')
const toastMessage = ref('')
const toastVisible = ref(false)
const selectedCase = ref<ClinicalCase | null>(null)
const showScenarioPage = ref(false)
let toastTimer: ReturnType<typeof setTimeout> | null = null
const filteredCases = computed(() => {
const value = keyword.value.trim().toLowerCase()
if (!value) return cases.value
return cases.value.filter(item => {
return [
item.title,
item.patientName,
item.gender,
String(item.age),
item.department,
item.scene,
item.caseNo
].some(field => field.toLowerCase().includes(value))
})
})
function loadCases() {
fetchCaseList().then(result => {
cases.value = result
})
}
function selectCase(item: ClinicalCase) {
uni.setStorageSync('clinical-thinking-selected-case', item)
selectedCase.value = item
showScenarioPage.value = true
}
function showToast(message: string) {
if (toastTimer) clearTimeout(toastTimer)
toastMessage.value = message
toastVisible.value = true
uni.showToast({
title: message,
icon: 'none'
})
toastTimer = setTimeout(() => {
toastVisible.value = false
}, 2200)
}
onMounted(loadCases)
onUnmounted(() => {
if (toastTimer) clearTimeout(toastTimer)
})
</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;
}
.search-row {
margin-bottom: 12px;
}
.search-box {
position: relative;
height: 40px;
border-radius: 8px;
background: #ffffff;
box-shadow: 0 2px 8px rgba(25, 28, 33, 0.04);
}
.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;
}
.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;
}
.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 {
color: #424752;
font-size: 13px;
line-height: 20px;
}
.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;
}
.empty-state {
padding: 40px 0;
text-align: center;
color: #727783;
font-size: 14px;
line-height: 20px;
}
.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);
}
</style>