Files
xk-doctor-wx/subPackages/sub_workbench/workbench_infoPatient.vue

711 lines
23 KiB
Vue

<template>
<view class="page-bg safe-padding">
<!-- 状态头部 (Modern Banner) -->
<view class="status-banner"
:class="infoList.status==1&&'bg_blue'||infoList.status==2&&'bg_green'||infoList.status==3&&'bg_purple'||infoList.status==7&&'bg_red'">
<view class="status-content flex-row flex-jus-sp flex-ali-center">
<text class="fs-36 font-bold text-white">{{infoList.status==1&&'待接诊'||infoList.status==2&&'已接诊'||infoList.status==3&&'已完成'||infoList.status==7&&'已拒诊'}}</text>
<image class="status-icon"
:src="infoList.status==1&&'/static/info/djz.png'||infoList.status==2&&'/static/info/yjz.png'||infoList.status==3&&'/static/info/ywc.png'||infoList.status==7&&'/static/info/jz.png'"
mode="aspectFit"></image>
</view>
</view>
<!-- 内容区域 -->
<view class="content-container">
<!-- 就诊人信息 -->
<view class="modern-card">
<view class="card-title">就诊人</view>
<view class="info-row">
<text class="info-label">姓名</text>
<text class="info-value font-bold">{{infoList.patient.name}}</text>
</view>
<view class="info-row">
<text class="info-label">性别</text>
<text class="info-value">{{infoList.patient.sex%2==0?'女':'男'}}</text>
</view>
<view class="info-row">
<text class="info-label">年龄</text>
<text class="info-value">{{infoList.patient.age}}</text>
</view>
<view class="info-row">
<text class="info-label">手机号</text>
<text class="info-value">{{infoList.patient.mobile}}</text>
</view>
<view class="info-row">
<text class="info-label">身份证号</text>
<text class="info-value">{{infoList.patient.id_card}}</text>
</view>
</view>
<!-- 挂号信息 -->
<view class="modern-card">
<view class="card-title">挂号信息</view>
<view class="info-row">
<text class="info-label">就诊时间</text>
<text class="info-value">{{ $u.timeFormat(infoList.created_at, 'yyyy-mm-dd')}}</text>
</view>
<view class="info-row">
<text class="info-label">就诊诊所</text>
<text class="info-value">{{ infoList.store.name}}</text>
</view>
<view class="info-row">
<text class="info-label">科室</text>
<text class="info-value">{{infoList.depart.name}}</text>
</view>
<view class="info-row">
<text class="info-label">医生</text>
<text class="info-value">{{infoList.doctor.name}}</text>
</view>
<view class="info-row">
<text class="info-label">挂号费用</text>
<text class="info-value error-text font-bold">{{infoList.price}}</text>
</view>
<view class="info-row">
<text class="info-label">挂号单号</text>
<text class="info-value">{{infoList.order_no}}</text>
</view>
</view>
<!-- 健康信息 -->
<view class="modern-card">
<view class="card-title">健康信息</view>
<view class="info-row">
<text class="info-label">既往史</text>
<text class="info-value">{{infoList.healthInquery.person_status==0?'无':infoList.healthInquery.person_history}}</text>
</view>
<view class="info-row">
<text class="info-label">过敏史</text>
<text class="info-value" :class="{'error-text font-bold': infoList.healthInquery.allergic_status!=0}">{{infoList.healthInquery.allergic_status==0?'无':infoList.healthInquery.allergic_history}}</text>
</view>
<view class="info-row">
<text class="info-label">家族遗传史</text>
<text class="info-value">{{infoList.healthInquery.family_status==0?'无':infoList.healthInquery.family_history}}</text>
</view>
<view class="info-row">
<text class="info-label">肝功能异常</text>
<text class="info-value" :class="{'error-text font-bold': infoList.healthInquery.liver_function!=0}">{{infoList.healthInquery.liver_function==0?'正常':'异常'}}</text>
</view>
<view class="info-row">
<text class="info-label">肾功能异常</text>
<text class="info-value" :class="{'error-text font-bold': infoList.healthInquery.renal_function!=0}">{{infoList.healthInquery.renal_function==0?'正常':'异常'}}</text>
</view>
</view>
<!-- 处方/拒诊信息 -->
<view class="modern-card">
<view class="card-title">{{ infoList.status==7?'拒诊原因' :'处方信息'}}</view>
<template v-if="infoList.status==7">
<view class="info-row">
<text class="info-label">原因</text>
<text class="info-value error-text font-bold">{{infoList.refuse_reason}}</text>
</view>
<view class="info-row">
<text class="info-label">拒诊时间</text>
<text class="info-value">{{$u.timeFormat(infoList.updated_at, 'yyyy-mm-dd')}}</text>
</view>
</template>
<template v-else>
<view class="m-b-24">
<u-tabs bar-width="40" :list="rxTabs" :is-scroll="false" :current="rxTabIndex" @change="onRxTabChange" active-color="#00A88A" inactive-color="#8A92A3" bg-color="transparent"></u-tabs>
</view>
<!-- 本次挂号 -->
<view v-if="rxTabIndex===0" class="rx-panel">
<view class="modern-rx-item" v-for="(item, rxIdx) in rxCurrentList" :key="item.id">
<view class="rx-header flex-row flex-jus-sp flex-ali-center">
<view class="rx-no-wrapper flex-row flex-ali-center">
<u-icon name="file-text" color="#00A88A" size="32" class="m-r-8"></u-icon>
<text class="rx-no font-bold color-title">{{item.prescription_no}}</text>
</view>
</view>
<view class="rx-actions flex-row flex-jus-end m-t-16">
<view v-if="item.can_withdraw" class="action-btn-ghost danger" @click="withdrawRxByIndex('current', rxIdx)">撤回</view>
<view class="action-btn-ghost primary m-l-16" @click="goToReusePrescription(item.id)">复用</view>
<view class="action-btn-ghost default m-l-16" @click="openPrescriptionDetail(item.id)">查看处方</view>
</view>
</view>
<view class="empty-state m-t-32" v-if="!rxLoadingMore && rxCurrentList.length===0">
<u-empty text="该患者并未开处方" mode="list"></u-empty>
</view>
<view class="load-tip flex-row flex-jus-center flex-ali-center" v-if="rxLoadingMore">
<u-loading mode="circle" color="#00A88A"></u-loading> <text class="m-l-8 fs-24 color-sub">加载中</text>
</view>
<view class="load-tip fs-24 color-sub" v-if="!rxCurrentHasMore && rxCurrentList.length>0">已加载全部</view>
</view>
<!-- 历史处方 -->
<view v-if="rxTabIndex===1" class="rx-panel">
<block v-for="(item, idx) in rxHistoryList" :key="item.id">
<view class="rx-timeline-divider flex-row flex-ali-center" v-if="historyShowDivider(idx)">
<view class="timeline-dot"></view>
<text class="timeline-text">挂号 {{ formatRegisterTime(item.register) }} · 单号 {{ historyOrderNo(item) }}</text>
</view>
<view class="modern-rx-item">
<view class="rx-header flex-row flex-jus-sp flex-ali-center">
<view class="rx-no-wrapper flex-row flex-ali-center">
<u-icon name="file-text" color="#8A92A3" size="32" class="m-r-8"></u-icon>
<text class="rx-no color-title">{{item.prescription_no}}</text>
</view>
</view>
<view class="rx-actions flex-row flex-jus-end m-t-16">
<view v-if="item.can_withdraw" class="action-btn-ghost danger" @click="withdrawRxByIndex('history', idx)">撤回</view>
<view class="action-btn-ghost primary m-l-16" @click="goToReusePrescription(item.id)">复用</view>
<view class="action-btn-ghost default m-l-16" @click="openPrescriptionDetail(item.id)">查看处方</view>
</view>
</view>
</block>
<view class="empty-state m-t-32" v-if="!rxLoadingMore && rxHistoryList.length===0">
<u-empty text="暂无历史处方" mode="history"></u-empty>
</view>
<view class="load-tip flex-row flex-jus-center flex-ali-center" v-if="rxLoadingMore">
<u-loading mode="circle" color="#00A88A"></u-loading> <text class="m-l-8 fs-24 color-sub">加载中</text>
</view>
<view class="load-tip fs-24 color-sub" v-if="!rxHistoryHasMore && rxHistoryList.length>0">已加载全部</view>
</view>
</template>
</view>
</view>
<u-modal v-model="show" title="操作提示" :show-cancel-button="true" :content="content" @confirm="confirm"
confirm-color="#00A88A" cancel-color="#8A92A3"></u-modal>
<d-loading-page :loading="loading"></d-loading-page>
<!-- 底部操作栏 -->
<view class="bottom-action-bar safe-area-inset-bottom shadow-up flex-row flex-jus-between flex-ali-center white" v-if="flag&&infoList.status!=3">
<u-button v-if="infoList.status==1" @click="$go('workbench_reason?id='+infoList.id)" shape="circle"
:custom-style="{ height: '88rpx', width: '330rpx', backgroundColor: '#F4F6F8', color: '#2A2E35', border: 'none', fontSize: '30rpx', fontWeight: 'bold' }">拒绝
</u-button>
<u-button v-if="infoList.status==1" @click="accept(infoList.id,infoList.user_patient_id)" shape="circle"
:custom-style="{ height: '88rpx', width: '330rpx', backgroundColor: '#00A88A', color: '#fff', border: 'none', fontSize: '30rpx', fontWeight: 'bold', boxShadow: '0 8rpx 20rpx rgba(0, 168, 138, 0.25)' }">接诊
</u-button>
<u-button v-if="infoList.status==2" @click="acceptEnds(infoList.id,infoList.user_patient_id)" shape="circle"
:custom-style="{ height: '88rpx', width: '330rpx', backgroundColor: '#F4F6F8', color: '#2A2E35', border: 'none', fontSize: '30rpx', fontWeight: 'bold' }">结束问诊
</u-button>
<u-button v-if="infoList.status==2" @click="goToPrescriptionV2" shape="circle"
style="margin-left: 20rpx"
:custom-style="{ height: '88rpx', width: '330rpx', backgroundColor: '#00A88A', color: '#fff', border: 'none', fontSize: '30rpx', fontWeight: 'bold' }">开具处方
</u-button>
</view>
</view>
</template>
<script>
import {
getPatientItem,
getRegisterPrescriptionList,
getPatientPrescriptionHistory,
withdrawPrescriptionWx,
receptionApi,
endOfDiagnosisApi
} from '@/api/reception.js'
function emptyHealthInquiry() {
return {
person_status: 0,
person_history: '',
allergic_status: 0,
allergic_history: '',
family_status: 0,
family_history: '',
liver_function: 0,
renal_function: 0
}
}
function normalizeRegisterDetail(raw) {
if (!raw || typeof raw !== 'object') {
raw = {}
}
const up = raw.userPatient || raw.user_patient || {}
const dept = raw.department || raw.depart || {}
const doc = raw.doctorInfo || raw.doctor_info || {}
const hi = raw.userPatientHealthInquiry || raw.user_patient_health_inquiry || emptyHealthInquiry()
const store = raw.store || {}
return Object.assign({}, raw, {
patient: up,
depart: dept && dept.name !== undefined ? dept : {
name: ''
},
doctor: doc && doc.name !== undefined ? doc : {
name: ''
},
store: store && store.name !== undefined ? store : {
name: ''
},
healthInquery: hi || emptyHealthInquiry()
})
}
export default {
data() {
return {
flag: true,
item: {},
status: "loading",
infoList: normalizeRegisterDetail(null),
loading: true,
id: "",
re_id: "",
show: false,
content: '确认结束本次问诊吗?',
rxTabs: [{
name: '本次挂号'
}, {
name: '历史处方'
}],
rxTabIndex: 0,
rxCurrentList: [],
rxHistoryList: [],
rxCurrentNextPage: 1,
rxHistoryNextPage: 1,
rxCurrentHasMore: true,
rxHistoryHasMore: true,
rxLoadingMore: false
};
},
computed: {},
onLoad(op) {
this.id = op["id"]
this.re_id = op['re_id']
this.getList();
},
onReachBottom() {
if (this.infoList.status === 7) return
if (this.rxTabIndex === 0) {
this.loadRxCurrent(false)
} else {
this.loadRxHistory(false)
}
},
methods: {
apiOk(res) {
return res && (res.code === 0 || res.errcode === 0)
},
apiMsg(res) {
return res.message || res.msg || '请求失败'
},
historyShowDivider(idx) {
if (idx === 0) return true
const cur = this.rxHistoryList[idx]
const prev = this.rxHistoryList[idx - 1]
if (!cur || !prev) return false
const curRid = cur.register_id || (cur.register && cur.register.id)
const prevRid = prev.register_id || (prev.register && prev.register.id)
return curRid !== prevRid
},
formatRegisterTime(reg) {
if (!reg || reg.created_at === undefined || reg.created_at === null) return '-'
const t = reg.created_at
try {
return this.$u.timeFormat(t, 'yyyy-mm-dd hh:MM')
} catch (e) {
return String(t)
}
},
historyOrderNo(item) {
if (item && item.register && item.register.order_no) {
return item.register.order_no
}
return item && item.register_id ? item.register_id : ''
},
openPrescriptionDetail(prescriptionId) {
this.$go('/pages/my/prescriptionDetail?id=' + prescriptionId)
},
withdrawRxByIndex(which, index) {
const list = which === 'current' ? this.rxCurrentList : this.rxHistoryList
const item = list[index]
if (!item) return
this.withdrawRx(item)
},
onRxTabChange(index) {
this.rxLoadingMore = false
this.rxTabIndex = index
if (index === 0) {
this.rxCurrentNextPage = 1
this.rxCurrentList = []
this.rxCurrentHasMore = true
this.loadRxCurrent(true)
} else {
this.rxHistoryNextPage = 1
this.rxHistoryList = []
this.rxHistoryHasMore = true
this.loadRxHistory(true)
}
},
loadRxCurrent(isReset) {
if (this.rxLoadingMore) return
if (!isReset && !this.rxCurrentHasMore) return
if (isReset) {
this.rxCurrentNextPage = 1
this.rxCurrentList = []
this.rxCurrentHasMore = true
}
const page = this.rxCurrentNextPage
this.rxLoadingMore = true
getRegisterPrescriptionList({
register_id: this.re_id,
page,
page_size: 20
}).then((res) => {
this.rxLoadingMore = false
if (!this.apiOk(res)) {
this.$toast(this.apiMsg(res))
return
}
const payload = res.result || res.data || {}
const list = payload.list || []
this.rxCurrentList = page === 1 ? list : this.rxCurrentList.concat(list)
this.rxCurrentHasMore = payload.has_more === true
if (list.length) {
this.rxCurrentNextPage = page + 1
} else {
this.rxCurrentHasMore = false
}
}).catch(() => {
this.rxLoadingMore = false
})
},
loadRxHistory(isReset) {
if (this.rxLoadingMore) return
if (!isReset && !this.rxHistoryHasMore) return
if (isReset) {
this.rxHistoryNextPage = 1
this.rxHistoryList = []
this.rxHistoryHasMore = true
}
const page = this.rxHistoryNextPage
this.rxLoadingMore = true
getPatientPrescriptionHistory({
patient_id: this.id,
page,
page_size: 20,
exclude_register_id: this.re_id
}).then((res) => {
this.rxLoadingMore = false
if (!this.apiOk(res)) {
this.$toast(this.apiMsg(res))
return
}
const payload = res.result || res.data || {}
const list = payload.list || []
this.rxHistoryList = page === 1 ? list : this.rxHistoryList.concat(list)
this.rxHistoryHasMore = payload.has_more === true
if (list.length) {
this.rxHistoryNextPage = page + 1
} else {
this.rxHistoryHasMore = false
}
}).catch(() => {
this.rxLoadingMore = false
})
},
withdrawRx(item) {
const prescriptionId = item.id
const registerId = item.register_id || this.re_id
uni.showModal({
title: '撤回处方',
content: '确认撤回该处方?(仅待支付可撤回)',
success: (r) => {
if (!r.confirm) return
withdrawPrescriptionWx({
prescription_id: prescriptionId,
register_id: registerId,
reason: ''
}).then((res) => {
if (!this.apiOk(res)) {
this.$toast(this.apiMsg(res))
return
}
this.$toast('已撤回')
if (this.rxTabIndex === 0) {
this.loadRxCurrent(true)
} else {
this.loadRxHistory(true)
}
})
}
})
},
// 接诊
accept(e, pid) {
receptionApi(e).then((res) => {
if (this.apiOk(res)) {
this.$toast('已接诊');
this.getList()
setTimeout(() => {
this.$go(`../../subPackages/sub_workbench/workbench_recipe/index?r_id=` + e +
'&id=' + pid);
}, 800)
} else {
this.$toast(this.apiMsg(res));
}
})
},
// 结束问诊
acceptEnds(e) {
uni.setStorageSync('register_id_', e)
this.show = true;
},
confirm() {
const rid = uni.getStorageSync('register_id_')
endOfDiagnosisApi(rid).then((res) => {
if (this.apiOk(res)) {
this.$toast('已结束问诊');
setTimeout(() => {
this.getList()
}, 1000)
} else {
this.$toast(this.apiMsg(res));
}
})
},
getList() {
this.loading = true
getPatientItem(this.re_id).then((res) => {
this.loading = false
if (!this.apiOk(res)) {
this.$toast(this.apiMsg(res))
return
}
const raw = res.result || res.data
this.infoList = normalizeRegisterDetail(raw)
if (this.infoList.status !== 7) {
if (this.rxTabIndex === 0) {
this.loadRxCurrent(true)
} else {
this.loadRxHistory(true)
}
}
}).catch(() => {
this.loading = false
})
},
goToPrescriptionV2() {
const registerId = this.infoList.id;
const patientId = this.infoList.user_patient_id;
this.$go(`/subPackages/sub_workbench/prescription_v2/index?register_id=${registerId}&patient_id=${patientId}`);
},
goToReusePrescription(prescriptionId) {
const registerId = this.infoList.id;
const patientId = this.infoList.user_patient_id;
if (!registerId || !patientId || !prescriptionId) {
this.$toast('参数错误');
return;
}
this.$go(`/subPackages/sub_workbench/prescription_v2/index?register_id=${registerId}&patient_id=${patientId}&reuse_prescription_id=${prescriptionId}`);
},
},
onShow() {
this.getList();
}
};
</script>
<style>
page {
background-color: #F4F6F8;
}
</style>
<style lang="scss" scoped>
.page-bg {
background-color: #F4F6F8;
min-height: 100vh;
}
.safe-padding {
padding-bottom: 240rpx; /* 留出底部操作栏空间 */
}
/* Flex Layout Helpers */
.flex-row { display: flex; flex-direction: row; }
.flex-col { display: flex; flex-direction: column; }
.flex-jus-sp { justify-content: space-between; }
.flex-jus-center { justify-content: center; }
.flex-jus-end { justify-content: flex-end; }
.flex-ali-center { align-items: center; }
/* Colors & Typography */
.white { background: #fff; }
.text-white { color: #ffffff; }
.fs-24 { font-size: 24rpx; }
.fs-36 { font-size: 36rpx; }
.font-bold { font-weight: bold; }
.color-title { color: #2A2E35; }
.color-sub { color: #8A92A3; }
.error-text { color: #F53F3F; }
.m-b-24 { margin-bottom: 24rpx; }
.m-t-16 { margin-top: 16rpx; }
.m-t-32 { margin-top: 32rpx; }
.m-r-8 { margin-right: 8rpx; }
.m-l-8 { margin-left: 8rpx; }
.m-l-16 { margin-left: 16rpx; }
/* Status Banner (Modernized) */
.status-banner {
padding: 40rpx 48rpx;
border-radius: 0 0 40rpx 40rpx;
box-shadow: 0 8rpx 20rpx rgba(0,0,0,0.05);
&.bg_blue { background: linear-gradient(135deg, #60A5FA 0%, #3B82F6 100%); }
&.bg_green { background: linear-gradient(135deg, #6ACDBB 0%, #00A88A 100%); }
&.bg_purple { background: linear-gradient(135deg, #A78BFA 0%, #8B5CF6 100%); }
&.bg_red { background: linear-gradient(135deg, #F87171 0%, #EF4444 100%); }
.status-icon {
width: 64rpx;
height: 64rpx;
opacity: 0.9;
}
}
.content-container {
padding: 0 32rpx;
margin-top: 32rpx;
}
/* Modern Card UI */
.modern-card {
background: #fff;
border-radius: 16rpx;
padding: 32rpx;
margin-bottom: 24rpx;
box-shadow: 0 4rpx 16rpx rgba(42, 46, 53, 0.04);
.card-title {
font-size: 32rpx;
font-weight: bold;
color: #2A2E35;
margin-bottom: 24rpx;
display: flex;
align-items: center;
position: relative;
padding-left: 20rpx;
&::before {
content: '';
position: absolute;
left: 0;
width: 8rpx;
height: 32rpx;
background: #00A88A;
border-radius: 4rpx;
}
}
}
/* Info Rows */
.info-row {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 16rpx;
line-height: 1.5;
&:last-child {
margin-bottom: 0;
}
.info-label {
width: 180rpx;
font-size: 28rpx;
color: #8A92A3;
flex-shrink: 0;
}
.info-value {
flex: 1;
font-size: 28rpx;
color: #2A2E35;
text-align: right;
word-break: break-all;
}
}
/* Rx Timeline & Items */
.rx-panel {
padding-top: 16rpx;
}
.rx-timeline-divider {
margin: 32rpx 0 16rpx;
.timeline-dot {
width: 12rpx;
height: 12rpx;
background-color: #D1D5DB;
border-radius: 50%;
margin-right: 16rpx;
}
.timeline-text {
font-size: 26rpx;
color: #9CA3AF;
font-weight: bold;
}
}
.modern-rx-item {
background: #F8FAFC;
border: 1px solid #F1F5F9;
border-radius: 12rpx;
padding: 24rpx;
margin-bottom: 16rpx;
transition: all 0.2s;
.rx-no {
font-size: 28rpx;
}
}
/* Modern Ghost Buttons */
.action-btn-ghost {
padding: 10rpx 32rpx;
border-radius: 30rpx;
font-size: 26rpx;
font-weight: bold;
border: 1px solid transparent;
display: flex;
align-items: center;
&.primary {
color: #00A88A;
background: #E8F6F4;
}
&.danger {
color: #F53F3F;
background: #FEF0F0;
}
&.default {
color: #6C7380;
background: #fff;
border-color: #E2E8F0;
}
}
.load-tip {
text-align: center;
padding: 24rpx 0;
}
/* Bottom Action Bar */
.bottom-action-bar {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
padding: 24rpx 32rpx calc(24rpx + env(safe-area-inset-bottom));
background-color: #fff;
z-index: 99;
&.shadow-up {
box-shadow: 0 -4rpx 20rpx rgba(0,0,0,0.04);
}
}
</style>