提交 14112b41 编写于 作者: C chenruilong

feat: 新增 实名认证页面与人脸识别页面

上级 c499600d
export default function checkIdCard (idCardNumber) {
if (!idCardNumber) return false
const coefficient = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]
const checkCode = [1, 0, 'x', 9, 8, 7, 6, 5, 4, 3, 2]
const code = idCardNumber.substring(17)
let sum = 0
for (let i = 0; i < 17; i++) {
sum += Number(idCardNumber.charAt(i)) * coefficient[i]
}
return checkCode[sum % 11].toString() === code.toLowerCase()
}
\ No newline at end of file
...@@ -37,12 +37,21 @@ export const mutations = { ...@@ -37,12 +37,21 @@ export const mutations = {
}) })
} else { } else {
const uniIdCo = uniCloud.importObject("uni-id-co", {
customUI: true
})
try { try {
let res = await usersTable.where("'_id' == $cloudEnv_uid") let res = await usersTable.where("'_id' == $cloudEnv_uid")
.field('mobile,nickname,username,email,avatar_file') .field('mobile,nickname,username,email,avatar_file')
.get() .get()
const realNameRes = await uniIdCo.getRealNameInfo()
// console.log('fromDbData',res.result.data); // console.log('fromDbData',res.result.data);
this.setUserInfo(res.result.data[0]) this.setUserInfo({
...res.result.data[0],
realNameAuth: realNameRes
})
} catch (e) { } catch (e) {
this.setUserInfo({},{cover:true}) this.setUserInfo({},{cover:true})
console.error(e.message, e.errCode); console.error(e.message, e.errCode);
......
export default { export default {
//调试模式 // 调试模式
"debug": false, debug: false,
/* /*
登录类型 未列举到的或运行环境不支持的,将被自动隐藏。 登录类型 未列举到的或运行环境不支持的,将被自动隐藏。
如果需要在不同平台有不同的配置,直接用条件编译即可 如果需要在不同平台有不同的配置,直接用条件编译即可
*/ */
"isAdmin": false, // 区分管理端与用户端 isAdmin: false, // 区分管理端与用户端
"loginTypes": [ loginTypes: [
// "qq", // "qq",
// "xiaomi", // "xiaomi",
// "sinaweibo", // "sinaweibo",
// "taobao", // "taobao",
// "facebook", // "facebook",
// "google", // "google",
// "alipay", // "alipay",
// "douyin", // "douyin",
// #ifdef APP // #ifdef APP
"univerify", 'univerify',
// #endif // #endif
"weixin", 'weixin',
"username", 'username',
// #ifdef APP // #ifdef APP
"apple", 'apple',
// #endif // #endif
"smsCode" 'smsCode'
], ],
//政策协议 // 政策协议
"agreements": { agreements: {
"serviceUrl": "https://xxx", //用户服务协议链接 serviceUrl: 'https://xxx', // 用户服务协议链接
"privacyUrl": "https://xxx", //隐私政策条款链接 privacyUrl: 'https://xxx', // 隐私政策条款链接
// 哪些场景下显示,1.注册(包括登录并注册,如:微信登录、苹果登录、短信验证码登录)、2.登录(如:用户名密码登录) // 哪些场景下显示,1.注册(包括登录并注册,如:微信登录、苹果登录、短信验证码登录)、2.登录(如:用户名密码登录)
"scope": [ scope: [
'register', 'login' 'register', 'login', 'realNameVerify'
] ]
}, },
// 提供各类服务接入(如微信登录服务)的应用id // 提供各类服务接入(如微信登录服务)的应用id
"appid": { appid: {
"weixin": { weixin: {
// 微信公众号的appid,来源:登录微信公众号(https://mp.weixin.qq.com)-> 设置与开发 -> 基本配置 -> 公众号开发信息 -> AppID // 微信公众号的appid,来源:登录微信公众号(https://mp.weixin.qq.com)-> 设置与开发 -> 基本配置 -> 公众号开发信息 -> AppID
"h5": "xxxxxx", h5: 'xxxxxx',
// 微信开放平台的appid,来源:登录微信开放平台(https://open.weixin.qq.com) -> 管理中心 -> 网站应用 -> 选择对应的应用名称,点击查看 -> AppID // 微信开放平台的appid,来源:登录微信开放平台(https://open.weixin.qq.com) -> 管理中心 -> 网站应用 -> 选择对应的应用名称,点击查看 -> AppID
"web": "xxxxxx" web: 'xxxxxx'
} }
}, },
/** /**
* 密码强度 * 密码强度
* super(超强:密码必须包含大小写字母、数字和特殊符号,长度范围:8-16位之间) * super(超强:密码必须包含大小写字母、数字和特殊符号,长度范围:8-16位之间)
* strong(强: 密密码必须包含字母、数字和特殊符号,长度范围:8-16位之间) * strong(强: 密密码必须包含字母、数字和特殊符号,长度范围:8-16位之间)
...@@ -52,8 +52,8 @@ export default { ...@@ -52,8 +52,8 @@ export default {
* weak(弱:密码必须包含字母和数字,长度范围:6-16位之间) * weak(弱:密码必须包含字母和数字,长度范围:6-16位之间)
* 为空或false则不验证密码强度 * 为空或false则不验证密码强度
*/ */
"passwordStrength":"medium", passwordStrength: 'medium',
/** /**
* 登录后允许用户设置密码(只针对未设置密码得用户) * 登录后允许用户设置密码(只针对未设置密码得用户)
* 开启此功能将 setPasswordAfterLogin 设置为 true 即可 * 开启此功能将 setPasswordAfterLogin 设置为 true 即可
* "setPasswordAfterLogin": false * "setPasswordAfterLogin": false
...@@ -63,5 +63,5 @@ export default { ...@@ -63,5 +63,5 @@ export default {
* "allowSkip": true * "allowSkip": true
* } * }
* */ * */
"setPasswordAfterLogin": false setPasswordAfterLogin: false
} }
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1675667510055" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4003" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M807.936 106.656h-76a24.32 24.32 0 0 0-17.92 7.936 27.744 27.744 0 0 0-7.424 19.104c0 6.944 2.464 13.792 7.424 19.104a24.32 24.32 0 0 0 17.92 7.904h76v81.088c0 6.944 2.432 13.76 7.424 19.104a24.32 24.32 0 0 0 35.808 0 27.744 27.744 0 0 0 7.424-19.104V160.704c0-29.824-22.72-54.048-50.656-54.048zM833.248 512a25.12 25.12 0 0 0-17.92 7.392 25.12 25.12 0 0 0-7.392 17.92v76h-76a25.12 25.12 0 0 0-17.92 7.424c-1.344 1.344-2.08 3.072-3.072 4.704-28.576-27.52-60.704-50.112-96.256-65.152 72.192-43.136 117.888-126.08 103.872-219.296-13.216-87.456-81.056-160.576-167.648-178.656a228.16 228.16 0 0 0-46.944-4.896 217.056 217.056 0 0 0-217.12 217.12c0 79.264 42.976 147.936 106.368 185.824-35.456 15.04-67.648 37.632-96.256 65.152-0.96-1.632-1.696-3.36-3.072-4.704a25.12 25.12 0 0 0-17.92-7.424H200v-76a25.12 25.12 0 0 0-7.424-17.92 25.12 25.12 0 0 0-17.92-7.424 25.12 25.12 0 0 0-17.92 7.424 25.12 25.12 0 0 0-7.392 17.92v76c0 27.936 22.72 50.656 50.656 50.656H262.4c-42.336 54.816-71.712 123.488-80.96 200.192-3.424 28.224 19.104 53.12 47.488 53.12h550.048c28.416 0 50.848-24.96 47.488-53.12-9.216-76.8-38.624-145.472-80.96-200.288h62.4c27.968 0 50.688-22.72 50.688-50.656V537.28a25.12 25.12 0 0 0-7.424-17.92 25.12 25.12 0 0 0-17.92-7.392zM174.72 268.8a24.32 24.32 0 0 0 17.888-7.904 27.744 27.744 0 0 0 7.424-19.104V160.704h76a24.32 24.32 0 0 0 17.92-7.904 27.744 27.744 0 0 0 7.392-19.104 27.744 27.744 0 0 0-7.424-19.104 24.32 24.32 0 0 0-17.92-7.936H200c-27.968 0-50.656 24.224-50.656 54.08v81.056c0 6.944 2.432 13.76 7.392 19.104a24.32 24.32 0 0 0 17.92 7.904z" fill="#72a7ff" p-id="4004"></path></svg>
\ No newline at end of file
<template>
<view class="uni-content">
<template v-if="verifyFail">
<view class="face-icon">
<image src="./face-verify-icon.svg" class="face-icon-image" />
</view>
<view class="error-title">{{verifyFailTitle}}</view>
<view class="error-description">{{verifyFailContent}}</view>
<button type="primary" @click="retry" v-if="verifyFailCode !== 10013">重新开始验证</button>
<button type="primary" @click="retry" v-else>返回</button>
<view class="dev-tip" v-if="isDev">请在控制台查看详细错误(此提示仅在开发环境展示)</view>
</template>
<template v-else>
<view class="self-required" v-if="realName">确认<text class="name">{{realNameDesensitized}}</text>本人操作</view>
<view class="face-icon">
<image src="./face-verify-icon.svg" class="face-icon-image" />
</view>
<view class="suggestion">
<view class="text">温馨提示</view>
<view class="text" v-for="text in suggestion">
<text class="dot"></text>
<text>{{text}}</text>
</view>
</view>
<view class="footer">
<button type="primary" :disabled="verifyButtonDisabled" @click="getCertifyId">开始人脸识别</button>
</view>
</template>
</view>
</template>
<script>
import {
mutations
} from '@/uni_modules/uni-id-pages/common/store.js'
import checkIdCard from '@/uni_modules/uni-id-pages/common/check-id-card.js'
const uniIdCo = uniCloud.importObject('uni-id-co')
const tempFrvInfoKey = 'uni-id-pages-temp-frv'
export default {
data() {
return {
realName: "",
idCard: "",
certifyId: "",
suggestion: [
"请正对屏幕并使脸位于取景框内",
"请保持光线充足,避免光照过强或过弱",
"请根据画面提示完成动作检测",
"人脸识别过程中请保持手机稳定",
"请注意拍摄角度,距离摄像头不要过远或过近"
],
verifyFail: false,
verifyFailCode: 0,
verifyFailTitle: "",
verifyFailContent: ""
}
},
computed: {
realNameDesensitized() {
const firstChar = this.realName.slice(0, -1)
const lastChar = this.realName.slice(-1)
return Array.from(new Array(firstChar.length), (v) => '*').join('') + lastChar
},
verifyButtonDisabled() {
return !this.realName || !this.idCard
},
isDev () {
return process.env.NODE_ENV === 'development'
}
},
onLoad(e) {
this.realName = e.realName || ''
this.idCard = e.idCard || ''
},
methods: {
async getCertifyId() {
if (!this.realName || !this.idCard) {
uni.showModal({
title: "验证失败",
content: "缺少姓名或身份证号码",
showCancel: false
})
return
}
if (!checkIdCard(this.idCard)) {
uni.showToast({
title: "身份证不合法",
icon: "none"
})
return
}
if (!/^[\u4e00-\u9fa5]+$/.test(this.realName)) {
uni.showToast({
title: "姓名只能是汉字",
icon: "none"
})
return
}
const res = await uniIdCo.getFrvCertifyId({
realName: this.realName,
idCard: this.idCard
})
this.certifyId = res.certifyId
this.startFacialRecognitionVerify()
uni.setStorage({
key: tempFrvInfoKey,
data: {
realName: this.realName,
idCard: this.idCard
}
});
},
startFacialRecognitionVerify() {
// 每次刷脸重置状态
this.verifyFailCode = 0
this.verifyFail = false
this.verifyFailTitle = ''
this.verifyFailContent = ''
// #ifdef APP
uni.startFacialRecognitionVerify({
certifyId: this.certifyId,
quitAlertMessage: " ",
quitAlertTitle: "确定退出吗?",
success: (e) => {
this.getFrvAuthResult()
},
fail: (e) => {
let title = "验证失败"
let content
console.log(`[frv-debug] certifyId auth error: certifyId -> ${this.certifyId}, error -> ${JSON.stringify(e, null, 4)}`)
switch (e.errCode) {
case 10001:
content = '认证ID为空'
break
case 10010:
title = '刷脸异常'
content = e.cause.message || '错误代码: 10010'
break
case 10011:
title = '验证中断'
content = e.cause.message || '错误代码: 10011'
break
case 10012:
content = '网络异常'
break
case 10013:
this.verifyFailCode = e.errCode
this.verifyFailContent = e.cause.message || '错误代码: 10013'
this.getFrvAuthResult()
console.log(`[frv-debug] 刷脸失败, certifyId -> ${this.certifyId}, 如在开发环境请检查用户的姓名、身份证号与刷脸用户是否为同一用户。如遇到认证ID已使用请检查opendb-frv-logs表中certifyId状态`)
return
case 10020:
content = '设备设置时间异常'
break
default:
title = ''
content = `验证未知错误 (${e.errCode})`
break
}
this.verifyFail = true
this.verifyFailCode = e.errCode
this.verifyFailTitle = title
this.verifyFailContent = content
}
})
// #endif
},
async getFrvAuthResult() {
const uniIdCo = uniCloud.importObject('uni-id-co', {
customUI: true
})
try {
uni.showLoading({
title: "验证中...",
mask: false
})
const res = await uniIdCo.getFrvAuthResult({
certifyId: this.certifyId,
needAlivePhoto: 'Y_O'
})
const {
errCode,
...rest
} = res
if (this.verifyFailContent) {
console.log(`[frv-debug] 客户端刷脸失败,由实人认证服务查询具体原因,原因:${this.verifyFailContent}`)
}
uni.showModal({
content: "实名认证成功",
showCancel: false,
success: () => {
mutations.setUserInfo({
realNameAuth: rest
})
uni.navigateBack(-1)
}
})
uni.removeStorage({
key: tempFrvInfoKey
})
} catch (e) {
this.verifyFail = true
this.verifyFailTitle = e.errMsg
console.error(JSON.stringify(e));
} finally {
uni.hideLoading()
}
},
retry() {
if (this.verifyFailCode !== 10013) {
this.startFacialRecognitionVerify()
} else {
uni.navigateBack(-1)
}
}
},
}
</script>
<style lang="scss">
@import "@/uni_modules/uni-id-pages/common/login-page.scss";
.self-required {
margin-top: 20px;
font-size: 18px;
font-weight: bold;
text-align: center;
color: #333333;
.name {
color: #2979ff
}
}
.face-icon {
width: 100px;
height: 100px;
margin: 50px auto 30px;
}
.face-icon-image {
width: 100%;
height: 100%;
display: block;
}
.suggestion {
color: #999999;
font-size: 13px;
margin-bottom: 20px;
.text {
line-height: 20px;
display: flex;
align-items: center;
.dot {
width: 5px;
height: 5px;
background: #2979ff;
border-radius: 50%;
margin-right: 5px;
}
}
}
.error-title {
font-size: 18px;
text-align: center;
font-weight: bold;
}
.error-description {
font-size: 13px;
color: #999999;
margin: 10px 0 20px;
text-align: center;
}
.dev-tip {
margin-top: 20px;
font-size: 13px;
color: #999;
text-align: center;
}
</style>
<template>
<view>
<template v-if="isCertify">
<uni-list>
<uni-list-item class="item" title="姓名" :rightText="userInfo.realNameAuth.realName"></uni-list-item>
<uni-list-item class="item" title="身份证号码" :rightText="userInfo.realNameAuth.identity"></uni-list-item>
</uni-list>
</template>
<template v-else>
<view class="uni-content">
<text class="title">实名认证</text>
<uni-forms>
<uni-forms-item name="realName">
<uni-easyinput placeholder="姓名" class="input-box" v-model="realName" :clearable="false">
</uni-easyinput>
</uni-forms-item>
<uni-forms-item name="idCard">
<uni-easyinput placeholder="身份证号码" class="input-box" v-model="idCard" :clearable="false">
</uni-easyinput>
</uni-forms-item>
</uni-forms>
<uni-id-pages-agreements scope="realNameVerify" ref="agreements" style="margin-bottom: 20px;">
</uni-id-pages-agreements>
<button type="primary" :disabled="!certifyIdNext" @click="goToFaceVerifyPage">确定</button>
</view>
</template>
</view>
</template>
<script>
import checkIdCard from '@/uni_modules/uni-id-pages/common/check-id-card.js'
import mixin from '@/uni_modules/uni-id-pages/common/login-page.mixin.js';
import {store} from '@/uni_modules/uni-id-pages/common/store.js'
const uniIdCo = uniCloud.importObject('uni-id-co')
const tempFrvInfoKey = 'uni-id-pages-temp-frv'
export default {
mixins: [mixin],
data() {
return {
realName: '',
idCard: ''
}
},
computed: {
userInfo() {
return store.userInfo
},
certifyIdNext() {
return Boolean(this.realName) && Boolean(this.idCard) && (this.needAgreements && this.agree)
},
isCertify() {
return this.userInfo.realNameAuth && this.userInfo.realNameAuth.authStatus === 2
}
},
onLoad() {
const tempFrvInfo = uni.getStorageSync(tempFrvInfoKey);
if (tempFrvInfo) {
this.realName = tempFrvInfo.realName
this.idCard = tempFrvInfo.idCard
}
},
methods: {
async goToFaceVerifyPage() {
if (!this.certifyIdNext) return
// #ifndef APP
return uni.showModal({
content: "暂不支持实名认证",
showCancel: false
})
// #endif
if (!checkIdCard(this.idCard)) {
uni.showToast({
title: "身份证不合法",
icon: "none"
})
return
}
if (!/^[\u4e00-\u9fa5]+$/.test(this.realName)) {
uni.showToast({
title: "姓名只能是汉字",
icon: "none"
})
return
}
uni.navigateTo({
url: '/uni_modules/uni-id-pages/pages/userinfo/face-verify/face-verify?realName=' + this
.realName + '&idCard=' + this.idCard
});
}
}
}
</script>
<style lang="scss">
@import "@/uni_modules/uni-id-pages/common/login-page.scss";
.checkbox-box,
.uni-label-pointer {
align-items: center;
display: flex;
flex-direction: row;
}
.item {
flex-direction: row;
}
.text {
line-height: 26px;
}
.checkbox-box ::v-deep .uni-checkbox-input {
border-radius: 100%;
}
.checkbox-box ::v-deep .uni-checkbox-input.uni-checkbox-input-checked {
border-color: $uni-color-primary;
color: #FFFFFF !important;
background-color: $uni-color-primary;
}
.agreements {
margin-bottom: 20px;
}
</style>
...@@ -11,12 +11,19 @@ ...@@ -11,12 +11,19 @@
</uni-list-item> </uni-list-item>
<uni-list-item v-if="userInfo.email" class="item" title="电子邮箱" :rightText="userInfo.email"> <uni-list-item v-if="userInfo.email" class="item" title="电子邮箱" :rightText="userInfo.email">
</uni-list-item> </uni-list-item>
<!-- #ifdef APP -->
<!-- 如未开通实人认证服务,可以将实名认证入口注释 -->
<uni-list-item class="item" @click="realNameVerify" title="实名认证" :rightText="realNameStatus !== 2 ? '未认证': '已认证'" link>
</uni-list-item>
<!-- #endif -->
<uni-list-item v-if="hasPwd" class="item" @click="changePassword" title="修改密码" link> <uni-list-item v-if="hasPwd" class="item" @click="changePassword" title="修改密码" link>
</uni-list-item> </uni-list-item>
</uni-list> </uni-list>
<!-- #ifndef MP -->
<uni-list class="mt10"> <uni-list class="mt10">
<uni-list-item @click="deactivate" title="注销账号" link="navigateTo"></uni-list-item> <uni-list-item @click="deactivate" title="注销账号" link="navigateTo"></uni-list-item>
</uni-list> </uni-list>
<!-- #endif -->
<uni-popup ref="dialog" type="dialog"> <uni-popup ref="dialog" type="dialog">
<uni-popup-dialog mode="input" :value="userInfo.nickname" @confirm="setNickname" title="设置昵称" <uni-popup-dialog mode="input" :value="userInfo.nickname" @confirm="setNickname" title="设置昵称"
placeholder="请输入要设置的昵称"> placeholder="请输入要设置的昵称">
...@@ -30,9 +37,7 @@ ...@@ -30,9 +37,7 @@
</view> </view>
</template> </template>
<script> <script>
const db = uniCloud.database(); const uniIdCo = uniCloud.importObject("uni-id-co")
const usersTable = db.collection('uni-id-users')
const uniIdCo = uniCloud.importObject("uni-id-co")
import { import {
store, store,
mutations mutations
...@@ -41,7 +46,14 @@ ...@@ -41,7 +46,14 @@
computed: { computed: {
userInfo() { userInfo() {
return store.userInfo return store.userInfo
} },
realNameStatus () {
if (!this.userInfo.realNameAuth) {
return 0
}
return this.userInfo.realNameAuth.authStatus
}
}, },
data() { data() {
return { return {
...@@ -123,9 +135,7 @@ ...@@ -123,9 +135,7 @@
"provider": 'univerify', "provider": 'univerify',
"univerifyStyle": this.univerifyStyle, "univerifyStyle": this.univerifyStyle,
success: async e => { success: async e => {
// console.log(e.authResult);
uniIdCo.bindMobileByUniverify(e.authResult).then(res => { uniIdCo.bindMobileByUniverify(e.authResult).then(res => {
// console.log(res);
mutations.updateUserInfo() mutations.updateUserInfo()
}).catch(e => { }).catch(e => {
console.log(e); console.log(e);
...@@ -148,7 +158,6 @@ ...@@ -148,7 +158,6 @@
}) })
}, },
setNickname(nickname) { setNickname(nickname) {
// console.log(nickname);
if (nickname) { if (nickname) {
mutations.updateUserInfo({nickname}) mutations.updateUserInfo({nickname})
this.$refs.dialog.close() this.$refs.dialog.close()
...@@ -193,6 +202,11 @@ ...@@ -193,6 +202,11 @@
} }
}) })
} }
},
realNameVerify () {
uni.navigateTo({
url: "/uni_modules/uni-id-pages/pages/userinfo/realname-verify/realname-verify"
})
} }
} }
} }
......
...@@ -94,6 +94,18 @@ ...@@ -94,6 +94,18 @@
"navigationBarTitleText": "设置密码", "navigationBarTitleText": "设置密码",
"enablePullDownRefresh": false "enablePullDownRefresh": false
} }
},{
"path": "uni_modules/uni-id-pages/pages/userinfo/realname-verify/realname-verify",
"style": {
"navigationBarTitleText": "实名认证",
"enablePullDownRefresh": false
}
},{
"path": "uni_modules/uni-id-pages/pages/userinfo/face-verify/face-verify",
"style": {
"navigationBarTitleText": "人脸识别验证",
"enablePullDownRefresh": false
}
} }
] ]
} }
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册