添加人脸识别
This commit is contained in:
214
bundle/face/face-info.vue
Normal file
214
bundle/face/face-info.vue
Normal file
@ -0,0 +1,214 @@
|
||||
<template>
|
||||
<view class="content">
|
||||
<view class="title">请确保本人操作</view>
|
||||
<view class="desc">为了防范身份信息被冒用,保障资金安全,请保持正脸在取景框中根据屏幕指示完成识别</view>
|
||||
<view class="photo-box">
|
||||
<image src="https://xh.stnav.com/uploads/sport/face1.png" style="width: 280rpx; height: 350rpx;"></image>
|
||||
</view>
|
||||
<view>
|
||||
<view class="desc2">1.为了确保您账户的安全和真实性,我们需要对您进行身份验证;</view>
|
||||
<view class="desc3">2.请您本人亲自完成,请将脸部置于提示框内,并按提示做动作。</view>
|
||||
</view>
|
||||
|
||||
<view class="photo-list" >
|
||||
<view class="photo-item">
|
||||
<view class="">
|
||||
<image src="https://xh.stnav.com/uploads/sport/face2.png" style="width: 90rpx; height: 90rpx;"></image>
|
||||
</view>
|
||||
<view class="">正对手机</view>
|
||||
</view>
|
||||
|
||||
<view class="photo-item">
|
||||
<view class="">
|
||||
<image src="https://xh.stnav.com/uploads/sport/face3.png" style="width: 90rpx; height: 90rpx;"></image>
|
||||
</view>
|
||||
<view class="">正对手机</view>
|
||||
</view>
|
||||
|
||||
<view class="">
|
||||
<view class="photo-item">
|
||||
<image src="https://xh.stnav.com/uploads/sport/face4.png" style="width: 90rpx; height: 90rpx;"></image>
|
||||
</view>
|
||||
<view class="">正对手机</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="photo-btn" @click="takePhoto">开始录入</view>
|
||||
<!-- <image v-if="imgSrc" :src="imgSrc" style="width: 200rpx; height: 200rpx; margin-top: 20rpx;"></image> -->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
order_id: 0,
|
||||
imgSrc: '',
|
||||
loadding: true,
|
||||
}
|
||||
},
|
||||
|
||||
onLoad(args) {
|
||||
this.order_id = args.order_id || 0;
|
||||
},
|
||||
|
||||
methods: {
|
||||
takePhoto() {
|
||||
let self = this
|
||||
console.log('take photo')
|
||||
uni.navigateTo({
|
||||
url: `/bundle/face/face-photo?order_id=${this.order_id}`,
|
||||
events:{
|
||||
data: (e) => {
|
||||
// self.imgSrc = path
|
||||
self.uploadFile(e.path, e.order_id)
|
||||
// uni.redirectTo({
|
||||
// url: '/pages/order/cg-my-order'
|
||||
// });
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/*上传图片*/
|
||||
uploadFile: function(filePath, order_id) {
|
||||
console.log("🚀 ~ filePat1, order_id:", filePath)
|
||||
console.log("🚀 ~ filePat2, order_id:", order_id)
|
||||
let self = this;
|
||||
let params = {
|
||||
token: uni.getStorageSync('token'),
|
||||
app_id: self.getAppId()
|
||||
};
|
||||
uni.showLoading({
|
||||
title: '图片上传中'
|
||||
});
|
||||
uni.uploadFile({
|
||||
url: self.websiteUrl + '/index.php?s=/api/file.upload/image',
|
||||
filePath: filePath,
|
||||
name: 'iFile',
|
||||
formData: params,
|
||||
success: function(res) {
|
||||
uni.hideLoading();
|
||||
|
||||
let result = typeof res.data === 'object' ? res.data : JSON.parse(res.data);
|
||||
if (result.code === 1) {
|
||||
setTimeout(() => {
|
||||
uni.showLoading({
|
||||
title: '人脸录入中...'
|
||||
});
|
||||
}, 100);
|
||||
|
||||
console.log("🚀 ~ filePath, order_id2:", filePath, order_id)
|
||||
self._post(
|
||||
'haikang.createPerson/hikCreate',
|
||||
{
|
||||
app_id: self.getAppId(),
|
||||
faceUrl: result.data.file_path,
|
||||
order_id: order_id
|
||||
},
|
||||
function(res) {
|
||||
console.log("🚀 ~ res:", res)
|
||||
uni.hideLoading();
|
||||
setTimeout(() => {
|
||||
uni.showToast({
|
||||
title: '操作成功',
|
||||
duration: 1500,
|
||||
icon: 'success'
|
||||
});
|
||||
}, 100);
|
||||
|
||||
setTimeout(() => {
|
||||
uni.redirectTo({
|
||||
url: '/pages/order/cg-my-order?type=2'
|
||||
});
|
||||
}, 800)
|
||||
}
|
||||
);
|
||||
// self.imgSrc = result.data.file_path;
|
||||
} else {
|
||||
// self.showError(result.msg);
|
||||
}
|
||||
},
|
||||
complete: function() {
|
||||
uni.hideLoading();
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
page {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.content {
|
||||
margin: 16rpx 60rpx 0;
|
||||
padding-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 40rpx;
|
||||
color: #303133;
|
||||
line-height: 56rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.desc {
|
||||
margin-top: 20rpx;
|
||||
text-align: left;
|
||||
font-size: 26rpx;
|
||||
color: #909399;
|
||||
line-height: 36rpx;
|
||||
}
|
||||
|
||||
.photo-box {
|
||||
margin-top: 52rpx;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.desc2, .desc3 {
|
||||
font-size: 28rpx;
|
||||
color: #606266;
|
||||
line-height: 40rpx;
|
||||
}
|
||||
|
||||
.desc3 {
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.photo-list {
|
||||
margin: 70rpx 18rpx 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.photo-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
& view:last-child {
|
||||
margin-top: 20rpx;
|
||||
color: #606266;
|
||||
line-height: 36rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.photo-btn {
|
||||
margin-top: 222rpx;
|
||||
width: 630rpx;
|
||||
height: 90rpx;
|
||||
line-height: 90rpx;
|
||||
text-align: center;
|
||||
border-radius: 8rpx;
|
||||
background: #365A9A;
|
||||
font-weight: bold;
|
||||
font-size: 30rpx;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
</style>
|
||||
46
bundle/face/face-photo.vue
Normal file
46
bundle/face/face-photo.vue
Normal file
@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<view>
|
||||
<face-bio-assay :isDev="false" :action="['StraightenHead']" ref="faceDetect" @detectFailed="photoChange" @photoChange="photoChange">
|
||||
</face-bio-assay>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import faceBioAssay from '@/uni_modules/face-bio-assay/components/face-bio-assay/face-bio-assay.vue'
|
||||
export default {
|
||||
components: {
|
||||
faceBioAssay,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
order_id: 0,
|
||||
}
|
||||
},
|
||||
|
||||
onLoad(args) {
|
||||
this.order_id = args.order_id || 0;
|
||||
this.$refs.faceDetect.initData()
|
||||
},
|
||||
|
||||
methods: {
|
||||
detectFailed() {
|
||||
uni.showToast({
|
||||
title: "人脸核验失败~",
|
||||
icon: 'none'
|
||||
})
|
||||
uni.navigateBack()
|
||||
},
|
||||
|
||||
photoChange(path) {
|
||||
let self = this
|
||||
uni.navigateBack()
|
||||
this.getOpenerEventChannel().emit('data',{path: path, order_id: self.order_id});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
@ -688,7 +688,7 @@ export default {
|
||||
uni.$off("payment")
|
||||
if (params.result) {
|
||||
uni.redirectTo({
|
||||
url: `/bundle/reserve/notice?order_id=${params.order_id}`
|
||||
url: `/bundle/reserve/notice?order_id=${params.order_id}&ground_type=${self.venue.type_id}`
|
||||
})
|
||||
} else {
|
||||
uni.redirectTo({
|
||||
@ -761,11 +761,11 @@ export default {
|
||||
uni.$off("payment")
|
||||
if (params.result) {
|
||||
uni.redirectTo({
|
||||
url: `/bundle/reserve/notice?order_id=${params.order_id}`
|
||||
url: `/bundle/reserve/notice?order_id=${params.order_id}&ground_type=${self.venue.type_id}`
|
||||
})
|
||||
} else {
|
||||
uni.redirectTo({
|
||||
url: '/pages/order/cg-my-order'
|
||||
url: '/pages/order/cg-my-order?type=2'
|
||||
})
|
||||
}
|
||||
}, 500)
|
||||
|
||||
@ -12,18 +12,39 @@
|
||||
<view class="btn1" @click="seeOrder">查看订单</view>
|
||||
<view class="btn2" @click="done">完成</view>
|
||||
</view>
|
||||
|
||||
<!-- 人脸录入提示 -->
|
||||
<Popup :show="facePopup" radius="16rpx">
|
||||
<view class="notice-popup" style="width: 100%; position: relative;">
|
||||
<image style="width: 36rpx;height: 36rpx;position: absolute; top: 0; right: 46rpx;" src="https://xh.stnav.com/uploads/sport/icon_close.png" mode="" @click="facePopup = false"></image>
|
||||
|
||||
<view class="title">提示</view>
|
||||
<view class="desc" style="text-align: center;">
|
||||
为了给您提供更安全、便捷的入场体验,请完成人脸识别认证。只需几秒钟,轻松刷脸入场!
|
||||
</view>
|
||||
<view class="btn" @click="toTakeFacePhoto">去录入</view>
|
||||
</view>
|
||||
</Popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Popup from '@/components/uni-popup.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Popup,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
order_id: 0
|
||||
order_id: 0,
|
||||
ground_type: 1, // 1 网球 2篮球
|
||||
facePopup: false,
|
||||
};
|
||||
},
|
||||
|
||||
onLoad(args) {
|
||||
this.ground_type = args.ground_type || 1;
|
||||
this.order_id = args.order_id || 0;
|
||||
},
|
||||
|
||||
@ -39,10 +60,23 @@
|
||||
},
|
||||
|
||||
done() {
|
||||
uni.reLaunch({
|
||||
url: '/pages/index/index'
|
||||
if (this.ground_type == 1) {
|
||||
uni.reLaunch({
|
||||
url: '/pages/index/index'
|
||||
});
|
||||
} else if (this.ground_type == 2) {
|
||||
// 篮球场需要人脸录入
|
||||
this.facePopup = true;
|
||||
}
|
||||
},
|
||||
|
||||
// 去录入人脸照片
|
||||
toTakeFacePhoto() {
|
||||
this.facePopup = false;
|
||||
uni.navigateTo({
|
||||
url: `/bundle/face/face-info?order_id=${this.order_id}`
|
||||
});
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@ -111,4 +145,35 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.notice-popup {
|
||||
padding-bottom: 20rpx;
|
||||
|
||||
.title {
|
||||
font-size: 36rpx;
|
||||
color: #303133;
|
||||
line-height: 50rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.desc {
|
||||
margin-top: 48rpx;
|
||||
font-weight: 400;
|
||||
font-size: 32rpx;
|
||||
color: #303133;
|
||||
line-height: 52rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.btn {
|
||||
font-size: 32rpx;
|
||||
background: #365A9A;
|
||||
border-radius: 8rpx;
|
||||
margin: 92rpx 44rpx 0;
|
||||
height: 80rpx;
|
||||
line-height: 80rpx;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -157,6 +157,9 @@
|
||||
"permission" : {
|
||||
"scope.userLocation" : {
|
||||
"desc" : "获取您与体育场馆的距离"
|
||||
},
|
||||
"scope.camera": {
|
||||
"desc": "需要获取摄像头权限进行人脸录入"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
12
pages.json
12
pages.json
@ -1013,6 +1013,18 @@
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "face/face-info",
|
||||
"style": {
|
||||
"navigationBarTitleText": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "face/face-photo",
|
||||
"style": {
|
||||
"navigationBarTitleText": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
}],
|
||||
|
||||
@ -133,6 +133,11 @@
|
||||
|
||||
<block v-if="item.order_status >= 2">
|
||||
<button class="theme-btn del-btn" @click="onDelOrder(item.id)">删除订单</button>
|
||||
</block>
|
||||
|
||||
<!-- 在篮球场下且订单是已预约有个人脸录入 -->
|
||||
<block v-if="item.order_status == 1 && ballType == 2 && item.face_status == 0">
|
||||
<button class="theme-btn pay-btn" @click="onTakePhoto(item.id)">人脸录入</button>
|
||||
</block>
|
||||
</block>
|
||||
</view>
|
||||
@ -251,6 +256,10 @@
|
||||
}
|
||||
},
|
||||
onLoad(e) {
|
||||
if (e.type) {
|
||||
this.ballType = e.type;
|
||||
}
|
||||
|
||||
if (typeof e.dataType != 'undefined') {
|
||||
this.dataType = e.dataType;
|
||||
}
|
||||
@ -572,6 +581,12 @@
|
||||
uni.navigateTo({
|
||||
url: `/bundle/reserve/details?id=${ground_id}&typeId=${this.ballType}`
|
||||
});
|
||||
},
|
||||
|
||||
onTakePhoto(order_id) {
|
||||
uni.navigateTo({
|
||||
url: `/bundle/face/face-info?order_id=${order_id}`
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
36
uni_modules/face-bio-assay/changelog.md
Normal file
36
uni_modules/face-bio-assay/changelog.md
Normal file
@ -0,0 +1,36 @@
|
||||
## 1.1.1(2023-04-13)
|
||||
文档更新:动作容器的使用案例
|
||||
## 1.1.0(2023-04-13)
|
||||
整理代码,补充文档信息
|
||||
## 1.0.14(2023-04-13)
|
||||
更新文档
|
||||
## 1.0.13(2023-04-13)
|
||||
更新文档信息
|
||||
## 1.0.12(2023-04-13)
|
||||
修复授权弹窗bug
|
||||
## 1.0.11(2023-04-11)
|
||||
文档调整
|
||||
## 1.0.10(2023-04-11)
|
||||
更新文档
|
||||
## 1.0.9(2023-04-11)
|
||||
1.优化点头摇头动作
|
||||
2.解决页面回退在ios出现空白页
|
||||
3.调整ui布局
|
||||
## 1.0.8(2023-04-03)
|
||||
修改文档,并提出在ios中二次调用bug的解决方案
|
||||
## 1.0.7(2023-03-28)
|
||||
未通过核验停留两秒,对未授权拍照bug修复
|
||||
## 1.0.6(2023-03-27)
|
||||
提示用户开启摄像头权限
|
||||
## 1.0.5(2023-03-22)
|
||||
bug修复,文档修订
|
||||
## 1.0.4(2023-03-21)
|
||||
更新文档
|
||||
## 1.0.3(2023-03-21)
|
||||
新增 动作容器(ActionContainer) 类 开发者可通过自定义动作 添加到该容器中管理。也可使用已有动作进行排列组合创建动作组
|
||||
## 1.0.2(2023-03-16)
|
||||
调整通过条件,一次行为就可以通过验证
|
||||
## 1.0.1(2023-03-15)
|
||||
参数调整,提高通过率
|
||||
## 1.0.0(2023-03-14)
|
||||
基于微信小程序的人脸识别做的简易版活体检测
|
||||
@ -0,0 +1,59 @@
|
||||
class ActionContainer {
|
||||
//动作组 actions
|
||||
//起始动作下标 index
|
||||
//初始提示 tip
|
||||
//绑定 完成所有动作的回调 endFun
|
||||
//绑定 动作进行中 ingFun
|
||||
//绑定 动作完成时 successFun
|
||||
//绑定 动作失败时 failFun
|
||||
constructor(actions, index, tip, endFun, ingFun, successFun, failFun) {
|
||||
this.actions = actions || []
|
||||
this.index = index || 0
|
||||
this.tip = tip || '检测不到人脸'
|
||||
this.endFun = endFun || this.__tempFun
|
||||
this.ingFun = ingFun || this.__tempFun
|
||||
this.successFun = successFun || this.__tempFun
|
||||
this.failFun = failFun || this.__tempFun
|
||||
}
|
||||
__tempFun(){
|
||||
|
||||
}
|
||||
next(faceData) {
|
||||
if (this.index >= this.actions.length) {
|
||||
this.endFun()
|
||||
return this
|
||||
}
|
||||
if (this.actions[this.index].state === 'ing') {
|
||||
this.tip = this.actions[this.index].tip
|
||||
this.actions[this.index].check(faceData)
|
||||
this.ingFun()
|
||||
return this
|
||||
} else if (this.actions[this.index].state === 'success') {
|
||||
this.index++;
|
||||
this.successFun()
|
||||
return this
|
||||
} else if (this.actions[this.index].state === 'fail') {
|
||||
this.failFun()
|
||||
return this
|
||||
}
|
||||
return this
|
||||
}
|
||||
end(fun) { //绑定 完成所有动作的回调
|
||||
this.endFun = fun || this.__tempFun
|
||||
return this
|
||||
}
|
||||
ing(fun) { //绑定 动作进行中
|
||||
this.ingFun = fun || this.__tempFun
|
||||
return this
|
||||
}
|
||||
success(fun) { //绑定 动作完成时
|
||||
this.successFun = fun || this.__tempFun
|
||||
return this
|
||||
}
|
||||
fail(fun) { //绑定 动作失败时
|
||||
this.failFun = fun || this.__tempFun
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
export default ActionContainer
|
||||
@ -0,0 +1,57 @@
|
||||
const STATE = {
|
||||
ING:'ing',
|
||||
SUCCESS:'success',
|
||||
FAIL:'fail'
|
||||
}
|
||||
export default class Action {
|
||||
constructor(second=10,fun,limit,initTip) {
|
||||
this.second = second
|
||||
this.endTime = Infinity
|
||||
this.frames = []
|
||||
this.tip = initTip
|
||||
this.initTip = initTip
|
||||
this.state = STATE.ING
|
||||
this.fun = fun
|
||||
this.limit = limit
|
||||
}
|
||||
end(){
|
||||
if(this.fun){
|
||||
this.fun(this.state)
|
||||
}
|
||||
}
|
||||
check(faceData){
|
||||
if(this.endTime === Infinity){
|
||||
this.endTime = new Date().getTime() + (this.second*1000)
|
||||
}
|
||||
if(this.state !== STATE.ING ){
|
||||
return
|
||||
}
|
||||
if(new Date().getTime()>this.endTime){
|
||||
this.state = STATE.FAIL
|
||||
this.end()
|
||||
return
|
||||
}
|
||||
if(this.frames.length>=this.limit){
|
||||
this.state = STATE.SUCCESS
|
||||
this.end()
|
||||
return
|
||||
}
|
||||
this.takeFrameAfter(faceData)?.takeFrame(faceData)
|
||||
}
|
||||
takeFrame(faceData){
|
||||
|
||||
}
|
||||
takeFrameAfter(faceData){
|
||||
let face = faceData.faceInfo[0]
|
||||
this.tip = this.initTip
|
||||
if(faceData.x == -1 || faceData.y == -1) {
|
||||
this.tip = '检测不到人脸'
|
||||
return null
|
||||
}
|
||||
if(faceData.faceInfo.length > 1) {
|
||||
this.tip = '请保证只有一人做核验'
|
||||
return null
|
||||
}
|
||||
return this
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
import Action from "./Action.js"
|
||||
class NodHead extends Action {
|
||||
constructor(second=10,fun) {
|
||||
super(second,fun,1,'请点头')
|
||||
this.maxPitch = 0
|
||||
this.minPitch = 0
|
||||
}
|
||||
takeFrame(faceData){
|
||||
let face = faceData.faceInfo[0]
|
||||
if(face.angleArray.pitch>this.maxPitch){
|
||||
this.maxPitch = face.angleArray.pitch
|
||||
}
|
||||
if(face.angleArray.pitch<this.minPitch){
|
||||
this.minPitch = face.angleArray.pitch
|
||||
}
|
||||
if(Math.abs(this.minPitch-this.maxPitch)>0.45 && (this.minPitch || this.maxPitch) ){
|
||||
this.frames.push('点头')
|
||||
this.maxPitch = 0
|
||||
this.minPitch = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default NodHead
|
||||
@ -0,0 +1,24 @@
|
||||
import Action from "./Action.js"
|
||||
class ShakeHead extends Action {
|
||||
constructor(second = 10, fun) {
|
||||
super(second,fun,1,'请摇头')
|
||||
this.minYaw = 0
|
||||
this.maxYaw = 0
|
||||
}
|
||||
takeFrame(faceData) {
|
||||
let face = faceData.faceInfo[0]
|
||||
if(face.angleArray.yaw>this.maxYaw){
|
||||
this.maxYaw = face.angleArray.yaw
|
||||
}
|
||||
if(face.angleArray.yaw<this.minYaw){
|
||||
this.minYaw = face.angleArray.yaw
|
||||
}
|
||||
if(Math.abs(this.minYaw-this.maxYaw)>0.45 && (this.minYaw || this.maxYaw) ){
|
||||
this.frames.push('摇头')
|
||||
this.minYaw = 0
|
||||
this.maxYaw = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ShakeHead
|
||||
@ -0,0 +1,27 @@
|
||||
import Action from "./Action.js"
|
||||
class StraightenHead extends Action {
|
||||
constructor(second = 10, fun) {
|
||||
super(second, fun, 10, '请平视摄像头')
|
||||
}
|
||||
takeFrame(faceData) {
|
||||
let face = faceData.faceInfo[0]
|
||||
if (Math.abs(face.angleArray.pitch) >= 0.3 || Math.abs(face.angleArray.roll) >= 0.2 || Math.abs(face
|
||||
.angleArray.yaw) >= 0.2) {
|
||||
this.frames = []
|
||||
return
|
||||
}
|
||||
if (Math.abs(face.confArray.global) <= 0.8 || Math.abs(face.confArray.leftEye) <= 0.8 || Math.abs(
|
||||
face.confArray.mouth) <=
|
||||
0.8 || Math.abs(face.confArray.nose) <= 0.8 || Math.abs(face.confArray.rightEye) <= 0.8) {
|
||||
this.tip = '请勿遮挡五官'
|
||||
this.frames = []
|
||||
return
|
||||
}
|
||||
this.tip = '正在核验,请保持'
|
||||
this.frames.push('正')
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default StraightenHead
|
||||
@ -0,0 +1,9 @@
|
||||
import NodHead from "./NodHead.js"
|
||||
import ShakeHead from "./ShakeHead.js"
|
||||
import StraightenHead from "./StraightenHead.js"
|
||||
|
||||
export {
|
||||
NodHead,
|
||||
ShakeHead,
|
||||
StraightenHead,
|
||||
}
|
||||
@ -0,0 +1,436 @@
|
||||
<template>
|
||||
<view class="modal bottom-modal" :class="show ? 'show' : ''">
|
||||
<camera flash="off" device-position="front" resolution="high" @stop="stop" @error="error"
|
||||
style="width: 100vw;height: 100vh; position: fixed;top: 0;left: 0;">
|
||||
<cover-view class="cover">
|
||||
<cover-view class="cover-top cover-item">
|
||||
<cover-view class="bar bg-white" style="justify-content: flex-start;">
|
||||
<cover-view class="action" @tap="close">关闭
|
||||
</cover-view>
|
||||
</cover-view>
|
||||
<template>
|
||||
<slot>
|
||||
<cover-view v-if="isDev && face">
|
||||
<cover-view :style="Math.abs(face.pitch)>0.5?'color:red':''">
|
||||
{{ face.pitch ? face.pitch.toFixed(2): 'null'}}
|
||||
</cover-view>
|
||||
<cover-view :style="Math.abs(face.roll)>0.5?'color:red':''">
|
||||
{{ face.roll ? face.roll.toFixed(2):'null'}}
|
||||
</cover-view>
|
||||
<cover-view :style="Math.abs(face.yaw)>0.5?'color:red':''">
|
||||
{{ face.yaw ? face.yaw.toFixed(2):'null'}}
|
||||
</cover-view>
|
||||
</cover-view>
|
||||
</slot>
|
||||
</template>
|
||||
<cover-view class="detectInfo">{{ isSuccess ? '人脸检测成功' : tipsText}}</cover-view>
|
||||
</cover-view>
|
||||
<cover-view class="camera">
|
||||
<cover-image class="camera" src="../../static/images/cover.png"></cover-image>
|
||||
</cover-view>
|
||||
<cover-view class="cover-bottom cover-item"></cover-view>
|
||||
</cover-view>
|
||||
</camera>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// 人脸检测
|
||||
/**
|
||||
* @event {Function} photoChange 拍照完成事件
|
||||
* @event {Function} detectFailed 人脸检测失败
|
||||
* @event {Function} detectOver 人脸检测结束
|
||||
* @method {Function} initData 初始化人脸检测
|
||||
*/
|
||||
import {
|
||||
NodHead,
|
||||
StraightenHead,
|
||||
ShakeHead
|
||||
} from './actions/index.js'
|
||||
import ActionContainer from './ActionContainer.js'
|
||||
|
||||
export default {
|
||||
name: 'face-detect',
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
tipsText: '检测不到人脸',
|
||||
isSuccess: false, //是否检测完成
|
||||
face: {},
|
||||
actionsList: null,
|
||||
context: "",
|
||||
tipsTextCss: "tipsTextCss",
|
||||
listener: null
|
||||
}
|
||||
},
|
||||
props: {
|
||||
buildActionContainer: Function,
|
||||
actions: () => {
|
||||
return []
|
||||
},
|
||||
isDev: false,
|
||||
},
|
||||
mounted() {
|
||||
// uni.initFaceDetect()
|
||||
// this.onCameraFrame()
|
||||
},
|
||||
destroyed() {
|
||||
|
||||
},
|
||||
methods: {
|
||||
setMaxFace(faceData) {
|
||||
const faces = faceData.faceInfo // 获取到所有人脸信息
|
||||
let maxFaceIndex = 0
|
||||
let maxFaceSize = 0
|
||||
// 遍历所有人脸信息,找到最大的人脸
|
||||
for (let i = 0; i < faces.length; i++) {
|
||||
const face = faces[i]
|
||||
const faceSize = face.width * face.height
|
||||
if (faceSize > maxFaceSize) {
|
||||
maxFaceSize = faceSize
|
||||
maxFaceIndex = i
|
||||
}
|
||||
}
|
||||
// 返回最大的那张脸的坐标信息
|
||||
const maxFace = faces[maxFaceIndex]
|
||||
faceData.faceInfo = [maxFace]
|
||||
},
|
||||
onCameraFrame() {
|
||||
uni.initFaceDetect()
|
||||
let time = new Date().getTime()
|
||||
this.context = uni.createCameraContext()
|
||||
this.listener = this.context.onCameraFrame((frame) => {
|
||||
uni.faceDetect({
|
||||
frameBuffer: frame.data,
|
||||
width: frame.width,
|
||||
height: frame.height,
|
||||
enablePoint: true,
|
||||
enableConf: true,
|
||||
enableAngle: true,
|
||||
enableMultiFace: true,
|
||||
success: (faceData) => {
|
||||
time = new Date().getTime()
|
||||
this.setMaxFace(faceData)
|
||||
this.showData(faceData)
|
||||
this.actionsList.next(faceData)
|
||||
this.tipsText = this.actionsList.tip
|
||||
},
|
||||
fail: (err) => {
|
||||
if ((time + 10 * 1000) < new Date().getTime()) {
|
||||
this.tipsText = '检测不到人脸'
|
||||
this.cameraError()
|
||||
return
|
||||
}
|
||||
if (err.x == -1 || err.y == -1) {
|
||||
this.tipsText = '检测不到人脸'
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
this.listener.start()
|
||||
},
|
||||
error() {
|
||||
this.tipsText = '相机异常'
|
||||
this.cameraError()
|
||||
},
|
||||
stop() {
|
||||
this.tipsText = '相机异常'
|
||||
this.cameraError()
|
||||
},
|
||||
// 核验失败
|
||||
cameraError(e) {
|
||||
this.t = setTimeout(() => {
|
||||
clearTimeout(this.t)
|
||||
this.hideModal()
|
||||
this.$emit('detectFailed')
|
||||
}, 2000);
|
||||
},
|
||||
close() {
|
||||
clearTimeout(this.t)
|
||||
this.hideModal()
|
||||
this.$emit('detectFailed')
|
||||
},
|
||||
// 关闭
|
||||
hideModal() {
|
||||
uni.stopFaceDetect()
|
||||
this.show = false
|
||||
this.tipsText = '检测不到人脸'
|
||||
this.face = {}
|
||||
this.isSuccess = false
|
||||
},
|
||||
// 拍照
|
||||
takePhoto() {
|
||||
this.context.takePhoto({
|
||||
quality: 'low',
|
||||
success: (res) => {
|
||||
this.$emit('photoChange', res.tempImagePath)
|
||||
},
|
||||
fail: (e) => {
|
||||
console.log(e)
|
||||
},
|
||||
complete: (e) => {
|
||||
console.log(e)
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 检测完成
|
||||
detectOver() {
|
||||
this.isSuccess = true
|
||||
let t = setTimeout(() => {
|
||||
this.hideModal()
|
||||
clearTimeout(t)
|
||||
this.$emit('detectOver')
|
||||
}, 3000);
|
||||
},
|
||||
|
||||
initData() {
|
||||
uni.getSetting({
|
||||
success: (res) => {
|
||||
if (res.authSetting['scope.camera']===true) {
|
||||
this.onCameraFrame()
|
||||
this.faceDetect()
|
||||
} else if(res.authSetting['scope.camera']=== false) {
|
||||
this.getCameraAuth()
|
||||
}else{
|
||||
this.onCameraFrame()
|
||||
this.faceDetect()
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
getCameraAuth() {
|
||||
uni.showModal({
|
||||
title: '温馨提示',
|
||||
content: '需要获取您摄像头权限才能更好的为您服务!是否授权摄像头权限?',
|
||||
confirmText: '授权',
|
||||
confirmColor: '#f94218',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
// 选择弹框内授权
|
||||
uni.openSetting({
|
||||
success: (res) => {
|
||||
if (res.authSetting[
|
||||
'scope.camera'
|
||||
]) {
|
||||
this.onCameraFrame()
|
||||
this.faceDetect()
|
||||
} else {
|
||||
this.tipsText = "您未授权摄像头权限"
|
||||
this.cameraError()
|
||||
}
|
||||
}
|
||||
})
|
||||
} else if (res.cancel) {
|
||||
this.tipsText = "您未授权摄像头权限"
|
||||
this.cameraError()
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
showData(faceData) {
|
||||
this.$emit("showData", faceData)
|
||||
if (this.isDev) {
|
||||
let face = faceData.faceInfo[0].angleArray
|
||||
this.face = face
|
||||
}
|
||||
},
|
||||
|
||||
buildActions() {
|
||||
if (this.buildActionContainer) {
|
||||
return this.buildActionContainer()
|
||||
}
|
||||
let actions = []
|
||||
if (!this.actions?.length) {
|
||||
let nodHead = new NodHead()
|
||||
const fun = (state) => {
|
||||
if (state === 'success') {
|
||||
this.takePhoto()
|
||||
}
|
||||
}
|
||||
let straightenHead = new StraightenHead(10, fun)
|
||||
let straightenHead2 = new StraightenHead(10)
|
||||
let shakeHead = new ShakeHead()
|
||||
actions = [straightenHead2, nodHead, shakeHead, straightenHead]
|
||||
} else {
|
||||
actions = this.actions
|
||||
}
|
||||
let act = new ActionContainer(actions)
|
||||
act.end(() => {
|
||||
this.detectOver()
|
||||
}).fail(() => {
|
||||
this.cameraError()
|
||||
})
|
||||
return act
|
||||
},
|
||||
|
||||
// 初始化人脸检测
|
||||
faceDetect() {
|
||||
this.show = true
|
||||
this.actionsList = this.buildActions()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 1110;
|
||||
opacity: 0;
|
||||
outline: 0;
|
||||
text-align: center;
|
||||
/* -ms-transform: scale(1.185);
|
||||
transform: scale(1.185); */
|
||||
backface-visibility: hidden;
|
||||
perspective: 2000rpx;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
/* transition: all 0.3s ease-in-out 0s; */
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.modal::before {
|
||||
content: "\200B";
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.modal.show {
|
||||
opacity: 1;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.modal.bottom-modal::before {
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.modal.bottom-modal .dialog {
|
||||
width: 100%;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.modal.bottom-modal {
|
||||
margin-bottom: -1000rpx;
|
||||
}
|
||||
|
||||
.modal.bottom-modal.show {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.dialog {
|
||||
position: fixed;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
/* width: 680rpx; */
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
max-width: 100%;
|
||||
background-color: #f8f8f8;
|
||||
border-radius: 10rpx;
|
||||
overflow: hidden;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.bar {
|
||||
display: flex;
|
||||
position: relative;
|
||||
align-items: center;
|
||||
min-height: 180rpx;
|
||||
height: 180rpx;
|
||||
padding: 0rpx 40rpx;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.bg-white {
|
||||
background-color: #ffffff;
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.action {
|
||||
color: #0081ff;
|
||||
font-size: 35rpx;
|
||||
padding: 10rpx;
|
||||
}
|
||||
|
||||
.detectInfo {
|
||||
padding: 20rpx 0rpx;
|
||||
font-size: 34rpx;
|
||||
text-align: center;
|
||||
animation-duration: 1.5s;
|
||||
color: #000000;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.faceContent {
|
||||
height: 700rpx;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.successImage {
|
||||
overflow: hidden;
|
||||
width: 600rpx;
|
||||
height: 600rpx;
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
z-index: 999;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.tipsTextCss {
|
||||
animation: 1.5s tipsTextAnimation;
|
||||
animation-duration: 1.5s;
|
||||
}
|
||||
|
||||
@keyframes tipsTextAnimation {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
20% {
|
||||
transform: scale(1.5);
|
||||
}
|
||||
|
||||
70% {
|
||||
transform: scale(1.5);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.cover {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.cover-item {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
|
||||
.camera {
|
||||
width: 100vw;
|
||||
height: 100vw;
|
||||
transform: scale(1.05);
|
||||
z-index: 999;
|
||||
}
|
||||
</style>
|
||||
81
uni_modules/face-bio-assay/package.json
Normal file
81
uni_modules/face-bio-assay/package.json
Normal file
@ -0,0 +1,81 @@
|
||||
{
|
||||
"id": "face-bio-assay",
|
||||
"displayName": "face-bio-assay",
|
||||
"version": "1.1.1",
|
||||
"description": "纯前端简易版活体检测,易扩展,开发者可通过自定义动作 添加到该容器中管理。也可使用已有动作(点头、摇头、平视)进行排列组合创建动作组",
|
||||
"keywords": [
|
||||
"人脸识别",
|
||||
"活体检测",
|
||||
"易扩展"
|
||||
],
|
||||
"repository": "",
|
||||
"engines": {
|
||||
},
|
||||
"dcloudext": {
|
||||
"category": [
|
||||
"前端组件",
|
||||
"通用组件"
|
||||
],
|
||||
"sale": {
|
||||
"regular": {
|
||||
"price": "0.00"
|
||||
},
|
||||
"sourcecode": {
|
||||
"price": "0.00"
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"qq": ""
|
||||
},
|
||||
"declaration": {
|
||||
"ads": "无",
|
||||
"data": "无",
|
||||
"permissions": "无"
|
||||
},
|
||||
"npmurl": ""
|
||||
},
|
||||
"uni_modules": {
|
||||
"dependencies": [],
|
||||
"encrypt": [],
|
||||
"platforms": {
|
||||
"cloud": {
|
||||
"tcb": "y",
|
||||
"aliyun": "y"
|
||||
},
|
||||
"client": {
|
||||
"Vue": {
|
||||
"vue2": "y",
|
||||
"vue3": "u"
|
||||
},
|
||||
"App": {
|
||||
"app-vue": "u",
|
||||
"app-nvue": "u"
|
||||
},
|
||||
"H5-mobile": {
|
||||
"Safari": "u",
|
||||
"Android Browser": "u",
|
||||
"微信浏览器(Android)": "u",
|
||||
"QQ浏览器(Android)": "u"
|
||||
},
|
||||
"H5-pc": {
|
||||
"Chrome": "u",
|
||||
"IE": "u",
|
||||
"Edge": "u",
|
||||
"Firefox": "u",
|
||||
"Safari": "u"
|
||||
},
|
||||
"小程序": {
|
||||
"微信": "y",
|
||||
"阿里": "u",
|
||||
"百度": "u",
|
||||
"字节跳动": "u",
|
||||
"QQ": "u"
|
||||
},
|
||||
"快应用": {
|
||||
"华为": "u",
|
||||
"联盟": "u"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
185
uni_modules/face-bio-assay/readme.md
Normal file
185
uni_modules/face-bio-assay/readme.md
Normal file
@ -0,0 +1,185 @@
|
||||
# props
|
||||
|参数名|类型|默认值|可选择值|描述|
|
||||
|----|----|----|---|---|
|
||||
|buildActionContainer|Function|null|""|创建ActionContainer类的函数,要求返回值必需是ActionContainer类|
|
||||
|actions|Array|[]|""|动作组(目前已有动作:点头,摇头,平视)|
|
||||
|isDev|boolen|false|true|是否是开发者模式,开启后可显示人脸的三个角度|
|
||||
|
||||
# emits
|
||||
|方法名|参数|描述|
|
||||
|----|---|---|
|
||||
|detectFailed|[]|核验失败|
|
||||
|photoChange|[url:'照片路径']|拍照后的回调|
|
||||
|detectOver|[]|检测完成|
|
||||
|showData|[faceData:'人脸数据']|每一帧的人脸数据|
|
||||
# slots
|
||||
|插槽名|参数|描述|
|
||||
|----|---|---|
|
||||
|default|无|用户可配合showData钩子展示人脸数据方便调试|
|
||||
|
||||
# 方法
|
||||
## initData
|
||||
开始进行人脸核验 使用案例:
|
||||
```
|
||||
//html
|
||||
<face-bio-assay ref="faceDetect" ></face-bio-assay>
|
||||
//js
|
||||
//调用
|
||||
this.$refs.faceDetect.initData()
|
||||
```
|
||||
## takePhoto
|
||||
拍照获取照片,配合动作使用:
|
||||
```
|
||||
在每个动作创建时第二个参数是动作完成的回调 如平视动作的使用:
|
||||
const fun = (state) => {
|
||||
//state 有成功success和fail,ing(进行时不会触发该函数,忽略)
|
||||
if (state === 'success') {
|
||||
this.$refs.faceDetect.takePhoto() //调用拍照方法
|
||||
}
|
||||
}
|
||||
let straightenHead = new StraightenHead(10, fun)
|
||||
```
|
||||
|
||||
# 使用建议
|
||||
```
|
||||
ios中bug解决方案:
|
||||
在ios中,二次进入使用该组件有问题,解决办法:单独将该组件作为一个页面(或者下载demo查看),代码如下:
|
||||
|
||||
//主页面
|
||||
<template>
|
||||
<view>
|
||||
<button type="default" @click="init">人脸检测</button>
|
||||
<image mode="aspectFit" :src="imgSrc" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
imgSrc: '',
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
init(){
|
||||
uni.navigateTo({
|
||||
url:"/pages/face/face",
|
||||
events:{
|
||||
data: (path) => {
|
||||
this.imgSrc = path
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
//face.vue页
|
||||
<template>
|
||||
<view>
|
||||
<face-bio-assay :isDev="false" ref="faceDetect" @detectFailed="detectFailed" @photoChange="photoChange">
|
||||
</face-bio-assay>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import faceBioAssay from '@/uni_modules/face-bio-assay/components/face-bio-assay/face-bio-assay.vue'
|
||||
export default {
|
||||
components: {
|
||||
faceBioAssay,
|
||||
},
|
||||
onLoad(option) { //一定要onLoad,onShow在进入相机授权页面退回时会再次触发
|
||||
this.$refs.faceDetect.initData()
|
||||
},
|
||||
methods: {
|
||||
detectFailed() {
|
||||
uni.showToast({
|
||||
title: "人脸核验失败~",
|
||||
icon: 'none'
|
||||
})
|
||||
uni.navigateBack()
|
||||
},
|
||||
photoChange(path) {
|
||||
uni.navigateBack()
|
||||
this.getOpenerEventChannel().emit('data',path);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
```
|
||||
|
||||
# 类的使用
|
||||
## Action
|
||||
动作类,开发者可继承该类重写 takeFrame 方法 如:
|
||||
```
|
||||
//点头动作
|
||||
import Action from "./Action.js"
|
||||
class NodHead extends Action {
|
||||
constructor(second=10,fun) {
|
||||
//时间限制(s),结束时回调,完成次数(limit),基本提示
|
||||
super(second,fun,1,'请点头')
|
||||
this.maxPitch = 0
|
||||
this.minPitch = 0
|
||||
}
|
||||
takeFrame(faceData){
|
||||
let face = faceData.faceInfo[0]
|
||||
if(face.angleArray.pitch>this.maxPitch){
|
||||
this.maxPitch = face.angleArray.pitch
|
||||
}
|
||||
if(face.angleArray.pitch<this.minPitch){
|
||||
this.minPitch = face.angleArray.pitch
|
||||
}
|
||||
if(Math.abs(this.minPitch-this.maxPitch)>0.45 && (this.minPitch || this.maxPitch) ){
|
||||
this.frames.push('点头') //frames 完成的帧数组 根据该数组长度和limit判断是否完成
|
||||
this.maxPitch = 0
|
||||
this.minPitch = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default NodHead
|
||||
```
|
||||
## ActionContainer
|
||||
动作容器的使用案例
|
||||
```
|
||||
buildActions() {
|
||||
if (this.buildActionContainer) {
|
||||
return this.buildActionContainer()
|
||||
}
|
||||
let actions = []
|
||||
if (!this.actions?.length) {
|
||||
let nodHead = new NodHead()
|
||||
const fun = (state) => {
|
||||
if (state === 'success') {
|
||||
this.takePhoto() //拍照
|
||||
}
|
||||
}
|
||||
let straightenHead = new StraightenHead(10, fun) //平视 结束拍照
|
||||
let straightenHead2 = new StraightenHead(10) //平视
|
||||
let shakeHead = new ShakeHead()
|
||||
actions = [straightenHead2, nodHead, shakeHead, straightenHead]
|
||||
} else {
|
||||
actions = this.actions
|
||||
}
|
||||
//new ActionContainer(actions,...)
|
||||
//动作组 actions
|
||||
//起始动作下标 index
|
||||
//初始提示 tip
|
||||
//绑定 完成所有动作的回调 endFun
|
||||
//绑定 动作进行中 ingFun
|
||||
//绑定 动作完成时 successFun
|
||||
//绑定 动作失败时 failFun
|
||||
let act = new ActionContainer(actions)
|
||||
act.end(() => { //也可用该方法绑定endFun方法
|
||||
this.detectOver()
|
||||
}).fail(() => { //也可用该方法绑定failFun方法
|
||||
this.cameraError()
|
||||
})
|
||||
//....其他方法类似
|
||||
return act
|
||||
}
|
||||
```
|
||||
BIN
uni_modules/face-bio-assay/static/images/cover.png
Normal file
BIN
uni_modules/face-bio-assay/static/images/cover.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.8 KiB |
Reference in New Issue
Block a user