Files
faceFamilySource/src/components/MyPhoto.vue
2025-09-23 15:28:34 +08:00

644 lines
18 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup>
import { ref, onMounted, computed, watch } from "vue"
import ModalTransition from "./ModalTransition.vue"
import { useRouter } from 'vue-router'
import { globalStore } from "../globalstore.js";
import { ElMessage } from 'element-plus';
import { Storage, generateQR } from "../libs/utils"
// import { RecycleScroller } from "vue-virtual-scroller";
// import "vue-virtual-scroller/dist/vue-virtual-scroller.css";
const props = defineProps({
show: false
})
const emit = defineEmits(['go-photo-square', 'update:show']);
const router = useRouter();
const goBack = () => {
emit('update:show', false);
};
const imageList = ref([])
const displayZhuli = ref(false);
const fetchImages = async () => {
try {
const url = new URL('https://huodong2.lzlj.com/api/faceFamily/face/square');
url.searchParams.append('my_only', '1');
url.searchParams.append('page', '1');
url.searchParams.append('per_page', '100');
const response = await fetch(url.toString(), {
method: 'GET',
headers: {
'Authorization': `Bearer ${Storage.get("userinfos").api_token}`
}
})
const data = await response.json()
if (response.status == 200 || response.status == 201) {
// 只保留result_url存在的记录
images.value = data.data.filter(item => item.result_url);
// 测试数据
// images.value = [
// {
// "id": 186,
// "status": "success",
// "error_message": null,
// "is_public": false,
// "result_url": "https:\/\/lzlj123.oss-cn-shanghai.aliyuncs.com\/face-merged\/20250919\/face-merge-717b50c3-9c38-4713-929a-49d072b90ab6.jpg",
// "likes_count": 0,
// "created_at": "2025-09-19T04:30:26.000000Z",
// "user_name": "\u4f9d\u65ed"
// },
// {
// "id": 155,
// "status": "success",
// "error_message": null,
// "is_public": false,
// "result_url": "https:\/\/lzlj123.oss-cn-shanghai.aliyuncs.com\/face-merged\/20250917\/face-merge-1d2ddf63-b029-48f8-b0b7-de7643dbd184.jpg",
// "likes_count": 0,
// "created_at": "2025-09-17T13:37:08.000000Z",
// "user_name": "\u4f9d\u65ed"
// },
// {
// "id": 156,
// "status": "success",
// "error_message": null,
// "is_public": false,
// "result_url": "https:\/\/lzlj123.oss-cn-shanghai.aliyuncs.com\/face-merged\/20250917\/face-merge-eda32a25-4244-4041-8db8-25aeb41f5a2f.jpg",
// "likes_count": 2,
// "created_at": "2025-09-17T13:40:49.000000Z",
// "user_name": "\u4f9d\u65ed"
// }
// ]
const foundItem = images.value.find(item => item.is_public === true);
if (foundItem) {
globalStore.result_url = foundItem.result_url;
globalStore.mergeId = foundItem.id
displayZhuli.value = true;
} else {
displayZhuli.value = false;
}
imageList.value = images.value;
const hasPublicImage = images.value.some(item => item.is_public);
if (hasPublicImage) {
globalStore.chartsBattle = true;
const publicIndex = images.value.findIndex(item => item.is_public);
if (publicIndex !== -1) {
activeBorders.value = activeBorders.value.map((_, index) => index === publicIndex);
}
}
} else {
ElMessage.error(data.message);
}
} catch (error) {
console.error('Error:', error)
}
}
watch(() => props.show, async (newVal) => {
if (!newVal) {
return
}
fetchImages();
}, { immediate: true })
// 图片数据
const images = ref([]);
import defaultBorderImage from '../assets/images/my-photo-border.webp';
import activeBorderImage from '../assets/images/my-photo-selected-border.webp';
import inActiveBorderImage from '../assets/images/no-btn.webp';
const activeBorders = ref(images.value.map(() => false));
// 切换边框状态
const toggleBorder = (item, index) => {
activeBorders.value = activeBorders.value.map(() => false);
// 如果没有打榜点击切换图片时把当前背景图赋值给globalStore.result_url做为最新的背景图
globalStore.result_url = item.result_url;
activeBorders.value[index] = true;
globalStore.mergeId = item.id;
};
const handleDabangClick = () => {
if (!globalStore.mergeId) {
weui.alert("请先合成照片!")
return false;
}
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) {
ElMessage.success('打榜成功!');
displayZhuli.value = true;
globalStore.chartsBattle = true;
return { success: true, data };
} else {
ElMessage.error(data.message);
}
})
.catch((error) => {
ElMessage.error('打榜失败!');
return { success: false, error };
});
};
const handleZhuliClick = () => {
openHaibao();
console.log('助力被点击');
};
const downloadGenerateImg = (item) => {
openHaibao(item);
console.log('下载被点击');
}
import haibaoCoverBorderNoTitle from "../assets/images/haibao-cover-no-title.webp"
import haibaoCoverBorderSuccess from "../assets/images/haibao-cover-sucess.webp";
import failedImg from '../assets/images/failed.webp';
const getGenerateImgStatus = async (item) => {
fetch(`https://huodong2.lzlj.com/api/faceFamily/face/merge/${item.id}/status`, {
method: 'GET',
headers: {
'Authorization': `Bearer ${Storage.get("userinfos").api_token}`
}
})
.then(async response => {
const data = await response.json()
if (response.status == 200 || response.status == 201) {
if (data.status = 'failed') {
item.result_url = failedImg;
} else if (data.status = 'success') {
item.result_url = data.result_url;
}
} else {
ElMessage.error(data.message);
}
})
.catch((error) => {
ElMessage.error('获取状态失败!');
});
}
const getBackgroundImage = (item) => {
if (item.result_url) {
return `${item.result_url}?x-oss-process=image/resize,w_400/format,webp/quality,q_80`;
} else {
return item.result_url = failedImg;
}
};
// 海报
import Haibao from "@/libs/haibao";
import mask from "../assets/images/haibao-mask.webp";
import haibaoCoverBorder from "../assets/images/haibao-cover.webp";
import bg from "../assets/images/haibao-bg.webp"
const loadImage = (src) => {
return new Promise((resolve, reject) => {
const img = new Image();
img.crossOrigin = "Anonymous";
img.onload = () => resolve(img);
img.onerror = () => reject(new Error(`无法加载图片: ${src}`));
img.src = src;
});
}
const haibaoShow = ref(false);
const haibaoUrl = ref('');
const userHaibaoUrl = ref('');
const userhaibaoCover = computed(() => {
return { backgroundImage: `url(${userHaibaoUrl.value})` }
})
const openHaibao = (item) => {
haibaoShow.value = true
handleHaibao(item)
}
const handleHaibao = async (item) => {
const loading = weui.loading()
const infos = Storage.get("userinfos")
const haibaoCover = new Haibao(951, 1607)
let userPicture = '';
if (item && item.result_url) {
userPicture = await loadImage(item.result_url)
haibaoCover.add(userPicture, 0, 50, 951, 1698)
haibaoCover.add(mask, 10, 100)
haibaoCover.add(haibaoCoverBorderSuccess, 0, 0)
haibaoCover.draw('destination-in').then(() => {
haibaoCover.generate({ mimeType: 'image/png' }).then(async (url) => {
userHaibaoUrl.value = url
const haibaoSave = new Haibao(1080, 2160)
const qrcode = await generateQR(`fromid=${infos.invite_code}&merge_id=${globalStore.mergeId}`, 200, 200)
haibaoSave.add(bg, 0, 0)
haibaoSave.add(url, 64, 250)
haibaoSave.add(qrcode, 127, 1860)
haibaoSave.draw().then(() => {
haibaoSave.text(infos.nickname + '的全家福', haibaoSave.canvas.width / 2, 200, { font: 'bold 50px Arial', color: '#fcf2b3' })
haibaoSave.generate({ mimeType: 'image/png' }).then(url => {
if (item && item.result_url) {
haibaoUrl.value = item.result_url
}
loading.hide()
}).catch(err => {
console.log(err)
weui.alert("海报生成失败,请重新生成")
loading.hide()
})
}).catch(err => {
console.log(err)
weui.alert("海报生成失败,请重新生成")
loading.hide()
})
})
})
} else {
userPicture = await loadImage(globalStore.result_url)
const haibaoCoverNoTitleBorder = new Haibao(951, 1607)
haibaoCoverNoTitleBorder.add(userPicture, 0, 50, 951, 1698)
haibaoCoverNoTitleBorder.add(mask, 10, 100)
haibaoCoverNoTitleBorder.add(haibaoCoverBorder, 0, 0)
haibaoCoverNoTitleBorder.draw('destination-in').then(() => {
haibaoCoverNoTitleBorder.generate({ mimeType: 'image/png' }).then(async (url) => {
userHaibaoUrl.value = url
})
})
if (!globalStore.first_share_today) {
Request(`face/share/${globalStore.mergeId}`)
}
const haibaoCoverNoTitle = new Haibao(951, 1607)
haibaoCoverNoTitle.add(userPicture, 0, 50, 951, 1698)
haibaoCoverNoTitle.add(mask, 10, 100)
haibaoCoverNoTitle.add(haibaoCoverBorderNoTitle, 0, 0)
haibaoCoverNoTitle.draw('destination-in').then(() => {
haibaoCoverNoTitle.generate({ mimeType: 'image/png' }).then(async (url) => {
const haibaoSaveNoTitle = new Haibao(1080, 2160)
const qrcode = await generateQR(`fromid=${infos.invite_code}&merge_id=${globalStore.mergeId}`, 200, 200)
haibaoSaveNoTitle.add(bg, 0, 0)
haibaoSaveNoTitle.add(url, 64, 250)
haibaoSaveNoTitle.add(qrcode, 127, 1860)
haibaoSaveNoTitle.draw().then(() => {
haibaoSaveNoTitle.text(infos.nickname + '的全家福', haibaoSaveNoTitle.canvas.width / 2, 200, { font: 'bold 50px Arial', color: '#fcf2b3' })
haibaoSaveNoTitle.generate({ mimeType: 'image/png' }).then(url => {
haibaoUrl.value = url
loading.hide()
}).catch(err => {
console.log(err)
weui.alert("海报生成失败,请重新生成")
loading.hide()
})
}).catch(err => {
console.log(err)
weui.alert("海报生成失败,请重新生成")
loading.hide()
})
})
})
}
}
const markers = ref([]);
markers.value = [
{ x: 0, y: 32, width: 50, height: 14 }
];
const getBorder = (item, index) => {
if (globalStore.chartsBattle && index !== 0) {
return inActiveBorderImage;
} else {
if ((activeBorders.value[index] || (globalStore.chartsBattle && item.is_public))) {
return activeBorderImage;
} else {
return defaultBorderImage;
}
}
}
</script>
<template>
<ModalTransition class="myPhoto" :no-animation="true" :show="show">
<div class="myPhoto-bg">
<div class="scene-item item-1" @click="goBack">
<img src="../assets/images/close-btn.webp" alt="关闭按钮">
</div>
<div v-for="(marker, index) in markers" :key="index" class="marker" :style="{
left: marker.x + 'vw',
top: marker.y + 'vw',
width: marker.width + 'vw',
height: marker.height + 'vw'
}" @click.stop="$emit('go-photo-square')">
</div>
<p class="my-photo-desc">每位会员只能选一张照片参与打榜点赞前30名即可获得中秋精美礼品速速邀请好友为你点赞吧</p>
<div class="image-gallery">
<!-- <RecycleScroller
class="scroller"
:items="images"
:item-size="2"
key-field="id"
v-slot="{ item, index }"
>
<div class="image-wrapper">
<div class="image-container mask-background"
:style="{ backgroundImage: `url(${getBackgroundImage(item)})` }"
>
</div>
<div class="list-item">
<img v-if="item.status === 'progressing'" @click="getGenerateImgStatus(item)" src="../assets/images/refresh-btn.webp" class="refresh-btn" alt="刷新">
<img
:src="(activeBorders[index] || (globalStore.chartsBattle && item.is_public))
? activeBorderImage : defaultBorderImage"
class="border-image"
alt="border"
@click="!globalStore.chartsBattle && toggleBorder(item, index)"
/>
</div>
</div>
</RecycleScroller> -->
<div v-for="(item, index) in images" :key="index" class="image-wrapper">
<div class="image-container mask-background" @click="downloadGenerateImg(item)" :style="{ backgroundImage: `url(${getBackgroundImage(item)})` }">
</div>
<img v-if="item.status === 'progressing'" @click="getGenerateImgStatus(item)"
src="../assets/images/refresh-btn.webp" class="refresh-btn" alt="刷新">
<img :src="getBorder(item, index)" class="border-image" alt="border"
@click.stop="downloadGenerateImg(item)"
/>
<div class="mask-overlay"
@click="(!globalStore.chartsBattle && item.result_url !== failedImg) && toggleBorder(item, index)"></div>
</div>
</div>
<div class="scene-item item-2">
<img v-if="displayZhuli" @click="handleZhuliClick()" src="../assets/images/zhuli.webp" alt="助力">
<img v-if="!displayZhuli" @click="handleDabangClick()" src="../assets/images/dabang.webp" alt="打榜">
</div>
<div class="fullsection" v-show="haibaoShow">
<div class="haibao" :style="userhaibaoCover">
<img :src="haibaoUrl" alt="">
<div class="close" @click="haibaoShow = false"></div>
</div>
</div>
</div>
</ModalTransition>
</template>
<style scoped>
/* 蒙版样式 */
.mask-overlay {
position: absolute;
bottom: 0;
left: 14vw;
width: 10vw;
height: 8vw;
z-index: 2;
cursor: pointer;
}
.scroller {
height: 124vw;
overflow-y: auto;
}
.list-item {
height: 2vw;
line-height: 50px;
}
.marker {
position: absolute;
}
.fullsection {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
background-color: rgba(0, 0, 0, .7);
}
.haibao {
position: relative;
width: 65.37037vw;
height: 119.444444vw;
background-image: url("../assets/images/haibao-cover.webp");
background-repeat: no-repeat;
background-size: 100%;
}
.haibao img {
width: 100%;
height: 100%;
display: block;
opacity: 0;
}
.close {
position: absolute;
width: 8.148148vw;
height: 8.148148vw;
right: 29vw;
top: 114vw;
background-image: url("../assets/images/close-btn.webp");
background-repeat: no-repeat;
background-size: 100%;
}
.my-photo-desc {
position: absolute;
width: 87vw;
top: 48vw;
text-align: center;
color: #855211;
font-size: 3.6vw;
}
.download-btn {
width: 12.6vw;
position: absolute;
bottom: 7vw;
right: 3vw;
cursor: pointer;
}
.refresh-btn {
width: 12vw;
position: absolute;
top: 7vw;
right: 3vw;
cursor: pointer;
}
.image-wrapper {
position: relative;
margin-right: 2vw;
margin-left: 2vw;
width: 38vw;
height: 59vw;
}
.border-image {
position: absolute;
width: 40vw;
top: 2.8vw;
left: -0.8vw;
}
.image-gallery {
overflow-y: auto;
overflow-x: hidden;
width: 84vw;
height: 57vh;
position: relative;
display: flex;
flex-flow: row;
flex-wrap: wrap;
top: 26vw;
padding-bottom: 22vw;
}
.image-container {
width: 38vw;
height: 59vw;
top: 0;
margin-bottom: 3vw;
background-image: url('../assets/images/test.webp');
background-size: 100%;
background-repeat: no-repeat;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
position: relative;
min-height: -webkit-fill-available;
cursor: pointer;
}
.mask-background {
-webkit-mask-image: url("../assets/images/my-photo-mengban.webp");
mask-image: url("../assets/images/my-photo-mengban.webp");
-webkit-mask-size: contain;
mask-size: contain;
-webkit-mask-position: center;
mask-position: center;
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
}
.content-image {
position: relative;
}
.myPhoto-bg {
width: 100%;
height: 92vh;
background-image: url('../assets/images/my-photov2.webp');
background-size: 100%;
background-repeat: no-repeat;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
position: relative;
min-height: -webkit-fill-available;
}
.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: 9%;
width: 10vw;
right: 4%;
}
.item-3 {
top: 16.3vh;
width: 32vw;
position: absolute;
left: 11vw;
}
.item-2 {
position: fixed;
width: 100%;
bottom: 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>