933 lines
23 KiB
Plaintext
933 lines
23 KiB
Plaintext
<!--
|
||
UTS-Android 兼容性开发规范(重要,所有开发成员须遵循)
|
||
- 表单优先用form,变量声明用let/const,不能用var。
|
||
- 跟template交互的变量尽量用一维变量。
|
||
- 不用foreach/map/safeget,只用for和UTSJSONObject。
|
||
- 数组类型用Array<Type>,不用简写[]。
|
||
- 不用interface,只用type。
|
||
- 判断空用 !== null,不用!。
|
||
- 不支持undefined,变量为null时需判空。
|
||
- 逻辑或用??(空值合并),不用||。
|
||
- for循环i需指定Int类型:for (let i:Int = 0; ...)
|
||
- 不支持Intersection Type、Index Signature。
|
||
- picker用picker-view或uni.showActionSheet。
|
||
- scroll-view用direction="vertical"。
|
||
- CSS只用display:flex; 不用gap、grid、calc()、伪类、vh等。
|
||
- 复杂数据交互用utils/utis下的UTSJSONObject。
|
||
- 时间选择用uni_modules/lime-date-time-picker。
|
||
-->
|
||
|
||
|
||
<template>
|
||
<view class="doctor-dashboard">
|
||
<!-- Header -->
|
||
<view class="header">
|
||
<text class="header-title">医生工作台</text>
|
||
<text class="header-subtitle">{{ currentTime }}</text>
|
||
<view class="header-actions">
|
||
<button class="action-btn emergency" @click="showEmergencyPage">
|
||
<text class="btn-text">🚨 急诊</text>
|
||
</button>
|
||
<button class="action-btn" @click="showNewConsultation">
|
||
<text class="btn-text">➕ 新建诊疗</text>
|
||
</button>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- Stats Cards -->
|
||
<view class="stats-section">
|
||
<view class="stat-card">
|
||
<view class="stat-icon">👥</view>
|
||
<view class="stat-content">
|
||
<text class="stat-number">{{ stats.today_patients }}</text>
|
||
<text class="stat-label">今日患者</text>
|
||
</view>
|
||
</view>
|
||
<view class="stat-card">
|
||
<view class="stat-icon">⏰</view>
|
||
<view class="stat-content">
|
||
<text class="stat-number">{{ stats.pending_consultations }}</text>
|
||
<text class="stat-label">待诊疗</text>
|
||
</view>
|
||
</view>
|
||
<view class="stat-card">
|
||
<view class="stat-icon">💊</view>
|
||
<view class="stat-content">
|
||
<text class="stat-number">{{ stats.prescriptions_today }}</text>
|
||
<text class="stat-label">今日处方</text>
|
||
</view>
|
||
</view>
|
||
<view class="stat-card urgent">
|
||
<view class="stat-icon">🚨</view>
|
||
<view class="stat-content">
|
||
<text class="stat-number">{{ stats.urgent_cases }}</text>
|
||
<text class="stat-label">紧急病例</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- Quick Actions -->
|
||
<view class="quick-actions">
|
||
<text class="section-title">快速操作</text>
|
||
<view class="actions-grid">
|
||
<button class="quick-action-btn" @click="showPatientQueue">
|
||
<text class="action-icon">👥</text>
|
||
<text class="action-text">患者队列</text>
|
||
</button>
|
||
<button class="quick-action-btn" @click="showMedicalRecords">
|
||
<text class="action-icon">📋</text>
|
||
<text class="action-text">病历管理</text>
|
||
</button>
|
||
<button class="quick-action-btn" @click="showPrescriptions">
|
||
<text class="action-icon">💊</text>
|
||
<text class="action-text">处方管理</text>
|
||
</button>
|
||
<button class="quick-action-btn" @click="showHealthReports">
|
||
<text class="action-icon">📊</text>
|
||
<text class="action-text">健康报告</text>
|
||
</button>
|
||
<button class="quick-action-btn" @click="showVitalSigns">
|
||
<text class="action-icon">❤️</text>
|
||
<text class="action-text">生命体征</text>
|
||
</button>
|
||
<button class="quick-action-btn" @click="showMedicationManagement">
|
||
<text class="action-icon">💉</text>
|
||
<text class="action-text">用药管理</text>
|
||
</button>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- Today's Schedule -->
|
||
<view class="schedule-section">
|
||
<view class="section-header">
|
||
<text class="section-title">今日日程 ({{ todaySchedule.length }})</text>
|
||
<button class="view-all-btn" @click="showFullSchedule">
|
||
<text class="btn-text">查看全部</text>
|
||
</button>
|
||
</view>
|
||
<scroll-view class="schedule-list" scroll-y="true" :style="{ height: '400px' }">
|
||
<view
|
||
v-for="appointment in todaySchedule"
|
||
:key="appointment.id"
|
||
class="schedule-item"
|
||
:class="{
|
||
'current': isCurrentAppointment(appointment),
|
||
'urgent': appointment.priority === 'urgent',
|
||
'completed': appointment.status === 'completed'
|
||
}"
|
||
@click="openAppointment(appointment)"
|
||
>
|
||
<view class="appointment-time">
|
||
<text class="time-text">{{ formatTime(appointment.scheduled_time) }}</text>
|
||
<text class="status-text" :class="appointment.status">{{ getStatusText(appointment.status) }}</text>
|
||
</view>
|
||
<view class="appointment-details">
|
||
<text class="patient-name">{{ appointment.elder_name }}</text>
|
||
<text class="appointment-type">{{ getAppointmentTypeText(appointment.appointment_type) }}</text>
|
||
<text class="chief-complaint" v-if="appointment.chief_complaint">{{ appointment.chief_complaint }}</text>
|
||
<text class="room-info" v-if="appointment.room_number">房间: {{ appointment.room_number }}</text>
|
||
</view>
|
||
<view class="appointment-actions">
|
||
<button
|
||
v-if="appointment.status === 'scheduled'"
|
||
class="start-btn"
|
||
@click.stop="startConsultation(appointment)"
|
||
>
|
||
<text class="btn-text">开始诊疗</text>
|
||
</button>
|
||
<button
|
||
v-if="appointment.status === 'in_progress'"
|
||
class="continue-btn"
|
||
@click.stop="continueConsultation(appointment)"
|
||
>
|
||
<text class="btn-text">继续</text>
|
||
</button>
|
||
<button
|
||
v-if="appointment.status === 'completed'"
|
||
class="view-btn"
|
||
@click.stop="viewConsultation(appointment)"
|
||
>
|
||
<text class="btn-text">查看</text>
|
||
</button>
|
||
</view>
|
||
</view>
|
||
|
||
<view v-if="todaySchedule.length === 0" class="empty-state">
|
||
<text class="empty-text">今日暂无安排</text>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
|
||
<!-- Recent Consultations -->
|
||
<view class="recent-section">
|
||
<view class="section-header">
|
||
<text class="section-title">最近诊疗记录</text>
|
||
<button class="view-all-btn" @click="showAllConsultations">
|
||
<text class="btn-text">查看全部</text>
|
||
</button>
|
||
</view>
|
||
<scroll-view class="recent-list" scroll-y="true" :style="{ height: '300px' }">
|
||
<view
|
||
v-for="consultation in recentConsultations"
|
||
:key="consultation.id"
|
||
class="consultation-item"
|
||
@click="viewConsultationDetails(consultation)"
|
||
>
|
||
<view class="consultation-header">
|
||
<text class="patient-name">{{ consultation.elder_name }}</text>
|
||
<text class="consultation-date">{{ formatDateTime(consultation.scheduled_time) }}</text>
|
||
</view>
|
||
<view class="consultation-content">
|
||
<text class="diagnosis">诊断: {{ consultation.diagnosis || '未填写' }}</text>
|
||
<text class="treatment">治疗: {{ consultation.treatment || '未填写' }}</text>
|
||
</view>
|
||
<view class="consultation-meta">
|
||
<text class="consultation-type">{{ getConsultationTypeText(consultation.consultation_type) }}</text>
|
||
<text class="follow-up" v-if="consultation.follow_up_date">复诊: {{ formatDate(consultation.follow_up_date) }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view v-if="recentConsultations.length === 0" class="empty-state">
|
||
<text class="empty-text">暂无诊疗记录</text>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
|
||
<!-- Urgent Alerts -->
|
||
<view class="alerts-section" v-if="urgentAlerts.length > 0">
|
||
<view class="section-header">
|
||
<text class="section-title">紧急提醒</text>
|
||
<button class="view-all-btn" @click="showAllAlerts">
|
||
<text class="btn-text">查看全部</text>
|
||
</button>
|
||
</view>
|
||
<scroll-view class="alerts-list" scroll-y="true">
|
||
<view
|
||
v-for="alert in urgentAlerts"
|
||
:key="alert.id"
|
||
class="alert-item"
|
||
:class="alert.severity"
|
||
@click="handleAlert(alert)"
|
||
>
|
||
<view class="alert-header">
|
||
<text class="alert-title">{{ alert.title }}</text>
|
||
<text class="alert-time">{{ formatTime(alert.created_at) }}</text>
|
||
</view>
|
||
<view class="alert-content">
|
||
<text class="alert-description">{{ alert.description }}</text>
|
||
<text class="alert-patient">患者: {{ alert.elder_name }}</text>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup lang="uts">
|
||
import { ref, onMounted, onUnmounted } from 'vue'
|
||
import supa from '@/components/supadb/aksupainstance.uts'
|
||
|
||
import {
|
||
formatTime,
|
||
formatDate,
|
||
formatDateTime,
|
||
getCurrentTimeString,
|
||
getTodayStart,
|
||
getTodayEnd,
|
||
getRecentDate,
|
||
getSeverityText
|
||
} from '../types_new.uts'
|
||
|
||
// 数据类型定义
|
||
type DoctorStats = {
|
||
today_patients: number
|
||
pending_consultations: number
|
||
prescriptions_today: number
|
||
urgent_cases: number
|
||
}
|
||
|
||
type Appointment = {
|
||
id: string
|
||
elder_id: string
|
||
elder_name: string
|
||
doctor_id: string
|
||
scheduled_time: string
|
||
appointment_type: string
|
||
chief_complaint: string
|
||
status: string
|
||
priority: string
|
||
room_number: string
|
||
estimated_duration: number
|
||
created_at: string
|
||
}
|
||
|
||
type Consultation = {
|
||
id: string
|
||
elder_id: string
|
||
elder_name: string
|
||
doctor_id: string
|
||
scheduled_time: string
|
||
consultation_type: string
|
||
chief_complaint: string
|
||
diagnosis: string
|
||
treatment: string
|
||
prescription: string
|
||
follow_up_date: string
|
||
notes: string
|
||
created_at: string
|
||
}
|
||
|
||
type HealthAlert = {
|
||
id: string
|
||
elder_id: string
|
||
elder_name: string
|
||
title: string
|
||
description: string
|
||
severity: string
|
||
alert_type: string
|
||
status: string
|
||
created_at: string
|
||
}
|
||
|
||
// 响应式数据
|
||
const currentTime = ref<string>('')
|
||
const stats = ref<DoctorStats>({
|
||
today_patients: 0,
|
||
pending_consultations: 0,
|
||
prescriptions_today: 0,
|
||
urgent_cases: 0
|
||
})
|
||
const todaySchedule = ref<Appointment[]>([])
|
||
const recentConsultations = ref<Consultation[]>([])
|
||
const urgentAlerts = ref<HealthAlert[]>([])
|
||
|
||
let timeInterval: number = 0
|
||
|
||
// 生命周期
|
||
onMounted(() => {
|
||
loadData()
|
||
updateCurrentTime()
|
||
timeInterval = setInterval(updateCurrentTime, 60000) // 每分钟更新时间
|
||
})
|
||
|
||
onUnmounted(() => {
|
||
if (timeInterval) {
|
||
clearInterval(timeInterval)
|
||
}
|
||
})
|
||
|
||
// 更新当前时间
|
||
const updateCurrentTime = () => {
|
||
currentTime.value = getCurrentTimeString()
|
||
}
|
||
|
||
// 加载数据
|
||
const loadData = async () => {
|
||
await Promise.all([
|
||
loadStats(),
|
||
loadTodaySchedule(),
|
||
loadRecentConsultations(),
|
||
loadUrgentAlerts()
|
||
])
|
||
}
|
||
|
||
// 加载统计数据
|
||
const loadStats = async () => {
|
||
try {
|
||
// 今日患者数
|
||
const patientsResult = await supa
|
||
.from('ec_appointments')
|
||
.select('*', { count: 'exact' })
|
||
.gte('scheduled_time', getTodayStart())
|
||
.lte('scheduled_time', getTodayEnd())
|
||
.executeAs<Appointment[]>()
|
||
|
||
// 待诊疗数量
|
||
const pendingResult = await supa
|
||
.from('ec_appointments')
|
||
.select('*', { count: 'exact' })
|
||
.in('status', ['scheduled', 'in_progress'])
|
||
.executeAs<Appointment[]>()
|
||
|
||
// 今日处方数量
|
||
const prescriptionsResult = await supa
|
||
.from('ec_medications')
|
||
.select('*', { count: 'exact' })
|
||
.gte('created_at', getTodayStart())
|
||
.lte('created_at', getTodayEnd())
|
||
.executeAs<any[]>()
|
||
|
||
// 紧急病例数量
|
||
const urgentResult = await supa
|
||
.from('ec_health_alerts')
|
||
.select('*', { count: 'exact' })
|
||
.in('severity', ['high', 'critical'])
|
||
.eq('status', 'active')
|
||
.executeAs<HealthAlert[]>()
|
||
|
||
stats.value = {
|
||
today_patients: patientsResult.count ?? 0,
|
||
pending_consultations: pendingResult.count ?? 0,
|
||
prescriptions_today: prescriptionsResult.count ?? 0,
|
||
urgent_cases: urgentResult.count ?? 0
|
||
}
|
||
} catch (error) {
|
||
console.error('加载统计数据失败:', error)
|
||
}
|
||
}
|
||
|
||
// 加载今日日程
|
||
const loadTodaySchedule = async () => {
|
||
try {
|
||
const result = await supa
|
||
.from('ec_appointments')
|
||
.select(`
|
||
id,
|
||
elder_id,
|
||
elder_name,
|
||
doctor_id,
|
||
scheduled_time,
|
||
appointment_type,
|
||
chief_complaint,
|
||
status,
|
||
priority,
|
||
room_number,
|
||
estimated_duration,
|
||
created_at
|
||
`)
|
||
.gte('scheduled_time', getTodayStart())
|
||
.lte('scheduled_time', getTodayEnd())
|
||
.order('scheduled_time', { ascending: true })
|
||
.executeAs<Appointment[]>()
|
||
|
||
if (result.error == null && result.data != null) {
|
||
todaySchedule.value = result.data
|
||
}
|
||
} catch (error) {
|
||
console.error('加载今日日程失败:', error)
|
||
}
|
||
}
|
||
|
||
// 加载最近诊疗记录
|
||
const loadRecentConsultations = async () => {
|
||
try {
|
||
const result = await supa
|
||
.from('ec_consultations')
|
||
.select(`
|
||
id,
|
||
elder_id,
|
||
elder_name,
|
||
doctor_id,
|
||
scheduled_time,
|
||
consultation_type,
|
||
chief_complaint,
|
||
diagnosis,
|
||
treatment,
|
||
prescription,
|
||
follow_up_date,
|
||
notes,
|
||
created_at
|
||
`)
|
||
.gte('scheduled_time', getRecentDate(7))
|
||
.order('scheduled_time', { ascending: false })
|
||
.limit(10)
|
||
.executeAs<Consultation[]>()
|
||
|
||
if (result.error == null && result.data != null) {
|
||
recentConsultations.value = result.data
|
||
}
|
||
} catch (error) {
|
||
console.error('加载最近诊疗记录失败:', error)
|
||
}
|
||
}
|
||
|
||
// 加载紧急提醒
|
||
const loadUrgentAlerts = async () => {
|
||
try {
|
||
const result = await supa
|
||
.from('ec_health_alerts')
|
||
.select(`
|
||
id,
|
||
elder_id,
|
||
elder_name,
|
||
title,
|
||
description,
|
||
severity,
|
||
alert_type,
|
||
status,
|
||
created_at
|
||
`)
|
||
.in('severity', ['high', 'critical'])
|
||
.eq('status', 'active')
|
||
.order('created_at', { ascending: false })
|
||
.limit(5)
|
||
.executeAs<HealthAlert[]>()
|
||
|
||
if (result.error == null && result.data != null) {
|
||
urgentAlerts.value = result.data
|
||
}
|
||
} catch (error) {
|
||
console.error('加载紧急提醒失败:', error)
|
||
}
|
||
}
|
||
|
||
// 辅助函数
|
||
const getStatusText = (status: string): string => {
|
||
const statusMap = new Map([
|
||
['scheduled', '已预约'],
|
||
['in_progress', '进行中'],
|
||
['completed', '已完成'],
|
||
['cancelled', '已取消'],
|
||
['no_show', '未到']
|
||
])
|
||
return statusMap.get(status) ?? status
|
||
}
|
||
|
||
const getAppointmentTypeText = (type: string): string => {
|
||
const typeMap = new Map([
|
||
['routine', '常规检查'],
|
||
['follow_up', '复诊'],
|
||
['emergency', '急诊'],
|
||
['consultation', '会诊'],
|
||
['physical', '体检']
|
||
])
|
||
return typeMap.get(type) ?? type
|
||
}
|
||
|
||
const getConsultationTypeText = (type: string): string => {
|
||
const typeMap = new Map([
|
||
['initial', '初诊'],
|
||
['follow_up', '复诊'],
|
||
['emergency', '急诊'],
|
||
['consultation', '会诊'],
|
||
['phone', '电话咨询']
|
||
])
|
||
return typeMap.get(type) ?? type
|
||
}
|
||
|
||
const isCurrentAppointment = (appointment: Appointment): boolean => {
|
||
const now = new Date()
|
||
const appointmentTime = new Date(appointment.scheduled_time)
|
||
const diff = Math.abs(now.getTime() - appointmentTime.getTime())
|
||
return diff <= 30 * 60 * 1000 && appointment.status === 'in_progress' // 30分钟内
|
||
}
|
||
|
||
// 事件处理
|
||
const showEmergencyPage = () => {
|
||
uni.navigateTo({ url: '/pages/ec/doctor/emergency' })
|
||
}
|
||
|
||
const showNewConsultation = () => {
|
||
uni.navigateTo({ url: '/pages/ec/doctor/consultation-form' })
|
||
}
|
||
|
||
const showPatientQueue = () => {
|
||
uni.navigateTo({ url: '/pages/ec/doctor/patient-queue' })
|
||
}
|
||
|
||
const showMedicalRecords = () => {
|
||
uni.navigateTo({ url: '/pages/ec/doctor/medical-records' })
|
||
}
|
||
|
||
const showPrescriptions = () => {
|
||
uni.navigateTo({ url: '/pages/ec/doctor/prescriptions' })
|
||
}
|
||
|
||
const showHealthReports = () => {
|
||
uni.navigateTo({ url: '/pages/ec/doctor/health-reports' })
|
||
}
|
||
|
||
const showVitalSigns = () => {
|
||
uni.navigateTo({ url: '/pages/ec/doctor/vital-signs' })
|
||
}
|
||
|
||
const showMedicationManagement = () => {
|
||
uni.navigateTo({ url: '/pages/ec/doctor/medication-management' })
|
||
}
|
||
|
||
const showFullSchedule = () => {
|
||
uni.navigateTo({ url: '/pages/ec/doctor/schedule' })
|
||
}
|
||
|
||
const showAllConsultations = () => {
|
||
uni.navigateTo({ url: '/pages/ec/doctor/consultations' })
|
||
}
|
||
|
||
const showAllAlerts = () => {
|
||
uni.navigateTo({ url: '/pages/ec/doctor/alerts' })
|
||
}
|
||
|
||
const openAppointment = (appointment: Appointment) => {
|
||
uni.navigateTo({
|
||
url: `/pages/ec/doctor/appointment-detail?id=${appointment.id}`
|
||
})
|
||
}
|
||
|
||
const startConsultation = async (appointment: Appointment) => {
|
||
try {
|
||
await supa
|
||
.from('ec_appointments')
|
||
.update({ status: 'in_progress' })
|
||
.eq('id', appointment.id)
|
||
.executeAs<any>()
|
||
|
||
uni.navigateTo({
|
||
url: `/pages/ec/doctor/consultation?appointmentId=${appointment.id}`
|
||
})
|
||
} catch (error) {
|
||
console.error('开始诊疗失败:', error)
|
||
uni.showToast({ title: '操作失败', icon: 'error' })
|
||
}
|
||
}
|
||
|
||
const continueConsultation = (appointment: Appointment) => {
|
||
uni.navigateTo({
|
||
url: `/pages/ec/doctor/consultation?appointmentId=${appointment.id}`
|
||
})
|
||
}
|
||
|
||
const viewConsultation = (appointment: Appointment) => {
|
||
uni.navigateTo({
|
||
url: `/pages/ec/doctor/consultation-detail?appointmentId=${appointment.id}`
|
||
})
|
||
}
|
||
|
||
const viewConsultationDetails = (consultation: Consultation) => {
|
||
uni.navigateTo({
|
||
url: `/pages/ec/doctor/consultation-detail?id=${consultation.id}`
|
||
})
|
||
}
|
||
|
||
const handleAlert = (alert: HealthAlert) => {
|
||
uni.navigateTo({
|
||
url: `/pages/ec/doctor/alert-detail?id=${alert.id}`
|
||
})
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.doctor-dashboard {
|
||
padding: 20px;
|
||
background-color: #f5f5f5;
|
||
min-height: 100vh;
|
||
}
|
||
|
||
.header {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
margin-bottom: 20px;
|
||
padding: 16px;
|
||
background-color: #667eea;
|
||
border-radius: 12px;
|
||
color: #fff;
|
||
}
|
||
.header-title {
|
||
font-size: 22px;
|
||
font-weight: bold;
|
||
}
|
||
.header-subtitle {
|
||
font-size: 14px;
|
||
opacity: 0.8;
|
||
margin-top: 5px;
|
||
}
|
||
.header-actions {
|
||
display: flex;
|
||
flex-direction: row;
|
||
margin-top: 10px;
|
||
}
|
||
.action-btn {
|
||
padding: 8px 16px;
|
||
border-radius: 20px;
|
||
border: none;
|
||
background-color: #fff;
|
||
color: #667eea;
|
||
margin-right: 10px;
|
||
}
|
||
.action-btn.emergency {
|
||
background-color: #ff4757;
|
||
color: #fff;
|
||
}
|
||
.action-btn:last-child {
|
||
margin-right: 0;
|
||
}
|
||
|
||
.stats-section {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
flex-direction:row;
|
||
margin-bottom: 20px;
|
||
}
|
||
.stat-card {
|
||
flex: 1 1 140px;
|
||
background-color: #fff;
|
||
border-radius: 10px;
|
||
padding: 16px;
|
||
margin-right: 10px;
|
||
margin-bottom: 10px;
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
.stat-card:last-child {
|
||
margin-right: 0;
|
||
}
|
||
.stat-card.urgent {
|
||
background-color: #ff6b6b;
|
||
color: #fff;
|
||
}
|
||
.stat-icon {
|
||
font-size: 22px;
|
||
width: 36px;
|
||
height: 36px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background-color: #f0f0f0;
|
||
border-radius: 50%;
|
||
margin-right: 10px;
|
||
}
|
||
.stat-card.urgent .stat-icon {
|
||
background-color: #fff2f0;
|
||
color: #ff6b6b;
|
||
}
|
||
.stat-content {
|
||
flex: 1;
|
||
}
|
||
.stat-number {
|
||
font-size: 20px;
|
||
font-weight: bold;
|
||
display: block;
|
||
}
|
||
.stat-label {
|
||
font-size: 13px;
|
||
opacity: 0.7;
|
||
margin-top: 4px;
|
||
}
|
||
|
||
.quick-actions {
|
||
margin-bottom: 20px;
|
||
}
|
||
.section-title {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
margin-bottom: 10px;
|
||
color: #333;
|
||
}
|
||
.actions-grid {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
flex-direction:row;
|
||
}
|
||
.quick-action-btn {
|
||
width: 46%;
|
||
margin-right: 4%;
|
||
margin-bottom: 12px;
|
||
padding: 16px 0;
|
||
background-color: #fff;
|
||
border-radius: 10px;
|
||
border: none;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
}
|
||
.quick-action-btn:nth-child(2n) {
|
||
margin-right: 0;
|
||
}
|
||
.action-icon {
|
||
font-size: 20px;
|
||
margin-bottom: 6px;
|
||
}
|
||
.action-text {
|
||
font-size: 13px;
|
||
color: #666;
|
||
}
|
||
|
||
.schedule-section, .recent-section, .alerts-section {
|
||
margin-bottom: 20px;
|
||
}
|
||
.section-header {
|
||
display: flex;
|
||
flex-direction: row;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 10px;
|
||
}
|
||
.view-all-btn {
|
||
padding: 6px 12px;
|
||
border-radius: 12px;
|
||
border: 1px solid #ddd;
|
||
background-color: #fff;
|
||
color: #666;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.schedule-list, .recent-list, .alerts-list {
|
||
background-color: #fff;
|
||
border-radius: 10px;
|
||
}
|
||
.schedule-item {
|
||
padding: 14px 10px;
|
||
border-bottom: 1px solid #f0f0f0;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: flex-start;
|
||
}
|
||
.schedule-item:last-child {
|
||
border-bottom: none;
|
||
}
|
||
.schedule-item.current {
|
||
background-color: #e8f5e8;
|
||
border-left: 4px solid #4caf50;
|
||
}
|
||
.schedule-item.urgent {
|
||
background-color: #fffbe6;
|
||
border-left: 4px solid #fdcb6e;
|
||
}
|
||
.schedule-item.completed {
|
||
opacity: 0.6;
|
||
}
|
||
.appointment-time {
|
||
width: 80px;
|
||
flex-shrink: 0;
|
||
}
|
||
.time-text {
|
||
font-size: 15px;
|
||
font-weight: 600;
|
||
color: #333;
|
||
}
|
||
.status-text {
|
||
font-size: 12px;
|
||
color: #666;
|
||
margin-top: 4px;
|
||
}
|
||
.status-text.scheduled { color: #3498db; }
|
||
.status-text.in_progress { color: #f39c12; }
|
||
.status-text.completed { color: #27ae60; }
|
||
.status-text.cancelled { color: #e74c3c; }
|
||
.appointment-details {
|
||
flex: 1;
|
||
margin-left: 10px;
|
||
}
|
||
.patient-name {
|
||
font-size: 15px;
|
||
font-weight: 600;
|
||
color: #333;
|
||
}
|
||
.appointment-type {
|
||
font-size: 13px;
|
||
color: #666;
|
||
margin-top: 4px;
|
||
}
|
||
.chief-complaint {
|
||
font-size: 12px;
|
||
color: #888;
|
||
margin-top: 4px;
|
||
}
|
||
.room-info {
|
||
font-size: 12px;
|
||
color: #999;
|
||
margin-top: 4px;
|
||
}
|
||
.appointment-actions {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: flex-end;
|
||
margin-left: 10px;
|
||
}
|
||
.start-btn, .continue-btn, .view-btn {
|
||
padding: 6px 14px;
|
||
border-radius: 16px;
|
||
border: none;
|
||
font-size: 13px;
|
||
color: #fff;
|
||
margin-bottom: 6px;
|
||
}
|
||
.start-btn { background-color: #27ae60; }
|
||
.continue-btn { background-color: #f39c12; }
|
||
.view-btn { background-color: #3498db; }
|
||
|
||
.consultation-item {
|
||
padding: 14px 10px;
|
||
border-bottom: 1px solid #f0f0f0;
|
||
}
|
||
.consultation-item:last-child {
|
||
border-bottom: none;
|
||
}
|
||
.consultation-header {
|
||
display: flex;
|
||
flex-direction: row;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 8px;
|
||
}
|
||
.consultation-date {
|
||
font-size: 13px;
|
||
color: #666;
|
||
}
|
||
.diagnosis, .treatment {
|
||
font-size: 13px;
|
||
color: #555;
|
||
margin-bottom: 4px;
|
||
}
|
||
.consultation-meta {
|
||
display: flex;
|
||
flex-direction: row;
|
||
justify-content: space-between;
|
||
}
|
||
.consultation-type, .follow-up {
|
||
font-size: 12px;
|
||
color: #888;
|
||
}
|
||
|
||
.alert-item {
|
||
padding: 10px;
|
||
border-bottom: 1px solid #f0f0f0;
|
||
border-left: 4px solid #ddd;
|
||
}
|
||
.alert-item:last-child {
|
||
border-bottom: none;
|
||
}
|
||
.alert-item.high {
|
||
border-left-color: #f39c12;
|
||
background-color: #fef9e7;
|
||
}
|
||
.alert-item.critical {
|
||
border-left-color: #e74c3c;
|
||
background-color: #fdf2f2;
|
||
}
|
||
.alert-header {
|
||
display: flex;
|
||
flex-direction: row;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 6px;
|
||
}
|
||
.alert-title {
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
color: #333;
|
||
}
|
||
.alert-time {
|
||
font-size: 12px;
|
||
color: #666;
|
||
}
|
||
.alert-description {
|
||
font-size: 13px;
|
||
color: #555;
|
||
margin-bottom: 4px;
|
||
}
|
||
.alert-patient {
|
||
font-size: 12px;
|
||
color: #888;
|
||
}
|
||
|
||
.empty-state {
|
||
padding: 30px 0;
|
||
text-align: center;
|
||
}
|
||
.empty-text {
|
||
font-size: 13px;
|
||
color: #999;
|
||
}
|
||
</style>
|