| @@ -12,23 +12,17 @@ | |||
| <u-icon name="account-fill" size="18" color="#0e63e3" /> | |||
| <text>基本信息</text> | |||
| </view> | |||
| <view class="form-group"> | |||
| <text class="form-label">姓名</text> | |||
| <view class="readonly-input">{{ info.name }}</view> | |||
| </view> | |||
| <view class="form-group"> | |||
| <text class="form-label">身份证号</text> | |||
| <view class="readonly-input">{{ maskedIdCard }}</view> | |||
| </view> | |||
| <view class="form-group"> | |||
| <text class="form-label">手机号</text> | |||
| <view class="readonly-input">{{ maskedPhone }}</view> | |||
| </view> | |||
| <view class="form-group" v-if="info.gender"> | |||
| <text class="form-label">性别</text> | |||
| <view class="readonly-input">{{ info.gender }}</view> | |||
| <view class="info-compact"> | |||
| <view class="info-compact-row"> | |||
| <text class="info-compact-item">姓名:{{ info.name }}</text> | |||
| <text class="info-compact-item" v-if="info.gender">性别:{{ info.gender }}</text> | |||
| </view> | |||
| <view class="info-compact-row"> | |||
| <text class="info-compact-item">身份证:{{ maskedIdCard }}</text> | |||
| <text class="info-compact-item">手机号:{{ maskedPhone }}</text> | |||
| </view> | |||
| </view> | |||
| <view class="form-group" v-else> | |||
| <view class="form-group" v-if="!info.gender"> | |||
| <text class="form-label">性别</text> | |||
| <view class="gender-row"> | |||
| <view class="gender-item" :class="{ active: form.gender === '男' }" @tap="form.gender = '男'">男</view> | |||
| @@ -45,7 +39,7 @@ | |||
| :customStyle="{ marginTop: '16rpx' }" /> | |||
| </view> | |||
| <view class="form-group"> | |||
| <text class="form-label">紧急联系人(选填)</text> | |||
| <text class="form-label">紧急联系人</text> | |||
| <view class="contact-row"> | |||
| <view class="contact-input"> | |||
| <u-input v-model="form.emergency_contact" placeholder="联系人姓名" border="surround" /> | |||
| @@ -57,6 +51,85 @@ | |||
| </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">医院名称</text> | |||
| <u-input v-model="form.hospital" placeholder="请输入就诊医院名称" border="surround" /> | |||
| </view> | |||
| <view class="form-group"> | |||
| <text class="form-label">癌种</text> | |||
| <u-radio-group v-model="form.tag" placement="row" :wrap="true"> | |||
| <u-radio v-for="t in tagOptions" :key="t" :label="t" :name="t" | |||
| activeColor="#0E63E3" :customStyle="{ marginRight: '24rpx', marginBottom: '16rpx' }" /> | |||
| </u-radio-group> | |||
| </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 class="section"> | |||
| <view class="section-title"> | |||
| @@ -144,6 +217,10 @@ | |||
| <u-picker v-if="regionColumns[0].length" :show="showRegionPicker" :columns="regionColumns" @confirm="onRegionConfirm" | |||
| @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"> | |||
| <view class="confirm-popup"> | |||
| @@ -170,9 +247,22 @@ const form = reactive({ | |||
| city_code: '', | |||
| district_code: '', | |||
| address: '', | |||
| hospital: '', | |||
| emergency_contact: '', | |||
| emergency_phone: '', | |||
| tag: '', | |||
| 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_privacy: '', | |||
| sign_privacy_jhr: '', | |||
| @@ -185,6 +275,75 @@ const showConfirmPopup = ref(false) | |||
| const subscribeTmplId = ref('') | |||
| const agreed = ref(false) | |||
| // 瘤种选项和送检样本类型 | |||
| const tagOptions = ref([]) | |||
| const sampleTypeList = ref([]) | |||
| const sampleRequired = ref(false) | |||
| const showReturnRegionPicker = ref(false) | |||
| 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({ | |||
| income_amount: '', | |||
| @@ -227,7 +386,7 @@ const regionDefaultIndex = ref([0, 0, 0]) | |||
| const maskedIdCard = computed(() => { | |||
| const v = info.value.id_card || '' | |||
| if (v.length === 18) return v.slice(0, 3) + '***********' + v.slice(-4) | |||
| if (v.length === 18) return v.slice(0, 3) + '****' + v.slice(-4) | |||
| return v | |||
| }) | |||
| @@ -308,6 +467,8 @@ onLoad(async () => { | |||
| await loadRegions() | |||
| await loadInfo() | |||
| loadSubscribeConfig() | |||
| loadTagOptions() | |||
| loadSampleTypes() | |||
| }) | |||
| onBeforeUnmount(() => { | |||
| @@ -395,9 +556,22 @@ const loadInfo = async () => { | |||
| form.city_code = res.data.city_code || '' | |||
| form.district_code = res.data.district_code || '' | |||
| form.address = res.data.address || '' | |||
| form.hospital = res.data.hospital || '' | |||
| form.emergency_contact = res.data.emergency_contact || '' | |||
| form.emergency_phone = res.data.emergency_phone || '' | |||
| form.tag = res.data.tag || '' | |||
| 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_privacy = res.data.sign_privacy || '' | |||
| form.sign_privacy_jhr = res.data.sign_privacy_jhr || '' | |||
| @@ -423,6 +597,55 @@ const loadInfo = async () => { | |||
| } catch (e) {} | |||
| } | |||
| const loadTagOptions = async () => { | |||
| try { | |||
| const res = await get('/common/tagOptions') | |||
| tagOptions.value = res.data || [] | |||
| } 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 = () => { | |||
| uni.chooseImage({ | |||
| count: 9 - form.documents.length, | |||
| @@ -462,6 +685,35 @@ const handleSubmit = async () => { | |||
| if (!form.address.trim()) { | |||
| return uni.showToast({ title: '请填写详细地址', icon: 'none' }) | |||
| } | |||
| if (!form.emergency_contact || !form.emergency_phone) { | |||
| return uni.showToast({ title: '请填写紧急联系人信息', icon: 'none' }) | |||
| } | |||
| if (form.emergency_phone === info.value.phone) { | |||
| return uni.showToast({ title: '紧急联系人电话不能与本人手机号一致', icon: 'none' }) | |||
| } | |||
| if (!form.hospital || !form.hospital.trim()) { | |||
| return uni.showToast({ title: '请填写医院名称', icon: 'none' }) | |||
| } | |||
| if (!form.tag) { | |||
| 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) { | |||
| return uni.showToast({ title: '请至少上传一份检查报告或诊断证明', icon: 'none' }) | |||
| @@ -499,9 +751,22 @@ const doSubmit = async () => { | |||
| city_code: form.city_code, | |||
| district_code: form.district_code, | |||
| address: form.address.trim(), | |||
| hospital: form.hospital, | |||
| emergency_contact: form.emergency_contact, | |||
| emergency_phone: form.emergency_phone, | |||
| tag: form.tag, | |||
| 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_privacy: form.sign_privacy, | |||
| sign_privacy_jhr: form.sign_privacy_jhr, | |||
| @@ -578,6 +843,35 @@ const doSubmit = async () => { | |||
| } | |||
| } | |||
| .info-compact { | |||
| padding-bottom: 20rpx; | |||
| border-bottom: 1rpx solid #f0f0f0; | |||
| } | |||
| .info-compact-row { | |||
| display: flex; | |||
| gap: 32rpx; | |||
| margin-bottom: 12rpx; | |||
| &:last-child { | |||
| margin-bottom: 0; | |||
| } | |||
| } | |||
| .info-compact-item { | |||
| font-size: 26rpx; | |||
| color: #666; | |||
| } | |||
| .fill-self-btn { | |||
| font-size: 24rpx; | |||
| color: #0e63e3; | |||
| padding: 6rpx 16rpx; | |||
| border: 1rpx solid #d0e0ff; | |||
| border-radius: 20rpx; | |||
| background: #f0f5ff; | |||
| } | |||
| .form-label { | |||
| font-size: 28rpx; | |||
| color: #555; | |||