index.vue 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. <template>
  2. <view class="qual-container">
  3. <!-- 顶部提示 -->
  4. <view class="top-tip">根据国家政策要求,请尽快完成实名认证与健康认证,否则无法开展配送业务。我们承诺将严格保管好您的个人信息。</view>
  5. <!-- 如果没有选择服务类型 -->
  6. <view class="empty-state" v-if="serviceTypes.length === 0">
  7. <text class="empty-tip">请返回第一步选择服务类型</text>
  8. <button class="back-btn" @click="goBackToForm">返回选择</button>
  9. </view>
  10. <!-- 动态渲染资质卡片 -->
  11. <view class="qual-card" v-for="(item, index) in serviceTypes" :key="item.id">
  12. <view class="card-title">{{ item.name }}服务资质</view>
  13. <view class="upload-wrapper">
  14. <!-- 已有图片列表 -->
  15. <view class="img-item" v-for="(img, imgIndex) in qualifications[item.name]" :key="imgIndex">
  16. <image :src="img" class="preview-img" mode="aspectFill" @click="previewImage(item.name, imgIndex)"></image>
  17. <view class="delete-btn" @click.stop="deleteImage(item.name, imgIndex)">×</view>
  18. </view>
  19. <!-- 上传/添加按钮 -->
  20. <view class="upload-box" @click="chooseImage(item.name)">
  21. <text class="plus-icon">+</text>
  22. <text class="upload-text">上传</text>
  23. </view>
  24. </view>
  25. </view>
  26. <!-- 底部按钮 -->
  27. <view class="footer-actions">
  28. <button class="submit-btn" @click="submit">立即提交</button>
  29. </view>
  30. </view>
  31. </template>
  32. <script>
  33. import { submitAudit, uploadFile } from '@/api/fulfiller/app'
  34. export default {
  35. data() {
  36. return {
  37. serviceTypes: [],
  38. qualifications: {},
  39. qualOssIds: {},
  40. }
  41. },
  42. onLoad(options) {
  43. if (options.services) {
  44. try {
  45. this.serviceTypes = JSON.parse(decodeURIComponent(options.services));
  46. this.serviceTypes.forEach(item => {
  47. this.qualifications[item.name] = this.qualifications[item.name] || [];
  48. this.qualOssIds[item.name] = this.qualOssIds[item.name] || [];
  49. });
  50. } catch (e) { console.error('Parse services failed', e); uni.showToast({ title: e.message || e.msg || '请求失败', icon: 'none' }); }
  51. }
  52. this.restoreQualData();
  53. },
  54. methods: {
  55. saveQualData() {
  56. try { uni.setStorageSync('recruit_qual_data', JSON.stringify({ qualifications: this.qualifications, qualOssIds: this.qualOssIds })); }
  57. catch (e) { console.error('保存资质数据失败', e); uni.showToast({ title: e.message || e.msg || '请求失败', icon: 'none' }); }
  58. },
  59. restoreQualData() {
  60. try {
  61. const saved = uni.getStorageSync('recruit_qual_data');
  62. if (saved) { const d = JSON.parse(saved); this.qualifications = d.qualifications || {}; this.qualOssIds = d.qualOssIds || {}; this.$forceUpdate(); }
  63. } catch (e) { console.error('恢复资质数据失败', e); uni.showToast({ title: e.message || e.msg || '请求失败', icon: 'none' }); }
  64. },
  65. chooseImage(serviceName) {
  66. uni.chooseImage({
  67. count: 6, sizeType: ['compressed'], sourceType: ['album', 'camera'],
  68. success: async (res) => {
  69. if (!this.qualifications[serviceName]) { this.qualifications[serviceName] = []; this.qualOssIds[serviceName] = []; }
  70. for (const tempPath of res.tempFilePaths) {
  71. this.qualifications[serviceName].push(tempPath); this.$forceUpdate();
  72. try { const uploadRes = await uploadFile(tempPath); this.qualOssIds[serviceName].push(uploadRes.data.ossId); this.saveQualData(); }
  73. catch (err) { console.error('上传资质图片失败:', err); uni.showToast({ title: err.message || err.msg || '上传失败', icon: 'none' }); }
  74. }
  75. }
  76. });
  77. },
  78. deleteImage(serviceName, index) {
  79. this.qualifications[serviceName].splice(index, 1);
  80. if (this.qualOssIds[serviceName]) this.qualOssIds[serviceName].splice(index, 1);
  81. this.saveQualData(); this.$forceUpdate();
  82. },
  83. goBackToForm() {
  84. const pages = getCurrentPages();
  85. if (pages.length > 2) uni.navigateBack({ delta: 2 });
  86. else uni.reLaunch({ url: '/pages/recruit/form/index' });
  87. },
  88. previewImage(serviceName, index) {
  89. const urls = this.qualifications[serviceName] || [];
  90. uni.previewImage({ current: index, urls });
  91. },
  92. async submit() {
  93. let recruitData = {};
  94. try { const stored = uni.getStorageSync('recruit_form_data'); if (stored) recruitData = JSON.parse(stored); }
  95. catch (e) { console.error('读取招募表单数据失败', e); uni.showToast({ title: e.message || e.msg || '请求失败', icon: 'none' }); }
  96. const allQualOssIds = [];
  97. Object.values(this.qualOssIds).forEach(ids => allQualOssIds.push(...ids));
  98. const auditData = {
  99. name: recruitData.name || '', phone: recruitData.mobile || '', password: recruitData.password || '',
  100. gender: recruitData.gender === 1 ? '0' : '1', birthday: recruitData.birthday || '',
  101. serviceTypes: (recruitData.serviceType || []).join(','), city: recruitData.areaPath || '',
  102. stationId: recruitData.stationId || null, realName: recruitData.realName || '',
  103. idCard: recruitData.idNumber || '', idValidDate: recruitData.expiryDate || '',
  104. idCardFront: recruitData.idCardFrontOssId || null, idCardBack: recruitData.idCardBackOssId || null,
  105. qualifications: allQualOssIds.join(',')
  106. };
  107. uni.showLoading({ title: '提交中...' });
  108. try {
  109. await submitAudit(auditData); uni.hideLoading();
  110. const s = encodeURIComponent(recruitData.station || '');
  111. const n = encodeURIComponent(recruitData.name || '');
  112. const p = recruitData.mobile || '';
  113. uni.removeStorageSync('recruit_form_data'); uni.removeStorageSync('recruit_auth_data'); uni.removeStorageSync('recruit_qual_data');
  114. uni.reLaunch({ url: `/pages/recruit/success/index?station=${s}&name=${n}&phone=${p}` });
  115. } catch (err) { uni.hideLoading(); console.error('提交申请失败:', err); uni.showToast({ title: err.message || err.msg || '提交失败', icon: 'none' }); }
  116. }
  117. }
  118. }
  119. </script>
  120. <style>
  121. page {
  122. background-color: #F8F8F8;
  123. padding-bottom: 200rpx;
  124. font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Helvetica, Segoe UI, Arial, Roboto, 'PingFang SC', 'miui', 'Hiragino Sans GB', 'Microsoft Yahei', sans-serif;
  125. }
  126. .qual-container {
  127. padding: 20rpx;
  128. }
  129. .top-tip {
  130. font-size: 24rpx;
  131. color: #666;
  132. margin-bottom: 20rpx;
  133. padding: 0 10rpx;
  134. line-height: 1.5;
  135. }
  136. .empty-state {
  137. display: flex;
  138. flex-direction: column;
  139. align-items: center;
  140. padding-top: 100rpx;
  141. padding-bottom: 60rpx;
  142. }
  143. .empty-tip {
  144. font-size: 30rpx;
  145. color: #999;
  146. margin-bottom: 40rpx;
  147. }
  148. .back-btn {
  149. width: 300rpx;
  150. height: 80rpx;
  151. line-height: 80rpx;
  152. border: 2rpx solid #FF5722;
  153. color: #FF5722;
  154. background-color: #fff;
  155. border-radius: 40rpx;
  156. font-size: 30rpx;
  157. }
  158. .qual-card {
  159. background-color: #fff;
  160. border-radius: 20rpx;
  161. padding: 30rpx;
  162. margin-bottom: 20rpx;
  163. }
  164. .card-title {
  165. font-size: 28rpx;
  166. font-weight: bold;
  167. color: #333;
  168. margin-bottom: 30rpx;
  169. }
  170. .upload-wrapper {
  171. display: flex;
  172. flex-wrap: wrap;
  173. }
  174. .img-item {
  175. width: 200rpx;
  176. height: 200rpx;
  177. position: relative;
  178. margin-right: 20rpx;
  179. margin-bottom: 20rpx;
  180. }
  181. .upload-box {
  182. width: 200rpx;
  183. height: 200rpx;
  184. background-color: #F8F8F8;
  185. border: 2rpx dashed #eee;
  186. border-radius: 12rpx;
  187. display: flex;
  188. flex-direction: column;
  189. align-items: center;
  190. justify-content: center;
  191. margin-bottom: 20rpx;
  192. }
  193. .plus-icon {
  194. font-size: 60rpx;
  195. color: #ccc;
  196. font-weight: 300;
  197. margin-bottom: 10rpx;
  198. }
  199. .upload-text {
  200. font-size: 24rpx;
  201. color: #999;
  202. }
  203. .preview-img {
  204. width: 100%;
  205. height: 100%;
  206. border-radius: 12rpx;
  207. }
  208. .delete-btn {
  209. position: absolute;
  210. top: -10rpx;
  211. right: -10rpx;
  212. width: 36rpx;
  213. height: 36rpx;
  214. background-color: #FF5722;
  215. border-radius: 50%;
  216. color: #fff;
  217. font-size: 24rpx;
  218. display: flex;
  219. align-items: center;
  220. justify-content: center;
  221. z-index: 10;
  222. border: 2rpx solid #fff;
  223. }
  224. .footer-actions {
  225. margin-top: 60rpx;
  226. padding: 0 20rpx;
  227. }
  228. .submit-btn {
  229. background: linear-gradient(90deg, #FF6F00 0%, #FF5722 100%);
  230. color: #fff;
  231. font-size: 28rpx;
  232. font-weight: bold;
  233. height: 90rpx;
  234. line-height: 90rpx;
  235. border-radius: 45rpx;
  236. box-shadow: 0 10rpx 20rpx rgba(255, 87, 34, 0.2);
  237. }
  238. .submit-btn::after {
  239. border: none;
  240. }
  241. </style>