This commit is contained in:
yixu
2025-12-19 11:49:19 +08:00
parent 258e14e27c
commit 82e25179a2
9 changed files with 3 additions and 407 deletions

View File

@@ -1,7 +1,5 @@
<script setup>
import { Request, Storage } from "../libs/utils"
import ModalTransition from "./ModalTransition.vue"
import { globalStore } from "../globalstore.js";
defineProps({
show: false

View File

@@ -37,8 +37,6 @@ const props = defineProps({
const emit = defineEmits(['close'])
// const emit = defineEmits(['update:show'])
// Lottery 相关状态
const lotteryShow = ref(false)
const lotteryType = ref('draw')
@@ -51,10 +49,6 @@ const circularArea = ref({
size: '25vw' // 可配置:圆形区域的大小
})
// const handleGoHome = () => {
// emit('update:show', false)
// }
const handleGoBack = () => {
emit('close')
}

View File

@@ -60,7 +60,6 @@ import ModalTransition from "./ModalTransition.vue"
import confetti from "canvas-confetti";
import { Request, Sleep } from "../libs/utils"
import { globalStore } from "@/globalstore";
import getUserPicture from "../libs/getUserPicture";
const navigatePopupMore = () => {
globalToastEvent.emit(ToastType.SHOW_POPUPMORE)

View File

@@ -1,159 +0,0 @@
<script setup>
import { Request, Storage } from "../libs/utils"
import ModalTransition from "./ModalTransition.vue"
import { globalStore } from "../globalstore.js";
import { ElMessage } from 'element-plus';
defineProps({
show: false
})
const emit = defineEmits(['update:show']);
const cancelBtn = () => {
emit('update:show', false);
}
const confirmBtn = () => {
fetch(`https://huodong2.lzlj.com/api/faceFamily/face/publish/${globalStore.mergeId}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: "application/json",
'Authorization': `Bearer ${Storage.get("userinfos").api_token}`
},
body: {}
})
.then(async response => {
const data = await response.json()
if (response.status == 200 || response.status == 201) {
if (data.message === '您已经有一张发布的照片,不能再次发布') {
weui.alert('您已经有一张发布的照片,不能再次发布')
return
}
ElMessage.success('打榜成功!');
console.log('Success:', data);
return { success: true, data };
} else {
ElMessage.error(data.message);
}
})
.catch((error) => {
ElMessage.error('打榜失败!');
return { success: false, error };
});
emit('update:show', false);
}
</script>
<template>
<ModalTransition class="popup" :show="show">
<div class="home-bg">
<div class="popup-bg">
<div class="scene-item item-1">
<img src="../assets/images/confirm.webp" @click="cancelBtn" alt="确认">
</div>
<p class="message">活动已结束</p>
<!-- <div class="scene-item item-2">
<img src="../assets/images/cancel.webp" @click="cancelBtn" alt="取消">
</div> -->
</div>
</div>
</ModalTransition>
</template>
<style scoped>
.home-bg {
position: absolute;
top: 60vw;
left: 12vw;
}
.message {
width: 60vw;
position: absolute;
top: 42%;
color: #774107;
text-align: center;
}
.popup-bg {
width: 77vw;
height: 60vw;
background-image: url('../assets/images/popup.webp');
background-size: 100%;
background-repeat: no-repeat;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
position: relative;
}
.scene-item {
position: absolute;
cursor: pointer;
transition: all 0.4s ease;
border-radius: 8px;
overflow: hidden;
border: 3px solid transparent;
animation: float 4s ease-in-out infinite;
}
.scene-item:hover {
transform: scale(1.05);
}
.btn-login {
text-align: center;
border-radius: 1vw;
background-color: #70b2e2;
margin-top: 4vw;
padding: 3vw;
color: #fff;
position: relative;
}
.item-1 {
top: 50vw;
width: 40vw;
}
.item-2 {
top: 50vw;
width: 40vw;
left: 0;
}
.scene-item img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.btn-login.disable {
opacity: .6;
}
.btn-login.subloading {
opacity: .6;
}
.btn-login.subloading::before {
content: "";
position: absolute;
border: .5vw solid #fff;
border-color: rgba(255, 255, 255, .8) transparent transparent transparent;
border-radius: 50%;
width: 3vw;
height: 3vw;
top: 30%;
left: calc(50% - 14vw);
animation: loginloading 1s linear infinite;
}
@keyframes loginloading {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>

View File

@@ -1,14 +1,12 @@
<script setup>
import { isWeixinPlatform, miniJumpToScene, getMiniPageBtnHack } from "../libs/utils"
import ModalTransition from "./ModalTransition.vue"
import { globalStore } from "../globalstore.js";
defineProps({
show: false
})
const emit = defineEmits(['close'])
// const emit = defineEmits(['update:show']);
const cancelBtn = () => {
emit('close')

View File

@@ -27,7 +27,7 @@
</template>
<script setup>
import { ref, watch } from "vue"
import { ref } from "vue"
import { isWeixinPlatform, miniJumpToScene, getMiniPageBtnHack } from "../libs/utils"
import ModalTransition from "./ModalTransition.vue";
const props = defineProps({

View File

@@ -1,7 +1,7 @@
<script setup>
import { Request, Storage, generateQR } from "../libs/utils.js"
import { Storage, generateQR } from "../libs/utils.js"
import ModalTransition from "./ModalTransition.vue"
import { ref, onMounted, computed } from "vue"
import { ref, onMounted } from "vue"
import Haibao from "../libs/haibao.js"
import ruleBg from "../assets/images/new/rule-share.png"
import globalToastEvent, { ToastType } from '../globalToastEvent';
@@ -11,7 +11,6 @@ const props = defineProps({
})
const emit = defineEmits(['close'])
// const emit = defineEmits(['update:show'])
const gotoLottery = () => {
globalToastEvent.emit(ToastType.SHOW_LOTTERY);
}

View File

@@ -1,204 +0,0 @@
import * as faceapi from 'face-api.js'
// 模型加载状态
let modelsLoaded = false
/**
* 加载 face-api.js 模型
*/
export async function loadFaceApiModels() {
if (modelsLoaded) {
console.log('Face API 模型已经加载过了')
return true
}
try {
console.log('开始加载 Face API 模型...')
// 获取基础路径,处理 vite 的 base 配置
const basePath = import.meta.env.BASE_URL || '/'
const modelsPath = basePath.endsWith('/') ? basePath + 'models' : basePath + '/models'
console.log('模型加载路径:', modelsPath)
// 加载必需的模型
await Promise.all([
faceapi.nets.tinyFaceDetector.loadFromUri(modelsPath),
faceapi.nets.faceLandmark68Net.loadFromUri(modelsPath),
faceapi.nets.faceRecognitionNet.loadFromUri(modelsPath)
])
console.log('Face API 模型加载完成')
modelsLoaded = true
return true
} catch (error) {
console.error('Face API 模型加载失败:', error)
}
}
/**
* 检查模型是否已加载
*/
export function areModelsLoaded() {
return modelsLoaded
}
/**
* 验证图片中的人脸
* @param {File} file - 图片文件
* @returns {Promise<Object>} - 验证结果
*/
export async function validateFaceInImage(file) {
// 如果模型未加载,先加载模型
if (!modelsLoaded) {
const loaded = await loadFaceApiModels()
if (!loaded) {
return {
success: false,
message: '人脸识别模型加载失败,请刷新页面重试'
}
}
}
return new Promise((resolve) => {
const img = new Image()
img.onload = async () => {
try {
console.log('开始进行人脸检测...')
// 使用 TinyFaceDetector 进行人脸检测
const detections = await faceapi
.detectAllFaces(img, new faceapi.TinyFaceDetectorOptions({
inputSize: 416,
scoreThreshold: 0.3
}))
.withFaceLandmarks()
console.log(`检测到 ${detections.length} 张人脸`)
// 检查是否检测到人脸
if (detections.length === 0) {
resolve({
success: false,
message: '未检测到人脸,请上传包含清晰人脸的图片'
})
return
}
// 检查是否只有一张人脸
if (detections.length > 1) {
resolve({
success: false,
message: '检测到多张人脸,请上传只包含一张人脸的图片'
})
return
}
const detection = detections[0]
const confidence = detection.detection.score
console.log('人脸检测置信度:', confidence)
// 检查置信度是否足够高
const minConfidence = 0.3 // 降低到 0.6 以提高通过率
if (confidence < minConfidence) {
resolve({
success: false,
message: `人脸清晰度不够(置信度: ${(confidence * 100).toFixed(1)}%),请上传更清晰的人脸图片`
})
return
}
// 检查人脸大小(相对于图片尺寸)
const faceBox = detection.detection.box
const imageArea = img.naturalWidth * img.naturalHeight
const faceArea = faceBox.width * faceBox.height
const faceRatio = faceArea / imageArea
console.log('人脸占图片比例:', (faceRatio * 100).toFixed(2) + '%')
const minFaceRatio = 0.01 // 降低到 1.5% 以提高通过率
if (faceRatio < minFaceRatio) {
resolve({
success: false,
message: '人脸在图片中占比太小,请上传人脸更大更清晰的图片'
})
return
}
// 检查人脸角度(通过关键点判断)
if (detection.landmarks) {
const landmarks = detection.landmarks
const leftEye = landmarks.getLeftEye()
const rightEye = landmarks.getRightEye()
// 计算眼睛之间的角度来判断人脸是否正面
if (leftEye.length > 0 && rightEye.length > 0) {
const leftEyeCenter = leftEye[0]
const rightEyeCenter = rightEye[3]
const eyeAngle = Math.abs(
Math.atan2(
rightEyeCenter.y - leftEyeCenter.y,
rightEyeCenter.x - leftEyeCenter.x
) * 180 / Math.PI
)
console.log('人脸角度:', eyeAngle.toFixed(1) + '度')
const maxAngle = 30 // 放宽到 20 度
if (eyeAngle > maxAngle) {
resolve({
success: false,
message: '请上传正面人脸照片,避免侧脸或过度倾斜的角度'
})
return
}
}
}
// 所有验证通过
resolve({
success: true,
message: `人脸验证通过(置信度: ${(confidence * 100).toFixed(1)}%`,
confidence: confidence,
faceRatio: faceRatio,
faceBox: {
x: faceBox.x,
y: faceBox.y,
width: faceBox.width,
height: faceBox.height
}
})
} catch (error) {
console.error('人脸检测过程中出错:', error)
resolve({
success: false,
message: '人脸检测失败,请重试或更换图片'
})
}
}
img.onerror = () => {
resolve({
success: false,
message: '图片加载失败,请检查图片格式'
})
}
// 加载图片
const reader = new FileReader()
reader.onload = (e) => {
img.src = e.target.result
}
reader.onerror = () => {
resolve({
success: false,
message: '图片读取失败,请重试'
})
}
reader.readAsDataURL(file)
})
}

View File

@@ -1,29 +0,0 @@
import { globalStore } from "@/globalstore";
import { generateQR, Request } from "../libs/utils"
import Haibao from "@/libs/haibao"
import mask from "../assets/images/haibao-mask.webp"
import haibaoCoverBorder from "../assets/images/haibao-cover.webp"
export default async () => {
let userUrl = globalStore.result_url
if (!userUrl) {
const result = await Request('face/square', { my_only: 1, page: 1, per_page: 100 }, "GET", true)
if (result.res.status === 200) {
const dataHit = result.json.data.find(v => v.is_public)
if (!dataHit) {
return weui.alert("请先去参与打榜")
}
userUrl = dataHit.result_url
}
}
const haibaoCover = new Haibao(951, 1607)
haibaoCover.add(userUrl, 0, 50, 951, 1698)
haibaoCover.add(mask, 10, 100)
haibaoCover.add(haibaoCoverBorder, 0, 0)
await haibaoCover.draw('destination-in');
return await haibaoCover.generate({ mimeType: 'image/png' });
}