fix(mobile): 修复移动端核心功能问题

- 新增 getPatientList API 调用正确的患者列表接口
- PatientDetail: Promise.all 并发加载患者信息/医嘱/体征/评估
- 所有页面添加 loading 状态和 ElMessage 错误提示
- 任务完成添加 ElMessageBox 确认对话框
- TaskList 添加刷新按钮
- Mine 退出登录添加确认对话框
This commit is contained in:
2026-06-19 12:44:43 +08:00
parent 05332ce2d9
commit 38bc99ee14
7 changed files with 158 additions and 59 deletions

View File

@@ -19,7 +19,9 @@ request.interceptors.response.use(
export const nursingApi = {
getTasks: (params) => request.get('/mp/nursing/tasks', { params }),
completeTask: (id, data) => request.post(`/mp/nursing/tasks/${id}/complete`, data),
getPatientList: (params) => request.get('/mp/nursing/patient/list', { params }),
getPatientInfo: (id) => request.get(`/mp/nursing/patient/${id}`),
getOrders: (patientId) => request.get(`/mp/nursing/orders/${patientId}`),
getVitalSigns: (patientId) => request.get(`/mp/nursing/vital-signs/${patientId}`),
submitVitalSign: (data) => request.post('/mp/nursing/vital-sign', data),
getAssessments: (patientId) => request.get(`/mp/nursing/assessments/${patientId}`),

View File

@@ -61,11 +61,19 @@ const riskLevel = computed(() => {
})
const riskLevelText = computed(() => ({ HIGH: '高风险', MEDIUM: '中风险', LOW: '低风险' }[riskLevel.value]))
const loading = ref(false)
const submit = async () => {
loading.value = true
try {
await nursingApi.submitAssessment({ patientId: route.params.patientId, assessmentType: selectedType.value, totalScore: totalScore.value, riskLevel: riskLevel.value, detail: JSON.stringify(formData.value) })
ElMessage.success('评估提交成功')
} catch (e) { ElMessage.error('提交失败') }
} catch (e) {
console.error(e)
ElMessage.error('提交失败')
} finally {
loading.value = false
}
}
</script>

View File

@@ -17,7 +17,15 @@
</template>
<script setup>
const logout = () => { localStorage.removeItem('token'); window.location.href = '/login' }
import { ElMessageBox } from 'element-plus'
const logout = async () => {
try {
await ElMessageBox.confirm('确认退出登录?', '提示', { confirmButtonText: '确认', cancelButtonText: '取消' })
localStorage.removeItem('token')
window.location.href = '/login'
} catch {}
}
</script>
<style scoped>

View File

@@ -1,40 +1,44 @@
<template>
<div class="patient-detail">
<div class="patient-header">
<div class="avatar">{{ patient.name?.charAt(0) }}</div>
<div class="info">
<div class="name">{{ patient.name }} <span class="bed">{{ patient.bedNo }}</span></div>
<div class="diag">{{ patient.diagnosis }}</div>
</div>
</div>
<div class="tabs">
<div v-for="tab in tabs" :key="tab.key" class="tab" :class="{ active: activeTab === tab.key }" @click="activeTab = tab.key">{{ tab.label }}</div>
</div>
<div class="tab-content">
<div v-if="activeTab === 'orders'">
<div v-for="order in orders" :key="order.id" class="order-item">
<div class="order-name">{{ order.orderName }}</div>
<div class="order-dose">{{ order.dosage }} {{ order.frequency }}</div>
<button v-if="order.status === 'PENDING'" class="exec-btn" @click="executeOrder(order)">执行</button>
<div v-if="loading" class="empty">加载中...</div>
<template v-else>
<div class="patient-header">
<div class="avatar">{{ patient.name?.charAt(0) }}</div>
<div class="info">
<div class="name">{{ patient.name }} <span class="bed">{{ patient.bedNo }}</span></div>
<div class="diag">{{ patient.diagnosis }}</div>
</div>
</div>
<div v-if="activeTab === 'vitals'">
<div class="vital-grid">
<div class="vital-item" v-for="v in latestVitals" :key="v.key">
<div class="vital-value" :class="v.status">{{ v.value }}</div>
<div class="vital-label">{{ v.label }}</div>
<div class="tabs">
<div v-for="tab in tabs" :key="tab.key" class="tab" :class="{ active: activeTab === tab.key }" @click="activeTab = tab.key">{{ tab.label }}</div>
</div>
<div class="tab-content">
<div v-if="activeTab === 'orders'">
<div v-for="order in orders" :key="order.id" class="order-item">
<div class="order-name">{{ order.orderName }}</div>
<div class="order-dose">{{ order.dosage }} {{ order.frequency }}</div>
<button v-if="order.status === 'PENDING'" class="exec-btn" @click="executeOrder(order)">执行</button>
</div>
<div v-if="orders.length === 0" class="empty">暂无医嘱</div>
</div>
<button class="add-btn" @click="$router.push(`/mobile/vital-entry/${$route.params.id}`)">录入体征</button>
</div>
<div v-if="activeTab === 'assessments'">
<div v-for="a in assessments" :key="a.id" class="assess-item">
<div class="assess-type">{{ a.assessmentType }}</div>
<div class="assess-score">评分: {{ a.totalScore }} <span :class="'risk-' + a.riskLevel">{{ a.riskLevel }}</span></div>
<div v-if="activeTab === 'vitals'">
<div class="vital-grid">
<div class="vital-item" v-for="v in latestVitals" :key="v.key">
<div class="vital-value" :class="v.status">{{ v.value }}</div>
<div class="vital-label">{{ v.label }}</div>
</div>
</div>
<button class="add-btn" @click="$router.push(`/mobile/vital-entry/${$route.params.id}`)">录入体征</button>
</div>
<div v-if="activeTab === 'assessments'">
<div v-for="a in assessments" :key="a.id" class="assess-item">
<div class="assess-type">{{ a.assessmentType }}</div>
<div class="assess-score">评分: {{ a.totalScore }} <span :class="'risk-' + a.riskLevel">{{ a.riskLevel }}</span></div>
</div>
<button class="add-btn" @click="$router.push(`/mobile/assessment/${$route.params.id}`)">新建评估</button>
</div>
<button class="add-btn" @click="$router.push(`/mobile/assessment/${$route.params.id}`)">新建评估</button>
</div>
</div>
</template>
</div>
</template>
@@ -42,6 +46,7 @@
import { ref, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import { nursingApi } from '../api'
import { ElMessage } from 'element-plus'
const route = useRoute()
const patient = ref({})
@@ -49,18 +54,40 @@ const orders = ref([])
const latestVitals = ref([])
const assessments = ref([])
const activeTab = ref('orders')
const loading = ref(false)
const tabs = [{ key: 'orders', label: '医嘱' }, { key: 'vitals', label: '体征' }, { key: 'assessments', label: '评估' }]
onMounted(async () => {
const id = route.params.id
loading.value = true
try {
const res = await nursingApi.getPatientInfo(id)
patient.value = res.data || {}
} catch (e) { console.error(e) }
const [patientRes, ordersRes, vitalsRes, assessRes] = await Promise.all([
nursingApi.getPatientInfo(id),
nursingApi.getOrders(id),
nursingApi.getVitalSigns(id),
nursingApi.getAssessments(id)
])
patient.value = patientRes.data || {}
orders.value = ordersRes.data || []
latestVitals.value = vitalsRes.data || []
assessments.value = assessRes.data || []
} catch (e) {
console.error(e)
ElMessage.error('加载患者数据失败')
} finally {
loading.value = false
}
})
const executeOrder = async (order) => {
try { await nursingApi.completeTask(order.id, { result: '执行完成' }); order.status = 'COMPLETED' } catch (e) { console.error(e) }
try {
await nursingApi.completeTask(order.id, { result: '执行完成' })
order.status = 'COMPLETED'
ElMessage.success('医嘱执行成功')
} catch (e) {
console.error(e)
ElMessage.error('医嘱执行失败')
}
}
</script>

View File

@@ -3,33 +3,47 @@
<div class="search-bar">
<input v-model="searchText" placeholder="搜索患者姓名/床号..." class="search-input" />
</div>
<div v-for="p in filteredPatients" :key="p.id" class="patient-card" @click="$router.push(`/mobile/patient-detail/${p.id}`)">
<div class="patient-avatar">{{ p.name?.charAt(0) }}</div>
<div class="patient-info">
<div class="patient-name">{{ p.name }} <span class="bed-no">{{ p.bedNo }}</span></div>
<div class="patient-diag">{{ p.diagnosis || '暂无诊断' }}</div>
<div class="patient-level">
<span :class="'level-' + p.nursingLevel">{{ p.nursingLevel }}级护理</span>
<div v-if="loading" class="empty">加载中...</div>
<template v-else>
<div v-for="p in filteredPatients" :key="p.id" class="patient-card" @click="$router.push(`/mobile/patient-detail/${p.id}`)">
<div class="patient-avatar">{{ p.name?.charAt(0) }}</div>
<div class="patient-info">
<div class="patient-name">{{ p.name }} <span class="bed-no">{{ p.bedNo }}</span></div>
<div class="patient-diag">{{ p.diagnosis || '暂无诊断' }}</div>
<div class="patient-level">
<span :class="'level-' + p.nursingLevel">{{ p.nursingLevel }}级护理</span>
</div>
</div>
</div>
</div>
<div v-if="filteredPatients.length === 0" class="empty">暂无患者</div>
<div v-if="filteredPatients.length === 0" class="empty">暂无患者</div>
</template>
</div>
</template>
<script setup>
import { ref, onMounted, computed } from 'vue'
import { nursingApi } from '../api'
import { ElMessage } from 'element-plus'
const patients = ref([])
const searchText = ref('')
const loading = ref(false)
const filteredPatients = computed(() => {
if (!searchText.value) return patients.value
return patients.value.filter(p => p.name?.includes(searchText.value) || p.bedNo?.includes(searchText.value))
})
onMounted(async () => {
try { const res = await nursingApi.getPatientInfo('list'); patients.value = res.data || [] } catch (e) { console.error(e) }
loading.value = true
try {
const res = await nursingApi.getPatientList()
patients.value = res.data || []
} catch (e) {
console.error(e)
ElMessage.error('加载患者列表失败')
} finally {
loading.value = false
}
})
</script>

View File

@@ -1,25 +1,34 @@
<template>
<div class="task-list">
<div v-for="(group, type) in groupedTasks" :key="type" class="task-group">
<div class="group-header">{{ type }}</div>
<div v-for="task in group" :key="task.id" class="task-card" @touchstart="swipeStart" @touchend="swipeEnd($event, task)">
<div class="task-info">
<div class="task-patient">{{ task.patientName }} - {{ task.bedNo }}</div>
<div class="task-content">{{ task.taskContent }}</div>
<div class="task-time">{{ task.dueTime }}</div>
</div>
<div class="task-status" :class="task.taskStatus">{{ statusText(task.taskStatus) }}</div>
</div>
<div class="task-header">
<h3>护理任务</h3>
<button class="refresh-btn" @click="loadTasks" :disabled="loading">{{ loading ? '刷新中...' : '刷新' }}</button>
</div>
<div v-if="tasks.length === 0" class="empty">暂无任务</div>
<div v-if="loading" class="empty">加载中...</div>
<template v-else>
<div v-for="(group, type) in groupedTasks" :key="type" class="task-group">
<div class="group-header">{{ type }}</div>
<div v-for="task in group" :key="task.id" class="task-card" @touchstart="swipeStart" @touchend="swipeEnd($event, task)">
<div class="task-info">
<div class="task-patient">{{ task.patientName }} - {{ task.bedNo }}</div>
<div class="task-content">{{ task.taskContent }}</div>
<div class="task-time">{{ task.dueTime }}</div>
</div>
<div class="task-status" :class="task.taskStatus">{{ statusText(task.taskStatus) }}</div>
</div>
</div>
<div v-if="tasks.length === 0" class="empty">暂无任务</div>
</template>
</div>
</template>
<script setup>
import { ref, onMounted, computed } from 'vue'
import { nursingApi } from '../api'
import { ElMessage, ElMessageBox } from 'element-plus'
const tasks = ref([])
const loading = ref(false)
const groupedTasks = computed(() => {
const groups = {}
tasks.value.forEach(t => {
@@ -33,7 +42,16 @@ const groupedTasks = computed(() => {
const statusText = (s) => ({ PENDING: '待完成', IN_PROGRESS: '进行中', COMPLETED: '已完成' }[s] || s)
const loadTasks = async () => {
try { const res = await nursingApi.getTasks({ status: 'PENDING' }); tasks.value = res.data || [] } catch (e) { console.error(e) }
loading.value = true
try {
const res = await nursingApi.getTasks({ status: 'PENDING' })
tasks.value = res.data || []
} catch (e) {
console.error(e)
ElMessage.error('加载任务失败')
} finally {
loading.value = false
}
}
let startX = 0
@@ -41,7 +59,17 @@ const swipeStart = (e) => { startX = e.touches[0].clientX }
const swipeEnd = async (e, task) => {
const diff = startX - e.changedTouches[0].clientX
if (diff > 80 && task.taskStatus === 'PENDING') {
try { await nursingApi.completeTask(task.id, { result: '完成' }); task.taskStatus = 'COMPLETED' } catch (e) { console.error(e) }
try {
await ElMessageBox.confirm('确认完成此任务?', '提示', { confirmButtonText: '确认', cancelButtonText: '取消' })
await nursingApi.completeTask(task.id, { result: '完成' })
task.taskStatus = 'COMPLETED'
ElMessage.success('任务已完成')
} catch (e) {
if (e !== 'cancel') {
console.error(e)
ElMessage.error('任务完成失败')
}
}
}
}
@@ -50,6 +78,10 @@ onMounted(loadTasks)
<style scoped>
.task-list { padding: 0; }
.task-header { display: flex; justify-content: space-between; align-items: center; padding: 8px 0; }
.task-header h3 { margin: 0; font-size: 16px; }
.refresh-btn { background: #1890ff; color: #fff; border: none; padding: 6px 16px; border-radius: 4px; font-size: 14px; }
.refresh-btn:disabled { background: #ccc; }
.group-header { padding: 8px 16px; font-size: 14px; color: #666; background: #f0f0f0; margin: 8px 0; border-radius: 4px; }
.task-card { background: #fff; border-radius: 8px; padding: 12px 16px; margin-bottom: 8px; display: flex; justify-content: space-between; align-items: center; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
.task-patient { font-weight: 600; font-size: 16px; }

View File

@@ -45,11 +45,19 @@ const painLabel = computed(() => {
return '重度疼痛'
})
const loading = ref(false)
const submit = async () => {
loading.value = true
try {
await nursingApi.submitVitalSign({ ...formData.value, patientId: route.params.patientId })
ElMessage.success('提交成功')
} catch (e) { ElMessage.error('提交失败') }
} catch (e) {
console.error(e)
ElMessage.error('提交失败')
} finally {
loading.value = false
}
}
</script>