|
|
|
@@ -25,65 +25,95 @@ |
|
|
|
</view> |
|
|
|
</view> |
|
|
|
|
|
|
|
<!-- 平台收件信息 --> |
|
|
|
<view class="section"> |
|
|
|
<view class="section-title"> |
|
|
|
<u-icon name="order" size="18" color="#0e63e3" /> |
|
|
|
<text>平台收件信息</text> |
|
|
|
</view> |
|
|
|
<view class="receiver-info"> |
|
|
|
<view class="receiver-row"> |
|
|
|
<text class="receiver-label">收件地址</text> |
|
|
|
<text class="receiver-value">{{ sampleReceiverInfo.address || '—' }}</text> |
|
|
|
</view> |
|
|
|
<view class="receiver-row"> |
|
|
|
<text class="receiver-label">收件人</text> |
|
|
|
<text class="receiver-value">{{ sampleReceiverInfo.receiver || '—' }}</text> |
|
|
|
</view> |
|
|
|
<view class="receiver-row"> |
|
|
|
<text class="receiver-label">电话</text> |
|
|
|
<text class="receiver-value">{{ sampleReceiverInfo.phone || '—' }}</text> |
|
|
|
</view> |
|
|
|
</view> |
|
|
|
<view class="copy-receiver" @tap="copySampleReceiverInfo">复制送检信息</view> |
|
|
|
</view> |
|
|
|
|
|
|
|
<!-- 送检信息 --> |
|
|
|
<view class="section"> |
|
|
|
<view class="section-title"> |
|
|
|
<u-icon name="file-text" size="18" color="#fa541c" /> |
|
|
|
<text>送检信息</text> |
|
|
|
<text v-if="sampleInfoStatusText" class="status-pill" :class="sampleInfoStatusClass">{{ sampleInfoStatusText }}</text> |
|
|
|
</view> |
|
|
|
<view v-if="isSampleReturned" class="return-info"> |
|
|
|
<text class="return-title">样品已经寄回</text> |
|
|
|
<text class="return-text">回寄物流单号:{{ returnTrackingNo || '—' }}</text> |
|
|
|
<text v-if="returnTime" class="return-time">回寄时间:{{ returnTime }}</text> |
|
|
|
<text class="copy-return-no" @tap="copyReturnTrackingNo">复制单号</text> |
|
|
|
</view> |
|
|
|
<view class="form-group"> |
|
|
|
<text class="form-label">送检样本类型{{ sampleRequired ? '' : '(选填)' }}</text> |
|
|
|
<u-checkbox-group v-model="form.sample_types" placement="row" :wrap="true" @change="onSampleTypesChange"> |
|
|
|
<u-checkbox v-for="st in sampleTypeList" :key="st.id" :label="st.name" :name="st.name" |
|
|
|
activeColor="#0E63E3" :customStyle="{ marginRight: '24rpx', marginBottom: '16rpx' }" /> |
|
|
|
:disabled="isSampleInfoLocked" activeColor="#0E63E3" :customStyle="{ marginRight: '24rpx', marginBottom: '16rpx' }" /> |
|
|
|
</u-checkbox-group> |
|
|
|
</view> |
|
|
|
<view class="form-group" v-if="showWaxReturn"> |
|
|
|
<text class="form-label">{{ needReturnNames }}是否需寄回</text> |
|
|
|
<u-radio-group v-model="form.wax_return" placement="row"> |
|
|
|
<u-radio label="是" :name="1" activeColor="#0E63E3" :customStyle="{ marginRight: '40rpx' }" /> |
|
|
|
<u-radio label="否" :name="0" activeColor="#0E63E3" /> |
|
|
|
<u-radio label="是" :name="1" :disabled="isSampleInfoLocked" activeColor="#0E63E3" :customStyle="{ marginRight: '40rpx' }" /> |
|
|
|
<u-radio label="否" :name="0" :disabled="isSampleInfoLocked" activeColor="#0E63E3" /> |
|
|
|
</u-radio-group> |
|
|
|
</view> |
|
|
|
<template v-if="form.wax_return === 1 && showWaxReturn"> |
|
|
|
<view class="form-group"> |
|
|
|
<view style="display:flex;align-items:center;justify-content:space-between;"> |
|
|
|
<text class="form-label" style="margin-bottom:0;">收件人姓名</text> |
|
|
|
<text class="fill-self-btn" @tap="fillSelfReturn">本人接收</text> |
|
|
|
<text :class="['fill-self-btn', isSampleInfoLocked ? 'disabled' : '']" @tap="fillSelfReturn">本人接收</text> |
|
|
|
</view> |
|
|
|
<u-input v-model="form.return_name" placeholder="请输入收件人姓名" border="surround" :customStyle="{ marginTop: '16rpx' }" /> |
|
|
|
<u-input v-model="form.return_name" :disabled="isSampleInfoLocked" placeholder="请输入收件人姓名" border="surround" :customStyle="{ marginTop: '16rpx' }" /> |
|
|
|
</view> |
|
|
|
<view class="form-group"> |
|
|
|
<text class="form-label">收件人电话</text> |
|
|
|
<u-input v-model="form.return_phone" type="number" placeholder="请输入收件人电话" border="surround" maxlength="11" /> |
|
|
|
<u-input v-model="form.return_phone" :disabled="isSampleInfoLocked" type="number" placeholder="请输入收件人电话" border="surround" maxlength="11" /> |
|
|
|
</view> |
|
|
|
<view class="form-group"> |
|
|
|
<text class="form-label">收件地址</text> |
|
|
|
<view class="region-row" @tap="showRegionPicker = true"> |
|
|
|
<view :class="['region-row', isSampleInfoLocked ? 'disabled' : '']" @tap="openRegionPicker"> |
|
|
|
<text :class="['region-text', returnRegionText ? '' : 'placeholder']">{{ returnRegionText || '请选择省/市/区' }}</text> |
|
|
|
<text class="arrow">›</text> |
|
|
|
</view> |
|
|
|
<u-input v-model="form.return_address" placeholder="详细门牌号" border="surround" |
|
|
|
<u-input v-model="form.return_address" :disabled="isSampleInfoLocked" placeholder="详细门牌号" border="surround" |
|
|
|
:customStyle="{ marginTop: '16rpx' }" /> |
|
|
|
</view> |
|
|
|
</template> |
|
|
|
<template v-if="(form.sample_types && form.sample_types.length) || sampleRequired"> |
|
|
|
<view class="form-group"> |
|
|
|
<text class="form-label">报告接收邮箱</text> |
|
|
|
<u-input v-model="form.report_email" placeholder="请输入邮箱地址" border="surround" /> |
|
|
|
<u-input v-model="form.report_email" :disabled="isSampleInfoLocked" placeholder="请输入邮箱地址" border="surround" /> |
|
|
|
</view> |
|
|
|
<view class="form-group"> |
|
|
|
<text class="form-label">送检样本物流单号</text> |
|
|
|
<u-input v-model="form.sample_tracking_no" placeholder="请输入物流单号" border="surround" /> |
|
|
|
<u-input v-model="form.sample_tracking_no" :disabled="isSampleInfoLocked" placeholder="请输入物流单号" border="surround" /> |
|
|
|
</view> |
|
|
|
<view class="form-group"> |
|
|
|
<text class="form-label">送检单照片(可上传多张)</text> |
|
|
|
<view class="upload-row"> |
|
|
|
<view class="upload-item" v-for="(photo, idx) in form.sample_photos" :key="'sp'+idx"> |
|
|
|
<image class="upload-img" :src="photo" mode="aspectFill" @tap="previewSamplePhoto(idx)" /> |
|
|
|
<view class="upload-del" @tap="form.sample_photos.splice(idx, 1)">×</view> |
|
|
|
<view v-if="!isSampleInfoLocked" class="upload-del" @tap="form.sample_photos.splice(idx, 1)">×</view> |
|
|
|
</view> |
|
|
|
<view class="upload-box" @tap="chooseSamplePhoto"> |
|
|
|
<view v-if="!isSampleInfoLocked" class="upload-box" @tap="chooseSamplePhoto"> |
|
|
|
<text class="upload-icon">+</text> |
|
|
|
<text class="upload-text">上传图片</text> |
|
|
|
</view> |
|
|
|
@@ -94,14 +124,14 @@ |
|
|
|
|
|
|
|
<!-- 提交按钮 --> |
|
|
|
<view class="btn-wrap"> |
|
|
|
<view class="agree-row" @tap="agreed = !agreed"> |
|
|
|
<view class="agree-row" @tap="toggleAgree"> |
|
|
|
<u-checkbox-group> |
|
|
|
<u-checkbox :checked="agreed" shape="circle" activeColor="#0E63E3" size="18" @change="agreed = !agreed" /> |
|
|
|
<u-checkbox :checked="agreed" :disabled="isSampleInfoLocked" shape="circle" activeColor="#0E63E3" size="18" /> |
|
|
|
</u-checkbox-group> |
|
|
|
<text class="agree-text">请阅读并同意</text> |
|
|
|
<text class="agree-link" @tap.stop="openNotice">《患者告知书》</text> |
|
|
|
</view> |
|
|
|
<u-button text="提交送检信息" :loading="submitting" @click="handleSubmit" color="#0E63E3" size="large" /> |
|
|
|
<u-button :text="submitButtonText" :loading="submitting" @click="handleSubmit" color="#0E63E3" size="large" /> |
|
|
|
</view> |
|
|
|
|
|
|
|
<!-- 地区选择器 --> |
|
|
|
@@ -133,6 +163,23 @@ const submitting = ref(false) |
|
|
|
const agreed = ref(false) |
|
|
|
const sampleTypeList = ref([]) |
|
|
|
const sampleRequired = ref(false) |
|
|
|
const sampleReceiverInfo = ref({ address: '', receiver: '', phone: '' }) |
|
|
|
const sampleInfoStatus = ref(0) |
|
|
|
const returnTrackingNo = ref('') |
|
|
|
const returnTime = ref('') |
|
|
|
const isSampleReturned = computed(() => sampleInfoStatus.value === 2) |
|
|
|
const isSampleInfoLocked = computed(() => sampleInfoStatus.value === 1 || sampleInfoStatus.value === 2) |
|
|
|
const sampleInfoStatusText = computed(() => { |
|
|
|
if (sampleInfoStatus.value === 2) return '已寄回' |
|
|
|
if (sampleInfoStatus.value === 1) return '已生效' |
|
|
|
return '' |
|
|
|
}) |
|
|
|
const sampleInfoStatusClass = computed(() => sampleInfoStatus.value === 2 ? 'returned' : 'effective') |
|
|
|
const submitButtonText = computed(() => { |
|
|
|
if (sampleInfoStatus.value === 2) return '样品已寄回' |
|
|
|
if (sampleInfoStatus.value === 1) return '送检信息已生效' |
|
|
|
return '提交送检信息' |
|
|
|
}) |
|
|
|
|
|
|
|
// 地区数据 |
|
|
|
const allRegions = ref([]) |
|
|
|
@@ -196,6 +243,7 @@ const returnRegionText = computed(() => { |
|
|
|
}) |
|
|
|
|
|
|
|
const onSampleTypesChange = () => { |
|
|
|
if (isSampleInfoLocked.value) return |
|
|
|
if (!showWaxReturn.value) { |
|
|
|
form.wax_return = 0 |
|
|
|
} |
|
|
|
@@ -214,6 +262,7 @@ const onSampleTypesChange = () => { |
|
|
|
} |
|
|
|
|
|
|
|
const fillSelfReturn = () => { |
|
|
|
if (isSampleInfoLocked.value) return showLockedTip() |
|
|
|
form.return_name = patient.value.name || '' |
|
|
|
form.return_phone = patient.value.phone || '' |
|
|
|
form.return_province_code = patient.value.province_code || '' |
|
|
|
@@ -222,6 +271,11 @@ const fillSelfReturn = () => { |
|
|
|
form.return_address = patient.value.address || '' |
|
|
|
} |
|
|
|
|
|
|
|
const openRegionPicker = () => { |
|
|
|
if (isSampleInfoLocked.value) return showLockedTip() |
|
|
|
showRegionPicker.value = true |
|
|
|
} |
|
|
|
|
|
|
|
onLoad(async () => { |
|
|
|
await loadRegions() |
|
|
|
await loadPatientInfo() |
|
|
|
@@ -245,6 +299,13 @@ const loadPatientInfo = async () => { |
|
|
|
form.report_email = res.data.report_email || '' |
|
|
|
form.sample_tracking_no = res.data.sample_tracking_no || '' |
|
|
|
form.sample_photos = res.data.sample_photos || [] |
|
|
|
sampleInfoStatus.value = Number(res.data.sample_info_status) || 0 |
|
|
|
returnTrackingNo.value = res.data.return_tracking_no || '' |
|
|
|
returnTime.value = res.data.return_time || '' |
|
|
|
sampleReceiverInfo.value = res.data.sample_receiver_info || { address: '', receiver: '', phone: '' } |
|
|
|
if (sampleInfoStatus.value === 1) { |
|
|
|
setTimeout(() => showLockedTip(), 300) |
|
|
|
} |
|
|
|
} |
|
|
|
} catch (e) {} |
|
|
|
} |
|
|
|
@@ -288,6 +349,7 @@ const onRegionChange = (e) => { |
|
|
|
} |
|
|
|
|
|
|
|
const onRegionConfirm = (e) => { |
|
|
|
if (isSampleInfoLocked.value) return |
|
|
|
const idxs = e.indexs || e.index || [0, 0, 0] |
|
|
|
const provinces = allRegions.value |
|
|
|
const prov = provinces[idxs[0]] |
|
|
|
@@ -300,6 +362,7 @@ const onRegionConfirm = (e) => { |
|
|
|
} |
|
|
|
|
|
|
|
const chooseSamplePhoto = () => { |
|
|
|
if (isSampleInfoLocked.value) return showLockedTip() |
|
|
|
uni.chooseImage({ |
|
|
|
count: 9 - form.sample_photos.length, |
|
|
|
sizeType: ['compressed'], |
|
|
|
@@ -325,7 +388,87 @@ const openNotice = () => { |
|
|
|
uni.navigateTo({ url: '/pages/content/content?key=patient_information_sheet' }) |
|
|
|
} |
|
|
|
|
|
|
|
const getContactPhone = () => sampleReceiverInfo.value.contact_phone || '' |
|
|
|
const getCleanPhone = () => getContactPhone().replace(/[^\d+]/g, '') |
|
|
|
|
|
|
|
const contactPlatform = () => { |
|
|
|
const phone = getContactPhone() |
|
|
|
if (!phone) return uni.showToast({ title: '暂无联系电话', icon: 'none' }) |
|
|
|
// #ifdef H5 |
|
|
|
uni.setClipboardData({ |
|
|
|
data: phone, |
|
|
|
success: () => uni.showToast({ title: '电话已复制', icon: 'success' }) |
|
|
|
}) |
|
|
|
// #endif |
|
|
|
// #ifndef H5 |
|
|
|
uni.makePhoneCall({ phoneNumber: getCleanPhone() }) |
|
|
|
// #endif |
|
|
|
} |
|
|
|
|
|
|
|
const showLockedTip = () => { |
|
|
|
if (isSampleReturned.value) { |
|
|
|
uni.showModal({ |
|
|
|
title: '提示', |
|
|
|
content: `样品已经寄回,回寄物流单号:${returnTrackingNo.value || '—'}。`, |
|
|
|
cancelText: '知道了', |
|
|
|
confirmText: '复制单号', |
|
|
|
success: (res) => { |
|
|
|
if (res.confirm) copyReturnTrackingNo() |
|
|
|
} |
|
|
|
}) |
|
|
|
return |
|
|
|
} |
|
|
|
const phone = getContactPhone() |
|
|
|
if (!phone) { |
|
|
|
uni.showModal({ |
|
|
|
title: '提示', |
|
|
|
content: '送检信息已生效,如需修改请联系平台重置修改权限。', |
|
|
|
showCancel: false, |
|
|
|
confirmText: '知道了' |
|
|
|
}) |
|
|
|
return |
|
|
|
} |
|
|
|
uni.showModal({ |
|
|
|
title: '提示', |
|
|
|
content: `送检信息已生效,如需修改请联系平台【${phone}】重置修改权限。`, |
|
|
|
cancelText: '知道了', |
|
|
|
confirmText: '联系平台', |
|
|
|
success: (res) => { |
|
|
|
if (res.confirm) contactPlatform() |
|
|
|
} |
|
|
|
}) |
|
|
|
} |
|
|
|
|
|
|
|
const copyReturnTrackingNo = () => { |
|
|
|
if (!returnTrackingNo.value) return uni.showToast({ title: '暂无单号', icon: 'none' }) |
|
|
|
uni.setClipboardData({ |
|
|
|
data: returnTrackingNo.value, |
|
|
|
success: () => uni.showToast({ title: '单号已复制', icon: 'success' }) |
|
|
|
}) |
|
|
|
} |
|
|
|
|
|
|
|
const copySampleReceiverInfo = () => { |
|
|
|
const info = sampleReceiverInfo.value |
|
|
|
const text = [ |
|
|
|
`收件地址:${info.address || ''}`, |
|
|
|
`收件人:${info.receiver || ''}`, |
|
|
|
`电话:${info.phone || ''}` |
|
|
|
].join('\n') |
|
|
|
uni.setClipboardData({ |
|
|
|
data: text, |
|
|
|
success: () => uni.showToast({ title: '已复制', icon: 'success' }) |
|
|
|
}) |
|
|
|
} |
|
|
|
|
|
|
|
const toggleAgree = () => { |
|
|
|
if (isSampleInfoLocked.value) return showLockedTip() |
|
|
|
agreed.value = !agreed.value |
|
|
|
} |
|
|
|
|
|
|
|
const handleSubmit = async () => { |
|
|
|
if (isSampleInfoLocked.value) { |
|
|
|
return showLockedTip() |
|
|
|
} |
|
|
|
if (!agreed.value) { |
|
|
|
return uni.showToast({ title: '请阅读并同意《患者告知书》', icon: 'none' }) |
|
|
|
} |
|
|
|
@@ -347,9 +490,17 @@ const handleSubmit = async () => { |
|
|
|
submitting.value = true |
|
|
|
try { |
|
|
|
await post('/api/mp/saveSampleInfo', { ...form }) |
|
|
|
sampleInfoStatus.value = 1 |
|
|
|
uni.showToast({ title: '提交成功', icon: 'success' }) |
|
|
|
setTimeout(() => uni.navigateBack(), 1500) |
|
|
|
} catch (e) { |
|
|
|
if (e && e.data && [1, 2].includes(Number(e.data.sample_info_status))) { |
|
|
|
sampleInfoStatus.value = Number(e.data.sample_info_status) |
|
|
|
returnTrackingNo.value = e.data.return_tracking_no || returnTrackingNo.value |
|
|
|
returnTime.value = e.data.return_time || returnTime.value |
|
|
|
if (e.data.sample_receiver_info) sampleReceiverInfo.value = e.data.sample_receiver_info |
|
|
|
return showLockedTip() |
|
|
|
} |
|
|
|
if (e && e.msg) uni.showToast({ title: e.msg, icon: 'none' }) |
|
|
|
} finally { |
|
|
|
submitting.value = false |
|
|
|
@@ -361,15 +512,26 @@ const handleSubmit = async () => { |
|
|
|
.page { min-height: 100vh; background: #f4f4f5; padding: 24rpx; padding-bottom: 260rpx; } |
|
|
|
.section { background: #fff; border-radius: 10rpx; padding: 32rpx; margin-bottom: 24rpx; border: 1rpx solid #ebeef5; } |
|
|
|
.section-title { display: flex; align-items: center; gap: 12rpx; font-size: 32rpx; font-weight: 600; color: #333; margin-bottom: 28rpx; } |
|
|
|
.status-pill { margin-left: auto; font-size: 22rpx; font-weight: 500; padding: 4rpx 14rpx; border-radius: 6rpx; &.effective { color: #52c41a; background: #f6ffed; border: 1rpx solid #b7eb8f; } &.returned { color: #0e63e3; background: #f0f5ff; border: 1rpx solid #adc6ff; } } |
|
|
|
.info-compact { padding-bottom: 8rpx; } |
|
|
|
.info-compact-row { display: flex; gap: 32rpx; margin-bottom: 12rpx; &:last-child { margin-bottom: 0; } } |
|
|
|
.info-compact-item { font-size: 26rpx; color: #666; } |
|
|
|
.return-info { background: #f0f5ff; border-left: 6rpx solid #0e63e3; border-radius: 8rpx; padding: 24rpx; margin-bottom: 20rpx; } |
|
|
|
.return-title { display: block; font-size: 30rpx; font-weight: 600; color: #303133; margin-bottom: 12rpx; } |
|
|
|
.return-text { display: block; font-size: 28rpx; color: #0e63e3; font-weight: 600; line-height: 1.5; } |
|
|
|
.return-time { display: block; font-size: 24rpx; color: #909399; margin-top: 8rpx; } |
|
|
|
.copy-return-no { display: block; text-align: right; font-size: 26rpx; color: #0e63e3; margin-top: 12rpx; } |
|
|
|
.receiver-info { padding-bottom: 8rpx; } |
|
|
|
.receiver-row { display: flex; gap: 20rpx; margin-bottom: 14rpx; &:last-child { margin-bottom: 0; } } |
|
|
|
.receiver-label { width: 120rpx; flex-shrink: 0; font-size: 26rpx; color: #909399; } |
|
|
|
.receiver-value { flex: 1; font-size: 26rpx; color: #303133; line-height: 1.6; } |
|
|
|
.copy-receiver { margin-top: 20rpx; color: #0e63e3; font-size: 26rpx; text-align: right; } |
|
|
|
.form-group { padding: 20rpx 0; border-bottom: 1rpx solid #f0f0f0; &:last-child { border-bottom: none; } } |
|
|
|
.form-label { font-size: 28rpx; color: #555; margin-bottom: 16rpx; display: block; } |
|
|
|
.region-row { display: flex; align-items: center; justify-content: space-between; padding: 20rpx 24rpx; border: 1rpx solid #ddd; border-radius: 8rpx; } |
|
|
|
.region-row { display: flex; align-items: center; justify-content: space-between; padding: 20rpx 24rpx; border: 1rpx solid #ddd; border-radius: 8rpx; &.disabled { background: #f7f8fa; } } |
|
|
|
.region-text { font-size: 28rpx; color: #333; &.placeholder { color: #c0c4cc; } } |
|
|
|
.arrow { font-size: 28rpx; color: #c0c4cc; } |
|
|
|
.fill-self-btn { font-size: 24rpx; color: #0e63e3; padding: 6rpx 16rpx; border: 1rpx solid #d0e0ff; border-radius: 20rpx; background: #f0f5ff; } |
|
|
|
.fill-self-btn { font-size: 24rpx; color: #0e63e3; padding: 6rpx 16rpx; border: 1rpx solid #d0e0ff; border-radius: 20rpx; background: #f0f5ff; &.disabled { color: #c0c4cc; border-color: #e4e7ed; background: #f7f8fa; } } |
|
|
|
.upload-row { display: flex; gap: 16rpx; flex-wrap: wrap; } |
|
|
|
.upload-item { position: relative; width: 180rpx; height: 180rpx; } |
|
|
|
.upload-img { width: 180rpx; height: 180rpx; border-radius: 8rpx; border: 1rpx solid #eee; } |
|
|
|
|