service-role-apply.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748
  1. <template>
  2. <view class="page">
  3. <!-- 审核状态展示 -->
  4. <view v-if="showStatusView" class="status-view">
  5. <view class="status-card" :class="'status-' + applyStatus">
  6. <view class="status-icon">{{ statusIcon }}</view>
  7. <text class="status-title">{{ statusTitle }}</text>
  8. <text class="status-desc">{{ statusDesc }}</text>
  9. </view>
  10. <view v-if="applyStatus === 'pending'" class="status-tip">
  11. <text>预计 1-3 个工作日内完成审核,请耐心等待</text>
  12. </view>
  13. <view v-if="applyStatus === 'rejected'" class="reject-info">
  14. <text class="reject-label">拒绝原因</text>
  15. <text class="reject-reason">{{ applyRecord && applyRecord.rejectReason }}</text>
  16. </view>
  17. <view class="action-buttons">
  18. <button v-if="applyStatus === 'rejected'" class="btn-reapply" @click="handleReapply">
  19. 重新申请
  20. </button>
  21. <button v-if="applyStatus === 'approved'" class="btn-center" @click="goToCenter">
  22. 进入服务商中心
  23. </button>
  24. <button class="btn-back" @click="goBack">返回首页</button>
  25. </view>
  26. </view>
  27. <!-- 申请表单 -->
  28. <view v-else class="form-wrap">
  29. <view class="header">
  30. <text class="title">服务角色申请</text>
  31. <text class="subtitle">选择申请角色,提交资质信息</text>
  32. </view>
  33. <view class="section">
  34. <view class="section-title">选择申请角色 <text class="required">*</text></view>
  35. <!-- 严格三列:roleRows 每行固定 3 个单元格,flex:1 等分,不依赖 calc/gap -->
  36. <view class="role-grid">
  37. <view
  38. v-for="(row, rowIdx) in roleRows"
  39. :key="getRoleRowKey(rowIdx)"
  40. class="role-row"
  41. >
  42. <view
  43. v-for="role in row"
  44. :key="role.key"
  45. class="role-cell"
  46. >
  47. <view
  48. v-if="!role.placeholder"
  49. class="role-card"
  50. :class="{ selected: selectedRoles.indexOf(role.key) > -1 }"
  51. @click="toggleRole(role.key)"
  52. >
  53. <text class="role-icon">{{ role.icon }}</text>
  54. <text class="role-name">{{ role.name }}</text>
  55. <view v-if="selectedRoles.indexOf(role.key) > -1" class="check-mark">✓</view>
  56. </view>
  57. </view>
  58. </view>
  59. </view>
  60. </view>
  61. <view class="section">
  62. <view class="section-title">身份证信息</view>
  63. <input class="input" v-model="form.idCard" placeholder="请输入身份证号码" maxlength="18" />
  64. <view class="upload-row">
  65. <view class="upload-item" @click="chooseIdCardImage('front')">
  66. <image v-if="form.idCardFront" :src="form.idCardFront" mode="aspectFill" class="upload-preview" />
  67. <view v-else class="upload-placeholder">
  68. <text class="upload-icon">+</text>
  69. <text class="upload-label">身份证正面</text>
  70. </view>
  71. </view>
  72. <view class="upload-item" @click="chooseIdCardImage('back')">
  73. <image v-if="form.idCardBack" :src="form.idCardBack" mode="aspectFill" class="upload-preview" />
  74. <view v-else class="upload-placeholder">
  75. <text class="upload-icon">+</text>
  76. <text class="upload-label">身份证反面</text>
  77. </view>
  78. </view>
  79. </view>
  80. </view>
  81. <view class="section">
  82. <view class="section-title">资质证书</view>
  83. <view class="cert-list">
  84. <view v-for="(img, idx) in form.certificateImages" :key="getCertKey(idx)" class="cert-item">
  85. <image :src="img" mode="aspectFill" class="cert-preview" />
  86. <text class="cert-del" @click="removeCert(idx)">×</text>
  87. </view>
  88. <view class="cert-add" @click="chooseCertificate">
  89. <text class="upload-icon">+</text>
  90. <text class="upload-label">添加证书</text>
  91. </view>
  92. </view>
  93. </view>
  94. <view class="section">
  95. <view class="section-title">联系地址 <text class="required">*</text></view>
  96. <view class="address-row" @click="openAddressPicker">
  97. <text class="address-label">所在地区</text>
  98. <view class="address-value-wrap">
  99. <text v-if="addressText" class="address-value">{{ addressText }}</text>
  100. <text v-else class="address-placeholder">请选择省市区街道</text>
  101. <text class="address-arrow">›</text>
  102. </view>
  103. </view>
  104. <input class="input" v-model="form.addressDetail" placeholder="详细地址(门牌号等)" />
  105. <!-- 与收件人地址共用同一组件 components/address-picker.vue -->
  106. <AddressPicker ref="addressPicker" :show-trigger="false" @change="onAddressChange" />
  107. </view>
  108. <button class="submit-btn" :disabled="submitting" @click="handleSubmit">
  109. {{ submitting ? '提交中...' : '提交申请' }}
  110. </button>
  111. <view class="footer-hint">
  112. <text>提交后将在 1-3 个工作日内完成审核</text>
  113. </view>
  114. </view>
  115. </view>
  116. </template>
  117. <script>
  118. import { submitServiceRoleApply, getMyServiceRoleApply, uploadServiceRoleImage } from '../../utils/api'
  119. import AddressPicker from '@/components/address-picker.vue'
  120. export default {
  121. components: { AddressPicker },
  122. data() {
  123. return {
  124. roleList: [
  125. { key: 'planner', name: '规划师', icon: '📋' },
  126. { key: 'nutritionist', name: '营养师', icon: '🥗' },
  127. { key: 'butler', name: '管家', icon: '🔔' },
  128. { key: 'vendor', name: '服务商', icon: '🏪' },
  129. { key: 'article_admin', name: '文章管理员', icon: '📝' },
  130. { key: 'activity_provider', name: '活动提供商', icon: '🎪' }
  131. ],
  132. selectedRoles: [],
  133. addressText: '',
  134. form: {
  135. idCard: '',
  136. idCardFront: '',
  137. idCardBack: '',
  138. certificateImages: [],
  139. province: '',
  140. city: '',
  141. district: '',
  142. street: '',
  143. addressDetail: ''
  144. },
  145. submitting: false,
  146. applyRecord: null,
  147. showStatusView: false
  148. }
  149. },
  150. computed: {
  151. // 按每行 3 个拆分行;最后一行不足 3 个用空占位单元格补齐,保证各行严格三列等宽
  152. roleRows() {
  153. const rows = []
  154. const list = this.roleList || []
  155. const total = Math.ceil(list.length / 3) * 3
  156. for (let i = 0; i < total; i += 3) {
  157. const row = []
  158. for (let j = 0; j < 3; j++) {
  159. const item = list[i + j]
  160. row.push(
  161. item
  162. ? item
  163. : { key: 'role-placeholder-' + i + '-' + j, name: '', icon: '', placeholder: true }
  164. )
  165. }
  166. rows.push(row)
  167. }
  168. return rows
  169. },
  170. applyStatus() {
  171. if (!this.applyRecord) return 'none'
  172. const s = this.applyRecord.status
  173. return s === 0 ? 'pending' : s === 1 ? 'approved' : 'rejected'
  174. },
  175. statusIcon() {
  176. return { pending: '⏳', approved: '✅', rejected: '❌' }[this.applyStatus] || '📋'
  177. },
  178. statusTitle() {
  179. return { pending: '审核中', approved: '申请已通过', rejected: '申请被驳回' }[this.applyStatus] || ''
  180. },
  181. statusDesc() {
  182. return {
  183. pending: '您的申请正在审核中,请留意消息通知',
  184. approved: '恭喜!您已成功通过审核,可以开始使用服务商功能',
  185. rejected: '您的申请未通过审核,可查看原因后重新提交'
  186. }[this.applyStatus] || ''
  187. }
  188. },
  189. onLoad() {
  190. this.checkExistingApply()
  191. },
  192. methods: {
  193. // 模板里不能直接调用 uni.*(渲染时实例上没有 uni,会报
  194. // "Property or method uni is not defined"),统一收进方法
  195. goBack() {
  196. var pages = getCurrentPages()
  197. if (pages && pages.length > 1) {
  198. uni.navigateBack()
  199. } else {
  200. uni.switchTab({ url: '/pages/index/index' })
  201. }
  202. },
  203. async checkExistingApply() {
  204. try {
  205. const res = await getMyServiceRoleApply()
  206. if (res.code === 200 && res.data) {
  207. this.applyRecord = res.data
  208. const status = res.data.status
  209. if (status === 0) {
  210. this.showStatusView = true
  211. } else if (status === 1) {
  212. // 已通过,跳转服务商中心
  213. setTimeout(() => uni.redirectTo({ url: '/pages/vendor/center' }), 800)
  214. } else if (status === 2) {
  215. // 已拒绝,显示原因
  216. this.showStatusView = true
  217. }
  218. }
  219. } catch (e) {
  220. // 无申请记录,显示表单
  221. }
  222. },
  223. toggleRole(key) {
  224. const idx = this.selectedRoles.indexOf(key)
  225. if (idx > -1) {
  226. this.selectedRoles.splice(idx, 1)
  227. } else {
  228. this.selectedRoles.push(key)
  229. }
  230. },
  231. chooseIdCardImage(side) {
  232. uni.chooseImage({
  233. count: 1,
  234. sizeType: ['compressed'],
  235. sourceType: ['album', 'camera'],
  236. success: async (res) => {
  237. uni.showLoading({ title: '上传中...' })
  238. try {
  239. const uploadRes = await uploadServiceRoleImage(res.tempFilePaths[0])
  240. uni.hideLoading()
  241. if (uploadRes.code === 200) {
  242. if (side === 'front') {
  243. this.form.idCardFront = uploadRes.data.url
  244. } else {
  245. this.form.idCardBack = uploadRes.data.url
  246. }
  247. uni.showToast({ title: '上传成功', icon: 'success' })
  248. } else {
  249. uni.showToast({ title: uploadRes.message || '上传失败', icon: 'none' })
  250. }
  251. } catch (e) {
  252. uni.hideLoading()
  253. uni.showToast({ title: '上传失败', icon: 'none' })
  254. }
  255. }
  256. })
  257. },
  258. async chooseCertificate() {
  259. uni.chooseImage({
  260. count: 9 - this.form.certificateImages.length,
  261. sizeType: ['compressed'],
  262. sourceType: ['album', 'camera'],
  263. success: async (res) => {
  264. uni.showLoading({ title: '上传中...' })
  265. try {
  266. for (const filePath of res.tempFilePaths) {
  267. const uploadRes = await uploadServiceRoleImage(filePath)
  268. if (uploadRes.code === 200) {
  269. this.form.certificateImages.push(uploadRes.data.url)
  270. }
  271. }
  272. uni.hideLoading()
  273. uni.showToast({ title: '上传成功', icon: 'success' })
  274. } catch (e) {
  275. uni.hideLoading()
  276. uni.showToast({ title: '上传失败', icon: 'none' })
  277. }
  278. }
  279. })
  280. },
  281. removeCert(idx) {
  282. this.form.certificateImages.splice(idx, 1)
  283. },
  284. getCertKey(idx) {
  285. return 'cert-' + idx
  286. },
  287. getRoleRowKey(rowIdx) {
  288. return 'role-row-' + rowIdx
  289. },
  290. openAddressPicker() {
  291. if (this.$refs.addressPicker) {
  292. this.$refs.addressPicker.open()
  293. }
  294. },
  295. onAddressChange(e) {
  296. this.form.province = e.province || ''
  297. this.form.city = e.city || ''
  298. this.form.district = e.district || ''
  299. this.form.street = e.street || ''
  300. this.addressText = e.fullName || ''
  301. },
  302. async handleSubmit() {
  303. if (this.selectedRoles.length === 0) {
  304. uni.showToast({ title: '请至少选择一个角色', icon: 'none' })
  305. return
  306. }
  307. if (!this.form.idCardFront || !this.form.idCardBack) {
  308. uni.showToast({ title: '请上传身份证正反面照片', icon: 'none' })
  309. return
  310. }
  311. if (!this.form.province) {
  312. uni.showToast({ title: '请选择联系地址', icon: 'none' })
  313. return
  314. }
  315. this.submitting = true
  316. uni.showLoading({ title: '提交中...' })
  317. try {
  318. const res = await submitServiceRoleApply({
  319. roles: this.selectedRoles,
  320. idCard: this.form.idCard,
  321. idCardImages: [this.form.idCardFront, this.form.idCardBack],
  322. certificateImages: this.form.certificateImages,
  323. province: this.form.province,
  324. city: this.form.city,
  325. district: this.form.district,
  326. street: this.form.street,
  327. addressDetail: this.form.addressDetail
  328. })
  329. uni.hideLoading()
  330. if (res.code === 200) {
  331. // 提交成功,刷新状态
  332. await this.checkExistingApply()
  333. if (!this.showStatusView) this.showStatusView = true
  334. } else {
  335. uni.showToast({ title: res.message || '提交失败', icon: 'none' })
  336. }
  337. } catch (e) {
  338. uni.hideLoading()
  339. uni.showToast({ title: '提交失败,请重试', icon: 'none' })
  340. } finally {
  341. this.submitting = false
  342. }
  343. },
  344. handleReapply() {
  345. this.applyRecord = null
  346. this.showStatusView = false
  347. this.selectedRoles = []
  348. this.addressText = ''
  349. this.form = {
  350. idCard: '',
  351. idCardFront: '',
  352. idCardBack: '',
  353. certificateImages: [],
  354. province: '',
  355. city: '',
  356. district: '',
  357. street: '',
  358. addressDetail: ''
  359. }
  360. },
  361. goToCenter() {
  362. uni.redirectTo({ url: '/pages/vendor/center' })
  363. }
  364. }
  365. }
  366. </script>
  367. <style>
  368. .page {
  369. min-height: 100vh;
  370. background: linear-gradient(180deg, #FFF7ED 0%, #F8F8F8 30%);
  371. padding: 0 30rpx 120rpx;
  372. }
  373. /* ===== 状态展示区 ===== */
  374. .status-view {
  375. padding-top: 60rpx;
  376. display: flex;
  377. flex-direction: column;
  378. align-items: center;
  379. }
  380. .status-card {
  381. width: 100%;
  382. border-radius: 24rpx;
  383. padding: 60rpx 40rpx;
  384. text-align: center;
  385. margin-bottom: 30rpx;
  386. }
  387. .status-card.status-pending {
  388. background: linear-gradient(135deg, #FFFBEB, #FEF3C7);
  389. border: 2rpx solid #FDE68A;
  390. }
  391. .status-card.status-approved {
  392. background: linear-gradient(135deg, #F0FDF4, #DCFCE7);
  393. border: 2rpx solid #86EFAC;
  394. }
  395. .status-card.status-rejected {
  396. background: linear-gradient(135deg, #FEF2F2, #FEE2E2);
  397. border: 2rpx solid #FCA5A5;
  398. }
  399. .status-icon {
  400. font-size: 80rpx;
  401. display: block;
  402. margin-bottom: 20rpx;
  403. }
  404. .status-title {
  405. font-size: 36rpx;
  406. font-weight: bold;
  407. color: #1F2937;
  408. display: block;
  409. margin-bottom: 12rpx;
  410. }
  411. .status-desc {
  412. font-size: 26rpx;
  413. color: #6B7280;
  414. line-height: 1.6;
  415. display: block;
  416. }
  417. .status-tip {
  418. background: #fff;
  419. border-radius: 12rpx;
  420. padding: 20rpx 28rpx;
  421. margin-bottom: 40rpx;
  422. width: 100%;
  423. box-sizing: border-box;
  424. }
  425. .status-tip text {
  426. font-size: 24rpx;
  427. color: #92400E;
  428. }
  429. .reject-info {
  430. background: #fff;
  431. border-radius: 12rpx;
  432. padding: 28rpx;
  433. margin-bottom: 40rpx;
  434. width: 100%;
  435. box-sizing: border-box;
  436. }
  437. .reject-label {
  438. font-size: 24rpx;
  439. color: #9CA3AF;
  440. display: block;
  441. margin-bottom: 12rpx;
  442. }
  443. .reject-reason {
  444. font-size: 28rpx;
  445. color: #DC2626;
  446. line-height: 1.6;
  447. display: block;
  448. }
  449. .action-buttons {
  450. width: 100%;
  451. display: flex;
  452. flex-direction: column;
  453. gap: 20rpx;
  454. }
  455. .btn-reapply, .btn-center, .btn-back {
  456. width: 100%;
  457. height: 88rpx;
  458. line-height: 88rpx;
  459. border-radius: 44rpx;
  460. font-size: 30rpx;
  461. text-align: center;
  462. border: none;
  463. }
  464. .btn-reapply {
  465. background: linear-gradient(135deg, #F97316, #FB923C);
  466. color: #fff;
  467. }
  468. .btn-center {
  469. background: linear-gradient(135deg, #10B981, #34D399);
  470. color: #fff;
  471. }
  472. .btn-back {
  473. background: #F3F4F6;
  474. color: #6B7280;
  475. }
  476. /* ===== 表单区 ===== */
  477. .header {
  478. text-align: center;
  479. padding: 50rpx 0 30rpx;
  480. }
  481. .title {
  482. font-size: 42rpx;
  483. font-weight: bold;
  484. color: #1F2937;
  485. display: block;
  486. }
  487. .subtitle {
  488. font-size: 26rpx;
  489. color: #9CA3AF;
  490. margin-top: 10rpx;
  491. display: block;
  492. }
  493. .section {
  494. background: #fff;
  495. border-radius: 20rpx;
  496. padding: 30rpx;
  497. margin-bottom: 24rpx;
  498. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.04);
  499. }
  500. .section-title {
  501. font-size: 28rpx;
  502. font-weight: bold;
  503. margin-bottom: 24rpx;
  504. color: #374151;
  505. }
  506. .required {
  507. color: #EF4444;
  508. margin-left: 4rpx;
  509. }
  510. /* 角色网格:固定三列
  511. 不使用 calc() 与 flex gap(旧版 iOS WebView 不支持 gap,calc 除法在不同机型上
  512. 会出现取整偏差导致换行)——改用「每行 3 个 flex:1 单元格 + 固定 rpx 间距」,
  513. 所有机型/分辨率下都严格等分三列 */
  514. .role-grid {
  515. display: flex;
  516. flex-direction: column;
  517. }
  518. .role-row {
  519. display: flex;
  520. flex-direction: row;
  521. justify-content: space-between;
  522. }
  523. .role-cell {
  524. /* flex:1 + 可收缩,保证同一行三等分,长文案不撑破列宽 */
  525. flex: 1;
  526. min-width: 0;
  527. box-sizing: border-box;
  528. }
  529. /* 单元格之间留白:左右各 8rpx,首尾行视情况抵消,保证总宽不溢出 */
  530. .role-cell:not(:first-child) {
  531. margin-left: 16rpx;
  532. }
  533. .role-row:not(:first-child) {
  534. margin-top: 20rpx;
  535. }
  536. .role-card {
  537. width: 100%;
  538. box-sizing: border-box;
  539. background: #F9FAFB;
  540. border-radius: 16rpx;
  541. padding: 30rpx 0;
  542. display: flex;
  543. flex-direction: column;
  544. align-items: center;
  545. position: relative;
  546. border: 3rpx solid transparent;
  547. transition: all 0.2s;
  548. }
  549. .role-card.selected {
  550. background: #FFF7ED;
  551. border-color: #F97316;
  552. }
  553. .role-icon {
  554. font-size: 44rpx;
  555. margin-bottom: 10rpx;
  556. }
  557. .role-name {
  558. font-size: 24rpx;
  559. color: #374151;
  560. font-weight: 500;
  561. /* 保证长角色名不换行撑高导致行高不一致 */
  562. white-space: nowrap;
  563. overflow: hidden;
  564. text-overflow: ellipsis;
  565. max-width: 100%;
  566. }
  567. .check-mark {
  568. position: absolute;
  569. top: 8rpx;
  570. right: 12rpx;
  571. color: #F97316;
  572. font-size: 32rpx;
  573. font-weight: bold;
  574. }
  575. /* 联系地址入口行(与收件人地址页保持同一套视觉) */
  576. .address-row {
  577. display: flex;
  578. align-items: center;
  579. padding: 24rpx 24rpx;
  580. background: #FAFAFA;
  581. border: 2rpx solid #E5E7EB;
  582. border-radius: 12rpx;
  583. margin-bottom: 16rpx;
  584. }
  585. .address-label {
  586. font-size: 26rpx;
  587. color: #374151;
  588. width: 140rpx;
  589. flex-shrink: 0;
  590. }
  591. .address-value-wrap {
  592. flex: 1;
  593. display: flex;
  594. align-items: center;
  595. justify-content: flex-end;
  596. min-width: 0;
  597. }
  598. .address-value {
  599. font-size: 26rpx;
  600. color: #374151;
  601. text-align: right;
  602. overflow: hidden;
  603. text-overflow: ellipsis;
  604. white-space: nowrap;
  605. }
  606. .address-placeholder {
  607. font-size: 26rpx;
  608. color: #9CA3AF;
  609. text-align: right;
  610. }
  611. .address-arrow {
  612. font-size: 34rpx;
  613. color: #9CA3AF;
  614. margin-left: 8rpx;
  615. flex-shrink: 0;
  616. }
  617. /* 输入框 */
  618. .input {
  619. width: 100%;
  620. height: 72rpx;
  621. border: 2rpx solid #E5E7EB;
  622. border-radius: 12rpx;
  623. padding: 0 24rpx;
  624. font-size: 26rpx;
  625. box-sizing: border-box;
  626. margin-bottom: 16rpx;
  627. background: #FAFAFA;
  628. }
  629. /* 图片上传 */
  630. .upload-row {
  631. display: flex;
  632. gap: 20rpx;
  633. }
  634. .upload-item {
  635. flex: 1;
  636. height: 200rpx;
  637. border: 3rpx dashed #D1D5DB;
  638. border-radius: 16rpx;
  639. display: flex;
  640. align-items: center;
  641. justify-content: center;
  642. overflow: hidden;
  643. background: #FAFAFA;
  644. }
  645. .upload-placeholder {
  646. display: flex;
  647. flex-direction: column;
  648. align-items: center;
  649. }
  650. .upload-icon {
  651. font-size: 48rpx;
  652. color: #9CA3AF;
  653. }
  654. .upload-label {
  655. font-size: 22rpx;
  656. color: #9CA3AF;
  657. margin-top: 8rpx;
  658. }
  659. .upload-preview {
  660. width: 100%;
  661. height: 100%;
  662. }
  663. /* 证书列表 */
  664. .cert-list {
  665. display: flex;
  666. flex-wrap: wrap;
  667. gap: 16rpx;
  668. }
  669. .cert-item {
  670. width: 160rpx;
  671. height: 160rpx;
  672. position: relative;
  673. border-radius: 12rpx;
  674. overflow: hidden;
  675. }
  676. .cert-preview {
  677. width: 100%;
  678. height: 100%;
  679. }
  680. .cert-del {
  681. position: absolute;
  682. top: 4rpx;
  683. right: 4rpx;
  684. width: 36rpx;
  685. height: 36rpx;
  686. background: rgba(0,0,0,0.55);
  687. color: #fff;
  688. border-radius: 50%;
  689. text-align: center;
  690. line-height: 36rpx;
  691. font-size: 28rpx;
  692. }
  693. .cert-add {
  694. width: 160rpx;
  695. height: 160rpx;
  696. border: 3rpx dashed #D1D5DB;
  697. border-radius: 12rpx;
  698. display: flex;
  699. flex-direction: column;
  700. align-items: center;
  701. justify-content: center;
  702. background: #FAFAFA;
  703. }
  704. /* 提交按钮 */
  705. .submit-btn {
  706. width: 100%;
  707. height: 96rpx;
  708. line-height: 96rpx;
  709. background: linear-gradient(135deg, #F97316, #FB923C);
  710. color: #fff;
  711. border-radius: 48rpx;
  712. font-size: 32rpx;
  713. font-weight: 600;
  714. margin-top: 16rpx;
  715. border: none;
  716. box-shadow: 0 8rpx 24rpx rgba(249, 115, 22, 0.3);
  717. }
  718. .submit-btn[disabled] {
  719. opacity: 0.6;
  720. box-shadow: none;
  721. }
  722. .footer-hint {
  723. text-align: center;
  724. margin-top: 24rpx;
  725. }
  726. .footer-hint text {
  727. font-size: 24rpx;
  728. color: #9CA3AF;
  729. }
  730. </style>