SpringCloud+Vue虹软人脸识别图片上传(附前端代码)
这个配置文件是基于虹软提供的测试类来实现,感觉不够好用或者不够全,可以根据虹软测试类自己重新配置。
·
1、登录(注册)虹软开放平台
官网地址:虹软视觉开放平台—以免费人脸识别技术为核心的人脸识别算法开放平台 (arcsoft.com.cn)
2、登录后进入开发者中心、申请应用、添加SDK

3、申请通过后,在我的应用里可以看到APPID、SDKKEY。以及下载SDK。




4、下载解压后,文件夹目录如下

5、编写代码
这个配置文件是基于虹软提供的测试类来实现,感觉不够好用或者不够全,可以根据虹软测试类自己重新配置
- 配置类
package com.txx.config.hongRuanFace;
import cn.hutool.core.io.FileUtil;
import cn.hutool.http.HttpUtil;
import com.arcsoft.face.*;
import com.arcsoft.face.enums.*;
import com.arcsoft.face.toolkit.ImageInfo;
import com.stopCar.StopException;
import org.bouncycastle.util.Bytes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PreDestroy;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import static com.arcsoft.face.toolkit.ImageFactory.getRGBData;
/**
* @author txx
* @version [1.0]
* @description 虹软人脸识别配置类
* @date 2024/10/8 15:10
* @Email 17578999441@163.com
*/
@Configuration
public class FaceConfig {
private static final Logger logger = LoggerFactory.getLogger(FaceConfig.class);
@Value("${face-engine.app-id}")
private String appId;
@Value("${face-engine.sdk-key}")
private String sdkKey;
private FaceEngine faceEngine;
/**
* 单例模式初始化FaceEngine bean
* 确保在应用中只有一个FaceEngine实例,提高资源利用率并避免多次初始化带来的性能开销
*
* @return FaceEngine实例 通过单例模式获取FaceEngine的实例,确保全局唯一性
*/
@Bean
public FaceEngine faceEngine() {
// 检查faceEngine是否已经初始化,避免不必要的同步开销
if (faceEngine == null) {
// 同步代码块,确保在类的生命周期内,只有一个线程可以初始化faceEngine
synchronized (this) {
// 双重检查锁定,再次确认faceEngine是否已经初始化
if (faceEngine == null) {
faceEngine = initFaceEngine();
}
}
}
// 返回faceEngine的实例
return faceEngine;
}
private FaceEngine initFaceEngine() {
//指定引擎所在路径
// String libPath = System.getProperty("user.dir") + File.separator + "stopCar-service\\login-service\\src\\main\\resources\\" + "lib" + File.separator + "WIN64";
String libPath = "D:\\Java_Project\\P12(1)\\stop-cars\\stopCar-service\\login-service\\src\\main\\resources\\lib\\WIN64";
FaceEngine engine = new FaceEngine(libPath);
int errorCode = engine.activeOnline(appId, sdkKey);
//如果errorCode的状态码不等于0(激活成功),也不等于90114(SDK已激活,无需再次激活),就输出激活失败
if (errorCode != ErrorInfo.MOK.getValue() && errorCode != ErrorInfo.MERR_ASF_ALREADY_ACTIVATED.getValue()) {
logger.error("引擎激活失败,错误码:{}", errorCode);
throw new RuntimeException("Face engine activation failed");
}
ActiveFileInfo activeFileInfo = new ActiveFileInfo();
errorCode = engine.getActiveFileInfo(activeFileInfo);
if (errorCode != ErrorInfo.MOK.getValue()) {
logger.error("获取激活文件信息失败,错误码:{}", errorCode);
throw new RuntimeException("Failed to get active file info");
}
/* 引擎配置 */
EngineConfiguration engineConfiguration = new EngineConfiguration();
// 设置检测模式为图像检测
engineConfiguration.setDetectMode(DetectMode.ASF_DETECT_MODE_IMAGE);
// 设置人脸方向检测优先级为全向检测
engineConfiguration.setDetectFaceOrientPriority(DetectOrient.ASF_OP_ALL_OUT);
// 设置最多检测的人脸数量为10个
engineConfiguration.setDetectFaceMaxNum(10);
// 设置人脸检测的缩放比例为16
engineConfiguration.setDetectFaceScaleVal(16);
/* 功能配置 */
FunctionConfiguration functionConfiguration = new FunctionConfiguration();
// 支持年龄检测
functionConfiguration.setSupportAge(true);
// 支持3D人脸角度检测
functionConfiguration.setSupportFace3dAngle(true);
// 支持人脸检测
functionConfiguration.setSupportFaceDetect(true);
// 支持人脸识别
functionConfiguration.setSupportFaceRecognition(true);
// 支持性别检测
functionConfiguration.setSupportGender(true);
// 支持活体检测
functionConfiguration.setSupportLiveness(true);
// 支持红外活体检测
functionConfiguration.setSupportIRLiveness(true);
// 将功能配置设置到引擎配置中
engineConfiguration.setFunctionConfiguration(functionConfiguration);
errorCode = engine.init(engineConfiguration);
if (errorCode != ErrorInfo.MOK.getValue()) {
logger.error("初始化引擎失败,错误码:{}", errorCode);
throw new RuntimeException("Face engine initialization failed");
}
return engine;
}
//人脸检测(判断人脸是否存在)
public boolean faceIsExist(String imageUrl) {
System.out.println("图片为:"+ imageUrl);
System.out.println(HttpUtil.get(imageUrl).getBytes());
ImageInfo imageInfo = getRGBData(HttpUtil.downloadBytes(imageUrl));
if (imageInfo == null) {
logger.error("图片加载失败");
throw new RuntimeException("Image loading failed");
}
List<FaceInfo> faceInfoList = new ArrayList<>();
int errorCode = faceEngine().detectFaces(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(), imageInfo.getImageFormat(), faceInfoList);
return errorCode == ErrorInfo.MOK.getValue() && !faceInfoList.isEmpty();
}
//特征提取(提取照片的人脸特征值)
public FaceFeature faceFeature(String imageUrl) {
ImageInfo imageInfo=null;
//判断路径是本地还是网络路径
if (imageUrl.startsWith("http")) {
imageInfo = getRGBData(HttpUtil.downloadBytes(imageUrl));
} else {
imageInfo = getRGBData(new File(imageUrl));
}
/*ImageInfo imageInfo = getRGBData(HttpUtil.downloadBytes(imageUrl));*/
List<FaceInfo> faceInfoList = new ArrayList<>();
int detectCode = faceEngine().detectFaces(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(), imageInfo.getImageFormat(), faceInfoList);
if (detectCode != ErrorInfo.MOK.getValue() || faceInfoList.isEmpty()) {
logger.error("人脸检测失败,错误码:{}", detectCode);
throw new StopException("人脸检测失败");
}
FaceFeature faceFeature = new FaceFeature();
int extractCode = faceEngine().extractFaceFeature(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(), imageInfo.getImageFormat(), faceInfoList.get(0), faceFeature);
if (extractCode != ErrorInfo.MOK.getValue()) {
logger.error("特征提取失败,错误码:{}", extractCode);
throw new RuntimeException("Feature extraction failed");
}
return faceFeature;
}
//特征比对
public boolean faceCompare(FaceFeature faceFeature1, FaceFeature faceFeature2) {
FaceSimilar faceSimilar = new FaceSimilar();
int compareCode = faceEngine().compareFaceFeature(faceFeature1, faceFeature2, faceSimilar);
if (compareCode != ErrorInfo.MOK.getValue()) {
logger.error("人脸比对失败,错误码:{}", compareCode);
throw new RuntimeException("Face comparison failed");
}
logger.info("人脸相似度:{}", faceSimilar.getScore());
return faceSimilar.getScore() > 0.8;
}
@PreDestroy
/**
* 在Spring容器关闭前调用此方法,进行资源清理
*/
public void destroy() {
// 判断面部识别引擎是否初始化过
if (faceEngine != null) {
// 反初始化面部识别引擎,释放资源
faceEngine.unInit();
}
}
public FaceFeature faceFeatureSetBytes(byte[] imageBase) {
ImageInfo imageInfo = getRGBData(imageBase);
List<FaceInfo> faceInfoList = new ArrayList<>();
int detectCode = faceEngine().detectFaces(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(), imageInfo.getImageFormat(), faceInfoList);
if (detectCode != ErrorInfo.MOK.getValue() || faceInfoList.isEmpty()) {
logger.error("人脸检测失败,错误码:{}", detectCode);
throw new RuntimeException("Face detection failed");
}
FaceFeature faceFeature = new FaceFeature();
int extractCode = faceEngine().extractFaceFeature(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(), imageInfo.getImageFormat(), faceInfoList.get(0), faceFeature);
if (extractCode != ErrorInfo.MOK.getValue()) {
logger.error("特征提取失败,错误码:{}", extractCode);
throw new RuntimeException("Feature extraction failed");
}
return faceFeature;
}
}
- yaml配置文件
# 人脸认证引擎配置
face-engine:
# 应用id
app-id: 你自己的appid
# sdk密匙
sdk-key: 你自己的key
# 人脸对比阀值(建议0.8)
face-similar-score: 0.8
# RGB活体检测阀值(建议0.5)
rgb-threshold: 0.5
# IR活体检测阀值(建议0.7)
ir-threshold: 0.7
- 实现方法
@Autowired
private FaceConfig faceEngineApi;
//人脸录入
@Override
public String faceEntering(MultipartFile file) {
if (ObjectUtil.isEmpty(file)){
throw new StopException("参数不能为空");
}
//将图片上传到oss
InputStream inputStream = null;
try {
inputStream = file.getInputStream();
} catch (IOException e) {
throw new RuntimeException(e);
}
String originalFilename = file.getOriginalFilename();
//上传到阿里云之后会返回图片地址
String s1 = aliyunOSSUtils.putObject(inputStream,originalFilename);
//调用人脸识别配置类检查地址中的图片是否存在人脸
boolean b = faceEngineApi.faceIsExist(s1);
if(!b){
return "没有识别到人脸,请重试";
}
return s1;
}
//人脸比对
@Override
public Boolean detectFace(String imageBase,String username) {
//根据手机号或邮箱查询用户
QueryWrapper<User> uqw = new QueryWrapper<>();
if(isPhone(username)){
uqw.eq("phone", username);
}
if (isEmail(username)){
uqw.eq("email", username);
}
User user = baseMapper.selectOne(uqw);
//获取用户人脸照片地址
String faceImg = user.getFaceImg();
if (ObjectUtil.isEmpty(faceImg)){
return false;
}
//将图片转换为字节数组
byte[] imageBytes = Base64.getDecoder().decode(imageBase.split(",")[1]);
// 创建临时文件
File tempFile = null;
try {
tempFile = File.createTempFile("face", ".jpg");
} catch (IOException e) {
throw new RuntimeException(e);
}
try (FileOutputStream fos = new FileOutputStream(tempFile)) {
fos.write(imageBytes);
} catch (IOException e) {
throw new RuntimeException(e);
}
System.out.println("图片:"+imageBase );
//比较是否是同一个人
boolean b = faceEngineApi.faceCompare(faceEngineApi.faceFeature(tempFile.getAbsolutePath()), faceEngineApi.faceFeature(faceImg));
// 删除临时文件
tempFile.delete();
return b;
}
- Vue前端打开摄像头(带有页面)
<template>
<div class="verification-container">
<el-card class="verification-card">
<h1>实名认证</h1>
<div class="id-upload">
<div class="upload-area">
<div class="upload-item">
<el-upload
class="avatar-uploader"
action="http://localhost:8081/user/uploadImage"
:show-file-list="false"
:on-success="handleFrontSuccess"
:before-upload="beforeAvatarUpload">
<img v-if="frontImageUrl" :src="frontImageUrl" class="avatar">
<div v-else class="upload-placeholder">
<i class="el-icon-plus"></i>
<span>上传身份证正面</span>
</div>
</el-upload>
</div>
<div class="upload-item">
<el-upload
class="avatar-uploader"
action="http://localhost:8081/user/uploadImage"
:show-file-list="false"
:on-success="handleBackSuccess"
:before-upload="beforeAvatarUpload">
<img v-if="backImageUrl" :src="backImageUrl" class="avatar">
<div v-else class="upload-placeholder">
<i class="el-icon-plus"></i>
<span>上传身份证反面</span>
</div>
</el-upload>
</div>
</div>
</div>
<el-form :model="form" label-width="80px" class="verification-form" :rules="rules" ref="form">
<el-form-item label="姓名" prop="name">
<el-input v-model="form.name" placeholder="上传证件自动识别姓名"></el-input>
</el-form-item>
<el-form-item label="证件号" prop="idNumber">
<el-input v-model="form.idNumber" placeholder="上传证件自动识别证件号"></el-input>
</el-form-item>
<el-form-item label="地址" prop="address">
<el-input v-model="form.address" placeholder="上传证件自动识别地址"></el-input>
</el-form-item>
<el-form-item label="性别" prop="sex">
<el-radio-group v-model="form.sex">
<el-radio :label="1">男</el-radio>
<el-radio :label="2">女</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="生日" prop="birthday">
<el-date-picker v-model="form.birthday" type="date" placeholder="上传证件自动识别日期"></el-date-picker>
</el-form-item>
</el-form>
<el-button type="primary" @click="openFaceCapture" class="face-capture-btn">录入人脸信息</el-button>
<el-dialog title="人脸信息录入" v-model="dialogVisible" width="60%" class="face-capture-dialog">
<div class="camera-container">
<video ref="video" width="100%" height="auto" autoplay></video>
<canvas ref="canvas" style="display:none;"></canvas>
<div class="face-outline"></div>
</div>
<div class="capture-instructions">
<p>请将您的脸部置于框内,并保持不动</p>
<p>确保光线充足,避免背光</p>
</div>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button type="primary" @click="captureFace">确认录入</el-button>
</span>
</el-dialog>
<el-button @click="submitForm">提交</el-button>
</el-card>
</div>
</template>
<script>
import axios from "axios";
// import {ElMessage} from "element-plus";
export default {
data() {
return {
frontImageUrl: '',
backImageUrl: '',
form: {
name: '',
idNumber: '',
address: '',
gender: '',
birthday: ''
},
dialogVisible: false,
stream: null,
rules: {
name: [
{ required: true, message: '请输入姓名', trigger: 'blur' },
{ min: 2, max: 20, message: '姓名长度应在 2 到 20 个字符之间', trigger: 'blur' }
],
idNumber: [
{ required: true, message: '请输入证件号', trigger: 'blur' },
{ pattern: /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/, message: '请输入有效的身份证号', trigger: 'blur' }
],
address: [
{ required: true, message: '请输入地址', trigger: 'blur' },
{ min: 5, max: 100, message: '地址长度应在 5 到 100 个字符之间', trigger: 'blur' }
],
sex: [
{ required: true, message: '请选择性别', trigger: 'change' }
],
birthday: [
{ required: true, message: '请选择出生日期', trigger: 'change' },
{ type: 'date', message: '请输入有效的日期', trigger: 'change' }
]
}
};
},
created() {
// 检查是否有传入的参数
if (this.$route.query.formData) {
try {
this.form = JSON.parse(this.$route.query.formData);
console.log('formData:')
console.log(this.form)
} catch (e) {
console.error('Error parsing form data:', e);
}
}
},
methods: {
handleFrontSuccess(ress) {
//识别身份证号
axios({
method: "get",
url: "http://localhost:8081/user/discernIdNumber",
params:{
img: ress.data
},
}).then(res=>{
if (res.data.code==200){
const data =JSON.parse(res.data.data)
//判断data中是否包含data.back.data
try {
if (data.data.face.data) {
console.log("身份证返回值:")
console.log(data.data.face.data)
this.frontImageUrl = ress.data;
this.form.name=data.data.face.data.name
this.form.idNumber=data.data.face.data.idNumber
this.form.address=data.data.face.data.address
this.form.sex=data.data.face.data.sex=="男"?1:2
// 正则表达式匹配年、月、日
const regex = /(\d{4})年(\d{1,2})月(\d{1,2})日/;
const match = data.data.face.data.birthDate.match(regex);
if (!match) {
throw new Error('Invalid date format');
}
// 解析年、月、日
const year = parseInt(match[1], 10);
const month = parseInt(match[2], 10) - 1; // 月份是从 0 开始的,1 月对应 0
const day = parseInt(match[3], 10);
// 创建 Date 对象
this.form.birthday=new Date(year, month, day);
}
} catch (error) {
// 属性不存在,继续执行
this.$message.error("请上传身份证正面");
return;
}
}else {
this.$message.error("识别失败,请重新上传");
return;
}
})
},
handleBackSuccess(ress) {
axios({
method: "get",
url: "http://localhost:8081/user/discernIdNumber",
params:{
img: ress.data
},
}).then(res=>{
if (res.data.code==200){
const data =JSON.parse(res.data.data)
try {
if(data.data.back.data.validPeriod){
console.log(data)
//判断当前有效期是否过期
console.log(data.data.back.data.validPeriod)
// 分割字符串获取起始和结束日期
const [startDateStr, endDateStr] = data.data.back.data.validPeriod.split('-');
// 格式化日期字符串
const startDate = new Date(startDateStr.replace('.', '-').replace('.', '-'));
const endDate = new Date(endDateStr.replace('.', '-').replace('.', '-'));
//获取当前日期
const currentDate = new Date();
console.log(currentDate)
console.log(startDate)
console.log(endDate)
if (currentDate > endDate) {
this.$message.error("身份证已过期,请更新您的身份证");
} else if (currentDate < startDate) {
this.$message.warning("身份证尚未生效");
} else {
// 身份证在有效期内
this.$message.success("上传成功");
this.backImageUrl = ress.data;
console.log(this.backImageUrl)
}
}
}catch (error){
this.$message.error("请上传身份证背面");
return;
}
}else{
this.$message.error("识别失败,请重新上传");
}
})
},
beforeAvatarUpload() {
/*const isJPG = file.type === 'image/jpeg';
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isJPG) {
this.$message.error('上传头像图片只能是 JPG 格式!');
}
if (!isLt2M) {
this.$message.error('上传头像图片大小不能超过 2MB!');
}
return isJPG && isLt2M;*/
return true;
},
openFaceCapture() {
this.dialogVisible = true;
this.$nextTick(() => {
navigator.mediaDevices.getUserMedia({ video: true })
.then(stream => {
this.stream = stream;
this.$refs.video.srcObject = stream;
})
.catch(err => {
console.error("摄像头访问失败:", err);
});
});
},
// 定义 dataURLtoBlob 函数
dataURLtoBlob(dataURL) {
console.log("开始转换DataURL到Blob");
const binary = atob(dataURL);
const array = [];
for (let i = 0; i < binary.length; i++) {
array.push(binary.charCodeAt(i));
}
console.log("Blob创建完成");
return new Blob([new Uint8Array(array)], {type: 'image/jpeg'});
},
captureFace() {
console.log("开始捕获人脸");
const canvas = this.$refs.canvas;
const video = this.$refs.video;
if (!canvas || !video) {
console.error('Canvas or video element not found');
this.$message.error('无法捕获图像,请检查设备');
return;
}
console.log("Canvas和Video元素已找到");
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
canvas.getContext('2d').drawImage(video, 0, 0, canvas.width, canvas.height);
console.log("图像已绘制到Canvas上");
let dataURL;
try {
dataURL = canvas.toDataURL('image/jpeg');
console.log("DataURL生成成功:", dataURL.substring(0, 50) + "...");
} catch (error) {
console.error('Error generating dataURL:', error);
this.$message.error('生成图像数据失败,请重试');
return;
}
const parts = dataURL.split(',');
if (parts.length < 2) {
console.error('Invalid data URL format');
this.$message.error('图像数据格式无效,请重试');
return;
}
const fileData = parts[1];
console.log("文件数据提取成功");
let fileBlob;
try {
fileBlob = this.dataURLtoBlob(fileData);
console.log("Blob对象创建成功");
} catch (error) {
console.error('Error creating Blob:', error);
this.$message.error('创建图像文件失败,请重试');
return;
}
const formData = new FormData();
formData.append('file', fileBlob, 'image.jpg');
console.log("准备发送请求到服务器");
axios({
method: "post",
url: "http://localhost:8081/user/faceEntering",
data: formData,
headers: {
'Content-Type': 'multipart/form-data'
}
}).then(res => {
console.log("服务器响应:", res);
if (res.data.code === 200) {
this.$message.success("人脸录入成功");
this.form.faceImg = res.data.data;
} else {
this.$message.error(res.data.data || '人脸录入失败');
}
}).catch(error => {
console.error('Error sending image to server:', error);
this.$message.error('发送图像到服务器失败,请重试');
}).finally(() => {
this.dialogVisible = false;
if (this.stream) {
this.stream.getTracks().forEach(track => track.stop());
}
console.log("捕获过程结束");
});
},
submitForm(){
// 在这里添加提交表单的逻辑
if (!this.frontImageUrl || !this.backImageUrl) {
this.$message.error('请上传身份证正反面照片');
return;
}
if (!this.form.faceImg){
this.$message.error('请先录入人脸信息');
return;
}
if (!this.form.name){
this.$message.error('请输入姓名');
return;
}
if (!this.form.idNumber){
this.$message.error('请输入身份证号');
return;
}
if(!this.form.address){
this.$message.error('请输入地址');
return;
}
if(!this.form.sex){
this.$message.error('请选择性别');
return;
}
if (!this.form.birthday){
this.$message.error('请输入出生日期');
return;
}
this.form.idCardFontImg=this.frontImageUrl;
this.form.idCardBackImg=this.backImageUrl;
//携带参数到login页面
this.$router.push({
path: '/',
query: {
tab: 'second',
formData: JSON.stringify(this.form)
}
});
},
}
}
</script>
<style scoped>
.verification-container {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
/*
background: linear-gradient(135deg, #43cea2 0%, #185a9d 100%);
*/
padding: 20px;
background-image: url(https://www.rolls-roycemotorcars.com.cn/content/dam/rollsroyce-website/black-badge-ghost-2021/page-components/home_kv.jpg);
background-size: cover;
background-position: center;
background-repeat: no-repeat;
min-height: 100vh;
position: relative;
/* background-color: rgba(0, 0, 0, 0.5);
background-blend-mode: multiply;*/
}
/*body {
background-image: url('https://images-porsche.imgix.net/-/media/221CEF1CBAE547758D86C68AB019A076_45E63369E01D40ABB12254BC07EC11F2_16-9_porsche_finder?iar=0&w=1759&ar=16%3A9&q=85&auto=format');
background-size: cover;
background-position: center;
background-repeat: no-repeat;
min-height: 100vh;
position: relative;
}*/
/*.verification-container::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5); !* 调整这里的透明度来改变阴影深浅 *!
z-index: -1;
}*/
.verification-card {
width: 100%;
max-width: 700px;
background: rgba(255, 255, 255, 0.1);
border-radius: 20px;
padding: 40px;
box-shadow: 0 15px 30px rgba(0,0,0,0.1);
}
h1 {
text-align: center;
color: #185a9d;
margin-bottom: 40px;
font-size: 32px;
font-weight: 600;
}
.id-upload {
margin-bottom: 40px;
}
.upload-area {
display: flex;
justify-content: space-between;
}
.upload-item {
width: 48%;
}
.avatar-uploader {
border: 2px dashed #43cea2;
border-radius: 10px;
cursor: pointer;
position: relative;
overflow: hidden;
height: 180px;
display: flex;
justify-content: center;
align-items: center;
background-color: #f8f8f8;
transition: all 0.3s ease;
}
.avatar-uploader:hover {
border-color: #185a9d;
background-color: #f0f0f0;
}
.upload-placeholder {
display: flex;
flex-direction: column;
align-items: center;
color: #43cea2;
}
.upload-placeholder i {
font-size: 40px;
margin-bottom: 10px;
}
.avatar {
width: 100%;
height: 100%;
object-fit: cover;
}
.verification-form {
margin-top: 30px;
}
.el-form-item {
margin-bottom: 25px;
}
.face-capture-btn {
width: 100%;
margin-top: 30px;
background: linear-gradient(to right, #43cea2 0%, #185a9d 100%);
border: none;
font-size: 18px;
height: 50px;
transition: all 0.3s ease;
}
.face-capture-btn:hover {
transform: translateY(-3px);
box-shadow: 0 7px 14px rgba(50, 50, 93, .1), 0 3px 6px rgba(0, 0, 0, .08);
}
.face-capture-dialog .el-dialog__body {
padding: 0;
}
.camera-container {
width: 100%;
height: 450px;
overflow: hidden;
background-color: #000;
display: flex;
justify-content: center;
align-items: center;
position: relative;
}
.face-outline {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 300px;
height: 300px;
border: 3px solid #43cea2;
border-radius: 50%;
box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.5);
}
.camera-container video {
min-width: 100%;
min-height: 100%;
}
.capture-instructions {
padding: 20px;
text-align: center;
background-color: #f8f8f8;
}
.capture-instructions p {
margin: 10px 0;
color: #185a9d;
font-size: 16px;
}
.el-dialog__title {
color: #185a9d;
font-weight: 600;
}
.el-button--primary {
background-color: #43cea2;
border-color: #43cea2;
}
.el-button--primary:hover, .el-button--primary:focus {
background-color: #185a9d;
border-color: #185a9d;
}
.verification-card {
background: rgba(255, 255, 255, 0.8); /* 增加卡片背景透明度 */
/* 其他属性保持不变 */
}
.verification-form {
margin-top: 30px;
}
.el-form-item {
margin-bottom: 25px;
}
</style>
更多推荐


所有评论(0)