Initial commit of akmon project
This commit is contained in:
374
pages/ec/doctor/consultations.uvue
Normal file
374
pages/ec/doctor/consultations.uvue
Normal file
@@ -0,0 +1,374 @@
|
||||
<template>
|
||||
<view class="consultations-page">
|
||||
<text class="title">全部诊疗记录</text>
|
||||
|
||||
<!-- 查询区域 -->
|
||||
<view class="search-section">
|
||||
<view class="search-input-group">
|
||||
<input
|
||||
v-model="searchQuery"
|
||||
placeholder="请输入患者姓名或诊疗ID"
|
||||
class="search-input"
|
||||
@confirm="onInputConfirm"
|
||||
/>
|
||||
<button class="search-btn" @click="performSearch">查询</button>
|
||||
</view>
|
||||
|
||||
<view class="filter-group">
|
||||
<picker
|
||||
mode="date"
|
||||
:value="startDate"
|
||||
@change="onStartDateChange"
|
||||
class="date-picker date-picker-left"
|
||||
>
|
||||
<view class="picker-display">
|
||||
开始日期: {{ startDate != '' ? startDate : '请选择' }}
|
||||
</view>
|
||||
</picker>
|
||||
|
||||
<picker
|
||||
mode="date"
|
||||
:value="endDate"
|
||||
@change="onEndDateChange"
|
||||
class="date-picker"
|
||||
>
|
||||
<view class="picker-display">
|
||||
结束日期: {{ endDate != '' ? endDate : '请选择' }}
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 诊疗记录列表 -->
|
||||
<view class="records-list" v-if="records.length > 0">
|
||||
<view
|
||||
v-for="record in records"
|
||||
:key="record.id"
|
||||
class="record-item"
|
||||
:class="getUrgencyClass(record.urgency)"
|
||||
>
|
||||
<view class="record-header">
|
||||
<view class="record-header-left">
|
||||
<text class="urgency-tag" v-if="record.urgency != 'normal'">{{ getUrgencyText(record.urgency) }}</text>
|
||||
<text class="patient-name">{{ record.patientName }}</text>
|
||||
</view>
|
||||
<text class="consultation-date">{{ record.date }}</text>
|
||||
</view>
|
||||
<view class="record-content">
|
||||
<text class="diagnosis">{{ record.diagnosis }}</text>
|
||||
</view>
|
||||
<view class="record-actions">
|
||||
<button class="detail-btn" @click="viewDetail(record)">查看详情</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="empty" v-else>暂无数据</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<view class="loading" v-if="loading">加载中...</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
// 全部诊疗记录页面
|
||||
import { ref, onMounted } from 'vue'
|
||||
|
||||
// 定义诊疗记录类
|
||||
type RecordItem = {
|
||||
id : number,
|
||||
patientName : string,
|
||||
date : string,
|
||||
diagnosis : string,
|
||||
urgency : string // emergency, urgent, normal
|
||||
}
|
||||
|
||||
// 数据
|
||||
const searchQuery = ref('')
|
||||
const startDate = ref('')
|
||||
const endDate = ref('')
|
||||
const records = ref([] as RecordItem[])
|
||||
const loading = ref(false)
|
||||
|
||||
/**
|
||||
* 查询诊疗记录
|
||||
*/
|
||||
function performSearch() {
|
||||
if (loading.value) return
|
||||
loading.value = true
|
||||
|
||||
// 模拟异步请求
|
||||
setTimeout(() => {
|
||||
try {
|
||||
// 暂时使用模拟数据
|
||||
const mockRecords : RecordItem[] = [
|
||||
{
|
||||
id: 1,
|
||||
patientName: '张三',
|
||||
date: '2024-01-15',
|
||||
diagnosis: '感冒,发热,建议休息多喝水',
|
||||
urgency: 'normal'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
patientName: '李四',
|
||||
date: '2024-01-14',
|
||||
diagnosis: '高血压,建议定期检查',
|
||||
urgency: 'urgent'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
patientName: '王五',
|
||||
date: '2024-01-16',
|
||||
diagnosis: '急性胸痛,疑心肌梗死,需紧急处置',
|
||||
urgency: 'emergency'
|
||||
}
|
||||
]
|
||||
|
||||
// 过滤数据(模拟查询)
|
||||
records.value = mockRecords.filter((record : RecordItem) : boolean => {
|
||||
const matchesQuery = searchQuery.value == '' ||
|
||||
record.patientName.includes(searchQuery.value) ||
|
||||
record.id.toString().includes(searchQuery.value)
|
||||
|
||||
const matchesDate = (startDate.value == '' || record.date >= startDate.value) &&
|
||||
(endDate.value == '' || record.date <= endDate.value)
|
||||
|
||||
return matchesQuery && matchesDate
|
||||
})
|
||||
|
||||
} catch (e : any) {
|
||||
console.error('查询失败:', e)
|
||||
uni.showToast({
|
||||
title: '查询失败',
|
||||
icon: 'none'
|
||||
})
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}, 200)
|
||||
}
|
||||
|
||||
// 输入确认搜索
|
||||
function onInputConfirm(_ : any) {
|
||||
performSearch()
|
||||
}
|
||||
|
||||
// 日期选择器变化
|
||||
function onStartDateChange(e : any) {
|
||||
startDate.value = e.detail.value as string
|
||||
}
|
||||
|
||||
function onEndDateChange(e : any) {
|
||||
endDate.value = e.detail.value as string
|
||||
}
|
||||
|
||||
// 查看详情
|
||||
function viewDetail(record : RecordItem) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/ec/doctor/consultation-detail?id=${record.id}`
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取紧急程度对应的样式类
|
||||
*/
|
||||
function getUrgencyClass(urgency : string) : string {
|
||||
if (urgency == 'emergency') {
|
||||
return 'record-item-emergency'
|
||||
} else if (urgency == 'urgent') {
|
||||
return 'record-item-urgent'
|
||||
}
|
||||
return 'record-item-normal'
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取紧急程度文字
|
||||
*/
|
||||
function getUrgencyText(urgency : string) : string {
|
||||
if (urgency == 'emergency') {
|
||||
return '【紧急】'
|
||||
} else if (urgency == 'urgent') {
|
||||
return '【优先】'
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
// 页面加载时获取所有记录
|
||||
onMounted(() => {
|
||||
performSearch()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.consultations-page {
|
||||
padding: 30px;
|
||||
background-color: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 20px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.search-section {
|
||||
background-color: #ffffff;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.search-input-group {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
padding: 10px;
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
border-color: #dddddd;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.search-btn {
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
background-color: #007aff;
|
||||
color: #ffffff;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.filter-group {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.date-picker {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.date-picker-left {
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.picker-display {
|
||||
padding: 10px;
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
border-color: #dddddd;
|
||||
border-radius: 4px;
|
||||
background-color: #f9f9f9;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.records-list {
|
||||
background-color: #ffffff;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.record-item {
|
||||
padding: 15px;
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-color: #eeeeee;
|
||||
}
|
||||
|
||||
.record-item-emergency {
|
||||
background-color: #fff1f0;
|
||||
}
|
||||
|
||||
.record-item-urgent {
|
||||
background-color: #fff7e6;
|
||||
}
|
||||
|
||||
.record-item-normal {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.record-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.record-header-left {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.urgency-tag {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.record-item-emergency .urgency-tag {
|
||||
color: #ff4d4f;
|
||||
}
|
||||
|
||||
.record-item-urgent .urgency-tag {
|
||||
color: #fa8c16;
|
||||
}
|
||||
|
||||
.patient-name {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.consultation-date {
|
||||
font-size: 14px;
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.record-content {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.diagnosis {
|
||||
font-size: 14px;
|
||||
color: #555555;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.record-actions {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.detail-btn {
|
||||
padding-left: 12px;
|
||||
padding-right: 12px;
|
||||
background-color: #28a745;
|
||||
color: #ffffff;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.empty {
|
||||
color: #aaaaaa;
|
||||
text-align: center;
|
||||
margin-top: 60px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.loading {
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
color: #666666;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
932
pages/ec/doctor/dashboard.uvue
Normal file
932
pages/ec/doctor/dashboard.uvue
Normal file
@@ -0,0 +1,932 @@
|
||||
<!--
|
||||
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>
|
||||
166
pages/ec/doctor/emergency.uvue
Normal file
166
pages/ec/doctor/emergency.uvue
Normal file
@@ -0,0 +1,166 @@
|
||||
<template>
|
||||
<view class="emergency-page">
|
||||
<view class="header">
|
||||
<text class="title">急诊管理</text>
|
||||
<button class="add-btn" @click="showAddEmergency">➕ 新建急诊</button>
|
||||
</view>
|
||||
<view class="stats">
|
||||
<text>今日急诊: {{ stats.today }}</text>
|
||||
<text>处理中: {{ stats.processing }}</text>
|
||||
<text>已解决: {{ stats.resolved }}</text>
|
||||
</view>
|
||||
<scroll-view class="emergency-list" scroll-y="true">
|
||||
<view v-for="item in emergencies" :key="item.id" class="emergency-item" :class="item.severity">
|
||||
<view class="item-header">
|
||||
<text class="type">{{ getTypeText(item.emergency_type) }}</text>
|
||||
<text class="severity">{{ getSeverityText(item.severity) }}</text>
|
||||
<text class="status">{{ getStatusText(item.status) }}</text>
|
||||
</view>
|
||||
<view class="item-content">
|
||||
<text class="elder">患者: {{ item.elder_name }}</text>
|
||||
<text class="desc">{{ item.description }}</text>
|
||||
</view>
|
||||
<view class="item-footer">
|
||||
<text class="time">{{ formatDateTime(item.occurred_at) }}</text>
|
||||
<button v-if="item.status==='active'" class="handle-btn" @click="handleEmergency(item)">处理</button>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="emergencies.length===0" class="empty-state">
|
||||
<text>暂无急诊事件</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import supa from '@/components/supadb/aksupainstance.uts'
|
||||
import { formatDateTime } from '../types_new.uts'
|
||||
|
||||
type Emergency = {
|
||||
id: string
|
||||
appointment_id: string
|
||||
elder_id: string
|
||||
elder_name: string
|
||||
doctor_id: string
|
||||
emergency_type: string
|
||||
severity: string
|
||||
status: string
|
||||
description: string
|
||||
occurred_at: string
|
||||
handled_at: string
|
||||
handler_notes: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
type EmergencyStats = {
|
||||
today: number
|
||||
processing: number
|
||||
resolved: number
|
||||
}
|
||||
|
||||
const emergencies = ref<Emergency[]>([])
|
||||
const stats = ref<EmergencyStats>({ today: 0, processing: 0, resolved: 0 })
|
||||
|
||||
onMounted(() => {
|
||||
loadEmergencies()
|
||||
loadStats()
|
||||
})
|
||||
|
||||
const loadEmergencies = async () => {
|
||||
const result = await supa
|
||||
.from('ec_emergencies')
|
||||
.select('*')
|
||||
.order('occurred_at', { ascending: false })
|
||||
.limit(30)
|
||||
.executeAs<Emergency[]>()
|
||||
if (result.error == null && result.data != null) {
|
||||
emergencies.value = result.data
|
||||
}
|
||||
}
|
||||
|
||||
const loadStats = async () => {
|
||||
const todayStart = new Date()
|
||||
todayStart.setHours(0,0,0,0)
|
||||
const todayEnd = new Date()
|
||||
todayEnd.setHours(23,59,59,999)
|
||||
const todayResult = await supa
|
||||
.from('ec_emergencies')
|
||||
.select('*', { count: 'exact' })
|
||||
.gte('occurred_at', todayStart.toISOString())
|
||||
.lte('occurred_at', todayEnd.toISOString())
|
||||
.executeAs<Emergency[]>()
|
||||
const processingResult = await supa
|
||||
.from('ec_emergencies')
|
||||
.select('*', { count: 'exact' })
|
||||
.eq('status', 'processing')
|
||||
.executeAs<Emergency[]>()
|
||||
const resolvedResult = await supa
|
||||
.from('ec_emergencies')
|
||||
.select('*', { count: 'exact' })
|
||||
.eq('status', 'resolved')
|
||||
.executeAs<Emergency[]>()
|
||||
stats.value = {
|
||||
today: todayResult.count ?? 0,
|
||||
processing: processingResult.count ?? 0,
|
||||
resolved: resolvedResult.count ?? 0
|
||||
}
|
||||
}
|
||||
|
||||
const getTypeText = (type: string): string => {
|
||||
const map = new Map([
|
||||
['fall', '跌倒'],
|
||||
['stroke', '中风'],
|
||||
['cardiac', '心脏'],
|
||||
['other', '其他']
|
||||
])
|
||||
return map.get(type) ?? type
|
||||
}
|
||||
const getSeverityText = (sev: string): string => {
|
||||
const map = new Map([
|
||||
['low', '低'],
|
||||
['medium', '中'],
|
||||
['high', '高'],
|
||||
['critical', '危急']
|
||||
])
|
||||
return map.get(sev) ?? sev
|
||||
}
|
||||
const getStatusText = (status: string): string => {
|
||||
const map = new Map([
|
||||
['active', '待处理'],
|
||||
['processing', '处理中'],
|
||||
['resolved', '已解决'],
|
||||
['cancelled', '已取消']
|
||||
])
|
||||
return map.get(status) ?? status
|
||||
}
|
||||
const showAddEmergency = () => {
|
||||
uni.navigateTo({ url: '/pages/ec/doctor/emergency-form' })
|
||||
}
|
||||
const handleEmergency = (item: Emergency) => {
|
||||
uni.navigateTo({ url: `/pages/ec/doctor/emergency-handle?id=${item.id}` })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.emergency-page { padding: 20px; background: #f5f5f5; min-height: 100vh; }
|
||||
.header { display: flex; flex-direction: row; justify-content: space-between; align-items: center; margin-bottom: 16px; }
|
||||
.title { font-size: 20px; font-weight: bold; }
|
||||
.add-btn { background: #ff4757; color: #fff; border: none; border-radius: 16px; padding: 8px 18px; font-size: 15px; }
|
||||
.stats { display: flex; flex-direction: row; gap: 18px; margin-bottom: 12px; }
|
||||
.emergency-list { background: #fff; border-radius: 10px; }
|
||||
.emergency-item { padding: 14px 10px; border-bottom: 1px solid #f0f0f0; }
|
||||
.emergency-item:last-child { border-bottom: none; }
|
||||
.item-header { display: flex; flex-direction: row; gap: 10px; margin-bottom: 6px; }
|
||||
.type { font-size: 14px; font-weight: 600; }
|
||||
.severity { font-size: 13px; color: #e67e22; }
|
||||
.status { font-size: 13px; color: #3498db; }
|
||||
.item-content { font-size: 13px; color: #555; margin-bottom: 6px; }
|
||||
.elder { color: #888; margin-right: 10px; }
|
||||
.desc { color: #555; }
|
||||
.item-footer { display: flex; flex-direction: row; justify-content: space-between; align-items: center; }
|
||||
.time { font-size: 12px; color: #999; }
|
||||
.handle-btn { background: #27ae60; color: #fff; border: none; border-radius: 12px; padding: 6px 14px; font-size: 13px; }
|
||||
.empty-state { padding: 30px 0; text-align: center; color: #999; }
|
||||
</style>
|
||||
106
pages/ec/doctor/health-reports.uvue
Normal file
106
pages/ec/doctor/health-reports.uvue
Normal file
@@ -0,0 +1,106 @@
|
||||
<template>
|
||||
<view class="health-reports-page">
|
||||
<view class="header">
|
||||
<text class="title">健康报告</text>
|
||||
</view>
|
||||
<view class="content">
|
||||
<view v-if="reports.length === 0" class="empty-state">
|
||||
<text class="empty-text">暂无健康报告</text>
|
||||
</view>
|
||||
<view v-for="report in reports" :key="report.id" class="report-item">
|
||||
<text class="report-title">{{ report.title }}</text>
|
||||
<text class="report-date">{{ formatDate(report.created_at) }}</text>
|
||||
<text class="report-summary">{{ report.summary }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import supa from '@/components/supadb/aksupainstance.uts'
|
||||
import { formatDate } from '../types_new.uts'
|
||||
|
||||
type HealthReport = {
|
||||
id: string
|
||||
elder_id: string
|
||||
elder_name: string
|
||||
title: string
|
||||
summary: string
|
||||
created_at: string
|
||||
}
|
||||
|
||||
const reports = ref<Array<HealthReport>>([])
|
||||
|
||||
onMounted(() => {
|
||||
loadReports()
|
||||
})
|
||||
|
||||
const loadReports = async () => {
|
||||
try {
|
||||
const result = await supa
|
||||
.from('ec_health_reports')
|
||||
.select('*')
|
||||
.order('created_at', { ascending: false })
|
||||
.limit(20)
|
||||
.executeAs<HealthReport[]>()
|
||||
if (result.error == null && result.data != null) {
|
||||
reports.value = result.data
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载健康报告失败:', error)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.health-reports-page {
|
||||
padding: 20px;
|
||||
background: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
}
|
||||
.header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.title {
|
||||
font-size: 22px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
.content {
|
||||
background: #fff;
|
||||
border-radius: 10px;
|
||||
padding: 16px;
|
||||
}
|
||||
.report-item {
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
padding: 12px 0;
|
||||
}
|
||||
.report-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.report-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #222;
|
||||
}
|
||||
.report-date {
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
margin-left: 10px;
|
||||
}
|
||||
.report-summary {
|
||||
font-size: 13px;
|
||||
color: #555;
|
||||
margin-top: 6px;
|
||||
display: block;
|
||||
}
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 40px 0;
|
||||
}
|
||||
.empty-text {
|
||||
color: #aaa;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
137
pages/ec/doctor/medical-records.uvue
Normal file
137
pages/ec/doctor/medical-records.uvue
Normal file
@@ -0,0 +1,137 @@
|
||||
<template>
|
||||
<view class="medical-records">
|
||||
<view class="header">
|
||||
<text class="header-title">病历管理</text>
|
||||
</view>
|
||||
<scroll-view class="records-list" scroll-y="true" :style="{ height: '600px' }">
|
||||
<view
|
||||
v-for="record in records"
|
||||
:key="record.id"
|
||||
class="record-item"
|
||||
@click="openRecord(record)"
|
||||
>
|
||||
<view class="record-header">
|
||||
<text class="patient-name">{{ record.elder_name }}</text>
|
||||
<text class="visit-date">{{ formatDateTime(record.visit_date) }}</text>
|
||||
</view>
|
||||
<view class="record-content">
|
||||
<text class="visit-type">类型: {{ getVisitTypeText(record.visit_type) }}</text>
|
||||
<text class="chief-complaint">主诉: {{ record.chief_complaint || '无' }}</text>
|
||||
<text class="diagnosis">诊断: {{ record.diagnosis || '未填写' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="records.length === 0" class="empty-state">
|
||||
<text class="empty-text">暂无病历记录</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import supa from '@/components/supadb/aksupainstance.uts'
|
||||
import { formatDateTime } from '../types_new.uts'
|
||||
|
||||
type MedicalRecord = {
|
||||
id: string
|
||||
elder_id: string
|
||||
elder_name: string
|
||||
visit_type: string
|
||||
visit_date: string
|
||||
chief_complaint: string
|
||||
diagnosis: string
|
||||
}
|
||||
|
||||
const records = ref<Array<MedicalRecord>>([])
|
||||
|
||||
onMounted(() => {
|
||||
loadRecords()
|
||||
})
|
||||
|
||||
const loadRecords = async () => {
|
||||
try {
|
||||
const result = await supa
|
||||
.from('ec_medical_records')
|
||||
.select('id, elder_id, elder_name, visit_type, visit_date, chief_complaint, diagnosis')
|
||||
.order('visit_date', { ascending: false })
|
||||
.executeAs<MedicalRecord[]>()
|
||||
if (result.error == null && result.data != null) {
|
||||
records.value = result.data
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载病历失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const getVisitTypeText = (type: string): string => {
|
||||
const typeMap = new Map([
|
||||
['routine', '常规'],
|
||||
['emergency', '急诊'],
|
||||
['consultation', '会诊'],
|
||||
['follow_up', '复诊']
|
||||
])
|
||||
return typeMap.get(type) ?? type
|
||||
}
|
||||
|
||||
const openRecord = (record: MedicalRecord) => {
|
||||
uni.navigateTo({ url: `/pages/ec/doctor/medical-record-detail?id=${record.id}` })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.medical-records {
|
||||
padding: 20px;
|
||||
background-color: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
}
|
||||
.header {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.header-title {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.records-list {
|
||||
background-color: #fff;
|
||||
border-radius: 10px;
|
||||
}
|
||||
.record-item {
|
||||
padding: 14px 10px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
.record-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.record-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.patient-name {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
.visit-date {
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
}
|
||||
.record-content {
|
||||
font-size: 13px;
|
||||
color: #555;
|
||||
}
|
||||
.visit-type, .chief-complaint, .diagnosis {
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.empty-state {
|
||||
padding: 30px 0;
|
||||
text-align: center;
|
||||
}
|
||||
.empty-text {
|
||||
font-size: 13px;
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
28
pages/ec/doctor/medication-management.uvue
Normal file
28
pages/ec/doctor/medication-management.uvue
Normal file
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<view class="medication-management-page">
|
||||
<text class="title">用药管理</text>
|
||||
<view class="empty">暂无数据</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
// 用药管理页面骨架
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.medication-management-page {
|
||||
padding: 30px;
|
||||
background: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
}
|
||||
.title {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.empty {
|
||||
color: #aaa;
|
||||
text-align: center;
|
||||
margin-top: 60px;
|
||||
}
|
||||
</style>
|
||||
163
pages/ec/doctor/patient-queue.uvue
Normal file
163
pages/ec/doctor/patient-queue.uvue
Normal file
@@ -0,0 +1,163 @@
|
||||
<template>
|
||||
<view class="patient-queue">
|
||||
<view class="header">
|
||||
<text class="header-title">今日患者队列</text>
|
||||
</view>
|
||||
<scroll-view class="queue-list" scroll-y="true" :style="{ height: '600px' }">
|
||||
<view
|
||||
v-for="appointment in queue"
|
||||
:key="appointment.id"
|
||||
class="queue-item"
|
||||
:class="{ 'current': isCurrent(appointment), 'completed': appointment.status === 'completed' }"
|
||||
@click="openAppointment(appointment)"
|
||||
>
|
||||
<view class="queue-time">
|
||||
<text class="time-text">{{ formatTime(appointment.scheduled_time) }}</text>
|
||||
<text class="status-text" :class="appointment.status">{{ getStatusText(appointment.status) }}</text>
|
||||
</view>
|
||||
<view class="queue-details">
|
||||
<text class="patient-name">{{ appointment.elder_name }}</text>
|
||||
<text class="room-info" v-if="appointment.room_number">房间: {{ appointment.room_number }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="queue.length === 0" class="empty-state">
|
||||
<text class="empty-text">今日暂无预约</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import supa from '@/components/supadb/aksupainstance.uts'
|
||||
import { formatTime, getTodayStart, getTodayEnd } from '../types_new.uts'
|
||||
|
||||
type Appointment = {
|
||||
id: string
|
||||
elder_id: string
|
||||
elder_name: string
|
||||
scheduled_time: string
|
||||
status: string
|
||||
room_number: string
|
||||
}
|
||||
|
||||
const queue = ref<Array<Appointment>>([])
|
||||
|
||||
onMounted(() => {
|
||||
loadQueue()
|
||||
})
|
||||
|
||||
const loadQueue = async () => {
|
||||
try {
|
||||
const result = await supa
|
||||
.from('ec_appointments')
|
||||
.select('id, elder_id, elder_name, scheduled_time, status, room_number')
|
||||
.gte('scheduled_time', getTodayStart())
|
||||
.lte('scheduled_time', getTodayEnd())
|
||||
.order('scheduled_time', { ascending: true })
|
||||
.executeAs<Appointment[]>()
|
||||
if (result.error == null && result.data != null) {
|
||||
queue.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 isCurrent = (appointment: Appointment): boolean => {
|
||||
const now = new Date()
|
||||
const t = new Date(appointment.scheduled_time)
|
||||
return Math.abs(now.getTime() - t.getTime()) <= 30 * 60 * 1000 && appointment.status === 'in_progress'
|
||||
}
|
||||
|
||||
const openAppointment = (appointment: Appointment) => {
|
||||
uni.navigateTo({ url: `/pages/ec/doctor/appointment-detail?id=${appointment.id}` })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.patient-queue {
|
||||
padding: 20px;
|
||||
background-color: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
}
|
||||
.header {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.header-title {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.queue-list {
|
||||
background-color: #fff;
|
||||
border-radius: 10px;
|
||||
}
|
||||
.queue-item {
|
||||
padding: 14px 10px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
}
|
||||
.queue-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.queue-item.current {
|
||||
background-color: #e8f5e8;
|
||||
border-left: 4px solid #4caf50;
|
||||
}
|
||||
.queue-item.completed {
|
||||
opacity: 0.6;
|
||||
}
|
||||
.queue-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; }
|
||||
.queue-details {
|
||||
flex: 1;
|
||||
margin-left: 10px;
|
||||
}
|
||||
.patient-name {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
.room-info {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin-top: 4px;
|
||||
}
|
||||
.empty-state {
|
||||
padding: 30px 0;
|
||||
text-align: center;
|
||||
}
|
||||
.empty-text {
|
||||
font-size: 13px;
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
136
pages/ec/doctor/prescriptions.uvue
Normal file
136
pages/ec/doctor/prescriptions.uvue
Normal file
@@ -0,0 +1,136 @@
|
||||
<template>
|
||||
<view class="prescriptions">
|
||||
<view class="header">
|
||||
<text class="header-title">处方管理</text>
|
||||
</view>
|
||||
<scroll-view class="prescription-list" scroll-y="true" :style="{ height: '600px' }">
|
||||
<view
|
||||
v-for="item in prescriptions"
|
||||
:key="item.id"
|
||||
class="prescription-item"
|
||||
@click="openPrescription(item)"
|
||||
>
|
||||
<view class="prescription-header">
|
||||
<text class="patient-name">{{ item.elder_name }}</text>
|
||||
<text class="medication-name">{{ item.medication_name }}</text>
|
||||
</view>
|
||||
<view class="prescription-content">
|
||||
<text class="dosage">剂量: {{ item.dosage || '未填写' }}</text>
|
||||
<text class="status">状态: {{ getStatusText(item.status) }}</text>
|
||||
<text class="date-range">{{ item.start_date }} ~ {{ item.end_date || '...' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="prescriptions.length === 0" class="empty-state">
|
||||
<text class="empty-text">暂无处方记录</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import supa from '@/components/supadb/aksupainstance.uts'
|
||||
|
||||
type Prescription = {
|
||||
id: string
|
||||
elder_id: string
|
||||
elder_name: string
|
||||
medication_name: string
|
||||
dosage: string
|
||||
status: string
|
||||
start_date: string
|
||||
end_date: string
|
||||
}
|
||||
|
||||
const prescriptions = ref<Array<Prescription>>([])
|
||||
|
||||
onMounted(() => {
|
||||
loadPrescriptions()
|
||||
})
|
||||
|
||||
const loadPrescriptions = async () => {
|
||||
try {
|
||||
const result = await supa
|
||||
.from('ec_medications')
|
||||
.select('id, elder_id, elder_name, medication_name, dosage, status, start_date, end_date')
|
||||
.order('created_at', { ascending: false })
|
||||
.executeAs<Prescription[]>()
|
||||
if (result.error == null && result.data != null) {
|
||||
prescriptions.value = result.data
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载处方失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const getStatusText = (status: string): string => {
|
||||
const statusMap = new Map([
|
||||
['active', '进行中'],
|
||||
['completed', '已完成'],
|
||||
['discontinued', '已停用']
|
||||
])
|
||||
return statusMap.get(status) ?? status
|
||||
}
|
||||
|
||||
const openPrescription = (item: Prescription) => {
|
||||
uni.navigateTo({ url: `/pages/ec/doctor/prescription-detail?id=${item.id}` })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.prescriptions {
|
||||
padding: 20px;
|
||||
background-color: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
}
|
||||
.header {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.header-title {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.prescription-list {
|
||||
background-color: #fff;
|
||||
border-radius: 10px;
|
||||
}
|
||||
.prescription-item {
|
||||
padding: 14px 10px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
.prescription-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.prescription-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.patient-name {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
.medication-name {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
.prescription-content {
|
||||
font-size: 13px;
|
||||
color: #555;
|
||||
}
|
||||
.dosage, .status, .date-range {
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.empty-state {
|
||||
padding: 30px 0;
|
||||
text-align: center;
|
||||
}
|
||||
.empty-text {
|
||||
font-size: 13px;
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
28
pages/ec/doctor/schedule.uvue
Normal file
28
pages/ec/doctor/schedule.uvue
Normal file
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<view class="schedule-page">
|
||||
<text class="title">今日日程</text>
|
||||
<view class="empty">暂无数据</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
// 日程页面骨架
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.schedule-page {
|
||||
padding: 30px;
|
||||
background: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
}
|
||||
.title {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.empty {
|
||||
color: #aaa;
|
||||
text-align: center;
|
||||
margin-top: 60px;
|
||||
}
|
||||
</style>
|
||||
28
pages/ec/doctor/vital-signs.uvue
Normal file
28
pages/ec/doctor/vital-signs.uvue
Normal file
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<view class="vital-signs-page">
|
||||
<text class="title">生命体征</text>
|
||||
<view class="empty">暂无数据</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
// 生命体征页面骨架
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.vital-signs-page {
|
||||
padding: 30px;
|
||||
background: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
}
|
||||
.title {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.empty {
|
||||
color: #aaa;
|
||||
text-align: center;
|
||||
margin-top: 60px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user