You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

940 rivejä
26 KiB

  1. <template>
  2. <view class="page">
  3. <!-- 驳回原因提示 -->
  4. <view v-if="info.status === 2 && info.reject_reason" class="reject-tip">
  5. <u-icon name="warning-fill" size="20" color="#fa8c16" />
  6. <text class="reject-text">驳回原因:{{ info.reject_reason }}</text>
  7. </view>
  8. <!-- 基本信息 -->
  9. <view class="section">
  10. <view class="section-title">
  11. <u-icon name="account-fill" size="18" color="#0e63e3" />
  12. <text>基本信息</text>
  13. </view>
  14. <view class="info-compact">
  15. <view class="info-compact-row">
  16. <text class="info-compact-item">姓名:{{ info.name }}</text>
  17. <text class="info-compact-item" v-if="info.gender">性别:{{ info.gender }}</text>
  18. </view>
  19. <view class="info-compact-row">
  20. <text class="info-compact-item">身份证:{{ maskedIdCard }}</text>
  21. <text class="info-compact-item">手机号:{{ maskedPhone }}</text>
  22. </view>
  23. </view>
  24. <view class="form-group" v-if="!info.gender">
  25. <text class="form-label">性别</text>
  26. <view class="gender-row">
  27. <view class="gender-item" :class="{ active: form.gender === '男' }" @tap="form.gender = '男'">男</view>
  28. <view class="gender-item" :class="{ active: form.gender === '女' }" @tap="form.gender = '女'">女</view>
  29. </view>
  30. </view>
  31. <view class="form-group">
  32. <text class="form-label">联系地址</text>
  33. <view class="region-row" @tap="showRegionPicker = true">
  34. <text :class="['region-text', regionText ? '' : 'placeholder']">{{ regionText || '请选择省/市/区' }}</text>
  35. <text class="arrow">›</text>
  36. </view>
  37. <u-input v-model="form.address" placeholder="详细街道地址" border="surround"
  38. :customStyle="{ marginTop: '16rpx' }" />
  39. </view>
  40. <view class="form-group">
  41. <text class="form-label">紧急联系人</text>
  42. <view class="contact-row">
  43. <view class="contact-input">
  44. <u-input v-model="form.emergency_contact" placeholder="联系人姓名" border="surround" />
  45. </view>
  46. <view class="contact-input">
  47. <u-input v-model="form.emergency_phone" type="number" placeholder="联系人电话" border="surround" maxlength="11" />
  48. </view>
  49. </view>
  50. </view>
  51. <view class="form-group">
  52. <text class="form-label">医院名称</text>
  53. <view class="region-row" @tap="openHospitalPicker">
  54. <text :class="['region-text', form.hospital ? '' : 'placeholder']">{{ form.hospital || '请选择就诊医院' }}</text>
  55. <text class="arrow">›</text>
  56. </view>
  57. </view>
  58. <view class="form-group">
  59. <text class="form-label">癌种</text>
  60. <u-radio-group v-model="form.tag" placement="row" :wrap="true">
  61. <u-radio v-for="t in tagOptions" :key="t" :label="t" :name="t"
  62. activeColor="#0E63E3" :customStyle="{ marginRight: '24rpx', marginBottom: '16rpx' }" />
  63. </u-radio-group>
  64. </view>
  65. </view>
  66. <!-- 资料上传 -->
  67. <view class="section">
  68. <view class="section-title">
  69. <u-icon name="attach" size="18" color="#fa8c16" />
  70. <text>资料上传</text>
  71. </view>
  72. <view class="upload-tip">请上传您的检查报告单或出院诊断证明书,上传图片请尽量平整清晰。可上传多张。</view>
  73. <view class="upload-row">
  74. <view class="upload-item" v-for="(doc, idx) in form.documents" :key="idx">
  75. <image class="upload-img" :src="doc" mode="aspectFill" @tap="previewImage(idx)" />
  76. <view class="upload-del" @tap="form.documents.splice(idx, 1)">×</view>
  77. </view>
  78. <view class="upload-box" @tap="chooseDocument">
  79. <text class="upload-icon">+</text>
  80. <text class="upload-text">上传图片</text>
  81. </view>
  82. </view>
  83. </view>
  84. <!-- 授权签名 -->
  85. <view class="section">
  86. <view class="section-title">
  87. <u-icon name="edit-pen-fill" size="18" color="#52c41a" />
  88. <text>授权签名</text>
  89. </view>
  90. <view class="sign-item">
  91. <view class="sign-left">
  92. <text class="sign-name">个人可支配收入声明</text>
  93. <text :class="['sign-status', signedIncome ? 'signed' : '']">{{ signedIncome ? '已签署' : '未签署' }}</text>
  94. </view>
  95. <view class="sign-btns" v-if="signedIncome">
  96. <view class="sign-btn view" @tap="previewSign('income')">查看</view>
  97. <view class="sign-btn resign" @tap="goSign('income')">重签</view>
  98. </view>
  99. <view class="sign-btn primary" v-else @tap="goSign('income')">去签署</view>
  100. </view>
  101. <view class="sign-item">
  102. <view class="sign-left">
  103. <text class="sign-name">个人信息处理同意书</text>
  104. <text :class="['sign-status', signedPrivacy ? 'signed' : '']">{{ signedPrivacy ? '已签署' : '未签署' }}</text>
  105. </view>
  106. <view class="sign-btns" v-if="signedPrivacy">
  107. <view class="sign-btn view" @tap="previewSign('privacy')">查看</view>
  108. <view class="sign-btn resign" @tap="goSign('privacy')">重签</view>
  109. </view>
  110. <view class="sign-btn primary" v-else @tap="goSign('privacy')">去签署</view>
  111. </view>
  112. <view class="sign-item" v-if="isMinor">
  113. <view class="sign-left">
  114. <text class="sign-name">个人信息处理同意书(监护人)</text>
  115. <text :class="['sign-status', signedPrivacyJhr ? 'signed' : '']">{{ signedPrivacyJhr ? '已签署' : '未签署' }}</text>
  116. </view>
  117. <view class="sign-btns" v-if="signedPrivacyJhr">
  118. <view class="sign-btn view" @tap="previewSign('privacy_jhr')">查看</view>
  119. <view class="sign-btn resign" @tap="goSign('privacy_jhr')">重签</view>
  120. </view>
  121. <view class="sign-btn primary" v-else @tap="goSign('privacy_jhr')">去签署</view>
  122. </view>
  123. <view class="sign-item">
  124. <view class="sign-left">
  125. <text class="sign-name">声明与承诺</text>
  126. <text :class="['sign-status', signedPromise ? 'signed' : '']">{{ signedPromise ? '已签署' : '未签署' }}</text>
  127. </view>
  128. <view class="sign-btns" v-if="signedPromise">
  129. <view class="sign-btn view" @tap="previewSign('promise')">查看</view>
  130. <view class="sign-btn resign" @tap="goSign('promise')">重签</view>
  131. </view>
  132. <view class="sign-btn primary" v-else @tap="goSign('promise')">去签署</view>
  133. </view>
  134. </view>
  135. <!-- 提交按钮 -->
  136. <view class="btn-wrap">
  137. <view class="agree-row" @tap="agreed = !agreed">
  138. <u-checkbox-group>
  139. <u-checkbox :checked="agreed" shape="circle" activeColor="#0E63E3" size="18" @change="agreed = !agreed" />
  140. </u-checkbox-group>
  141. <text class="agree-text">请阅读并同意</text>
  142. <text class="agree-link" @tap.stop="openNotice">《患者告知书》</text>
  143. </view>
  144. <u-button text="提交审核" :loading="submitting" @click="handleSubmit" color="#0E63E3" size="large" />
  145. </view>
  146. <!-- 地区选择器 -->
  147. <u-picker v-if="regionColumns[0].length" :show="showRegionPicker" :columns="regionColumns" @confirm="onRegionConfirm"
  148. @cancel="showRegionPicker = false" @change="onRegionChange" :defaultIndex="regionDefaultIndex" />
  149. <!-- 医院选择器组件 -->
  150. <hospital-picker ref="hospitalPickerRef" :hospital-data="hospitalTree" @confirm="onHospitalConfirm" />
  151. <!-- 已通过重新提交确认弹窗 -->
  152. <u-popup :show="showConfirmPopup" mode="center" round="12" :safeAreaInsetBottom="false" @close="showConfirmPopup = false">
  153. <view class="confirm-popup">
  154. <view class="confirm-title">提示</view>
  155. <view class="confirm-content">您的资料审核已通过,如果重新提交审核会变为待审核,需要平台重新审核,是否确认提交?</view>
  156. <view class="confirm-btns">
  157. <u-button text="取消" size="normal" :plain="true" shape="circle" @click="showConfirmPopup = false" />
  158. <u-button text="确认提交" size="normal" color="#0E63E3" shape="circle" @click="doSubmit" />
  159. </view>
  160. </view>
  161. </u-popup>
  162. </view>
  163. </template>
  164. <script setup>
  165. import { ref, reactive, computed, onBeforeUnmount } from 'vue'
  166. import { onLoad } from '@dcloudio/uni-app'
  167. import { get, post, upload } from '@/utils/request.js'
  168. const info = ref({})
  169. const form = reactive({
  170. gender: '',
  171. province_code: '',
  172. city_code: '',
  173. district_code: '',
  174. address: '',
  175. hospital: '',
  176. hospital_province_code: '',
  177. hospital_city_code: '',
  178. hospital_district_code: '',
  179. emergency_contact: '',
  180. emergency_phone: '',
  181. tag: '',
  182. documents: [],
  183. sign_income: '',
  184. sign_privacy: '',
  185. sign_privacy_jhr: '',
  186. sign_promise: '',
  187. income_amount: ''
  188. })
  189. const submitting = ref(false)
  190. const showRegionPicker = ref(false)
  191. const showConfirmPopup = ref(false)
  192. const subscribeTmplId = ref('')
  193. const agreed = ref(false)
  194. // 瘤种选项
  195. const tagOptions = ref([])
  196. // 医院选择器
  197. const hospitalTree = ref([])
  198. const hospitalPickerRef = ref(null)
  199. const openHospitalPicker = () => {
  200. if (!hospitalTree.value.length) {
  201. uni.showToast({ title: '医院列表加载中,请稍候', icon: 'none' })
  202. loadHospitalTree()
  203. return
  204. }
  205. hospitalPickerRef.value && hospitalPickerRef.value.open({
  206. hospital: form.hospital,
  207. province_code: form.hospital_province_code,
  208. city_code: form.hospital_city_code,
  209. district_code: form.hospital_district_code
  210. })
  211. }
  212. const onHospitalConfirm = (data) => {
  213. form.hospital = data.hospitalName || ''
  214. form.hospital_province_code = data.province_code || ''
  215. form.hospital_city_code = data.city_code || ''
  216. form.hospital_district_code = data.district_code || ''
  217. }
  218. // 签署时的额外信息(用于重签回显)
  219. const signExtra = reactive({
  220. income_amount: '',
  221. guardian_name: '',
  222. guardian_id_card: '',
  223. guardian_relation: ''
  224. })
  225. // 加载订阅消息模板配置
  226. const loadSubscribeConfig = async () => {
  227. try {
  228. const res = await get('/api/mp/subscribeConfig')
  229. if (res.data && res.data.audit_result) {
  230. subscribeTmplId.value = res.data.audit_result
  231. }
  232. } catch (e) {}
  233. }
  234. // 请求订阅消息授权
  235. const requestSubscribe = () => {
  236. return new Promise((resolve) => {
  237. if (!subscribeTmplId.value) return resolve(false)
  238. // #ifdef MP-WEIXIN
  239. wx.requestSubscribeMessage({
  240. tmplIds: [subscribeTmplId.value],
  241. success: () => resolve(true),
  242. fail: () => resolve(false)
  243. })
  244. // #endif
  245. // #ifndef MP-WEIXIN
  246. resolve(false)
  247. // #endif
  248. })
  249. }
  250. // 地区数据
  251. const allRegions = ref([])
  252. const regionColumns = ref([[], [], []])
  253. const regionDefaultIndex = ref([0, 0, 0])
  254. const maskedIdCard = computed(() => {
  255. const v = info.value.id_card || ''
  256. if (v.length === 18) return v.slice(0, 3) + '****' + v.slice(-4)
  257. return v
  258. })
  259. const maskedPhone = computed(() => {
  260. const v = info.value.phone || ''
  261. if (v.length === 11) return v.slice(0, 3) + '****' + v.slice(-4)
  262. return v
  263. })
  264. // 签署状态:form 中有新签的 URL 或 info 中有已保存的 URL
  265. const signedIncome = computed(() => form.sign_income || info.value.sign_income)
  266. const signedPrivacy = computed(() => form.sign_privacy || info.value.sign_privacy)
  267. const signedPrivacyJhr = computed(() => form.sign_privacy_jhr || info.value.sign_privacy_jhr)
  268. const signedPromise = computed(() => form.sign_promise || info.value.sign_promise)
  269. // 判断是否未成年(从身份证号解析年龄)
  270. const isMinor = computed(() => {
  271. const idCard = info.value.id_card || ''
  272. if (idCard.length !== 18) return false
  273. const birthYear = parseInt(idCard.substring(6, 10))
  274. const birthMonth = parseInt(idCard.substring(10, 12))
  275. const birthDay = parseInt(idCard.substring(12, 14))
  276. const now = new Date()
  277. let age = now.getFullYear() - birthYear
  278. const monthDiff = (now.getMonth() + 1) - birthMonth
  279. if (monthDiff < 0 || (monthDiff === 0 && now.getDate() < birthDay)) age--
  280. return age < 18
  281. })
  282. const regionText = computed(() => {
  283. const parts = []
  284. if (form.province_code) {
  285. const p = allRegions.value.find(r => r.code === form.province_code)
  286. if (p) parts.push(p.name)
  287. }
  288. if (form.city_code) {
  289. const prov = allRegions.value.find(r => r.code === form.province_code)
  290. if (prov && prov.children) {
  291. const c = prov.children.find(r => r.code === form.city_code)
  292. if (c) parts.push(c.name)
  293. }
  294. }
  295. if (form.district_code) {
  296. const prov = allRegions.value.find(r => r.code === form.province_code)
  297. if (prov && prov.children) {
  298. const city = prov.children.find(r => r.code === form.city_code)
  299. if (city && city.children) {
  300. const d = city.children.find(r => r.code === form.district_code)
  301. if (d) parts.push(d.name)
  302. }
  303. }
  304. }
  305. return parts.join(' ')
  306. })
  307. // 签署结果事件监听
  308. const onSignResult = (data) => {
  309. if (data.type === 'income') {
  310. form.sign_income = data.url
  311. if (data.amount) {
  312. form.income_amount = data.amount
  313. signExtra.income_amount = data.amount
  314. }
  315. } else if (data.type === 'privacy') {
  316. form.sign_privacy = data.url
  317. } else if (data.type === 'privacy_jhr') {
  318. form.sign_privacy_jhr = data.url
  319. if (data.guardianName) signExtra.guardian_name = data.guardianName
  320. if (data.guardianIdCard) signExtra.guardian_id_card = data.guardianIdCard
  321. if (data.guardianRelation) signExtra.guardian_relation = data.guardianRelation
  322. } else if (data.type === 'promise') {
  323. form.sign_promise = data.url
  324. }
  325. }
  326. onLoad(async () => {
  327. uni.$on('signResult', onSignResult)
  328. await loadRegions()
  329. await loadInfo()
  330. loadSubscribeConfig()
  331. loadTagOptions()
  332. loadHospitalTree()
  333. })
  334. onBeforeUnmount(() => {
  335. uni.$off('signResult', onSignResult)
  336. })
  337. const goSign = (type) => {
  338. let url = `/pages/sign/sign?type=${type}`
  339. if (type === 'income') {
  340. const amt = form.income_amount || signExtra.income_amount || ''
  341. if (amt) url += `&amount=${encodeURIComponent(amt)}`
  342. }
  343. if (type === 'privacy_jhr') {
  344. const gn = signExtra.guardian_name || ''
  345. const gi = signExtra.guardian_id_card || ''
  346. const gr = signExtra.guardian_relation || ''
  347. if (gn) url += `&guardianName=${encodeURIComponent(gn)}`
  348. if (gi) url += `&guardianIdCard=${encodeURIComponent(gi)}`
  349. if (gr) url += `&guardianRelation=${encodeURIComponent(gr)}`
  350. }
  351. uni.navigateTo({ url })
  352. }
  353. const previewSign = (type) => {
  354. const urlMap = {
  355. income: form.sign_income || info.value.sign_income,
  356. privacy: form.sign_privacy || info.value.sign_privacy,
  357. privacy_jhr: form.sign_privacy_jhr || info.value.sign_privacy_jhr,
  358. promise: form.sign_promise || info.value.sign_promise
  359. }
  360. const url = urlMap[type]
  361. if (url) uni.previewImage({ urls: [url], current: 0 })
  362. }
  363. const loadRegions = async () => {
  364. try {
  365. const res = await get('/common/regions')
  366. allRegions.value = res.data || []
  367. buildRegionColumns()
  368. } catch (e) {}
  369. }
  370. const buildRegionColumns = (pIdx = 0, cIdx = 0) => {
  371. const provinces = allRegions.value
  372. const col0 = provinces.map(p => p.name)
  373. const cities = (provinces[pIdx] && provinces[pIdx].children) || []
  374. const col1 = cities.map(c => c.name)
  375. const districts = (cities[cIdx] && cities[cIdx].children) || []
  376. const col2 = districts.map(d => d.name)
  377. regionColumns.value = [col0, col1, col2]
  378. }
  379. const onRegionChange = (e) => {
  380. const { columnIndex, index } = e
  381. if (columnIndex === 0) {
  382. buildRegionColumns(index, 0)
  383. regionDefaultIndex.value = [index, 0, 0]
  384. } else if (columnIndex === 1) {
  385. const pIdx = regionDefaultIndex.value[0]
  386. buildRegionColumns(pIdx, index)
  387. regionDefaultIndex.value = [pIdx, index, 0]
  388. }
  389. }
  390. const onRegionConfirm = (e) => {
  391. const idxs = e.indexs || e.index || [0, 0, 0]
  392. const provinces = allRegions.value
  393. const prov = provinces[idxs[0]]
  394. const city = prov && prov.children ? prov.children[idxs[1]] : null
  395. const dist = city && city.children ? city.children[idxs[2]] : null
  396. form.province_code = prov ? prov.code : ''
  397. form.city_code = city ? city.code : ''
  398. form.district_code = dist ? dist.code : ''
  399. showRegionPicker.value = false
  400. }
  401. const loadInfo = async () => {
  402. try {
  403. const res = await get('/api/mp/myInfo')
  404. if (!res.data) return
  405. info.value = res.data
  406. // 填充表单
  407. form.gender = res.data.gender || ''
  408. form.province_code = res.data.province_code || ''
  409. form.city_code = res.data.city_code || ''
  410. form.district_code = res.data.district_code || ''
  411. form.address = res.data.address || ''
  412. form.hospital = res.data.hospital || ''
  413. form.hospital_province_code = res.data.hospital_province_code || ''
  414. form.hospital_city_code = res.data.hospital_city_code || ''
  415. form.hospital_district_code = res.data.hospital_district_code || ''
  416. form.emergency_contact = res.data.emergency_contact || ''
  417. form.emergency_phone = res.data.emergency_phone || ''
  418. form.tag = res.data.tag || ''
  419. form.documents = res.data.documents || []
  420. form.sign_income = res.data.sign_income || ''
  421. form.sign_privacy = res.data.sign_privacy || ''
  422. form.sign_privacy_jhr = res.data.sign_privacy_jhr || ''
  423. form.sign_promise = res.data.sign_promise || ''
  424. form.income_amount = res.data.income_amount || ''
  425. signExtra.income_amount = res.data.income_amount || ''
  426. signExtra.guardian_name = res.data.guardian_name || ''
  427. signExtra.guardian_id_card = res.data.guardian_id_card || ''
  428. signExtra.guardian_relation = res.data.guardian_relation || ''
  429. // 设置地区选择器默认索引
  430. if (form.province_code && allRegions.value.length) {
  431. const pIdx = allRegions.value.findIndex(r => r.code === form.province_code)
  432. if (pIdx >= 0) {
  433. const cities = allRegions.value[pIdx].children || []
  434. const cIdx = cities.findIndex(r => r.code === form.city_code)
  435. const ci = cIdx >= 0 ? cIdx : 0
  436. const districts = (cities[ci] && cities[ci].children) || []
  437. const dIdx = districts.findIndex(r => r.code === form.district_code)
  438. buildRegionColumns(pIdx, ci)
  439. regionDefaultIndex.value = [pIdx, ci, dIdx >= 0 ? dIdx : 0]
  440. }
  441. }
  442. } catch (e) {}
  443. }
  444. const loadTagOptions = async () => {
  445. try {
  446. const res = await get('/common/tagOptions')
  447. tagOptions.value = res.data || []
  448. } catch (e) {}
  449. }
  450. const loadHospitalTree = async () => {
  451. try {
  452. const res = await get('/common/hospitalTree')
  453. hospitalTree.value = res.data || []
  454. } catch (e) {}
  455. }
  456. const chooseDocument = () => {
  457. uni.chooseImage({
  458. count: 9 - form.documents.length,
  459. sizeType: ['compressed'],
  460. sourceType: ['album', 'camera'],
  461. success: async (res) => {
  462. for (const filePath of res.tempFilePaths) {
  463. try {
  464. const uploadRes = await upload('/api/mp/upload', { filePath, name: 'file' })
  465. if (uploadRes.data && uploadRes.data.url) {
  466. form.documents.push(uploadRes.data.url)
  467. }
  468. } catch (e) {}
  469. }
  470. }
  471. })
  472. }
  473. const previewImage = (idx) => {
  474. uni.previewImage({ urls: form.documents, current: idx })
  475. }
  476. const openNotice = () => {
  477. uni.navigateTo({ url: '/pages/content/content?key=patient_information_sheet' })
  478. }
  479. const handleSubmit = async () => {
  480. if (!agreed.value) {
  481. return uni.showToast({ title: '请阅读并同意《患者告知书》', icon: 'none' })
  482. }
  483. if (!info.value.gender && !form.gender) {
  484. return uni.showToast({ title: '请选择性别', icon: 'none' })
  485. }
  486. if (!form.province_code || !form.city_code || !form.district_code) {
  487. return uni.showToast({ title: '请选择省市区', icon: 'none' })
  488. }
  489. if (!form.address.trim()) {
  490. return uni.showToast({ title: '请填写详细地址', icon: 'none' })
  491. }
  492. if (!form.emergency_contact || !form.emergency_phone) {
  493. return uni.showToast({ title: '请填写紧急联系人信息', icon: 'none' })
  494. }
  495. if (form.emergency_phone === info.value.phone) {
  496. return uni.showToast({ title: '紧急联系人电话不能与本人手机号一致', icon: 'none' })
  497. }
  498. if (!form.hospital || !form.hospital.trim()) {
  499. return uni.showToast({ title: '请填写医院名称', icon: 'none' })
  500. }
  501. if (!form.tag) {
  502. return uni.showToast({ title: '请选择癌种', icon: 'none' })
  503. }
  504. // 资料上传校验:至少上传一个
  505. if (!form.documents || form.documents.length === 0) {
  506. return uni.showToast({ title: '请至少上传一份检查报告或诊断证明', icon: 'none' })
  507. }
  508. // 签名校验:全部必须签
  509. if (!signedIncome.value) {
  510. return uni.showToast({ title: '请签署个人可支配收入声明', icon: 'none' })
  511. }
  512. if (!signedPrivacy.value) {
  513. return uni.showToast({ title: '请签署个人信息处理同意书', icon: 'none' })
  514. }
  515. if (isMinor.value && !signedPrivacyJhr.value) {
  516. return uni.showToast({ title: '请签署个人信息处理同意书(监护人)', icon: 'none' })
  517. }
  518. if (!signedPromise.value) {
  519. return uni.showToast({ title: '请签署声明与承诺', icon: 'none' })
  520. }
  521. // 已通过状态需要二次确认
  522. if (info.value.status === 1) {
  523. showConfirmPopup.value = true
  524. return
  525. }
  526. await doSubmit()
  527. }
  528. const doSubmit = async () => {
  529. showConfirmPopup.value = false
  530. // 先请求订阅消息授权(用户拒绝也继续提交)
  531. await requestSubscribe()
  532. submitting.value = true
  533. try {
  534. const params = {
  535. gender: info.value.gender || form.gender,
  536. province_code: form.province_code,
  537. city_code: form.city_code,
  538. district_code: form.district_code,
  539. address: form.address.trim(),
  540. hospital: form.hospital,
  541. hospital_province_code: form.hospital_province_code,
  542. hospital_city_code: form.hospital_city_code,
  543. hospital_district_code: form.hospital_district_code,
  544. emergency_contact: form.emergency_contact,
  545. emergency_phone: form.emergency_phone,
  546. tag: form.tag,
  547. documents: form.documents,
  548. sign_income: form.sign_income,
  549. sign_privacy: form.sign_privacy,
  550. sign_privacy_jhr: form.sign_privacy_jhr,
  551. sign_promise: form.sign_promise,
  552. income_amount: form.income_amount || null,
  553. guardian_name: signExtra.guardian_name || '',
  554. guardian_id_card: signExtra.guardian_id_card || '',
  555. guardian_relation: signExtra.guardian_relation || '',
  556. // #ifdef MP-WEIXIN
  557. mp_env_version: uni.getAccountInfoSync().miniProgram.envVersion || 'release'
  558. // #endif
  559. }
  560. await post('/api/mp/saveMyInfo', params)
  561. uni.showToast({ title: '提交成功', icon: 'success' })
  562. setTimeout(() => uni.navigateBack(), 1500)
  563. } catch (e) {
  564. if (e && e.msg) uni.showToast({ title: e.msg, icon: 'none' })
  565. } finally {
  566. submitting.value = false
  567. }
  568. }
  569. </script>
  570. <style lang="scss" scoped>
  571. .page {
  572. min-height: 100vh;
  573. background: #f4f4f5;
  574. padding: 24rpx;
  575. padding-bottom: 340rpx;
  576. }
  577. .section {
  578. background: #fff;
  579. border-radius: 10rpx;
  580. padding: 32rpx;
  581. margin-bottom: 24rpx;
  582. border: 1rpx solid #ebeef5;
  583. }
  584. .reject-tip {
  585. display: flex;
  586. align-items: flex-start;
  587. padding: 24rpx 28rpx;
  588. background: #fff2f0;
  589. border: 1rpx solid #ffccc7;
  590. border-radius: 10rpx;
  591. margin-bottom: 24rpx;
  592. .reject-text {
  593. flex: 1;
  594. font-size: 26rpx;
  595. color: #f5222d;
  596. margin-left: 16rpx;
  597. line-height: 1.5;
  598. }
  599. }
  600. .section-title {
  601. display: flex;
  602. align-items: center;
  603. gap: 12rpx;
  604. font-size: 32rpx;
  605. font-weight: 600;
  606. color: #333;
  607. margin-bottom: 28rpx;
  608. }
  609. .form-group {
  610. padding: 20rpx 0;
  611. border-bottom: 1rpx solid #f0f0f0;
  612. &:last-child {
  613. border-bottom: none;
  614. }
  615. }
  616. .info-compact {
  617. padding-bottom: 20rpx;
  618. border-bottom: 1rpx solid #f0f0f0;
  619. }
  620. .info-compact-row {
  621. display: flex;
  622. gap: 32rpx;
  623. margin-bottom: 12rpx;
  624. &:last-child {
  625. margin-bottom: 0;
  626. }
  627. }
  628. .info-compact-item {
  629. font-size: 26rpx;
  630. color: #666;
  631. }
  632. .form-label {
  633. font-size: 28rpx;
  634. color: #555;
  635. margin-bottom: 16rpx;
  636. display: block;
  637. }
  638. .readonly-input {
  639. padding: 20rpx 24rpx;
  640. background: #f5f5f5;
  641. border: 1rpx solid #ddd;
  642. border-radius: 12rpx;
  643. font-size: 28rpx;
  644. color: #333;
  645. }
  646. .gender-row {
  647. display: flex;
  648. gap: 20rpx;
  649. }
  650. .gender-item {
  651. flex: 1;
  652. text-align: center;
  653. padding: 16rpx 0;
  654. border: 1rpx solid #ddd;
  655. border-radius: 8rpx;
  656. font-size: 28rpx;
  657. color: #333;
  658. &.active {
  659. border-color: #0e63e3;
  660. color: #0e63e3;
  661. background: rgba(14, 99, 227, 0.05);
  662. }
  663. }
  664. .region-row {
  665. display: flex;
  666. align-items: center;
  667. justify-content: space-between;
  668. padding: 20rpx 24rpx;
  669. border: 1rpx solid #ddd;
  670. border-radius: 8rpx;
  671. }
  672. .region-text {
  673. font-size: 28rpx;
  674. color: #333;
  675. &.placeholder {
  676. color: #c0c4cc;
  677. }
  678. }
  679. .arrow {
  680. font-size: 28rpx;
  681. color: #c0c4cc;
  682. }
  683. .contact-row {
  684. display: flex;
  685. gap: 16rpx;
  686. }
  687. .contact-input {
  688. flex: 1;
  689. }
  690. .upload-tip {
  691. font-size: 26rpx;
  692. color: #888;
  693. line-height: 1.6;
  694. margin-bottom: 20rpx;
  695. }
  696. .upload-row {
  697. display: flex;
  698. gap: 16rpx;
  699. flex-wrap: wrap;
  700. }
  701. .upload-item {
  702. position: relative;
  703. width: 180rpx;
  704. height: 180rpx;
  705. }
  706. .upload-img {
  707. width: 180rpx;
  708. height: 180rpx;
  709. border-radius: 8rpx;
  710. border: 1rpx solid #eee;
  711. }
  712. .upload-del {
  713. position: absolute;
  714. top: -10rpx;
  715. right: -10rpx;
  716. width: 40rpx;
  717. height: 40rpx;
  718. background: rgba(0, 0, 0, 0.5);
  719. color: #fff;
  720. border-radius: 50%;
  721. font-size: 24rpx;
  722. display: flex;
  723. align-items: center;
  724. justify-content: center;
  725. }
  726. .upload-box {
  727. width: 180rpx;
  728. height: 180rpx;
  729. border: 2rpx dashed #ccc;
  730. border-radius: 8rpx;
  731. display: flex;
  732. flex-direction: column;
  733. align-items: center;
  734. justify-content: center;
  735. background: #fafafa;
  736. }
  737. .upload-icon {
  738. font-size: 56rpx;
  739. color: #ccc;
  740. }
  741. .upload-text {
  742. font-size: 22rpx;
  743. color: #999;
  744. margin-top: 4rpx;
  745. }
  746. .sign-item {
  747. display: flex;
  748. align-items: center;
  749. justify-content: space-between;
  750. padding: 28rpx 0;
  751. border-bottom: 1rpx solid #f0f0f0;
  752. &:last-child {
  753. border-bottom: none;
  754. }
  755. }
  756. .sign-left {
  757. display: flex;
  758. flex-direction: column;
  759. gap: 8rpx;
  760. }
  761. .sign-name {
  762. font-size: 28rpx;
  763. color: #333;
  764. }
  765. .sign-status {
  766. font-size: 24rpx;
  767. color: #f5222d;
  768. &.signed {
  769. color: #52c41a;
  770. }
  771. }
  772. .sign-btns {
  773. display: flex;
  774. gap: 12rpx;
  775. flex-shrink: 0;
  776. }
  777. .sign-btn {
  778. padding: 10rpx 28rpx;
  779. border-radius: 28rpx;
  780. font-size: 24rpx;
  781. text-align: center;
  782. flex-shrink: 0;
  783. &.primary {
  784. background: #0e63e3;
  785. color: #fff;
  786. }
  787. &.view {
  788. background: #f0f5ff;
  789. color: #0e63e3;
  790. border: 1rpx solid #d0e0ff;
  791. }
  792. &.resign {
  793. background: #fff7e6;
  794. color: #fa8c16;
  795. border: 1rpx solid #ffe7ba;
  796. }
  797. &:active {
  798. opacity: 0.7;
  799. }
  800. }
  801. .btn-wrap {
  802. position: fixed;
  803. bottom: 0;
  804. left: 0;
  805. right: 0;
  806. background: #fff;
  807. padding: 20rpx 32rpx;
  808. padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
  809. box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.06);
  810. z-index: 100;
  811. }
  812. .agree-row {
  813. display: flex;
  814. align-items: center;
  815. margin-bottom: 16rpx;
  816. padding-left: 4rpx;
  817. }
  818. .agree-text {
  819. font-size: 24rpx;
  820. color: #666;
  821. margin-left: 8rpx;
  822. }
  823. .agree-link {
  824. font-size: 24rpx;
  825. color: #0e63e3;
  826. }
  827. .confirm-popup {
  828. padding: 48rpx 40rpx 40rpx;
  829. width: 560rpx;
  830. }
  831. .confirm-title {
  832. font-size: 34rpx;
  833. font-weight: 600;
  834. color: #333;
  835. text-align: center;
  836. margin-bottom: 28rpx;
  837. }
  838. .confirm-content {
  839. font-size: 28rpx;
  840. color: #666;
  841. line-height: 1.7;
  842. text-align: center;
  843. margin-bottom: 40rpx;
  844. }
  845. .confirm-btns {
  846. display: flex;
  847. gap: 24rpx;
  848. }
  849. </style>