index.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. <template>
  2. <view class="auth-container">
  3. <!-- 顶部提示 -->
  4. <view class="top-tip">请确保身份信息的准确,以免影响后续履约费用结算。</view>
  5. <!-- 表单信息 -->
  6. <view class="form-card">
  7. <!-- 证件类型 -->
  8. <view class="form-item">
  9. <text class="label">证件类型</text>
  10. <view class="read-only-text">居民身份证</view>
  11. </view>
  12. <!-- 真实姓名 -->
  13. <view class="form-item">
  14. <text class="label">真实姓名</text>
  15. <view class="gray-input-box">
  16. <input class="input-area" type="text" v-model="formData.name" placeholder="证件姓名"
  17. placeholder-class="input-placeholder" />
  18. </view>
  19. </view>
  20. <!-- 证件号码 -->
  21. <view class="form-item">
  22. <text class="label">证件号码</text>
  23. <view class="gray-input-box">
  24. <input class="input-area" type="idcard" v-model="formData.idNumber" placeholder="身份证号"
  25. placeholder-class="input-placeholder" />
  26. </view>
  27. </view>
  28. <!-- 有效日期 -->
  29. <view class="form-item">
  30. <text class="label">有效日期</text>
  31. <view class="gray-input-box" @click="openPicker">
  32. <text class="input-area" :class="{ 'input-placeholder': !formData.expiryDate }">
  33. {{ formData.expiryDate || '选择有效结束期限' }}
  34. </text>
  35. <!-- 箭头SVG -->
  36. <svg class="arrow-right" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg">
  37. <path
  38. d="M340.864 149.312a30.592 30.592 0 0 0 0 42.752L652.736 512 340.864 831.872a30.592 30.592 0 0 0 0 42.752 29.12 29.12 0 0 0 41.728 0L714.24 534.336a32 32 0 0 0 0-45.056L382.592 149.312a29.12 29.12 0 0 0-41.728 0z"
  39. fill="#CCCCCC"></path>
  40. </svg>
  41. </view>
  42. </view>
  43. </view>
  44. <!-- 身份证正面 -->
  45. <view class="upload-card">
  46. <view class="upload-box" @click="chooseImage('front')">
  47. <image v-if="idCardFront" :src="idCardFront" class="preview-img" mode="aspectFill"></image>
  48. <template v-else>
  49. <!-- 相机图标SVG -->
  50. <svg class="camera-icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
  51. <path
  52. d="M12 12C14.7614 12 17 9.76142 17 7C17 4.23858 14.7614 2 12 2C9.23858 2 7 4.23858 7 7C7 9.76142 9.23858 12 12 12Z"
  53. fill="#E0E0E0" />
  54. <circle cx="12" cy="12" r="3" stroke="#CCCCCC" stroke-width="2" />
  55. <path
  56. d="M20 6H17.82L16.4 4.47C15.96 4 15.34 3.73 14.68 3.73H9.32C8.66 3.73 8.04 4 7.6 4.47L6.18 6H4C2.9 6 2 6.9 2 8V18C2 19.1 2.9 20 4 20H20C21.1 20 22 19.1 22 18V8C22 6.9 21.1 6 20 6ZM12 17C9.24 17 7 14.76 7 12C7 9.24 9.24 7 12 7C14.76 7 17 9.24 17 12C17 14.76 14.76 17 12 17Z"
  57. fill="#CCCCCC" />
  58. </svg>
  59. <text class="upload-text">点击上传</text>
  60. </template>
  61. </view>
  62. <text class="card-label">证件带照片面</text>
  63. </view>
  64. <!-- 身份证反面 -->
  65. <view class="upload-card">
  66. <view class="upload-box" @click="chooseImage('back')">
  67. <image v-if="idCardBack" :src="idCardBack" class="preview-img" mode="aspectFill"></image>
  68. <template v-else>
  69. <!-- 相机图标SVG -->
  70. <svg class="camera-icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
  71. <path
  72. d="M20 6H17.82L16.4 4.47C15.96 4 15.34 3.73 14.68 3.73H9.32C8.66 3.73 8.04 4 7.6 4.47L6.18 6H4C2.9 6 2 6.9 2 8V18C2 19.1 2.9 20 4 20H20C21.1 20 22 19.1 22 18V8C22 6.9 21.1 6 20 6ZM12 17C9.24 17 7 14.76 7 12C7 9.24 9.24 7 12 7C14.76 7 17 9.24 17 12C17 14.76 14.76 17 12 17Z"
  73. fill="#CCCCCC" />
  74. </svg>
  75. <text class="upload-text">点击上传</text>
  76. </template>
  77. </view>
  78. <text class="card-label">证件国徽面</text>
  79. </view>
  80. <!-- 底部按钮 -->
  81. <view class="footer-btn-area">
  82. <button class="next-btn" @click="goToQualifications">下一步,完善资质</button>
  83. </view>
  84. <!-- 自定义日期选择器 -->
  85. <view class="picker-mask" :class="{ show: showPicker }" @click="closePicker">
  86. <view class="picker-content" @click.stop>
  87. <view class="picker-header">
  88. <text class="picker-btn-cancel" @click="closePicker">取消</text>
  89. <text class="picker-title">选择有效结束期限</text>
  90. <text class="picker-btn-confirm" @click="confirmPicker">确定</text>
  91. </view>
  92. <picker-view class="picker-view" indicator-style="height: 50px;" :value="pickerValue" @change="onPickerChange">
  93. <picker-view-column>
  94. <view class="picker-item" v-for="(item, index) in years" :key="index">{{ item }}年</view>
  95. </picker-view-column>
  96. <picker-view-column>
  97. <view class="picker-item" v-for="(item, index) in months" :key="index">{{ item }}月</view>
  98. </picker-view-column>
  99. <picker-view-column>
  100. <view class="picker-item" v-for="(item, index) in days" :key="index">{{ item }}日</view>
  101. </picker-view-column>
  102. </picker-view>
  103. </view>
  104. </view>
  105. </view>
  106. </template>
  107. <script>
  108. import { uploadFile } from '@/api/fulfiller/app'
  109. export default {
  110. data() {
  111. return {
  112. formData: { idType: '居民身份证', name: '', idNumber: '', expiryDate: '' },
  113. idCardFront: '', idCardBack: '',
  114. idCardFrontOssId: '', idCardBackOssId: '',
  115. showPicker: false, pickerValue: [0, 0, 0],
  116. years: [], months: [], days: [],
  117. tempYear: 0, tempMonth: 0, tempDay: 0,
  118. serviceType: [], isChoosingImage: false
  119. }
  120. },
  121. onLoad(options) {
  122. if (options.services) {
  123. try { this.serviceType = JSON.parse(decodeURIComponent(options.services)); }
  124. catch (e) { console.error('Parse services failed', e); }
  125. }
  126. this.initDateData(); this.restoreAuthData();
  127. },
  128. onShow() { if (this.isChoosingImage) this.isChoosingImage = false; },
  129. methods: {
  130. initDateData() {
  131. const year = new Date().getFullYear();
  132. for (let i = year; i <= year + 50; i++) this.years.push(i);
  133. for (let i = 1; i <= 12; i++) this.months.push(i);
  134. for (let i = 1; i <= 31; i++) this.days.push(i);
  135. },
  136. openPicker() {
  137. const date = new Date();
  138. const dateStr = this.formData.expiryDate || `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
  139. const [y, m, d] = dateStr.split('-').map(Number);
  140. let yIndex = this.years.indexOf(y); let mIndex = this.months.indexOf(m); let dIndex = this.days.indexOf(d);
  141. this.pickerValue = [yIndex > -1 ? yIndex : 0, mIndex > -1 ? mIndex : 0, dIndex > -1 ? dIndex : 0];
  142. this.tempYear = this.years[this.pickerValue[0]]; this.tempMonth = this.months[this.pickerValue[1]]; this.tempDay = this.days[this.pickerValue[2]];
  143. this.showPicker = true;
  144. },
  145. closePicker() { this.showPicker = false; },
  146. onPickerChange(e) {
  147. const val = e.detail.value;
  148. this.tempYear = this.years[val[0]]; this.tempMonth = this.months[val[1]]; this.tempDay = this.days[val[2]];
  149. },
  150. confirmPicker() {
  151. const mStr = this.tempMonth < 10 ? '0' + this.tempMonth : this.tempMonth;
  152. const dStr = this.tempDay < 10 ? '0' + this.tempDay : this.tempDay;
  153. this.formData.expiryDate = `${this.tempYear}-${mStr}-${dStr}`; this.closePicker();
  154. },
  155. restoreAuthData() {
  156. try {
  157. const saved = uni.getStorageSync('recruit_auth_data');
  158. if (saved) {
  159. const d = JSON.parse(saved);
  160. this.formData.name = d.name || ''; this.formData.idNumber = d.idNumber || '';
  161. this.formData.expiryDate = d.expiryDate || '';
  162. this.idCardFront = d.idCardFront || ''; this.idCardBack = d.idCardBack || '';
  163. this.idCardFrontOssId = d.idCardFrontOssId || ''; this.idCardBackOssId = d.idCardBackOssId || '';
  164. }
  165. } catch (e) { console.error('恢复认证数据失败', e); }
  166. },
  167. saveAuthData() {
  168. try {
  169. uni.setStorageSync('recruit_auth_data', JSON.stringify({
  170. name: this.formData.name, idNumber: this.formData.idNumber, expiryDate: this.formData.expiryDate,
  171. idCardFront: this.idCardFront, idCardBack: this.idCardBack,
  172. idCardFrontOssId: this.idCardFrontOssId, idCardBackOssId: this.idCardBackOssId
  173. }));
  174. } catch (e) { console.error('保存认证数据失败', e); }
  175. },
  176. resetFormData() {
  177. this.formData.name = ''; this.formData.idNumber = ''; this.formData.expiryDate = '';
  178. this.idCardFront = ''; this.idCardBack = '';
  179. this.idCardFrontOssId = ''; this.idCardBackOssId = '';
  180. try { uni.removeStorageSync('recruit_auth_data'); } catch (e) { console.error('清除缓存失败', e); }
  181. },
  182. chooseImage(side) {
  183. this.isChoosingImage = true;
  184. uni.chooseImage({
  185. count: 1, sizeType: ['compressed'], sourceType: ['album', 'camera'],
  186. success: async (res) => {
  187. const tempPath = res.tempFilePaths[0];
  188. if (side === 'front') this.idCardFront = tempPath; else this.idCardBack = tempPath;
  189. try {
  190. uni.showLoading({ title: '上传中...' });
  191. const uploadRes = await uploadFile(tempPath);
  192. if (side === 'front') this.idCardFrontOssId = uploadRes.data.ossId;
  193. else this.idCardBackOssId = uploadRes.data.ossId;
  194. uni.hideLoading(); this.saveAuthData();
  195. } catch (err) { uni.hideLoading(); console.error('上传身份证图片失败:', err); }
  196. }
  197. });
  198. },
  199. goToQualifications() {
  200. this.saveAuthData();
  201. try {
  202. const stored = uni.getStorageSync('recruit_form_data');
  203. if (stored) {
  204. const data = JSON.parse(stored);
  205. data.realName = this.formData.name; data.idNumber = this.formData.idNumber;
  206. data.expiryDate = this.formData.expiryDate;
  207. data.idCardFrontOssId = this.idCardFrontOssId; data.idCardBackOssId = this.idCardBackOssId;
  208. uni.setStorageSync('recruit_form_data', JSON.stringify(data));
  209. }
  210. } catch (e) { console.error('保存认证数据失败', e); }
  211. const services = JSON.stringify(this.serviceType);
  212. uni.navigateTo({ url: `/pages/recruit/qualifications/index?services=${encodeURIComponent(services)}` });
  213. }
  214. }
  215. }
  216. </script>
  217. <style>
  218. page {
  219. background-color: #F8F8F8;
  220. font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Helvetica, Segoe UI, Arial, Roboto, 'PingFang SC', 'miui', 'Hiragino Sans GB', 'Microsoft Yahei', sans-serif;
  221. }
  222. .auth-container {
  223. padding: 20rpx;
  224. padding-bottom: 200rpx;
  225. }
  226. .top-tip {
  227. font-size: 24rpx;
  228. color: #666;
  229. margin-bottom: 20rpx;
  230. padding: 0 10rpx;
  231. }
  232. .form-card {
  233. background-color: #fff;
  234. border-radius: 20rpx;
  235. padding: 0 30rpx;
  236. margin-bottom: 20rpx;
  237. }
  238. .form-item {
  239. display: flex;
  240. align-items: center;
  241. height: 100rpx;
  242. border-bottom: none;
  243. }
  244. .label {
  245. width: 160rpx;
  246. font-size: 26rpx;
  247. font-weight: bold;
  248. color: #333;
  249. }
  250. .input-area {
  251. flex: 1;
  252. font-size: 26rpx;
  253. color: #333;
  254. }
  255. .input-placeholder {
  256. color: #ccc;
  257. font-size: 26rpx;
  258. }
  259. .read-only-text {
  260. color: #333;
  261. font-size: 26rpx;
  262. }
  263. .card-label {
  264. font-size: 26rpx;
  265. color: #333;
  266. font-weight: bold;
  267. }
  268. .next-btn {
  269. background: linear-gradient(90deg, #FF6F00 0%, #FF5722 100%);
  270. color: #fff;
  271. font-size: 28rpx;
  272. font-weight: bold;
  273. height: 90rpx;
  274. line-height: 90rpx;
  275. border-radius: 45rpx;
  276. box-shadow: 0 10rpx 20rpx rgba(255, 87, 34, 0.2);
  277. }
  278. .gray-input-box {
  279. flex: 1;
  280. height: 70rpx;
  281. background-color: #F8F8F8;
  282. border-radius: 8rpx;
  283. display: flex;
  284. align-items: center;
  285. padding: 0 20rpx;
  286. }
  287. .arrow-right {
  288. width: 24rpx;
  289. height: 24rpx;
  290. margin-left: auto;
  291. }
  292. .upload-card {
  293. background-color: #fff;
  294. border-radius: 20rpx;
  295. padding: 40rpx 0;
  296. margin-bottom: 20rpx;
  297. display: flex;
  298. flex-direction: column;
  299. align-items: center;
  300. justify-content: center;
  301. }
  302. .upload-box {
  303. width: 600rpx;
  304. height: 360rpx;
  305. background-color: #F8F8F8;
  306. border: 2rpx dashed #eee;
  307. border-radius: 12rpx;
  308. display: flex;
  309. flex-direction: column;
  310. align-items: center;
  311. justify-content: center;
  312. margin-bottom: 20rpx;
  313. position: relative;
  314. overflow: hidden;
  315. }
  316. .camera-icon {
  317. width: 64rpx;
  318. height: 64rpx;
  319. margin-bottom: 15rpx;
  320. }
  321. .upload-text {
  322. font-size: 26rpx;
  323. color: #ccc;
  324. }
  325. .preview-img {
  326. width: 100%;
  327. height: 100%;
  328. }
  329. .footer-btn-area {
  330. margin-top: 60rpx;
  331. padding: 0 20rpx;
  332. }
  333. .picker-mask {
  334. position: fixed;
  335. top: 0;
  336. left: 0;
  337. right: 0;
  338. bottom: 0;
  339. background-color: rgba(0, 0, 0, 0.5);
  340. z-index: 999;
  341. visibility: hidden;
  342. opacity: 0;
  343. transition: all 0.3s;
  344. }
  345. .picker-mask.show {
  346. visibility: visible;
  347. opacity: 1;
  348. }
  349. .picker-content {
  350. position: absolute;
  351. bottom: 0;
  352. left: 0;
  353. width: 100%;
  354. background-color: #fff;
  355. border-radius: 20rpx 20rpx 0 0;
  356. transform: translateY(100%);
  357. transition: all 0.3s;
  358. }
  359. .picker-mask.show .picker-content {
  360. transform: translateY(0);
  361. }
  362. .picker-header {
  363. display: flex;
  364. justify-content: space-between;
  365. align-items: center;
  366. padding: 30rpx;
  367. border-bottom: 1px solid #eee;
  368. font-size: 32rpx;
  369. }
  370. .picker-btn-cancel {
  371. color: #999;
  372. }
  373. .picker-title {
  374. font-weight: bold;
  375. color: #333;
  376. }
  377. .picker-btn-confirm {
  378. color: #FF5722;
  379. font-weight: bold;
  380. }
  381. .picker-view {
  382. width: 100%;
  383. height: 400rpx;
  384. }
  385. .picker-item {
  386. line-height: 50px;
  387. text-align: center;
  388. font-size: 32rpx;
  389. color: #333;
  390. }
  391. </style>