| @@ -48,6 +48,13 @@ | |||||
| "navigationBarTitleText": "我的资料" | "navigationBarTitleText": "我的资料" | ||||
| } | } | ||||
| }, | }, | ||||
| { | |||||
| "path": "pages/sample-info/sample-info", | |||||
| "style": { | |||||
| "navigationStyle": "default", | |||||
| "navigationBarTitleText": "送检信息" | |||||
| } | |||||
| }, | |||||
| { | { | ||||
| "path": "pages/sign/sign", | "path": "pages/sign/sign", | ||||
| "style": { | "style": { | ||||
| @@ -49,14 +49,6 @@ | |||||
| </view> | </view> | ||||
| </view> | </view> | ||||
| </view> | </view> | ||||
| </view> | |||||
| <!-- 情况描述 --> | |||||
| <view class="section"> | |||||
| <view class="section-title"> | |||||
| <u-icon name="file-text" size="18" color="#fa541c" /> | |||||
| <text>情况描述</text> | |||||
| </view> | |||||
| <view class="form-group"> | <view class="form-group"> | ||||
| <text class="form-label">医院名称</text> | <text class="form-label">医院名称</text> | ||||
| <u-input v-model="form.hospital" placeholder="请输入就诊医院名称" border="surround" /> | <u-input v-model="form.hospital" placeholder="请输入就诊医院名称" border="surround" /> | ||||
| @@ -68,66 +60,6 @@ | |||||
| activeColor="#0E63E3" :customStyle="{ marginRight: '24rpx', marginBottom: '16rpx' }" /> | activeColor="#0E63E3" :customStyle="{ marginRight: '24rpx', marginBottom: '16rpx' }" /> | ||||
| </u-radio-group> | </u-radio-group> | ||||
| </view> | </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' }" /> | |||||
| </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-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> | |||||
| </view> | |||||
| <u-input v-model="form.return_name" 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" /> | |||||
| </view> | |||||
| <view class="form-group"> | |||||
| <text class="form-label">收件地址</text> | |||||
| <view class="region-row" @tap="showReturnRegionPicker = true"> | |||||
| <text :class="['region-text', returnRegionText ? '' : 'placeholder']">{{ returnRegionText || '请选择省/市/区' }}</text> | |||||
| <text class="arrow">›</text> | |||||
| </view> | |||||
| <u-input v-model="form.return_address" 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" /> | |||||
| </view> | |||||
| <view class="form-group"> | |||||
| <text class="form-label">送检样本物流单号</text> | |||||
| <u-input v-model="form.sample_tracking_no" 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> | |||||
| <view class="upload-box" @tap="chooseSamplePhoto"> | |||||
| <text class="upload-icon">+</text> | |||||
| <text class="upload-text">上传图片</text> | |||||
| </view> | |||||
| </view> | |||||
| </view> | |||||
| </template> | |||||
| </view> | </view> | ||||
| <!-- 资料上传 --> | <!-- 资料上传 --> | ||||
| @@ -217,10 +149,6 @@ | |||||
| <u-picker v-if="regionColumns[0].length" :show="showRegionPicker" :columns="regionColumns" @confirm="onRegionConfirm" | <u-picker v-if="regionColumns[0].length" :show="showRegionPicker" :columns="regionColumns" @confirm="onRegionConfirm" | ||||
| @cancel="showRegionPicker = false" @change="onRegionChange" :defaultIndex="regionDefaultIndex" /> | @cancel="showRegionPicker = false" @change="onRegionChange" :defaultIndex="regionDefaultIndex" /> | ||||
| <!-- 寄回地址地区选择器 --> | |||||
| <u-picker v-if="regionColumns[0].length" :show="showReturnRegionPicker" :columns="regionColumns" @confirm="onReturnRegionConfirm" | |||||
| @cancel="showReturnRegionPicker = false" @change="onRegionChange" :defaultIndex="returnRegionDefaultIndex" /> | |||||
| <!-- 已通过重新提交确认弹窗 --> | <!-- 已通过重新提交确认弹窗 --> | ||||
| <u-popup :show="showConfirmPopup" mode="center" round="12" :safeAreaInsetBottom="false" @close="showConfirmPopup = false"> | <u-popup :show="showConfirmPopup" mode="center" round="12" :safeAreaInsetBottom="false" @close="showConfirmPopup = false"> | ||||
| <view class="confirm-popup"> | <view class="confirm-popup"> | ||||
| @@ -252,17 +180,6 @@ const form = reactive({ | |||||
| emergency_phone: '', | emergency_phone: '', | ||||
| tag: '', | tag: '', | ||||
| documents: [], | documents: [], | ||||
| sample_types: [], | |||||
| wax_return: 0, | |||||
| return_name: '', | |||||
| return_phone: '', | |||||
| return_province_code: '', | |||||
| return_city_code: '', | |||||
| return_district_code: '', | |||||
| return_address: '', | |||||
| report_email: '', | |||||
| sample_tracking_no: '', | |||||
| sample_photos: [], | |||||
| sign_income: '', | sign_income: '', | ||||
| sign_privacy: '', | sign_privacy: '', | ||||
| sign_privacy_jhr: '', | sign_privacy_jhr: '', | ||||
| @@ -275,75 +192,11 @@ const showConfirmPopup = ref(false) | |||||
| const subscribeTmplId = ref('') | const subscribeTmplId = ref('') | ||||
| const agreed = ref(false) | const agreed = ref(false) | ||||
| // 瘤种选项和送检样本类型 | |||||
| // 瘤种选项 | |||||
| const tagOptions = ref([]) | const tagOptions = ref([]) | ||||
| const sampleTypeList = ref([]) | |||||
| const sampleRequired = ref(false) | |||||
| const showReturnRegionPicker = ref(false) | const showReturnRegionPicker = ref(false) | ||||
| const returnRegionDefaultIndex = ref([0, 0, 0]) | const returnRegionDefaultIndex = ref([0, 0, 0]) | ||||
| // 是否显示"是否需寄回" | |||||
| const showWaxReturn = computed(() => { | |||||
| if (!form.sample_types || !form.sample_types.length) return false | |||||
| return form.sample_types.some(name => { | |||||
| const st = sampleTypeList.value.find(s => s.name === name) | |||||
| return st && st.need_return | |||||
| }) | |||||
| }) | |||||
| // 需要寄回的样本类型名称(用"、"分隔) | |||||
| const needReturnNames = computed(() => { | |||||
| if (!form.sample_types || !form.sample_types.length) return '' | |||||
| const names = form.sample_types.filter(name => { | |||||
| const st = sampleTypeList.value.find(s => s.name === name) | |||||
| return st && st.need_return | |||||
| }) | |||||
| return names.join('、') | |||||
| }) | |||||
| // 寄回地址文本 | |||||
| const returnRegionText = computed(() => { | |||||
| const parts = [] | |||||
| if (form.return_province_code) { | |||||
| const p = allRegions.value.find(r => r.code === form.return_province_code) | |||||
| if (p) parts.push(p.name) | |||||
| } | |||||
| if (form.return_city_code) { | |||||
| const prov = allRegions.value.find(r => r.code === form.return_province_code) | |||||
| if (prov && prov.children) { | |||||
| const c = prov.children.find(r => r.code === form.return_city_code) | |||||
| if (c) parts.push(c.name) | |||||
| } | |||||
| } | |||||
| if (form.return_district_code) { | |||||
| const prov = allRegions.value.find(r => r.code === form.return_province_code) | |||||
| if (prov && prov.children) { | |||||
| const city = prov.children.find(r => r.code === form.return_city_code) | |||||
| if (city && city.children) { | |||||
| const d = city.children.find(r => r.code === form.return_district_code) | |||||
| if (d) parts.push(d.name) | |||||
| } | |||||
| } | |||||
| } | |||||
| return parts.join(' ') | |||||
| }) | |||||
| const onSampleTypesChange = () => { | |||||
| // 如果取消了所有 need_return 的样本类型,重置寄回选项 | |||||
| if (!showWaxReturn.value) { | |||||
| form.wax_return = 0 | |||||
| } | |||||
| } | |||||
| const fillSelfReturn = () => { | |||||
| form.return_name = info.value.name || '' | |||||
| form.return_phone = info.value.phone || '' | |||||
| form.return_province_code = form.province_code | |||||
| form.return_city_code = form.city_code | |||||
| form.return_district_code = form.district_code | |||||
| form.return_address = form.address | |||||
| } | |||||
| // 签署时的额外信息(用于重签回显) | // 签署时的额外信息(用于重签回显) | ||||
| const signExtra = reactive({ | const signExtra = reactive({ | ||||
| income_amount: '', | income_amount: '', | ||||
| @@ -468,7 +321,6 @@ onLoad(async () => { | |||||
| await loadInfo() | await loadInfo() | ||||
| loadSubscribeConfig() | loadSubscribeConfig() | ||||
| loadTagOptions() | loadTagOptions() | ||||
| loadSampleTypes() | |||||
| }) | }) | ||||
| onBeforeUnmount(() => { | onBeforeUnmount(() => { | ||||
| @@ -561,17 +413,6 @@ const loadInfo = async () => { | |||||
| form.emergency_phone = res.data.emergency_phone || '' | form.emergency_phone = res.data.emergency_phone || '' | ||||
| form.tag = res.data.tag || '' | form.tag = res.data.tag || '' | ||||
| form.documents = res.data.documents || [] | form.documents = res.data.documents || [] | ||||
| form.sample_types = res.data.sample_types || [] | |||||
| form.wax_return = res.data.wax_return || 0 | |||||
| form.return_name = res.data.return_name || '' | |||||
| form.return_phone = res.data.return_phone || '' | |||||
| form.return_province_code = res.data.return_province_code || '' | |||||
| form.return_city_code = res.data.return_city_code || '' | |||||
| form.return_district_code = res.data.return_district_code || '' | |||||
| form.return_address = res.data.return_address || '' | |||||
| form.report_email = res.data.report_email || '' | |||||
| form.sample_tracking_no = res.data.sample_tracking_no || '' | |||||
| form.sample_photos = res.data.sample_photos || [] | |||||
| form.sign_income = res.data.sign_income || '' | form.sign_income = res.data.sign_income || '' | ||||
| form.sign_privacy = res.data.sign_privacy || '' | form.sign_privacy = res.data.sign_privacy || '' | ||||
| form.sign_privacy_jhr = res.data.sign_privacy_jhr || '' | form.sign_privacy_jhr = res.data.sign_privacy_jhr || '' | ||||
| @@ -604,48 +445,6 @@ const loadTagOptions = async () => { | |||||
| } catch (e) {} | } catch (e) {} | ||||
| } | } | ||||
| const loadSampleTypes = async () => { | |||||
| try { | |||||
| const res = await get('/common/sampleTypes') | |||||
| sampleTypeList.value = (res.data && res.data.list) || [] | |||||
| sampleRequired.value = (res.data && res.data.required) || false | |||||
| } catch (e) {} | |||||
| } | |||||
| const chooseSamplePhoto = () => { | |||||
| uni.chooseImage({ | |||||
| count: 9 - form.sample_photos.length, | |||||
| sizeType: ['compressed'], | |||||
| sourceType: ['album', 'camera'], | |||||
| success: async (res) => { | |||||
| for (const filePath of res.tempFilePaths) { | |||||
| try { | |||||
| const uploadRes = await upload('/api/mp/upload', { filePath, name: 'file' }) | |||||
| if (uploadRes.data && uploadRes.data.url) { | |||||
| form.sample_photos.push(uploadRes.data.url) | |||||
| } | |||||
| } catch (e) {} | |||||
| } | |||||
| } | |||||
| }) | |||||
| } | |||||
| const previewSamplePhoto = (idx) => { | |||||
| uni.previewImage({ urls: form.sample_photos, current: idx }) | |||||
| } | |||||
| const onReturnRegionConfirm = (e) => { | |||||
| const idxs = e.indexs || e.index || [0, 0, 0] | |||||
| const provinces = allRegions.value | |||||
| const prov = provinces[idxs[0]] | |||||
| const city = prov && prov.children ? prov.children[idxs[1]] : null | |||||
| const dist = city && city.children ? city.children[idxs[2]] : null | |||||
| form.return_province_code = prov ? prov.code : '' | |||||
| form.return_city_code = city ? city.code : '' | |||||
| form.return_district_code = dist ? dist.code : '' | |||||
| showReturnRegionPicker.value = false | |||||
| } | |||||
| const chooseDocument = () => { | const chooseDocument = () => { | ||||
| uni.chooseImage({ | uni.chooseImage({ | ||||
| count: 9 - form.documents.length, | count: 9 - form.documents.length, | ||||
| @@ -697,23 +496,6 @@ const handleSubmit = async () => { | |||||
| if (!form.tag) { | if (!form.tag) { | ||||
| return uni.showToast({ title: '请选择癌种', icon: 'none' }) | return uni.showToast({ title: '请选择癌种', icon: 'none' }) | ||||
| } | } | ||||
| // 送检样本校验 | |||||
| if (sampleRequired.value && (!form.sample_types || form.sample_types.length === 0)) { | |||||
| return uni.showToast({ title: '请选择送检样本类型', icon: 'none' }) | |||||
| } | |||||
| // 寄回信息校验 | |||||
| if (form.wax_return === 1 && showWaxReturn.value) { | |||||
| if (!form.return_name) return uni.showToast({ title: '请输入收件人姓名', icon: 'none' }) | |||||
| if (!form.return_phone) return uni.showToast({ title: '请输入收件人电话', icon: 'none' }) | |||||
| if (!form.return_province_code) return uni.showToast({ title: '请选择收件地址', icon: 'none' }) | |||||
| if (!form.return_address) return uni.showToast({ title: '请输入收件详细地址', icon: 'none' }) | |||||
| } | |||||
| // 选了送检样本后,邮箱/物流单号/送检照片必填 | |||||
| if (form.sample_types && form.sample_types.length > 0) { | |||||
| if (!form.report_email) return uni.showToast({ title: '请输入报告接收邮箱', icon: 'none' }) | |||||
| if (!form.sample_tracking_no) return uni.showToast({ title: '请输入送检样本物流单号', icon: 'none' }) | |||||
| if (!form.sample_photos || form.sample_photos.length === 0) return uni.showToast({ title: '请上传送检单照片', icon: 'none' }) | |||||
| } | |||||
| // 资料上传校验:至少上传一个 | // 资料上传校验:至少上传一个 | ||||
| if (!form.documents || form.documents.length === 0) { | if (!form.documents || form.documents.length === 0) { | ||||
| return uni.showToast({ title: '请至少上传一份检查报告或诊断证明', icon: 'none' }) | return uni.showToast({ title: '请至少上传一份检查报告或诊断证明', icon: 'none' }) | ||||
| @@ -756,17 +538,6 @@ const doSubmit = async () => { | |||||
| emergency_phone: form.emergency_phone, | emergency_phone: form.emergency_phone, | ||||
| tag: form.tag, | tag: form.tag, | ||||
| documents: form.documents, | documents: form.documents, | ||||
| sample_types: form.sample_types, | |||||
| wax_return: form.wax_return, | |||||
| return_name: form.return_name, | |||||
| return_phone: form.return_phone, | |||||
| return_province_code: form.return_province_code, | |||||
| return_city_code: form.return_city_code, | |||||
| return_district_code: form.return_district_code, | |||||
| return_address: form.return_address, | |||||
| report_email: form.report_email, | |||||
| sample_tracking_no: form.sample_tracking_no, | |||||
| sample_photos: form.sample_photos, | |||||
| sign_income: form.sign_income, | sign_income: form.sign_income, | ||||
| sign_privacy: form.sign_privacy, | sign_privacy: form.sign_privacy, | ||||
| sign_privacy_jhr: form.sign_privacy_jhr, | sign_privacy_jhr: form.sign_privacy_jhr, | ||||
| @@ -863,15 +634,6 @@ const doSubmit = async () => { | |||||
| color: #666; | color: #666; | ||||
| } | } | ||||
| .fill-self-btn { | |||||
| font-size: 24rpx; | |||||
| color: #0e63e3; | |||||
| padding: 6rpx 16rpx; | |||||
| border: 1rpx solid #d0e0ff; | |||||
| border-radius: 20rpx; | |||||
| background: #f0f5ff; | |||||
| } | |||||
| .form-label { | .form-label { | ||||
| font-size: 28rpx; | font-size: 28rpx; | ||||
| color: #555; | color: #555; | ||||
| @@ -38,6 +38,13 @@ | |||||
| <text v-else-if="patientStatus === 2" class="extra rejected">已驳回</text> | <text v-else-if="patientStatus === 2" class="extra rejected">已驳回</text> | ||||
| <u-icon name="arrow-right" size="16" color="#c0c4cc" /> | <u-icon name="arrow-right" size="16" color="#c0c4cc" /> | ||||
| </view> | </view> | ||||
| <view class="menu-item" @tap="goSampleInfo"> | |||||
| <view class="menu-icon"> | |||||
| <u-icon name="order" size="20" color="#fa541c" /> | |||||
| </view> | |||||
| <text class="text">送检信息</text> | |||||
| <u-icon name="arrow-right" size="16" color="#c0c4cc" /> | |||||
| </view> | |||||
| <view class="menu-item" @tap="goMessage"> | <view class="menu-item" @tap="goMessage"> | ||||
| <view class="menu-icon"> | <view class="menu-icon"> | ||||
| <u-icon name="chat-fill" size="20" color="#fa8c16" /> | <u-icon name="chat-fill" size="20" color="#fa8c16" /> | ||||
| @@ -185,6 +192,15 @@ const goMyInfo = () => { | |||||
| uni.navigateTo({ url: '/pages/myinfo/myinfo' }) | uni.navigateTo({ url: '/pages/myinfo/myinfo' }) | ||||
| } | } | ||||
| const goSampleInfo = () => { | |||||
| if (!checkAuth()) return | |||||
| if (patientStatus.value !== 1) { | |||||
| uni.showToast({ title: '审核通过后可填写送检信息', icon: 'none' }) | |||||
| return | |||||
| } | |||||
| uni.navigateTo({ url: '/pages/sample-info/sample-info' }) | |||||
| } | |||||
| const goChangePhone = () => { | const goChangePhone = () => { | ||||
| if (!checkAuth()) return | if (!checkAuth()) return | ||||
| uni.navigateTo({ url: '/pages/change-phone/change-phone' }) | uni.navigateTo({ url: '/pages/change-phone/change-phone' }) | ||||
| @@ -0,0 +1,383 @@ | |||||
| <template> | |||||
| <view class="page"> | |||||
| <!-- 患者基本信息(只读) --> | |||||
| <view class="section"> | |||||
| <view class="section-title"> | |||||
| <u-icon name="account-fill" size="18" color="#0e63e3" /> | |||||
| <text>基本信息</text> | |||||
| </view> | |||||
| <view class="info-compact"> | |||||
| <view class="info-compact-row"> | |||||
| <text class="info-compact-item">姓名:{{ patient.name }}</text> | |||||
| <text class="info-compact-item">性别:{{ patient.gender }}</text> | |||||
| </view> | |||||
| <view class="info-compact-row"> | |||||
| <text class="info-compact-item">身份证:{{ maskedIdCard }}</text> | |||||
| <text class="info-compact-item">手机号:{{ maskedPhone }}</text> | |||||
| </view> | |||||
| <view class="info-compact-row"> | |||||
| <text class="info-compact-item">联系地址:{{ patient.region_text }} {{ patient.address }}</text> | |||||
| </view> | |||||
| <view class="info-compact-row"> | |||||
| <text class="info-compact-item">医院:{{ patient.hospital || '—' }}</text> | |||||
| <text class="info-compact-item">癌种:{{ patient.tag || '—' }}</text> | |||||
| </view> | |||||
| </view> | |||||
| </view> | |||||
| <!-- 送检信息 --> | |||||
| <view class="section"> | |||||
| <view class="section-title"> | |||||
| <u-icon name="file-text" size="18" color="#fa541c" /> | |||||
| <text>送检信息</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' }" /> | |||||
| </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-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> | |||||
| </view> | |||||
| <u-input v-model="form.return_name" 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" /> | |||||
| </view> | |||||
| <view class="form-group"> | |||||
| <text class="form-label">收件地址</text> | |||||
| <view class="region-row" @tap="showRegionPicker = true"> | |||||
| <text :class="['region-text', returnRegionText ? '' : 'placeholder']">{{ returnRegionText || '请选择省/市/区' }}</text> | |||||
| <text class="arrow">›</text> | |||||
| </view> | |||||
| <u-input v-model="form.return_address" 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" /> | |||||
| </view> | |||||
| <view class="form-group"> | |||||
| <text class="form-label">送检样本物流单号</text> | |||||
| <u-input v-model="form.sample_tracking_no" 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> | |||||
| <view class="upload-box" @tap="chooseSamplePhoto"> | |||||
| <text class="upload-icon">+</text> | |||||
| <text class="upload-text">上传图片</text> | |||||
| </view> | |||||
| </view> | |||||
| </view> | |||||
| </template> | |||||
| </view> | |||||
| <!-- 提交按钮 --> | |||||
| <view class="btn-wrap"> | |||||
| <view class="agree-row" @tap="agreed = !agreed"> | |||||
| <u-checkbox-group> | |||||
| <u-checkbox :checked="agreed" shape="circle" activeColor="#0E63E3" size="18" @change="agreed = !agreed" /> | |||||
| </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" /> | |||||
| </view> | |||||
| <!-- 地区选择器 --> | |||||
| <u-picker v-if="regionColumns[0].length" :show="showRegionPicker" :columns="regionColumns" @confirm="onRegionConfirm" | |||||
| @cancel="showRegionPicker = false" @change="onRegionChange" :defaultIndex="regionDefaultIndex" /> | |||||
| </view> | |||||
| </template> | |||||
| <script setup> | |||||
| import { ref, reactive, computed } from 'vue' | |||||
| import { onLoad } from '@dcloudio/uni-app' | |||||
| import { get, post, upload } from '@/utils/request.js' | |||||
| const patient = ref({}) | |||||
| const form = reactive({ | |||||
| sample_types: [], | |||||
| wax_return: 0, | |||||
| return_name: '', | |||||
| return_phone: '', | |||||
| return_province_code: '', | |||||
| return_city_code: '', | |||||
| return_district_code: '', | |||||
| return_address: '', | |||||
| report_email: '', | |||||
| sample_tracking_no: '', | |||||
| sample_photos: [] | |||||
| }) | |||||
| const submitting = ref(false) | |||||
| const agreed = ref(false) | |||||
| const sampleTypeList = ref([]) | |||||
| const sampleRequired = ref(false) | |||||
| // 地区数据 | |||||
| const allRegions = ref([]) | |||||
| const regionColumns = ref([[], [], []]) | |||||
| const regionDefaultIndex = ref([0, 0, 0]) | |||||
| const showRegionPicker = ref(false) | |||||
| const maskedIdCard = computed(() => { | |||||
| const v = patient.value.id_card || '' | |||||
| if (v.length === 18) return v.slice(0, 3) + '****' + v.slice(-4) | |||||
| return v | |||||
| }) | |||||
| const maskedPhone = computed(() => { | |||||
| const v = patient.value.phone || '' | |||||
| if (v.length === 11) return v.slice(0, 3) + '****' + v.slice(-4) | |||||
| return v | |||||
| }) | |||||
| const showWaxReturn = computed(() => { | |||||
| if (!form.sample_types || !form.sample_types.length) return false | |||||
| return form.sample_types.some(name => { | |||||
| const st = sampleTypeList.value.find(s => s.name === name) | |||||
| return st && st.need_return | |||||
| }) | |||||
| }) | |||||
| const needReturnNames = computed(() => { | |||||
| if (!form.sample_types || !form.sample_types.length) return '' | |||||
| const names = form.sample_types.filter(name => { | |||||
| const st = sampleTypeList.value.find(s => s.name === name) | |||||
| return st && st.need_return | |||||
| }) | |||||
| return names.join('、') | |||||
| }) | |||||
| const returnRegionText = computed(() => { | |||||
| const parts = [] | |||||
| if (form.return_province_code) { | |||||
| const p = allRegions.value.find(r => r.code === form.return_province_code) | |||||
| if (p) parts.push(p.name) | |||||
| } | |||||
| if (form.return_city_code) { | |||||
| const prov = allRegions.value.find(r => r.code === form.return_province_code) | |||||
| if (prov && prov.children) { | |||||
| const c = prov.children.find(r => r.code === form.return_city_code) | |||||
| if (c) parts.push(c.name) | |||||
| } | |||||
| } | |||||
| if (form.return_district_code) { | |||||
| const prov = allRegions.value.find(r => r.code === form.return_province_code) | |||||
| if (prov && prov.children) { | |||||
| const city = prov.children.find(r => r.code === form.return_city_code) | |||||
| if (city && city.children) { | |||||
| const d = city.children.find(r => r.code === form.return_district_code) | |||||
| if (d) parts.push(d.name) | |||||
| } | |||||
| } | |||||
| } | |||||
| return parts.join(' ') | |||||
| }) | |||||
| const onSampleTypesChange = () => { | |||||
| if (!showWaxReturn.value) { | |||||
| form.wax_return = 0 | |||||
| } | |||||
| if (!form.sample_types || form.sample_types.length === 0) { | |||||
| form.report_email = '' | |||||
| form.sample_tracking_no = '' | |||||
| form.sample_photos = [] | |||||
| form.wax_return = 0 | |||||
| form.return_name = '' | |||||
| form.return_phone = '' | |||||
| form.return_province_code = '' | |||||
| form.return_city_code = '' | |||||
| form.return_district_code = '' | |||||
| form.return_address = '' | |||||
| } | |||||
| } | |||||
| const fillSelfReturn = () => { | |||||
| form.return_name = patient.value.name || '' | |||||
| form.return_phone = patient.value.phone || '' | |||||
| form.return_province_code = patient.value.province_code || '' | |||||
| form.return_city_code = patient.value.city_code || '' | |||||
| form.return_district_code = patient.value.district_code || '' | |||||
| form.return_address = patient.value.address || '' | |||||
| } | |||||
| onLoad(async () => { | |||||
| await loadRegions() | |||||
| await loadPatientInfo() | |||||
| await loadSampleTypes() | |||||
| }) | |||||
| const loadPatientInfo = async () => { | |||||
| try { | |||||
| const res = await get('/api/mp/sampleInfo') | |||||
| if (res.data) { | |||||
| patient.value = res.data.patient || {} | |||||
| // 回显送检信息 | |||||
| form.sample_types = res.data.sample_types || [] | |||||
| form.wax_return = res.data.wax_return || 0 | |||||
| form.return_name = res.data.return_name || '' | |||||
| form.return_phone = res.data.return_phone || '' | |||||
| form.return_province_code = res.data.return_province_code || '' | |||||
| form.return_city_code = res.data.return_city_code || '' | |||||
| form.return_district_code = res.data.return_district_code || '' | |||||
| form.return_address = res.data.return_address || '' | |||||
| form.report_email = res.data.report_email || '' | |||||
| form.sample_tracking_no = res.data.sample_tracking_no || '' | |||||
| form.sample_photos = res.data.sample_photos || [] | |||||
| } | |||||
| } catch (e) {} | |||||
| } | |||||
| const loadSampleTypes = async () => { | |||||
| try { | |||||
| const res = await get('/common/sampleTypes') | |||||
| sampleTypeList.value = (res.data && res.data.list) || [] | |||||
| sampleRequired.value = (res.data && res.data.required) || false | |||||
| } catch (e) {} | |||||
| } | |||||
| const loadRegions = async () => { | |||||
| try { | |||||
| const res = await get('/common/regions') | |||||
| allRegions.value = res.data || [] | |||||
| buildRegionColumns() | |||||
| } catch (e) {} | |||||
| } | |||||
| const buildRegionColumns = (pIdx = 0, cIdx = 0) => { | |||||
| const provinces = allRegions.value | |||||
| const col0 = provinces.map(p => p.name) | |||||
| const cities = (provinces[pIdx] && provinces[pIdx].children) || [] | |||||
| const col1 = cities.map(c => c.name) | |||||
| const districts = (cities[cIdx] && cities[cIdx].children) || [] | |||||
| const col2 = districts.map(d => d.name) | |||||
| regionColumns.value = [col0, col1, col2] | |||||
| } | |||||
| const onRegionChange = (e) => { | |||||
| const { columnIndex, index } = e | |||||
| if (columnIndex === 0) { | |||||
| buildRegionColumns(index, 0) | |||||
| regionDefaultIndex.value = [index, 0, 0] | |||||
| } else if (columnIndex === 1) { | |||||
| const pIdx = regionDefaultIndex.value[0] | |||||
| buildRegionColumns(pIdx, index) | |||||
| regionDefaultIndex.value = [pIdx, index, 0] | |||||
| } | |||||
| } | |||||
| const onRegionConfirm = (e) => { | |||||
| const idxs = e.indexs || e.index || [0, 0, 0] | |||||
| const provinces = allRegions.value | |||||
| const prov = provinces[idxs[0]] | |||||
| const city = prov && prov.children ? prov.children[idxs[1]] : null | |||||
| const dist = city && city.children ? city.children[idxs[2]] : null | |||||
| form.return_province_code = prov ? prov.code : '' | |||||
| form.return_city_code = city ? city.code : '' | |||||
| form.return_district_code = dist ? dist.code : '' | |||||
| showRegionPicker.value = false | |||||
| } | |||||
| const chooseSamplePhoto = () => { | |||||
| uni.chooseImage({ | |||||
| count: 9 - form.sample_photos.length, | |||||
| sizeType: ['compressed'], | |||||
| sourceType: ['album', 'camera'], | |||||
| success: async (res) => { | |||||
| for (const filePath of res.tempFilePaths) { | |||||
| try { | |||||
| const uploadRes = await upload('/api/mp/upload', { filePath, name: 'file' }) | |||||
| if (uploadRes.data && uploadRes.data.url) { | |||||
| form.sample_photos.push(uploadRes.data.url) | |||||
| } | |||||
| } catch (e) {} | |||||
| } | |||||
| } | |||||
| }) | |||||
| } | |||||
| const previewSamplePhoto = (idx) => { | |||||
| uni.previewImage({ urls: form.sample_photos, current: idx }) | |||||
| } | |||||
| const openNotice = () => { | |||||
| uni.navigateTo({ url: '/pages/content/content?key=patient_information_sheet' }) | |||||
| } | |||||
| const handleSubmit = async () => { | |||||
| if (!agreed.value) { | |||||
| return uni.showToast({ title: '请阅读并同意《患者告知书》', icon: 'none' }) | |||||
| } | |||||
| if (sampleRequired.value && (!form.sample_types || form.sample_types.length === 0)) { | |||||
| return uni.showToast({ title: '请选择送检样本类型', icon: 'none' }) | |||||
| } | |||||
| if (form.wax_return === 1 && showWaxReturn.value) { | |||||
| if (!form.return_name) return uni.showToast({ title: '请输入收件人姓名', icon: 'none' }) | |||||
| if (!form.return_phone) return uni.showToast({ title: '请输入收件人电话', icon: 'none' }) | |||||
| if (!form.return_province_code) return uni.showToast({ title: '请选择收件地址', icon: 'none' }) | |||||
| if (!form.return_address) return uni.showToast({ title: '请输入收件详细地址', icon: 'none' }) | |||||
| } | |||||
| if (form.sample_types && form.sample_types.length > 0) { | |||||
| if (!form.report_email) return uni.showToast({ title: '请输入报告接收邮箱', icon: 'none' }) | |||||
| if (!form.sample_tracking_no) return uni.showToast({ title: '请输入送检样本物流单号', icon: 'none' }) | |||||
| if (!form.sample_photos || form.sample_photos.length === 0) return uni.showToast({ title: '请上传送检单照片', icon: 'none' }) | |||||
| } | |||||
| submitting.value = true | |||||
| try { | |||||
| await post('/api/mp/saveSampleInfo', { ...form }) | |||||
| uni.showToast({ title: '提交成功', icon: 'success' }) | |||||
| setTimeout(() => uni.navigateBack(), 1500) | |||||
| } catch (e) { | |||||
| if (e && e.msg) uni.showToast({ title: e.msg, icon: 'none' }) | |||||
| } finally { | |||||
| submitting.value = false | |||||
| } | |||||
| } | |||||
| </script> | |||||
| <style lang="scss" scoped> | |||||
| .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; } | |||||
| .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; } | |||||
| .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-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; } | |||||
| .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; } | |||||
| .upload-del { position: absolute; top: -10rpx; right: -10rpx; width: 40rpx; height: 40rpx; background: rgba(0,0,0,0.5); color: #fff; border-radius: 50%; font-size: 24rpx; display: flex; align-items: center; justify-content: center; } | |||||
| .upload-box { width: 180rpx; height: 180rpx; border: 2rpx dashed #ccc; border-radius: 8rpx; display: flex; flex-direction: column; align-items: center; justify-content: center; background: #fafafa; } | |||||
| .upload-icon { font-size: 56rpx; color: #ccc; } | |||||
| .upload-text { font-size: 22rpx; color: #999; margin-top: 4rpx; } | |||||
| .btn-wrap { position: fixed; bottom: 0; left: 0; right: 0; background: #fff; padding: 20rpx 32rpx; padding-bottom: calc(20rpx + env(safe-area-inset-bottom)); box-shadow: 0 -4rpx 16rpx rgba(0,0,0,0.06); z-index: 100; } | |||||
| .agree-row { display: flex; align-items: center; margin-bottom: 16rpx; padding-left: 4rpx; } | |||||
| .agree-text { font-size: 24rpx; color: #666; margin-left: 8rpx; } | |||||
| .agree-link { font-size: 24rpx; color: #0e63e3; } | |||||
| </style> | |||||