Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 
 

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