| @@ -12,23 +12,17 @@ | |||||
| <u-icon name="account-fill" size="18" color="#0e63e3" /> | <u-icon name="account-fill" size="18" color="#0e63e3" /> | ||||
| <text>基本信息</text> | <text>基本信息</text> | ||||
| </view> | </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> | ||||
| <view class="form-group" v-else> | |||||
| <view class="form-group" v-if="!info.gender"> | |||||
| <text class="form-label">性别</text> | <text class="form-label">性别</text> | ||||
| <view class="gender-row"> | <view class="gender-row"> | ||||
| <view class="gender-item" :class="{ active: form.gender === '男' }" @tap="form.gender = '男'">男</view> | <view class="gender-item" :class="{ active: form.gender === '男' }" @tap="form.gender = '男'">男</view> | ||||
| @@ -45,7 +39,7 @@ | |||||
| :customStyle="{ marginTop: '16rpx' }" /> | :customStyle="{ marginTop: '16rpx' }" /> | ||||
| </view> | </view> | ||||
| <view class="form-group"> | <view class="form-group"> | ||||
| <text class="form-label">紧急联系人(选填)</text> | |||||
| <text class="form-label">紧急联系人</text> | |||||
| <view class="contact-row"> | <view class="contact-row"> | ||||
| <view class="contact-input"> | <view class="contact-input"> | ||||
| <u-input v-model="form.emergency_contact" placeholder="联系人姓名" border="surround" /> | <u-input v-model="form.emergency_contact" placeholder="联系人姓名" border="surround" /> | ||||
| @@ -57,6 +51,85 @@ | |||||
| </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"> | |||||
| <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"> | ||||
| <view class="section-title"> | <view class="section-title"> | ||||
| @@ -144,6 +217,10 @@ | |||||
| <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"> | ||||
| @@ -170,9 +247,22 @@ const form = reactive({ | |||||
| city_code: '', | city_code: '', | ||||
| district_code: '', | district_code: '', | ||||
| address: '', | address: '', | ||||
| hospital: '', | |||||
| emergency_contact: '', | emergency_contact: '', | ||||
| emergency_phone: '', | emergency_phone: '', | ||||
| 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: '', | ||||
| @@ -185,6 +275,75 @@ const showConfirmPopup = ref(false) | |||||
| const subscribeTmplId = ref('') | const subscribeTmplId = ref('') | ||||
| const agreed = ref(false) | 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({ | const signExtra = reactive({ | ||||
| income_amount: '', | income_amount: '', | ||||
| @@ -227,7 +386,7 @@ const regionDefaultIndex = ref([0, 0, 0]) | |||||
| const maskedIdCard = computed(() => { | const maskedIdCard = computed(() => { | ||||
| const v = info.value.id_card || '' | 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 | return v | ||||
| }) | }) | ||||
| @@ -308,6 +467,8 @@ onLoad(async () => { | |||||
| await loadRegions() | await loadRegions() | ||||
| await loadInfo() | await loadInfo() | ||||
| loadSubscribeConfig() | loadSubscribeConfig() | ||||
| loadTagOptions() | |||||
| loadSampleTypes() | |||||
| }) | }) | ||||
| onBeforeUnmount(() => { | onBeforeUnmount(() => { | ||||
| @@ -395,9 +556,22 @@ const loadInfo = async () => { | |||||
| form.city_code = res.data.city_code || '' | form.city_code = res.data.city_code || '' | ||||
| form.district_code = res.data.district_code || '' | form.district_code = res.data.district_code || '' | ||||
| form.address = res.data.address || '' | form.address = res.data.address || '' | ||||
| form.hospital = res.data.hospital || '' | |||||
| form.emergency_contact = res.data.emergency_contact || '' | form.emergency_contact = res.data.emergency_contact || '' | ||||
| form.emergency_phone = res.data.emergency_phone || '' | form.emergency_phone = res.data.emergency_phone || '' | ||||
| 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 || '' | ||||
| @@ -423,6 +597,55 @@ const loadInfo = async () => { | |||||
| } catch (e) {} | } 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 = () => { | const chooseDocument = () => { | ||||
| uni.chooseImage({ | uni.chooseImage({ | ||||
| count: 9 - form.documents.length, | count: 9 - form.documents.length, | ||||
| @@ -462,6 +685,35 @@ const handleSubmit = async () => { | |||||
| if (!form.address.trim()) { | if (!form.address.trim()) { | ||||
| return uni.showToast({ title: '请填写详细地址', icon: 'none' }) | 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) { | if (!form.documents || form.documents.length === 0) { | ||||
| return uni.showToast({ title: '请至少上传一份检查报告或诊断证明', icon: 'none' }) | return uni.showToast({ title: '请至少上传一份检查报告或诊断证明', icon: 'none' }) | ||||
| @@ -499,9 +751,22 @@ const doSubmit = async () => { | |||||
| city_code: form.city_code, | city_code: form.city_code, | ||||
| district_code: form.district_code, | district_code: form.district_code, | ||||
| address: form.address.trim(), | address: form.address.trim(), | ||||
| hospital: form.hospital, | |||||
| emergency_contact: form.emergency_contact, | emergency_contact: form.emergency_contact, | ||||
| emergency_phone: form.emergency_phone, | emergency_phone: form.emergency_phone, | ||||
| 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, | ||||
| @@ -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 { | .form-label { | ||||
| font-size: 28rpx; | font-size: 28rpx; | ||||
| color: #555; | color: #555; | ||||