@ -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> |