fix(mobile): 修复移动端核心功能问题
- 新增 getPatientList API 调用正确的患者列表接口 - PatientDetail: Promise.all 并发加载患者信息/医嘱/体征/评估 - 所有页面添加 loading 状态和 ElMessage 错误提示 - 任务完成添加 ElMessageBox 确认对话框 - TaskList 添加刷新按钮 - Mine 退出登录添加确认对话框
This commit is contained in:
@@ -19,7 +19,9 @@ request.interceptors.response.use(
|
|||||||
export const nursingApi = {
|
export const nursingApi = {
|
||||||
getTasks: (params) => request.get('/mp/nursing/tasks', { params }),
|
getTasks: (params) => request.get('/mp/nursing/tasks', { params }),
|
||||||
completeTask: (id, data) => request.post(`/mp/nursing/tasks/${id}/complete`, data),
|
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}`),
|
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}`),
|
getVitalSigns: (patientId) => request.get(`/mp/nursing/vital-signs/${patientId}`),
|
||||||
submitVitalSign: (data) => request.post('/mp/nursing/vital-sign', data),
|
submitVitalSign: (data) => request.post('/mp/nursing/vital-sign', data),
|
||||||
getAssessments: (patientId) => request.get(`/mp/nursing/assessments/${patientId}`),
|
getAssessments: (patientId) => request.get(`/mp/nursing/assessments/${patientId}`),
|
||||||
|
|||||||
@@ -61,11 +61,19 @@ const riskLevel = computed(() => {
|
|||||||
})
|
})
|
||||||
const riskLevelText = computed(() => ({ HIGH: '高风险', MEDIUM: '中风险', LOW: '低风险' }[riskLevel.value]))
|
const riskLevelText = computed(() => ({ HIGH: '高风险', MEDIUM: '中风险', LOW: '低风险' }[riskLevel.value]))
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
const submit = async () => {
|
const submit = async () => {
|
||||||
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
await nursingApi.submitAssessment({ patientId: route.params.patientId, assessmentType: selectedType.value, totalScore: totalScore.value, riskLevel: riskLevel.value, detail: JSON.stringify(formData.value) })
|
await nursingApi.submitAssessment({ patientId: route.params.patientId, assessmentType: selectedType.value, totalScore: totalScore.value, riskLevel: riskLevel.value, detail: JSON.stringify(formData.value) })
|
||||||
ElMessage.success('评估提交成功')
|
ElMessage.success('评估提交成功')
|
||||||
} catch (e) { ElMessage.error('提交失败') }
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
ElMessage.error('提交失败')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,15 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<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>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -1,40 +1,44 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="patient-detail">
|
<div class="patient-detail">
|
||||||
<div class="patient-header">
|
<div v-if="loading" class="empty">加载中...</div>
|
||||||
<div class="avatar">{{ patient.name?.charAt(0) }}</div>
|
<template v-else>
|
||||||
<div class="info">
|
<div class="patient-header">
|
||||||
<div class="name">{{ patient.name }} <span class="bed">{{ patient.bedNo }}床</span></div>
|
<div class="avatar">{{ patient.name?.charAt(0) }}</div>
|
||||||
<div class="diag">{{ patient.diagnosis }}</div>
|
<div class="info">
|
||||||
</div>
|
<div class="name">{{ patient.name }} <span class="bed">{{ patient.bedNo }}床</span></div>
|
||||||
</div>
|
<div class="diag">{{ patient.diagnosis }}</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>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="activeTab === 'vitals'">
|
<div class="tabs">
|
||||||
<div class="vital-grid">
|
<div v-for="tab in tabs" :key="tab.key" class="tab" :class="{ active: activeTab === tab.key }" @click="activeTab = tab.key">{{ tab.label }}</div>
|
||||||
<div class="vital-item" v-for="v in latestVitals" :key="v.key">
|
</div>
|
||||||
<div class="vital-value" :class="v.status">{{ v.value }}</div>
|
<div class="tab-content">
|
||||||
<div class="vital-label">{{ v.label }}</div>
|
<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>
|
||||||
|
<div v-if="orders.length === 0" class="empty">暂无医嘱</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="add-btn" @click="$router.push(`/mobile/vital-entry/${$route.params.id}`)">录入体征</button>
|
<div v-if="activeTab === 'vitals'">
|
||||||
</div>
|
<div class="vital-grid">
|
||||||
<div v-if="activeTab === 'assessments'">
|
<div class="vital-item" v-for="v in latestVitals" :key="v.key">
|
||||||
<div v-for="a in assessments" :key="a.id" class="assess-item">
|
<div class="vital-value" :class="v.status">{{ v.value }}</div>
|
||||||
<div class="assess-type">{{ a.assessmentType }}</div>
|
<div class="vital-label">{{ v.label }}</div>
|
||||||
<div class="assess-score">评分: {{ a.totalScore }} <span :class="'risk-' + a.riskLevel">{{ a.riskLevel }}</span></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>
|
</div>
|
||||||
<button class="add-btn" @click="$router.push(`/mobile/assessment/${$route.params.id}`)">新建评估</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -42,6 +46,7 @@
|
|||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import { nursingApi } from '../api'
|
import { nursingApi } from '../api'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const patient = ref({})
|
const patient = ref({})
|
||||||
@@ -49,18 +54,40 @@ const orders = ref([])
|
|||||||
const latestVitals = ref([])
|
const latestVitals = ref([])
|
||||||
const assessments = ref([])
|
const assessments = ref([])
|
||||||
const activeTab = ref('orders')
|
const activeTab = ref('orders')
|
||||||
|
const loading = ref(false)
|
||||||
const tabs = [{ key: 'orders', label: '医嘱' }, { key: 'vitals', label: '体征' }, { key: 'assessments', label: '评估' }]
|
const tabs = [{ key: 'orders', label: '医嘱' }, { key: 'vitals', label: '体征' }, { key: 'assessments', label: '评估' }]
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const id = route.params.id
|
const id = route.params.id
|
||||||
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const res = await nursingApi.getPatientInfo(id)
|
const [patientRes, ordersRes, vitalsRes, assessRes] = await Promise.all([
|
||||||
patient.value = res.data || {}
|
nursingApi.getPatientInfo(id),
|
||||||
} catch (e) { console.error(e) }
|
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) => {
|
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>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -3,33 +3,47 @@
|
|||||||
<div class="search-bar">
|
<div class="search-bar">
|
||||||
<input v-model="searchText" placeholder="搜索患者姓名/床号..." class="search-input" />
|
<input v-model="searchText" placeholder="搜索患者姓名/床号..." class="search-input" />
|
||||||
</div>
|
</div>
|
||||||
<div v-for="p in filteredPatients" :key="p.id" class="patient-card" @click="$router.push(`/mobile/patient-detail/${p.id}`)">
|
<div v-if="loading" class="empty">加载中...</div>
|
||||||
<div class="patient-avatar">{{ p.name?.charAt(0) }}</div>
|
<template v-else>
|
||||||
<div class="patient-info">
|
<div v-for="p in filteredPatients" :key="p.id" class="patient-card" @click="$router.push(`/mobile/patient-detail/${p.id}`)">
|
||||||
<div class="patient-name">{{ p.name }} <span class="bed-no">{{ p.bedNo }}</span></div>
|
<div class="patient-avatar">{{ p.name?.charAt(0) }}</div>
|
||||||
<div class="patient-diag">{{ p.diagnosis || '暂无诊断' }}</div>
|
<div class="patient-info">
|
||||||
<div class="patient-level">
|
<div class="patient-name">{{ p.name }} <span class="bed-no">{{ p.bedNo }}</span></div>
|
||||||
<span :class="'level-' + p.nursingLevel">{{ p.nursingLevel }}级护理</span>
|
<div class="patient-diag">{{ p.diagnosis || '暂无诊断' }}</div>
|
||||||
|
<div class="patient-level">
|
||||||
|
<span :class="'level-' + p.nursingLevel">{{ p.nursingLevel }}级护理</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div v-if="filteredPatients.length === 0" class="empty">暂无患者</div>
|
||||||
<div v-if="filteredPatients.length === 0" class="empty">暂无患者</div>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, computed } from 'vue'
|
import { ref, onMounted, computed } from 'vue'
|
||||||
import { nursingApi } from '../api'
|
import { nursingApi } from '../api'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
|
||||||
const patients = ref([])
|
const patients = ref([])
|
||||||
const searchText = ref('')
|
const searchText = ref('')
|
||||||
|
const loading = ref(false)
|
||||||
const filteredPatients = computed(() => {
|
const filteredPatients = computed(() => {
|
||||||
if (!searchText.value) return patients.value
|
if (!searchText.value) return patients.value
|
||||||
return patients.value.filter(p => p.name?.includes(searchText.value) || p.bedNo?.includes(searchText.value))
|
return patients.value.filter(p => p.name?.includes(searchText.value) || p.bedNo?.includes(searchText.value))
|
||||||
})
|
})
|
||||||
|
|
||||||
onMounted(async () => {
|
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>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,25 +1,34 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="task-list">
|
<div class="task-list">
|
||||||
<div v-for="(group, type) in groupedTasks" :key="type" class="task-group">
|
<div class="task-header">
|
||||||
<div class="group-header">{{ type }}</div>
|
<h3>护理任务</h3>
|
||||||
<div v-for="task in group" :key="task.id" class="task-card" @touchstart="swipeStart" @touchend="swipeEnd($event, task)">
|
<button class="refresh-btn" @click="loadTasks" :disabled="loading">{{ loading ? '刷新中...' : '刷新' }}</button>
|
||||||
<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>
|
||||||
<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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, computed } from 'vue'
|
import { ref, onMounted, computed } from 'vue'
|
||||||
import { nursingApi } from '../api'
|
import { nursingApi } from '../api'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
|
||||||
const tasks = ref([])
|
const tasks = ref([])
|
||||||
|
const loading = ref(false)
|
||||||
const groupedTasks = computed(() => {
|
const groupedTasks = computed(() => {
|
||||||
const groups = {}
|
const groups = {}
|
||||||
tasks.value.forEach(t => {
|
tasks.value.forEach(t => {
|
||||||
@@ -33,7 +42,16 @@ const groupedTasks = computed(() => {
|
|||||||
const statusText = (s) => ({ PENDING: '待完成', IN_PROGRESS: '进行中', COMPLETED: '已完成' }[s] || s)
|
const statusText = (s) => ({ PENDING: '待完成', IN_PROGRESS: '进行中', COMPLETED: '已完成' }[s] || s)
|
||||||
|
|
||||||
const loadTasks = async () => {
|
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
|
let startX = 0
|
||||||
@@ -41,7 +59,17 @@ const swipeStart = (e) => { startX = e.touches[0].clientX }
|
|||||||
const swipeEnd = async (e, task) => {
|
const swipeEnd = async (e, task) => {
|
||||||
const diff = startX - e.changedTouches[0].clientX
|
const diff = startX - e.changedTouches[0].clientX
|
||||||
if (diff > 80 && task.taskStatus === 'PENDING') {
|
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>
|
<style scoped>
|
||||||
.task-list { padding: 0; }
|
.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; }
|
.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-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; }
|
.task-patient { font-weight: 600; font-size: 16px; }
|
||||||
|
|||||||
@@ -45,11 +45,19 @@ const painLabel = computed(() => {
|
|||||||
return '重度疼痛'
|
return '重度疼痛'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
const submit = async () => {
|
const submit = async () => {
|
||||||
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
await nursingApi.submitVitalSign({ ...formData.value, patientId: route.params.patientId })
|
await nursingApi.submitVitalSign({ ...formData.value, patientId: route.params.patientId })
|
||||||
ElMessage.success('提交成功')
|
ElMessage.success('提交成功')
|
||||||
} catch (e) { ElMessage.error('提交失败') }
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
ElMessage.error('提交失败')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user