shop-wx/src/pages/login/faceLogin.vue

361 lines
9.9 KiB
Vue

<script setup lang="ts">
import { doGetToken } from "@/api/login/HuiBang";
import { loginFaceLogin } from "@/api/login";
definePage({
style: {
navigationBarTitleText: '人脸识别',
navigationStyle: 'custom',
},
})
import { ref, onMounted } from 'vue';
import { HUIBANG_BASE_URL } from '@/config/setting';
import {ensureDecodeURIComponent, parseUrlToObj} from "@/utils";
import {isPageTabbar} from "@/tabbar/store";
import {setToken} from "@/utils/token-util";
import {loginPublic} from "@/utils/login-util";
import {tabbarList} from "@/tabbar/config";
const code = ref<string>('');//用于辅助验证人脸的code
const showFace = ref<boolean>(false);
const facingMode = ref<string>("user");//摄像头类型
const mediaStreamTrack = ref();// 媒体流,用于关闭摄像头
const videoStyle = ref(
{
width: '250px',
height: '250px',
transform: 'rotate(0deg)',
borderRadius: '50%',
}
)
const videoStyle2 = ref(
{
width: '250px',
height: '250px',
borderRadius: '50%',
}
)
const ssoToken = ref<string>('');
const userFaceImage = ref<string>(''); //人脸图片
onMounted(() => {
code.value = uni.getStorageSync("u-code");
})
const onCodeConfirm = () => {
if (code.value.length < 4) {
uni.showToast({ title: "请填写完整信息", icon: 'none'})
return;
}
showFace.value = true;
init();
}
const init = () => {
getToken();
invokingCamera();
}
//初始化镜头
const invokingCamera = () => {
showFace.value = true;
// 检查浏览器对摄像头的支持情况
console.log(navigator,'参数数据')
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
uni.showToast({ title: "您的设备不支持打开摄像头功能", icon: 'none'})
return;
}
const constraints = {
audio: false,
video: {
// 前置摄像头
facingMode: {
exact: facingMode.value
},
// 手机端相当于高
width: 200,
// 手机端相当于宽
height: 200,
},
};
// 请求摄像头权限并获取视频流
navigator.mediaDevices.getUserMedia(constraints)
.then((stream) => {
mediaStreamTrack.value = stream;
// 成功获取到视频流,这里可以对视频流进行处理
// 例如,将视频流显示在页面上的某个元素中
const videoElement = document.querySelector("video");
videoElement!.srcObject = stream;
videoElement!.play();
setTimeout(() => {
handlePhotographClick();
}, 1000);
})
.catch((err) => {
// 获取视频流失败,进行相应提示
console.log('获取摄像头视频流失败:', err);
})
}
//关闭镜头
const handlePhotographCloseClick = () => {
if (mediaStreamTrack.value) {
// 关闭摄像头
mediaStreamTrack.value.getTracks().forEach(function (track: any) {
track.stop();
});
mediaStreamTrack.value = null;
}
}
//拍照
const handlePhotographClick = () => {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
const video = document.querySelector("video");
canvas.width = Math.min(video!.videoWidth, video!.videoHeight);
canvas.height = Math.max(video!.videoWidth, video!.videoHeight);
// canvas.width = Math.max(video.videoWidth, video.videoHeight);
// canvas.height = Math.min(video.videoWidth, video.videoHeight);
//console.log(canvas.width);
//console.log(canvas.height);
ctx!.drawImage(video!, 0, 0, canvas.width, canvas.height);
// ****** 镜像处理 ******
function getPixel(imageData: any, row: any, column: any) {
const uint8ClampedArray = imageData.data;
const width = imageData.width;
const height = imageData.height;
const pixel = [];
for (let i = 0; i < 4; i++) {
pixel.push(uint8ClampedArray[row * width * 4 + column * 4 + i]);
}
return pixel;
}
function setPixel(imageData: any, row: any, column: any, pixel: any) {
const uint8ClampedArray = imageData.data;
const width = imageData.width;
const height = imageData.height;
for (let i = 0; i < 4; i++) {
uint8ClampedArray[row * width * 4 + column * 4 + i] = pixel[i];
}
}
const mirrorImageData = ctx!.createImageData(canvas.width, canvas.height);
const imageData = ctx!.getImageData(0, 0, canvas.width, canvas.height);
for (let h = 0; h < canvas.height; h++) {
for (let w = 0; w < canvas.width; w++) {
const pixel = getPixel(imageData, h, canvas.width - w - 1);
setPixel(mirrorImageData, h, w, pixel);
}
}
ctx!.putImageData(mirrorImageData, 0, 0);
// ****** 镜像处理 ******
const base64 = canvas.toDataURL("image/jpeg");
userFaceImage.value = base64;
//console.log(self.userFaceImage);
faceLogin();
}
//切换镜头
const handleChangeFacingMode = () => {
facingMode.value = facingMode.value === 'user' ? 'environment' : 'user';
handlePhotographCloseClick();
invokingCamera();
}
//获取token
const getToken = () => {
doGetToken().then((token: string) => {
ssoToken.value = token
})
}
//人脸登录
const faceLogin = () => {
uni.request({
url: HUIBANG_BASE_URL + '/api/doInterface?code=doStrongFaceLogin',
data: {
token: ssoToken.value,
check_code: code.value,
imgBase64: userFaceImage.value.split(',')[1]
},
method: 'POST',
header: {
'Content-Type': 'application/json'
},
success: (res: any) => {
if (res.data.state === 'ok') {
uni.setStorageSync("u-code", code.value);
loginFaceLogin({ token: res.data.token }).then((res) => {
setToken(res.token)
doLogin()
handlePhotographCloseClick();
})
} else {
handlePhotographClick();
}
},
fail: (res) => {
uni.showToast({ title: JSON.stringify(res), icon: 'none'})
}
});
}
const back = () => {
uni.navigateBack({
delta: 1
})
}
const showKeyboard = ref(false);
const redirectUrl = ref('')
onLoad((options) => {
console.log('login options: ', options)
if (options.redirect) {
redirectUrl.value = ensureDecodeURIComponent(options.redirect)
}
else {
redirectUrl.value = tabbarList[0].pagePath
}
console.log('redirectUrl.value: ', redirectUrl.value)
})
function doLogin() {
console.log(redirectUrl.value)
let path = redirectUrl.value
if (!path.startsWith('/')) {
path = `/${path}`
}
const { path: _path, query } = parseUrlToObj(path)
console.log('_path:', _path, 'query:', query, 'path:', path)
console.log('isPageTabbar(_path):', isPageTabbar(_path))
if (isPageTabbar(_path)) {
// 经过我的测试 switchTab 不能带 query 参数, 不管是放到 url 还是放到 query ,
// 最后跳转过去的时候都会丢失 query 信息
uni.switchTab({
url: path,
})
}
else {
console.log('redirectTo:', path)
uni.redirectTo({
url: path,
})
}
}
</script>
<template>
<view class="content">
<wd-navbar title="人脸识别" left-text="返回" placeholder fixed left-arrow right-text="按钮" @click-left="back">
<template #right>
<wd-icon v-if="showFace" name="camera" size="22px" @click="handleChangeFacingMode"></wd-icon>
</template>
</wd-navbar>
<view class="body">
<view v-if="!showFace" class="code">
<view class="des">
<h3>填写您的手机/身份证后四位</h3>
<p style="margin-top: 15px;color:#909399 ;">请在此处输入</p>
</view>
<view class="code-input">
<!-- 密码输入框 -->
<wd-password-input :length="4" v-model="code" :mask="false" :focused="showKeyboard" @focus="showKeyboard = true" />
<!-- 数字键盘 -->
<wd-number-keyboard v-model="code" extra-key="X" v-model:visible="showKeyboard" :maxlength="4" @blur="showKeyboard = false" />
</view>
<view class="submit">
<view @click="onCodeConfirm" >确定</view>
</view>
</view>
<view v-else class="face">
<view class="face-box">
<video id="video" autoplay :style="facingMode === 'user' ? videoStyle2 : videoStyle" object-fit="fill">
</video>
</view>
<view class="face-tip">
<p>请平视摄像头,并保持光线充足</p>
</view>
<!-- <view class="face-button">
<u-button type="primary" text="开始识别" @click="init()"></u-button>
</view> -->
</view>
</view>
</view>
</template>
<style lang="scss" scoped>
.content {
height: 100%;
}
.body {
height: calc(100% - 85px);
background-color: #ffffff;
padding: 20px;
.code {
height: calc(100%);
//display: flex;
//flex-direction: column;
//align-items: center;
.des {
width: 100%;
margin-top: 20px;
}
.code-input {
margin-top: 40px;
margin-bottom: 80px;
}
.submit {
width: 100%;
div {
line-height: 96rpx;
border-radius: 20rpx;
text-align: center;
font-size: 32rpx;
color: #ffffff;
background-color: #409EFF;
}
}
}
.face {
height: calc(100%);
display: flex;
flex-direction: column;
align-items: center;
.face-tip {
color: #909399;
margin-top: 20px;
}
.face-button {
width: 100%;
margin-top: 20px;
}
}
video {
transform: rotateY(180deg);
-webkit-transform: rotateY(180deg);
/* Safari 和 Chrome */
-moz-transform: rotateY(180deg);
border: 1px solid;
}
/* 移除:deep()选择器,直接使用类名 */
.uni-video-bar {
display: none;
}
.uni-video-cover {
display: none;
}
}
</style>