Files
xk-client-wx/subPackages/register/components/RegisterPatientStep.vue
2026-07-02 14:11:24 +08:00

841 lines
24 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="container safe-area-inset-bottom">
<view class="header-section">
<view class="title-row">
<text class="main-title">您需要为谁{{ registerType === '3' || registerType === '2' ? '复诊' : '挂号' }}</text>
<view class="register-type-tag">
<text class="type-label">{{ registerTypeLabel }}</text>
</view>
</view>
<text v-if="showDelegateInfo" class="delegate-info">
委托诊所:{{ delegateStoreName }} 委托医生:{{ delegateDoctorName }}
</text>
<text class="sub-title">请选择患者信息,以便医生给出更准确的诊疗建议,患者信息仅医生可见</text>
</view>
<view class="detail">
<view class="one" :class="actived==item.id ? 'active' :''" @click="choose(item.id)"
v-for="item in setInfoList" :key="item.id">
<view class="name">
{{item.name}}
<text class="iconfont icon-bianji" v-if="actived==item.id" @click.stop="toEdit"></text>
</view>
<text class="patient-meta">{{(item.sex)% 2 == 1 ? "男" : "女"}} &nbsp; {{item.age}}岁</text>
<image class="choose-badge" src="https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk-client-wx/static/choose/choosed.png" mode=""></image>
</view>
<view class="other" @click="goAdd(0)">
<text class="add-plus">+</text>
</view>
</view>
<view class="card follow-up-card modern-card" v-if="showFollowUpForm">
<view class="section-header">
<text class="title-text">患者主诉</text>
<text class="req-tag">* 必填</text>
</view>
<view class="input-wrapper">
<textarea
class="chief-textarea"
v-model="chiefComplaint"
placeholder="请简要描述症状、持续时间等..."
placeholder-style="color:#94A3B8; font-size: 28rpx;"
maxlength="2000"
auto-height
/>
</view>
<view class="section-divider"></view>
<view class="section-header">
<text class="title-text">选择相关西药</text>
<text class="req-tag">* 必选</text>
</view>
<view class="hint-box">
<text class="sub-hint">请至少选择一种药品;仅展示本门店与总后台均已上架的西药;数量仅作展示,提交按药品种类关联。</text>
</view>
<view class="drug-container">
<FollowUpDrugDrawer
v-model="selectedFollowUpDrugs"
:store-id="resolveRegisterStoreId()"
/>
</view>
</view>
<view class="card" v-if="showHealthInfo" style="margin-top: 0;">
<view class="card_title">健康信息</view>
<view class="card_names">
<text class="health-label">肝功能</text>
<u-radio-group v-model="liver" size="44rpx">
<u-radio size="44rpx" active-color="#2979FF" :name="liver">{{liver}}</u-radio>
</u-radio-group>
</view>
<view class="card_names">
<text class="health-label">肾功能</text>
<u-radio-group v-model="renal" size="44rpx">
<u-radio :name="renal">{{renal}}</u-radio>
</u-radio-group>
</view>
<view class="card_history">
<view class="name">
<text class="health-label">过敏史</text>
<u-radio-group v-model="allergic" size="44rpx">
<u-radio :name="allergic">{{allergic}}</u-radio>
</u-radio-group>
</view>
<view class="tabs" v-if="allergicList[0]!='' && allergic === '有'">
<u-tag bg-color="#2979FF" color="#FFFFFF" v-for="(item,index) in allergicList" :key="index"
:text="item" mode="dark" />
</view>
</view>
<view class="card_history">
<view class="name">
<text class="health-label">个人病史</text>
<u-radio-group v-model="person" size="44rpx">
<u-radio :name="person">{{person}}</u-radio>
</u-radio-group>
</view>
<view class="tabs" v-if="personList[0]!='' && person === '有'">
<u-tag bg-color="#2979FF" color="#FFFFFF" v-for="(item,index) in personList" :key="index"
:text="item" mode="dark" />
</view>
</view>
<view class="card_history">
<view class="name">
<text class="health-label">家族病史</text>
<u-radio-group v-model="family" size="44rpx">
<u-radio :name="family">{{family}}</u-radio>
</u-radio-group>
</view>
<view class="tabs" v-if="familyList[0]!='' && family === '有'">
<u-tag bg-color="#2979FF" color="#FFFFFF" v-for="(item,index) in familyList" :key="index"
:text="item" mode="dark" />
</view>
</view>
</view>
<view class="footer">
<view class="infos" @click="subScriptionMessage">下一步</view>
</view>
<u-toast ref="uToast" />
</view>
</template>
<script>
import {
userList,
userEidtPatient,
registe,
storeInfo,
doctorDetail,
} from '@/request/api/api.js';
import { getClinicOnlineConsultationInfoApi } from '@/request/api/product';
import { createRegisterInfoApi, saveRegisterInfoApi } from '@/request/api/order';
import request from '@/request/api/request';
export default {
name: 'RegisterPatientStep',
props: {
doctorId: {
type: [String, Number],
default: '',
},
registerTypeProp: {
type: String,
default: '0',
},
initialRInfo: {
type: Object,
default: () => ({}),
},
delegateStoreIdProp: {
type: String,
default: '',
},
},
data() {
return {
Did: '',
actived: 1,
infoList: [],
register_id: '',
liver: '',
renal: '',
allergic: '',
allergicList: [],
person: '',
personList: [],
family: '',
familyList: [],
heathList: [],
rInfo: {},
registerType: '0',
delegateStoreId: '',
delegateStoreName: '',
delegateDoctorName: '',
chiefComplaint: '',
selectedFollowUpDrugs: [],
};
},
computed: {
setInfoList() {
return this.infoList.map((item) => ({ ...item }));
},
registerTypeLabel() {
const typeMap = {
'0': '线下就诊',
'1': '在线问诊',
'2': '在线复诊',
'3': '在线复诊'
};
return typeMap[this.registerType] || '未知类型';
},
hasPrefilledFollowUpInfo() {
const r = this.rInfo;
return !!(r && typeof r === 'object' && !Array.isArray(r) && r.info_id);
},
showFollowUpForm() {
const t = this.registerType;
return (t === '2' || t === '3') && !this.hasPrefilledFollowUpInfo;
},
showDelegateInfo() {
const isFollowUp = this.registerType === '2' || this.registerType === '3';
return isFollowUp && !!(this.delegateStoreName || this.delegateDoctorName);
},
showHealthInfo() {
return this.registerType === '0'
&& !!this.actived
&& this.infoList.some((p) => p.id === this.actived);
},
},
watch: {
doctorId: {
immediate: true,
handler(val) {
this.Did = val;
},
},
registerTypeProp: {
immediate: true,
handler(val) {
if (val) this.registerType = val;
},
},
initialRInfo: {
immediate: true,
deep: true,
handler(val) {
this.rInfo = val ? { ...val } : {};
},
},
delegateStoreIdProp: {
immediate: true,
handler(val) {
this.delegateStoreId = val || '';
this.loadDelegateDisplayInfo();
},
},
},
mounted() {
this.getInfolist();
},
methods: {
resolveRegisterStoreId() {
return (this.registerType !== '0' && this.delegateStoreId)
? this.delegateStoreId
: (uni.getStorageSync('store_id') || '11001');
},
async loadDelegateDisplayInfo() {
if (this.registerType !== '2' && this.registerType !== '3') return;
const currentStoreId = uni.getStorageSync('store_id') || '11001';
try {
if (this.registerType === '2') {
const res = await getClinicOnlineConsultationInfoApi({ store_id: currentStoreId });
const result = res.data?.result;
if (res.data?.code === 0 && result) {
this.delegateStoreName = result.delegate_store_name || result.store_name || '';
this.delegateDoctorName = result.doctor_name || '';
}
} else {
const storeId = this.delegateStoreId || this.resolveRegisterStoreId();
const [storeRes, doctorRes] = await Promise.all([
storeInfo({ method: 'post', data: { store_id: storeId } }),
doctorDetail({ method: 'post', data: { doctor_id: this.Did, store_id: currentStoreId } }),
]);
if (storeRes.data?.errcode == 0) {
this.delegateStoreName = storeRes.data.data?.store?.name || '';
}
const doctorData = doctorRes.data?.data || doctorRes.data?.result;
if (doctorRes.data?.errcode == 0 || doctorRes.data?.code === 0) {
this.delegateDoctorName = doctorData?.name || '';
}
}
} catch (e) {
console.warn('loadDelegateDisplayInfo', e);
}
},
followUpDrugIdsForApi() {
const ids = [];
const seen = new Set();
for (const it of this.selectedFollowUpDrugs || []) {
const id = Number(it.id);
if (id > 0 && !seen.has(id)) {
seen.add(id);
ids.push(id);
}
}
return ids;
},
validateFollowUpFields() {
const chief = String(this.chiefComplaint || '').trim();
if (!chief) return '请填写患者主诉';
if (this.followUpDrugIdsForApi().length === 0) return '请至少选择一种药品';
return '';
},
async prepareFollowUpRegisterInfo() {
let params = typeof this.rInfo === 'string' ? JSON.parse(this.rInfo) : { ...(this.rInfo || {}) };
if (params.info_id) {
return params;
}
const err = this.validateFollowUpFields();
if (err) {
throw new Error(err);
}
const chief = String(this.chiefComplaint || '').trim();
const storeId = this.resolveRegisterStoreId();
const westernDrugQty = {};
let qtySum = 0;
for (const d of this.selectedFollowUpDrugs || []) {
const id = Number(d.id);
const q = Math.min(99, Math.max(1, Number(d.quantity) || 1));
if (id > 0) {
westernDrugQty[String(id)] = q;
qtySum += q;
}
}
const createRes = await createRegisterInfoApi({
doctor_id: Number(this.Did),
drug_id: '',
has_visited: '',
has_used_drug: '',
illnessInfo: '',
type: 2,
number: qtySum >= 1 ? qtySum : 1,
store_id: Number(storeId) || storeId,
chief_complaint: chief,
drug_ids: this.followUpDrugIdsForApi(),
western_drug_qty: JSON.stringify(westernDrugQty),
});
const infoId = createRes.data?.result?.info_id;
if (!infoId) {
throw new Error('创建就诊信息失败');
}
const followUpDrugs = (this.selectedFollowUpDrugs || [])
.map((d) => ({
drug_id: Number(d.id),
name: d.drug_name || d.name || '',
quantity: Math.min(99, Math.max(1, Number(d.quantity) || 1)),
}))
.filter((d) => d.drug_id > 0);
const followUpNumber = followUpDrugs.reduce((s, r) => s + (Number(r.quantity) || 1), 0) || 1;
return { ...params, info_id: infoId, follow_up_drugs: followUpDrugs, follow_up_number: followUpNumber, number: followUpNumber };
},
goAdd() {
uni.navigateTo({ url: '/subPackages/my/myinfo-add?id=' });
},
toEdit() {
uni.navigateTo({ url: '/subPackages/my/myinfo-edit?id=' + this.actived });
},
choose(id) {
this.actived = id;
this.getHeath();
},
getInfolist() {
userList({
method: 'post',
data: {
store_id: uni.getStorageSync('store_id') || '11001',
}
}).then((res) => {
if (res.data.errcode == 0) {
this.infoList = res.data.data;
this.infoList.filter(item => {
if (item.is_default == 1) {
this.actived = item.id;
}
});
this.getHeath();
}
});
},
normalizeRInfo(followUpRInfo) {
if (typeof followUpRInfo === 'string') {
try {
return JSON.parse(followUpRInfo);
} catch (e) {
return {};
}
}
return followUpRInfo || {};
},
emitRegisterCreated(followUpRInfo, isHas) {
const rInfoObj = this.normalizeRInfo(followUpRInfo);
const payload = {
register_id: this.register_id,
doctor_id: this.Did,
rInfo: rInfoObj,
};
if (isHas) {
this.$refs.uToast.show({
title: '您已挂号该医生,请继续问诊服务',
type: 'default',
icon: false
});
setTimeout(() => {
this.$emit('register-created', payload);
}, 900);
} else {
this.$emit('register-created', payload);
}
},
async afterRegisterCreated(followUpRInfo) {
if (this.registerType === '3' || this.registerType === '2') {
const params = this.normalizeRInfo(followUpRInfo);
if (params.info_id) {
try {
await request('/xkApi/order/update-register-info-register-id', {
method: 'POST',
data: {
info_id: params.info_id,
register_id: this.register_id,
}
});
} catch (error) {
console.error('更新就诊信息关联失败:', error);
}
} else {
try {
await saveRegisterInfoApi({
register_id: this.register_id,
doctor_id: params.doctor_id || this.Did,
drug_id: params.drug_id || params.id,
has_visited: params.has_visited || params.hasVisited,
has_used_drug: params.has_used_drug || params.hasUsedDrug,
illnessInfo: params.illness_info || params.illnessInfo,
type: params.type,
number: params.number,
store_id: uni.getStorageSync('store_id') || '11001',
});
} catch (error) {
console.error('保存患者就诊信息失败:', error);
}
}
}
uni.setStorageSync('register_id', this.register_id);
this.emitRegisterCreated(followUpRInfo, false);
},
async getAsk() {
uni.showLoading({ title: '加载中...', mask: true });
const storeId = this.resolveRegisterStoreId();
let followUpRInfo = this.rInfo;
if (this.showFollowUpForm) {
try {
followUpRInfo = await this.prepareFollowUpRegisterInfo();
this.rInfo = followUpRInfo;
} catch (e) {
uni.hideLoading();
this.$refs.uToast.show({
title: e.message || '提交失败',
type: 'default',
icon: false
});
return;
}
}
const originStoreId = uni.getStorageSync('store_id') || '';
const shouldSendOriginStoreId =
this.registerType === '2' ||
this.registerType === '3' ||
(this.registerType !== '0' && !!this.delegateStoreId);
registe({
method: 'post',
data: {
user_id: uni.getStorageSync('user_id'),
user_patient_id: this.actived,
store_id: storeId,
origin_store_id: shouldSendOriginStoreId ? originStoreId : 0,
service_user_id: this.Did,
register_type: this.registerType
}
}).then(async (res) => {
if (res.data.errcode == 0) {
this.register_id = res.data.data.register_id;
if (this.registerType === '3' || this.registerType === '2') {
await this.afterRegisterCreated(followUpRInfo);
return;
}
uni.setStorageSync('register_id', this.register_id);
this.emitRegisterCreated(followUpRInfo, res.data.data.isHas == 1);
} else {
this.$refs.uToast.show({
title: res.data.msg,
type: 'default',
icon: false
});
}
}).finally(() => {
uni.hideLoading();
});
},
getHeath() {
userEidtPatient({
method: 'post',
data: {
user_patient_id: this.actived,
store_id: uni.getStorageSync('store_id') || '11001',
}
}).then((res) => {
if (res.data.errcode == 0) {
this.heathList = res.data.data;
this.liver = (this.heathList.liver_function) % 2 == 0 ? '正常' : '异常';
this.renal = (this.heathList.renal_function) % 2 == 0 ? '正常' : '异常';
this.allergic = (this.heathList.allergic_status) % 2 == 0 ? '无' : '有';
this.allergicList = (this.heathList.allergic_history) == 'Array' ? [] : (this.heathList.allergic_history);
this.person = (this.heathList.person_status) % 2 == 0 ? '无' : '有';
this.personList = (this.heathList.person_history) == 'Array' ? [] : (this.heathList.person_history);
this.family = (this.heathList.family_status) % 2 == 0 ? '无' : '有';
this.familyList = (this.heathList.family_history) == 'Array' ? [] : (this.heathList.family_history);
}
});
},
subScriptionMessage() {
if (this.showFollowUpForm) {
const err = this.validateFollowUpFields();
if (err) {
this.$refs.uToast.show({
title: err,
type: 'default',
icon: false
});
return;
}
}
uni.requestSubscribeMessage({
tmplIds: [
'O5P8ZZB6T01V5Z66tkbhmzlobSaSRoEfdoCLATuP2Mw',
'ddT3v-m_Sr4kXwGCBLf0O4P89mOS5q10LFMZeEuQm10'
],
complete: () => {
this.getAsk();
}
});
},
refreshPatientList() {
this.getInfolist();
},
},
};
</script>
<style lang="scss" scoped>
.container {
width: 750rpx;
max-height: 100%;
min-height: 100vh;
box-sizing: border-box;
background-color: #F8FAFC;
padding-bottom: 200rpx;
padding-bottom: calc(140rpx + constant(safe-area-inset-bottom));
padding-bottom: calc(140rpx + env(safe-area-inset-bottom));
.header-section {
padding: 32rpx 32rpx 0;
.title-row {
display: flex;
align-items: center;
margin-bottom: 12rpx;
.main-title {
font-size: 38rpx;
font-family: PingFang SC-Medium, PingFang SC;
font-weight: 600;
color: #0F172A;
margin-right: 16rpx;
}
.register-type-tag {
background: linear-gradient(135deg, #4175EE 0%, #298dff 100%);
padding: 4rpx 16rpx;
border-radius: 8rpx;
box-shadow: 0 2rpx 8rpx rgba(65, 117, 238, 0.2);
.type-label {
color: #FFFFFF;
font-size: 24rpx;
font-weight: 500;
}
}
}
.sub-title {
display: block;
font-size: 24rpx;
font-weight: 400;
color: #64748B;
line-height: 1.5;
}
.delegate-info {
display: block;
font-size: 26rpx;
font-weight: 400;
color: #4175EE;
line-height: 1.5;
margin-bottom: 8rpx;
}
}
.detail {
width: 710rpx;
min-height: 124rpx;
margin: 32rpx;
padding-bottom: 8rpx;
display: flex;
flex-wrap: wrap;
align-items: flex-start;
.one {
width: 218rpx;
height: 124rpx;
border-radius: 8rpx;
margin: 0 16rpx 16rpx 0;
border: 2rpx solid #CBD5E1;
position: relative;
background: #FFFFFF;
.name {
margin: 16rpx 0 8rpx 32rpx;
height: 44rpx;
font-size: 32rpx;
font-weight: 400;
color: #0F172A;
display: flex;
justify-content: space-between;
align-items: center;
padding-right: 18rpx;
.iconfont {
font-size: 32rpx;
color: #94A3B8;
}
}
.patient-meta {
font-size: 28rpx;
color: #94A3B8;
margin-left: 32rpx;
}
.choose-badge {
width: 50rpx;
height: 50rpx;
position: absolute;
right: -2rpx;
bottom: -1rpx;
display: none;
}
&.active {
border: 2rpx solid #536DFE;
background: rgba(83, 109, 254, 0.04);
.choose-badge {
display: block;
}
}
}
.other {
width: 218rpx;
height: 124rpx;
line-height: 104rpx;
text-align: center;
border-radius: 8rpx;
border: 2rpx dashed #CBD5E1;
background: #FFFFFF;
.add-plus {
font-size: 68rpx;
color: #94A3B8;
}
}
}
.card {
width: 710rpx;
background: #FFFFFF;
border-radius: 16rpx;
margin: 20rpx auto 40rpx;
padding: 0 32rpx 40rpx;
box-shadow: 0 4rpx 24rpx rgba(15, 23, 42, 0.02);
.card_title {
height: 114rpx;
line-height: 114rpx;
font-size: 34rpx;
font-weight: 600;
color: #1E293B;
border-bottom: 1rpx solid #F1F5F9;
}
.card_names {
width: 646rpx;
padding: 34rpx 0 32rpx 0;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 30rpx;
border-bottom: 1rpx solid #F1F5F9;
.health-label {
color: #1E293B;
}
}
.card_history {
width: 646rpx;
padding: 34rpx 0 32rpx 0;
font-size: 30rpx;
border-bottom: 1rpx solid #F1F5F9;
.name {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10rpx;
.health-label {
color: #1E293B;
}
}
.tabs {
display: flex;
flex-wrap: wrap;
::v-deep .u-tag {
margin: 10rpx 20rpx 10rpx 0;
}
}
}
.card_history:last-child {
border-bottom: 0;
}
}
.modern-card {
padding: 32rpx;
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24rpx;
.title-text {
font-size: 32rpx;
font-weight: 600;
color: #0F172A;
position: relative;
padding-left: 18rpx;
&::before {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 8rpx;
height: 28rpx;
background: #4175EE;
border-radius: 4rpx;
}
}
.req-tag {
color: #EF4444;
font-size: 24rpx;
background: rgba(239, 68, 68, 0.08);
padding: 4rpx 12rpx;
border-radius: 6rpx;
}
}
.section-divider {
height: 1rpx;
background-color: #F1F5F9;
margin: 40rpx 0 32rpx;
}
.input-wrapper {
background: #F8FAFC;
border-radius: 12rpx;
.chief-textarea {
width: 100%;
min-height: 160rpx;
padding: 24rpx;
box-sizing: border-box;
font-size: 28rpx;
color: #334155;
line-height: 1.5;
}
}
.hint-box {
background: #F1F5F9;
border-radius: 8rpx;
padding: 16rpx 24rpx;
margin-bottom: 24rpx;
.sub-hint {
font-size: 24rpx;
color: #64748B;
line-height: 1.6;
}
}
}
.footer {
width: 750rpx;
min-height: 104rpx;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
border-top: 2rpx solid #F1F5F9;
position: fixed;
left: 0;
right: 0;
bottom: 0;
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
background-color: #FFFFFF;
z-index: 100;
.infos {
width: 686rpx;
height: 90rpx;
line-height: 90rpx;
text-align: center;
margin: 16rpx 0;
font-size: 32rpx;
font-weight: 500;
background: linear-gradient(135deg, #4175EE 0%, #298dff 100%);
box-shadow: 0 8rpx 20rpx rgba(65, 117, 238, 0.3);
border-radius: 198rpx;
color: #FFFFFF;
}
}
}
</style>