调用封装的组件
1.1 调用组件
<template>
<div>
<FaceRecognitionPanel :launch-source="launchSource" @validated="handleValidated" />
</div>
</template>
1.2 js里面的函数(具体的业务逻辑参考自己的实际调整)
<script>
import { Toast } from "mand-mobile";
import { apiRequest } from "@/utils/apiList";
import FaceRecognitionPanel from "@/components/FaceRecognitionPanel/index.vue";
import CommSteps from "@/components/CommSteps/index.vue";
const NODE_FDD = "node6";
const UNVS_IMG_CTLG_NO = "202412240003";
const CERT_TYPE = "B0101";
export default {
name: "FaceRecognition",
components: {
FaceRecognitionPanel,
CommSteps
},
computed: {
launchSource() {
const route = this.$route || {};
return route.path || "";
}
},
data() {
return {
};
},
methods: {
handleValidated({ verifyResult }) {
const userInfo = this.$store.state.userInfo || {};
},
ordfacercg(orderNo, verifyResult, node) {
const userInfo = this.$store.state.userInfo || {};
apiRequest(
"ordfacercg",
{
ordNo: orderNo,
nodeCode: node,
participantType: "001",
ossid: verifyResult,
unvsImgCtlgNo1: UNVS_IMG_CTLG_NO,
certNo: userInfo.faceId,
certTyp: CERT_TYPE
},
{
headers: {},
onSuccess: () => {
this.$router.replace("/fdd_xfd/perAuth");
return;
},
onError: () => {
Toast.info("人脸识别失败");
}
}
).catch(error => {
if (error && error._handled === true) {
return;
}
});
}
}
};
</script>
FaceRecognitionPanel 组件封装
<template>
<div class="face-recognition-page">
<div class="face-recognition-body">
<div class="face-recognition-content">
<TipBox :text="tipText"></TipBox>
<div class="face-guide-container">
<div class="face-guide-circle">
<div class="face-pattern">
<img :src="facePic" alt="人脸识别" />
</div>
</div>
</div>
<div class="tips-list">
<div v-for="(item, index) in displayTips" :key="index" class="tip-item">
<div class="tip-icon-wrapper">
<img v-if="item.icon" :src="item.icon" :alt="item.text" class="tip-icon-img" />
</div>
<span class="tip-item-text">{{ item.text }}</span>
</div>
</div>
</div>
<div class="action-section">
<button class="start-btn" type="button" @click="handleStartRecognition">{{ startText }}</button>
</div>
</div>
<FaceRecognitionFlow ref="faceFlow" :launch-source="launchSource" @validated="onFlowValidated" />
<ContentDialog :visible.sync="showTextDialog" title="温馨提示" :show-cancel="true" cancel-text="取消" confirm-text="去识别"
@cancel="handleTestCancel" @confirm="handleTestConfirm">
<input v-model="testInfo.name" type="text" placeholder="请输入姓名" class="form-input" />
<input v-model="testInfo.id" type="text" placeholder="请输入身份证号" class="form-input" />
</ContentDialog>
</div>
</template>
<script>
import ContentDialog from "@/components/ContentDialog/index.vue";
import TipBox from "@/components/TipBox/index.vue";
import FaceRecognitionFlow from "@/components/FaceRecognitionFlow/index.vue";
import {
isMiniProgramEnvironment
} from "@/utils/miniProgram";
import { Toast } from "mand-mobile";
import { apiRequest } from "@/utils/apiList";
const DEFAULT_TIP_TEXT = "人脸识别功能验证您的身份信息,请确保为本人操作";
const DEFAULT_START_TEXT = "开始识别";
export default {
name: "FaceRecognitionPanel",
components: {
TipBox,
FaceRecognitionFlow,
ContentDialog
},
props: {
tipText: {
type: String,
default: DEFAULT_TIP_TEXT
},
startText: {
type: String,
default: DEFAULT_START_TEXT
},
launchSource: {
type: String,
default: ""
},
/**
* { icon: string (require 或 url), text: string }
* 不传则使用 RCD 默认三图标Tips
*/
tips: {
type: Array,
default: null
},
// 腾讯慧眼-控制配置的业务场景编号--默认通用场景 2
ruleId: {
type: String,
default: "2"
}
},
data() {
return {
showTextDialog: false, // 测试需要填写测试人员真实信息测试人脸
testInfo: {
name: "", // 测试人员姓名
id: "" // 测试人员身份证
},
facePic: require("@/assets/face-pattern.png"),
defaultTips: [
{ icon: require("@/assets/light.png"), text: "避免强弱光" },
{ icon: require("@/assets/face-cover.png"), text: "面部无遮挡" },
{ icon: require("@/assets/phone-stable.png"), text: "手机保持稳定" }
]
};
},
computed: {
displayTips() {
return Array.isArray(this.tips) && this.tips.length ? this.tips : this.defaultTips;
}
},
mounted() {
},
methods: {
handleLoginTimeoutConfirm() {
this.showForceDialog = false;
},
handleStartRecognition() {
const userInfo = (this.$store && this.$store.state && this.$store.state.userInfo) || {};
const name = userInfo.faceName || "";
const id = userInfo.faceId || "";
console.log(`\n`);
console.log("-------");
console.log("人脸识别,客户信息校验")
console.log(`姓\u3000名:${name}`)
console.log(`证件号:${id}`)
console.log("-------");
console.log(`\n`);
//测试-开发环境和测试环境
if (window.location.host == "testopen.shrbank.com:121" || window.location.host == "localhost:8091") {
this.showTextDialog = true;
} else {
// 生产环境,通过真实保存的数据发送接口
const inMiniProgram = isMiniProgramEnvironment();
if (!inMiniProgram) {
Toast.info("请前往微信小程序打开");
return
}
this.startFun(name, id);
}
},
startFun(name, idCard) {
// 兼容测试原人脸识别
// if (!name && !idCard) {
// if (this.$refs.faceFlow && typeof this.$refs.faceFlow.start === "function") {
// this.$emit("start");
// this.$refs.faceFlow.start();
// }
// return
// }
// this.ruleId || "腾讯慧眼控制台配置的ruleId";
// 发送接口获取bizToken
// 必传数据 ruleId 业务编号, IdCard 身份证号, Name 姓名
apiRequest(
"tencentFaceAuth",
{
name,
idCard,
ruleId: this.ruleId
},
{
onSuccess: res => {
const bizToken = (res.data && res.data.bizToken) || "";
if (bizToken) {
if (this.$refs.faceFlow && typeof this.$refs.faceFlow.start === "function") {
this.$emit("start");
this.$refs.faceFlow.start(bizToken, this.ruleId);
}
} else {
Toast.info("人脸SDK初始化失败,请重新获取");
}
},
onError: error => {
console.error("人脸获取bizToken失败:", error);
Toast.info("人脸SDK初始化失败");
}
}
).catch(error => {
console.error("人脸获取bizToken异常:", error);
Toast.info("人脸SDK初始化异常");
if (error && error._handled === true) {
return;
}
});
},
onFlowValidated(payload) {
this.$emit("validated", payload);
},
// 测试弹窗 - 取消
handleTestCancel() {
this.showTextDialog = false;
},
// 测试弹窗 - 确定
handleTestConfirm() {
console.log(this.testInfo)
// 通过输入的数据 发送接口 验证人脸
this.startFun(this.testInfo.name, this.testInfo.id);
},
}
};
</script>
<style lang="postcss" scoped>
/**
.face-recognition-page {
background-color: #fff;
padding-top: 44px;
height: inherit;
}
*/
.face-recognition-page {
width: 100%;
background-color: transparent;
margin-top: 44px;
box-sizing: border-box;
height: calc(100vh - 44px);
display: flex;
flex-direction: column;
}
.face-recognition-body {
width: 100%;
margin-top: 8px;
background: #fff;
box-sizing: border-box;
height: calc(100% - 8px);
display: flex;
flex-direction: column;
}
.face-recognition-content {
padding: 0 16px;
flex: 1;
overflow-y: auto;
}
.face-guide-container {
display: flex;
justify-content: center;
align-items: center;
margin: 40px 0;
padding: 20px 0;
}
.face-guide-circle {
position: relative;
width: 280px;
height: 280px;
display: flex;
justify-content: center;
align-items: center;
}
.face-pattern {
position: relative;
width: 240px;
height: 246px;
display: flex;
justify-content: center;
align-items: center;
z-index: 5;
}
.face-pattern img {
width: 100%;
height: 100%;
object-fit: contain;
}
.tips-list {
display: flex;
justify-content: space-around;
margin: 40px 0 60px;
padding: 0 20px;
}
.tip-item {
display: flex;
flex-direction: column;
align-items: center;
flex: 1;
}
.tip-icon-wrapper {
width: 32px;
height: 32px;
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 8px;
}
.tip-icon-img {
width: 100%;
height: 100%;
object-fit: contain;
}
.tip-item-text {
font-size: 12px;
color: #666;
text-align: center;
}
.action-section {
background: #FFFFFF;
box-shadow: 0px -1px 1px 1px rgba(0,0,0,0.03);
padding: 0 16px;
display: flex;
align-items: center;
padding-top: 16px;
padding-bottom: calc(16px + env(safe-area-inset-bottom));
min-height: 81px;
box-sizing: border-box;
}
.start-btn {
width: 100%;
height: 48px;
background: rgba(225, 183, 126, 1);
border-radius: 24px;
font-size: 16px;
font-weight: 500;
color: #fff;
border: none;
outline: none;
cursor: pointer;
-webkit-tap-highlight-color: transparent;
}
.start-btn:active {
opacity: 0.8;
}
</style>
FaceRecognitionPanel组件里面引用的 FaceRecognitionFlow 组件封装
<!--
FaceRecognitionFlow
复用逻辑:开始小程序人脸识别 -> hash 回传 -> validateficeresult -> commit wxFace -> emit validated
-->
<template>
<!-- 纯流程组件,不展示 UI -->
<div style="display: none" />
</template>
<script>
/* global wx */
import { Toast } from "mand-mobile";
import { apiRequest } from "@/utils/apiList";
const HASH_FACE_SUCCESS = "face_success";
const FACE_RECOGNIZE_ENTRY_URL = "../home/txFaceRecognize/txFaceRecognize";
/** H5 部署 host 前缀(与 window.location.href 一致,用于解析 env) */
const FACE_LAUNCH_HREF_HOST_PREFIX = "https://testopen.shrbank.com:121";
/** 静态资源路径片段,env 为此前缀之后、该片段之前的路径节 */
const FACE_LAUNCH_CCS_MIN_MARKER = "/ccs-min/";
function getFaceMiniProgramEnvFromLocation() {
const href = String(window.location.href || "");
if (!href.includes(FACE_LAUNCH_HREF_HOST_PREFIX) || !href.includes(FACE_LAUNCH_CCS_MIN_MARKER)) {
return "";
}
const afterHost = href.split(FACE_LAUNCH_HREF_HOST_PREFIX)[1] || "";
return String((afterHost.split(FACE_LAUNCH_CCS_MIN_MARKER)[0] || "").trim());
}
export default {
name: "FaceRecognitionFlow",
props: {
launchSource: {
type: String,
default: ""
}
},
data() {
return {
hashChangeHandler: null,
pending: false,
handled: false,
ruleId: "",
bizToken: ""
};
},
mounted() {
this.hashChangeHandler = () => {
this.handleHashChange();
};
window.addEventListener("hashchange", this.hashChangeHandler);
},
beforeDestroy() {
if (this.hashChangeHandler) {
window.removeEventListener("hashchange", this.hashChangeHandler);
}
},
methods: {
start(bizToken, ruleId) {
console.log("测试一下接受的数据",bizToken)
this.pending = true;
this.handled = false;
// 覆盖"回传参数已存在但未触发 hashchange"场景
this.handleHashNow();
this.ruleId = ruleId;
this.bizToken = bizToken;
this.gotoMini();
},
gotoMini() {
// launchSource 在这里语义为"回调地址",透传给小程序用于回跳
const callbackUrl = this.launchSource || "";
const sourceQuery = encodeURIComponent(callbackUrl);
const env = getFaceMiniProgramEnvFromLocation();
const envQuery = encodeURIComponent(env);
const userInfo = (this.$store && this.$store.state && this.$store.state.userInfo) || {};
const name = userInfo.faceName || "";
const id = userInfo.faceId || "";
const BizToken = this.bizToken;
console.log(
"face launch",
`${FACE_RECOGNIZE_ENTRY_URL}?bizToken=${BizToken}&name=${name}&id=${id}&callbackUrl=${sourceQuery}&env=${envQuery}`
);
wx.miniProgram.navigateTo({
url: `${FACE_RECOGNIZE_ENTRY_URL}?bizToken=${BizToken}&name=${name}&id=${id}&callbackUrl=${sourceQuery}&env=${envQuery}`
});
},
handleHashChange() {
if (!this.pending || this.handled) return;
const hash = String(window.location.hash || "");
if (!hash.includes(HASH_FACE_SUCCESS)) return;
this.handleHashNow();
},
handleHashNow() {
if (!this.pending || this.handled) return;
const hash = String(window.location.hash || "");
if (!hash.includes(HASH_FACE_SUCCESS)) return;
const href = decodeURIComponent(String(window.location.href || ""));
const params = this.parseHashQuery(href);
this.handled = true; // 先锁住,避免并发重复请求
this.pending = false;
if (this.bizToken) {
this.validateFace(params);
} else {
this.validateFace1(params);
}
},
parseHashQuery(href) {
const url = decodeURIComponent(String(href || ""));
const part = url.split("#")[1] || "";
const queryString = part.includes("?") ? part.split("?")[1] : "";
if (!queryString) return {};
return queryString.split("&").reduce((acc, kv) => {
const [k, v] = kv.split("=");
if (k) acc[k] = v;
return acc;
}, {});
},
validateFace(params) {
apiRequest(
"tencentFaceCheck",
{
ruleId: this.ruleId,
verifyResult: params && params.verifyResult
},
{
headers: {},
onSuccess: res => {
const data = res && res.data ? res.data : {};
// 复用原页面 commit 逻辑:只写 wxFace
this.$store.commit("updateUserInfo", { wxFace: data });
this.$emit("validated", { verifyResult: data.fileKey });
},
onError: () => {
Toast.info("人脸识别失败");
}
}
).catch(err => {
if (err && err._handled === true) return;
Toast.info("人脸识别失败");
});
},
// 废弃 人脸识别二次验证
validateFace1(params) {
apiRequest(
"validateficeresult",
{
verifyResult: params && params.verifyResult
},
{
headers: {},
onSuccess: res => {
const data = res && res.data ? res.data : {};
// 复用原页面 commit 逻辑:只写 wxFace
this.$store.commit("updateUserInfo", { wxFace: data });
this.$emit("validated", { verifyResult: data.verifyResult });
},
onError: () => {
Toast.info("人脸识别失败");
}
}
).catch(err => {
if (err && err._handled === true) return;
Toast.info("人脸识别失败");
});
}
}
};
</script>
备注说明:apiRequest里面是调用的后端接口,具体调用后端接口的逻辑业务请参考实际进行
4.
接口说明
//传入用户信息
tencentFaceAuth: {
apiUrl: reqHeadUrl + "/api/tencent/face/tencentFaceAuth",
method: "POST"
},
// 校验
tencentFaceCheck: {
apiUrl: reqHeadUrl + "/api/tencent/face/tencentFaceCheck",
method: "POST"
}
// 人脸对比 验证接口
validateficeresult: {
apiUrl: reqHeadUrl + "/api/wxcxc/validateficeresult",
method: "POST"
},
//订单人脸识别
ordfacercg:{
apiUrl: reqHeadUrl + "/api/w/mcl/ord/ordaplyinf/ordfacercg",
method: "POST"
},

