| @ -0,0 +1,268 @@ | |||||
| <template> | |||||
| <view class="page__view"> | |||||
| <navbar title="人脸验证" leftClick @leftClick="$utils.navigateBack" color="#191919" bgColor="#FFFFFF" /> | |||||
| <view class="main"> | |||||
| <view class="card"> | |||||
| <view class="card-header"> | |||||
| <view>为保证本人操作,请进行人脸验证</view> | |||||
| <view class="card-desc">请保持正脸在取景框根据屏幕提示完成识别</view> | |||||
| </view> | |||||
| <view class="camera-box"> | |||||
| <!-- todo: check clear --> | |||||
| <image class="camera-bg" src="@/pages_order/static/order/camera-bg.png" mode="scaleToFill" /> | |||||
| <template v-if="countdown"> | |||||
| <view class="camera-countdown"> | |||||
| <uv-count-down | |||||
| :time="3 * 1000" | |||||
| autoStart | |||||
| @change="onCountDownChange" | |||||
| @finish="onCountDownFinish" | |||||
| > | |||||
| <view class="camera-countdown-text">{{ timeData.seconds }}s</view> | |||||
| </uv-count-down> | |||||
| </view> | |||||
| </template> | |||||
| <template v-if="scanning"> | |||||
| <view class="flex camera-fg"> | |||||
| <view style="position: relative;"> | |||||
| <camera class="camera" device-position="front" flash="auto" @initdone="onCameraInited" @error="onCameraError"></camera> | |||||
| <cover-image class="camera-tag" src="@/pages_order/static/order/tag.png"></cover-image> | |||||
| </view> | |||||
| </view> | |||||
| </template> | |||||
| <!-- todo: delete --> | |||||
| <template v-else-if="src"> | |||||
| <view class="flex camera-fg"> | |||||
| <image class="camera" :src="src" mode="scaleToFill" /> | |||||
| </view> | |||||
| </template> | |||||
| </view> | |||||
| <view class="flex tips"> | |||||
| <view class="flex flex-column tips-item"> | |||||
| <image class="tips-icon" src="@/pages_order/static/order/tips-1.png" mode="widthFix"></image> | |||||
| <view class="tips-text">保持光线充足</view> | |||||
| </view> | |||||
| <view class="flex flex-column tips-item"> | |||||
| <image class="tips-icon" src="@/pages_order/static/order/tips-2.png" mode="widthFix"></image> | |||||
| <view class="tips-text">脸在取景框内</view> | |||||
| </view> | |||||
| <view class="flex flex-column tips-item"> | |||||
| <image class="tips-icon" src="@/pages_order/static/order/tips-3.png" mode="widthFix"></image> | |||||
| <view class="tips-text">面部正对平面</view> | |||||
| </view> | |||||
| </view> | |||||
| </view> | |||||
| </view> | |||||
| <view class="bottom"> | |||||
| <button class="btn" @click="onVerify">开始识别</button> | |||||
| </view> | |||||
| </view> | |||||
| </template> | |||||
| <script> | |||||
| export default { | |||||
| data() { | |||||
| return { | |||||
| src: null, | |||||
| scanning: false, | |||||
| countdown: false, | |||||
| timeData: {}, | |||||
| } | |||||
| }, | |||||
| methods: { | |||||
| onVerify() { | |||||
| // todo | |||||
| this.scanning = true | |||||
| }, | |||||
| onCameraInited() { | |||||
| this.countdown = true | |||||
| }, | |||||
| onCountDownChange(e) { | |||||
| this.timeData = e | |||||
| }, | |||||
| onCountDownFinish() { | |||||
| this.countdown = false | |||||
| this.takePhoto() | |||||
| }, | |||||
| takePhoto() { | |||||
| const ctx = uni.createCameraContext(); | |||||
| ctx.takePhoto({ | |||||
| quality: 'high', | |||||
| success: (res) => { | |||||
| this.src = res.tempImagePath | |||||
| this.scanning = false | |||||
| // todo: fetch match | |||||
| setTimeout(() => { | |||||
| this.$utils.navigateBack() | |||||
| }, 1500) | |||||
| } | |||||
| }); | |||||
| }, | |||||
| onCameraError(e) { | |||||
| console.log(e.detail); | |||||
| } | |||||
| }, | |||||
| } | |||||
| </script> | |||||
| <style lang="scss" scoped> | |||||
| .page__view { | |||||
| width: 100vw; | |||||
| min-height: 100vh; | |||||
| background-color: $uni-bg-color; | |||||
| position: relative; | |||||
| /deep/ .nav-bar__view { | |||||
| position: fixed; | |||||
| top: 0; | |||||
| left: 0; | |||||
| } | |||||
| } | |||||
| .main { | |||||
| padding: calc(var(--status-bar-height) + 144rpx) 32rpx 224rpx 32rpx; | |||||
| } | |||||
| .card { | |||||
| padding: 32rpx; | |||||
| background: #FAFAFF; | |||||
| border: 2rpx solid #FFFFFF; | |||||
| border-radius: 32rpx; | |||||
| & + & { | |||||
| margin-top: 40rpx; | |||||
| } | |||||
| &-header { | |||||
| font-family: PingFang SC; | |||||
| font-weight: 500; | |||||
| font-size: 36rpx; | |||||
| line-height: 1.4; | |||||
| color: #252545; | |||||
| margin-bottom: 48rpx; | |||||
| } | |||||
| &-desc { | |||||
| margin-top: 4rpx; | |||||
| font-family: PingFang SC; | |||||
| font-weight: 400; | |||||
| font-size: 26rpx; | |||||
| line-height: 1.4; | |||||
| color: #4E4E69; | |||||
| } | |||||
| } | |||||
| .camera { | |||||
| &-box { | |||||
| width: 622rpx; | |||||
| height: 678rpx; | |||||
| position: relative; | |||||
| } | |||||
| &-bg { | |||||
| width: 100%; | |||||
| height: 100%; | |||||
| } | |||||
| &-countdown { | |||||
| position: absolute; | |||||
| top: 14rpx; | |||||
| left: 50%; | |||||
| transform: translateX(-50%); | |||||
| &-text { | |||||
| font-family: PingFang SC; | |||||
| font-weight: 600; | |||||
| font-size: 72rpx; | |||||
| line-height: 1.4; | |||||
| color: $uni-color; | |||||
| } | |||||
| } | |||||
| &-fg { | |||||
| position: absolute; | |||||
| top: 0; | |||||
| left: 0; | |||||
| width: 100%; | |||||
| height: 100%; | |||||
| } | |||||
| & { | |||||
| width: 310rpx; | |||||
| height: 310rpx; | |||||
| border-radius: 50%; | |||||
| } | |||||
| &-tag { | |||||
| position: absolute; | |||||
| left: 50%; | |||||
| bottom: 0; | |||||
| transform: translate(-50%, 60%); | |||||
| width: 248rpx; | |||||
| height: 160rpx; | |||||
| } | |||||
| } | |||||
| .tips { | |||||
| margin-top: 32rpx; | |||||
| padding: 0 32rpx; | |||||
| column-gap: 24rpx; | |||||
| &-item { | |||||
| flex: 1; | |||||
| row-gap: 24rpx; | |||||
| } | |||||
| &-icon { | |||||
| width: 72rpx; | |||||
| height: auto; | |||||
| } | |||||
| &-text { | |||||
| font-family: PingFang SC; | |||||
| font-weight: 400; | |||||
| font-size: 22rpx; | |||||
| line-height: 1.4; | |||||
| color: #989898; | |||||
| } | |||||
| } | |||||
| .bottom { | |||||
| position: fixed; | |||||
| left: 0; | |||||
| bottom: 0; | |||||
| width: 100vw; | |||||
| height: 200rpx; | |||||
| padding: 24rpx 40rpx; | |||||
| background: #FFFFFF; | |||||
| box-sizing: border-box; | |||||
| .btn { | |||||
| width: 100%; | |||||
| padding: 16rpx 0; | |||||
| box-sizing: border-box; | |||||
| font-family: PingFang SC; | |||||
| font-weight: 500; | |||||
| font-size: 36rpx; | |||||
| line-height: 1; | |||||
| color: #FFFFFF; | |||||
| background-image: linear-gradient(to right, #4B348F, #845CFA); | |||||
| border-radius: 41rpx; | |||||
| } | |||||
| } | |||||
| </style> | |||||
| @ -0,0 +1,242 @@ | |||||
| <template> | |||||
| <view class="page__view"> | |||||
| <navbar title="身份认证" leftClick @leftClick="$utils.navigateBack" color="#191919" bgColor="#FFFFFF" /> | |||||
| <view class="main"> | |||||
| <view class="card"> | |||||
| <view class="card-header"> | |||||
| <view>身份认证</view> | |||||
| <view class="card-desc">信息仅用于身份认证,平台将保障你的信息安全</view> | |||||
| </view> | |||||
| <view class="form"> | |||||
| <uv-form | |||||
| ref="form" | |||||
| :model="form" | |||||
| :rules="rules" | |||||
| errorType="toast" | |||||
| > | |||||
| <view class="form-item"> | |||||
| <uv-form-item prop="idFront" :customStyle="formItemStyle"> | |||||
| <view class="form-item-content"> | |||||
| <view class="upload" @click="onUpload('idFront')"> | |||||
| <image v-if="form.idFront" class="upload-img" :src="form.idFront" mode="scaleToFill" /> | |||||
| <view v-else class="flex upload-default"> | |||||
| <image class="upload-default-img" src="@/pages_order/static/order/id-front-default.png" mode="aspectFill" /> | |||||
| </view> | |||||
| </view> | |||||
| </view> | |||||
| <view class="form-item-label">身份证国徽面(点击上传)</view> | |||||
| </uv-form-item> | |||||
| </view> | |||||
| <view class="form-item"> | |||||
| <uv-form-item prop="idBack" :customStyle="formItemStyle"> | |||||
| <view class="form-item-content"> | |||||
| <view class="upload" @click="onUpload('idBack')"> | |||||
| <image v-if="form.idBack" class="upload-img" :src="form.idBack" mode="scaleToFill" /> | |||||
| <view v-else class="flex upload-default"> | |||||
| <image class="upload-default-img" src="@/pages_order/static/order/id-back-default.png" mode="aspectFill" /> | |||||
| </view> | |||||
| </view> | |||||
| </view> | |||||
| <view class="form-item-label">身份证人像面(点击上传)</view> | |||||
| </uv-form-item> | |||||
| </view> | |||||
| </uv-form> | |||||
| </view> | |||||
| </view> | |||||
| </view> | |||||
| <view class="bottom"> | |||||
| <button class="btn" @click="onConfirm">确认</button> | |||||
| </view> | |||||
| </view> | |||||
| </template> | |||||
| <script> | |||||
| export default { | |||||
| data() { | |||||
| return { | |||||
| form: { | |||||
| idFront: null, | |||||
| idBack: null, | |||||
| }, | |||||
| rules: { | |||||
| 'idFront': { | |||||
| type: 'string', | |||||
| required: true, | |||||
| message: '请上传身份证国徽面', | |||||
| }, | |||||
| 'idBack': { | |||||
| type: 'string', | |||||
| required: true, | |||||
| message: '请上传身份证人像面', | |||||
| }, | |||||
| }, | |||||
| formItemStyle: { padding: 0 }, | |||||
| } | |||||
| }, | |||||
| methods: { | |||||
| onUpload(key) { | |||||
| uni.chooseImage({ | |||||
| count: 1, | |||||
| sizeType: ['original', 'compressed'], //可以指定是原图还是压缩图,默认二者都有 | |||||
| success: res => { | |||||
| let avatarUrl = res.tempFilePaths[0] // 将选择的图片赋值给我们定义的cover | |||||
| this.$Oss.ossUpload(avatarUrl) | |||||
| .then(url => { | |||||
| this.form[key] = url | |||||
| }) | |||||
| } | |||||
| }); | |||||
| }, | |||||
| async onConfirm() { | |||||
| try { | |||||
| const res = await this.$refs.form.validate() | |||||
| console.log('onSave res', res) | |||||
| // todo: save | |||||
| // todo: check | |||||
| // this.$utils.redirectTo(`/pages_order/order/userInfo/facialVerify`) | |||||
| this.$utils.redirectTo('/pages_order/order/userInfo/facialVerifyCustom') | |||||
| } catch (err) { | |||||
| console.log('onSave err', err) | |||||
| } | |||||
| }, | |||||
| }, | |||||
| } | |||||
| </script> | |||||
| <style lang="scss" scoped> | |||||
| .page__view { | |||||
| width: 100vw; | |||||
| min-height: 100vh; | |||||
| background-color: $uni-bg-color; | |||||
| position: relative; | |||||
| /deep/ .nav-bar__view { | |||||
| position: fixed; | |||||
| top: 0; | |||||
| left: 0; | |||||
| } | |||||
| } | |||||
| .main { | |||||
| padding: calc(var(--status-bar-height) + 144rpx) 32rpx 224rpx 32rpx; | |||||
| } | |||||
| .card { | |||||
| padding: 32rpx; | |||||
| background: #FAFAFF; | |||||
| border: 2rpx solid #FFFFFF; | |||||
| border-radius: 32rpx; | |||||
| & + & { | |||||
| margin-top: 40rpx; | |||||
| } | |||||
| &-header { | |||||
| font-family: PingFang SC; | |||||
| font-weight: 500; | |||||
| font-size: 36rpx; | |||||
| line-height: 1.4; | |||||
| color: #252545; | |||||
| margin-bottom: 48rpx; | |||||
| } | |||||
| &-desc { | |||||
| margin-top: 4rpx; | |||||
| font-family: PingFang SC; | |||||
| font-weight: 400; | |||||
| font-size: 26rpx; | |||||
| line-height: 1.4; | |||||
| color: #4E4E69; | |||||
| } | |||||
| } | |||||
| .form { | |||||
| padding: 8rpx 0 0 0; | |||||
| &-item { | |||||
| & + & { | |||||
| margin-top: 48rpx; | |||||
| } | |||||
| &-label { | |||||
| margin-top: 32rpx; | |||||
| text-align: center; | |||||
| font-family: PingFang SC; | |||||
| font-weight: 400; | |||||
| font-size: 30rpx; | |||||
| line-height: 1.4; | |||||
| color: #4E4E69; | |||||
| } | |||||
| } | |||||
| } | |||||
| .upload { | |||||
| position: relative; | |||||
| width: 100%; | |||||
| height: 386rpx; | |||||
| overflow: hidden; | |||||
| border-radius: 32rpx; | |||||
| box-shadow: -5rpx -5rpx 10rpx 0 #FFFFFF, | |||||
| 10rpx 10rpx 20rpx 0 #AAAACC80, | |||||
| 4rpx 4rpx 10rpx 0 #AAAACC40, | |||||
| -2rpx -2rpx 5rpx 0 #FFFFFF; | |||||
| &-img { | |||||
| width: 100%; | |||||
| height: 100%; | |||||
| } | |||||
| &-default { | |||||
| width: 100%; | |||||
| height: 100%; | |||||
| background-image: linear-gradient(#FAFAFF, #F3F3F3); | |||||
| &-img { | |||||
| width: 240rpx; | |||||
| height: 240rpx; | |||||
| } | |||||
| } | |||||
| } | |||||
| .bottom { | |||||
| position: fixed; | |||||
| left: 0; | |||||
| bottom: 0; | |||||
| width: 100vw; | |||||
| height: 200rpx; | |||||
| padding: 24rpx 40rpx; | |||||
| background: #FFFFFF; | |||||
| box-sizing: border-box; | |||||
| .btn { | |||||
| width: 100%; | |||||
| padding: 16rpx 0; | |||||
| box-sizing: border-box; | |||||
| font-family: PingFang SC; | |||||
| font-weight: 500; | |||||
| font-size: 36rpx; | |||||
| line-height: 1; | |||||
| color: #FFFFFF; | |||||
| background-image: linear-gradient(to right, #4B348F, #845CFA); | |||||
| border-radius: 41rpx; | |||||
| } | |||||
| } | |||||
| </style> | |||||
| @ -0,0 +1,269 @@ | |||||
| <template> | |||||
| <view class="page__view"> | |||||
| <navbar title="填写个人信息" leftClick @leftClick="$utils.navigateBack" color="#191919" bgColor="#FFFFFF" /> | |||||
| <view class="main"> | |||||
| <view class="card"> | |||||
| <view class="card-header">申请信息</view> | |||||
| <view class="form"> | |||||
| <uv-form | |||||
| ref="form" | |||||
| :model="form" | |||||
| :rules="rules" | |||||
| errorType="toast" | |||||
| > | |||||
| <view class="form-item"> | |||||
| <uv-form-item prop="name" :customStyle="formItemStyle"> | |||||
| <view class="form-item-label">联系人</view> | |||||
| <view class="form-item-content"> | |||||
| <formInput v-model="form.name"></formInput> | |||||
| </view> | |||||
| </uv-form-item> | |||||
| </view> | |||||
| <view class="form-item"> | |||||
| <uv-form-item prop="phone" :customStyle="formItemStyle"> | |||||
| <view class="form-item-label">手机号</view> | |||||
| <view class="form-item-content"> | |||||
| <formInput v-model="form.phone"></formInput> | |||||
| </view> | |||||
| </uv-form-item> | |||||
| </view> | |||||
| <view class="form-item"> | |||||
| <uv-form-item prop="phone" :customStyle="formItemStyle"> | |||||
| <view class="form-item-label">所在地区</view> | |||||
| <view class="form-item-content"> | |||||
| <picker mode="region" @change="onAreaChange" :value="form.area"> | |||||
| <view class="flex region"> | |||||
| <view v-if="form.area">{{ form.area.join('') }}</view> | |||||
| <view v-else class="placeholder">选择省市区街道</view> | |||||
| </view> | |||||
| </picker> | |||||
| </view> | |||||
| </uv-form-item> | |||||
| </view> | |||||
| <view class="form-item"> | |||||
| <uv-form-item prop="address" :customStyle="formItemStyle"> | |||||
| <view class="form-item-label">详细地址</view> | |||||
| <view class="form-item-content"> | |||||
| <formInput v-model="form.address" placeholder="小区楼栋、门牌号、村等"></formInput> | |||||
| </view> | |||||
| </uv-form-item> | |||||
| </view> | |||||
| </uv-form> | |||||
| </view> | |||||
| </view> | |||||
| </view> | |||||
| <view class="bottom"> | |||||
| <button class="btn" @click="onConfirm">确认</button> | |||||
| </view> | |||||
| </view> | |||||
| </template> | |||||
| <script> | |||||
| import { mapState } from 'vuex' | |||||
| import util from '@/utils/utils.js' | |||||
| import formInput from '@/pages_order/components/formInput.vue' | |||||
| export default { | |||||
| components: { | |||||
| formInput, | |||||
| }, | |||||
| data() { | |||||
| return { | |||||
| form: { | |||||
| name: null, | |||||
| phone: null, | |||||
| area: null, | |||||
| address: null, | |||||
| }, | |||||
| rules: { | |||||
| 'name': { | |||||
| type: 'string', | |||||
| required: true, | |||||
| message: '请输入联系人', | |||||
| }, | |||||
| 'phone': { | |||||
| type: 'string', | |||||
| required: true, | |||||
| message: '请输入正确的手机号', | |||||
| validator: (rule, value, callback) => { | |||||
| return util.validatePhone(value) | |||||
| }, | |||||
| }, | |||||
| 'area': { | |||||
| type: 'array', | |||||
| required: true, | |||||
| message: '请选择省市区', | |||||
| }, | |||||
| 'address': { | |||||
| type: 'string', | |||||
| required: true, | |||||
| message: '请输入详细地址', | |||||
| }, | |||||
| }, | |||||
| formItemStyle: { padding: 0 }, | |||||
| } | |||||
| }, | |||||
| onReady() { | |||||
| this.$refs.form.setRules(this.rules) | |||||
| }, | |||||
| methods: { | |||||
| onAreaChange(e) { | |||||
| this.form.area = e.detail.value | |||||
| }, | |||||
| async onConfirm() { | |||||
| try { | |||||
| const res = await this.$refs.form.validate() | |||||
| console.log('onSave res', res) | |||||
| // todo: save | |||||
| this.$utils.redirectTo(`/pages_order/order/userInfo/idUpload`) | |||||
| } catch (err) { | |||||
| console.log('onSave err', err) | |||||
| } | |||||
| }, | |||||
| }, | |||||
| } | |||||
| </script> | |||||
| <style lang="scss" scoped> | |||||
| .page__view { | |||||
| width: 100vw; | |||||
| min-height: 100vh; | |||||
| background-color: $uni-bg-color; | |||||
| position: relative; | |||||
| /deep/ .nav-bar__view { | |||||
| position: fixed; | |||||
| top: 0; | |||||
| left: 0; | |||||
| } | |||||
| } | |||||
| .main { | |||||
| padding: calc(var(--status-bar-height) + 144rpx) 32rpx 224rpx 32rpx; | |||||
| } | |||||
| .card { | |||||
| padding: 32rpx; | |||||
| background: #FAFAFF; | |||||
| border: 2rpx solid #FFFFFF; | |||||
| border-radius: 32rpx; | |||||
| & + & { | |||||
| margin-top: 40rpx; | |||||
| } | |||||
| &-header { | |||||
| font-family: PingFang SC; | |||||
| font-weight: 500; | |||||
| font-size: 36rpx; | |||||
| line-height: 1.4; | |||||
| color: #252545; | |||||
| margin-bottom: 32rpx; | |||||
| } | |||||
| } | |||||
| .row { | |||||
| justify-content: space-between; | |||||
| font-family: PingFang SC; | |||||
| font-weight: 400; | |||||
| line-height: 1.4; | |||||
| column-gap: 24rpx; | |||||
| & + & { | |||||
| margin-top: 32rpx; | |||||
| } | |||||
| &-label { | |||||
| flex: none; | |||||
| font-size: 26rpx; | |||||
| color: #8B8B8B; | |||||
| } | |||||
| &-content { | |||||
| font-size: 32rpx; | |||||
| color: #181818; | |||||
| } | |||||
| } | |||||
| .form { | |||||
| padding: 8rpx 0 0 0; | |||||
| &-item { | |||||
| border-bottom: 2rpx solid #EEEEEE; | |||||
| &:last-child { | |||||
| border: none; | |||||
| } | |||||
| & + & { | |||||
| margin-top: 40rpx; | |||||
| } | |||||
| &-label { | |||||
| font-family: PingFang SC; | |||||
| font-weight: 400; | |||||
| font-size: 26rpx; | |||||
| line-height: 1.4; | |||||
| color: #181818; | |||||
| } | |||||
| &-content { | |||||
| margin-top: 14rpx; | |||||
| padding: 6rpx 0; | |||||
| .placeholder { | |||||
| color: #C6C6C6; | |||||
| font-size: 32rpx; | |||||
| font-weight: 400; | |||||
| } | |||||
| .region { | |||||
| min-height: 44rpx; | |||||
| justify-content: flex-start; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| .bottom { | |||||
| position: fixed; | |||||
| left: 0; | |||||
| bottom: 0; | |||||
| width: 100vw; | |||||
| height: 200rpx; | |||||
| padding: 24rpx 40rpx; | |||||
| background: #FFFFFF; | |||||
| box-sizing: border-box; | |||||
| .btn { | |||||
| width: 100%; | |||||
| padding: 16rpx 0; | |||||
| box-sizing: border-box; | |||||
| font-family: PingFang SC; | |||||
| font-weight: 500; | |||||
| font-size: 36rpx; | |||||
| line-height: 1; | |||||
| color: #FFFFFF; | |||||
| background-image: linear-gradient(to right, #4B348F, #845CFA); | |||||
| border-radius: 41rpx; | |||||
| } | |||||
| } | |||||
| </style> | |||||