This commit is contained in:
yixu
2025-09-08 15:36:26 +08:00
commit 9a5cf53e3c
36 changed files with 7750 additions and 0 deletions

255
src/components/Address.vue Normal file
View File

@@ -0,0 +1,255 @@
<template>
<ModalTransition :show="addressShow">
<div class="address-wrapper">
<div class="address-title">收货地址填写</div>
<div class="address-content">
<div class="address-item">
<div class="address-item-label">收货人</div>
<input type="text" class="i-name" v-model="name" placeholder="请填写收货人姓名" />
</div>
<div class="address-item">
<div class="address-item-label">联系方式</div>
<input type="number" class="i-phone" v-model="phone" placeholder="请填写手机号码" />
</div>
<div class="address-item">
<div class="address-item-label">所在地区</div>
<div class="address-area" @click="changeArea">{{ area ? area : '请选择地理位置' }}</div>
</div>
<div class="address-item address-textarea">
<div class="address-item-label">详细地址</div>
<textarea placeholder="请填写详细地址" v-model="address"></textarea>
</div>
</div>
<div class="btn-submit" :class="{ disable: btnDisableClass }" @click="handleSubmit">保存</div>
<div class="address-close" @click="$emit('addressClose')"></div>
</div>
</ModalTransition>
</template>
<script setup>
import { ref, computed } from "vue"
import { Request } from "../libs/utils"
import AREA from "../libs/area"
import ModalTransition from "./ModalTransition.vue"
const props = defineProps({
show: false,
prizeId: Number
})
const emit = defineEmits(['addressSubmit','addressClose'])
const addressShow = computed(() => props.prizeId ? props.show : false)
const btnDisableClass = ref(false)
const name = ref('')
const phone = ref('')
const province = ref('')
const city = ref('')
const county = ref('')
const area = computed(() => `${province.value}${city.value}${county.value}`)
const address = ref('')
const changeArea = () => {
weui.picker(AREA, {
defaultValue: ["110000", "110000", '110101'],
onConfirm: (result) => {
province.value = result[0].label
city.value = result[1].label
county.value = result[2].label
}
})
}
const checkForm = () => {
if (name.value == "") {
weui.alert("请输入联系人")
return false
} else if (phone.value === "" || phone.value.toString().length !== 11) {
weui.alert("请输入正确的手机号")
return false
} else if (!province.value || !city.value || !county.value) {
weui.alert("请选择地区")
return false
} else if (address.value == "") {
weui.alert("请输入详细地址")
return false
} else {
return true
}
}
const handleSubmit = async () => {
if (btnDisableClass.value) {
return
}
if (!checkForm()) {
return
}
btnDisableClass.value = true
const result = await Request(`user/address`, {
name: name.value,
phone: phone.value,
province: province.value,
city: city.value,
county: county.value,
address: address.value,
lottery_log_id: props.prizeId,
})
if (result.res.status == 200 || result.res.status == 201) {
emit("addressSubmit", { id: props.prizeId })
}else{
emit('addressClose')
}
btnDisableClass.value = false
}
</script>
<style scoped>
.address-wrapper {
width: 100%;
background-color: #f2f3f8;
border-radius: 2vw 2vw 0 0;
animation: transitionIn ease 0.3s forwards;
position: relative;
padding: 0 4vw;
padding-bottom: 14vw;
position: relative;
}
.address-title {
font-size: 3.703704vw;
font-weight: 700;
padding: 4vw 0;
}
.address-content {
display: flex;
flex-direction: column;
align-items: flex-end;
margin-bottom: 10vw;
}
.address-content input,
.address-content textarea {
padding: 0;
border: none;
width: 100%;
height: 100%;
resize: none;
background: none;
line-height: 1.2;
}
.address-area,
.address-content input,
.address-content textarea {
font-size: 2.962963vw;
}
.address-content textarea {
margin-top: .1vw;
}
.address-area {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.address-content input::placeholder,
.address-content textarea::placeholder,
.address-area {
color: #86746e;
}
.address-item {
width: 100%;
position: relative;
display: flex;
padding: 3vw 0;
align-items: center;
}
.address-item::after {
content: "";
box-sizing: border-box;
position: absolute;
left: 0;
top: 0;
width: 200%;
height: 200%;
border-bottom: 1px solid gray;
transform: scale(0.5);
transform-origin: 0 0;
pointer-events: none;
}
.address-item-label {
min-width: 20vw;
font-size: 3vw;
line-height: 1.2;
color: #000;
}
.address-item input {
flex: 1;
line-height: 100%;
height: 6vw;
}
.btn-submit {
text-align: center;
border-radius: 1vw;
background-color: #70b2e2;
margin-top: 4vw;
padding: 2vw;
color: #fff;
position: relative;
}
.btn-submit.disable {
opacity: .7;
}
.btn-submit.disable::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;
}
.address-area {
flex: 1;
font-weight: 700;
}
.address-textarea {
height: 10vw;
align-items: normal;
}
.address-textarea::after {
display: none;
}
.address-close {
display: block;
position: absolute;
right: 4vw;
top: 4vw;
width: 8.425926vw;
height: 8.703704vw;
background-repeat: no-repeat;
background-position: 0 0;
background: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciICB2aWV3Qm94PSIwIDAgNTAgNTAiIHdpZHRoPSIxMDBweCIgaGVpZ2h0PSIxMDBweCI+PHBhdGggZD0iTTI1LDJDMTIuMzE5LDIsMiwxMi4zMTksMiwyNXMxMC4zMTksMjMsMjMsMjNzMjMtMTAuMzE5LDIzLTIzUzM3LjY4MSwyLDI1LDJ6IE0zMy43MSwzMi4yOWMwLjM5LDAuMzksMC4zOSwxLjAzLDAsMS40MglDMzMuNTEsMzMuOSwzMy4yNiwzNCwzMywzNHMtMC41MS0wLjEtMC43MS0wLjI5TDI1LDI2LjQybC03LjI5LDcuMjlDMTcuNTEsMzMuOSwxNy4yNiwzNCwxNywzNHMtMC41MS0wLjEtMC43MS0wLjI5CWMtMC4zOS0wLjM5LTAuMzktMS4wMywwLTEuNDJMMjMuNTgsMjVsLTcuMjktNy4yOWMtMC4zOS0wLjM5LTAuMzktMS4wMywwLTEuNDJjMC4zOS0wLjM5LDEuMDMtMC4zOSwxLjQyLDBMMjUsMjMuNThsNy4yOS03LjI5CWMwLjM5LTAuMzksMS4wMy0wLjM5LDEuNDIsMGMwLjM5LDAuMzksMC4zOSwxLjAzLDAsMS40MkwyNi40MiwyNUwzMy43MSwzMi4yOXoiLz48L3N2Zz4=) center center no-repeat;
background-size: 80%;
}
</style>

View File

@@ -0,0 +1,67 @@
<template>
<ModalTransition :show="show">
<div class="agreement">
<div class="agreement-wrapper">
<div class="agreement-content" v-html="agreeHtml"></div>
</div>
<div class="agreement-close" @click="$emit('close')"></div>
</div>
</ModalTransition>
</template>
<script setup>
import { ref } from "vue"
import ModalTransition from "./ModalTransition.vue";
const show = ref(false)
const agreeHtml = ref("")
const loading = weui.loading("加载中……")
fetch(`./agreement.html`, {
method: "GET",
headers: new Headers({
"Content-Type": "text/html"
})
}).then(res => res.text())
.catch(error => {
loading.hide()
console.log(error)
})
.then(res => {
loading.hide()
agreeHtml.value = res
show.value = true
})
</script>
<style scoped>
.agreement {
background-color: #fff;
border-radius: 2vw 2vw 0 0;
}
.agreement-wrapper {
width: 100%;
height: 90vh;
overflow: auto;
}
.agreement-content {
padding: 6vw;
}
.agreement-close {
z-index: 1;
position: absolute;
top: 25vw;
right: 3vw;
border-radius: 50%;
width: 8.425926vw;
height: 8.703704vw;
background-repeat: no-repeat;
background-position: center center;
background-color: rgba(255,255,255,.5);
background-size: 80% auto;
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA3MiA3MiIgd2lkdGg9IjEyOHB4IiBoZWlnaHQ9IjEyOHB4Ij48cGF0aCBkPSJNIDE5IDE1IEMgMTcuOTc3IDE1IDE2Ljk1MTg3NSAxNS4zOTA4NzUgMTYuMTcxODc1IDE2LjE3MTg3NSBDIDE0LjYwOTg3NSAxNy43MzM4NzUgMTQuNjA5ODc1IDIwLjI2NjEyNSAxNi4xNzE4NzUgMjEuODI4MTI1IEwgMzAuMzQzNzUgMzYgTCAxNi4xNzE4NzUgNTAuMTcxODc1IEMgMTQuNjA5ODc1IDUxLjczMzg3NSAxNC42MDk4NzUgNTQuMjY2MTI1IDE2LjE3MTg3NSA1NS44MjgxMjUgQyAxNi45NTE4NzUgNTYuNjA4MTI1IDE3Ljk3NyA1NyAxOSA1NyBDIDIwLjAyMyA1NyAyMS4wNDgxMjUgNTYuNjA5MTI1IDIxLjgyODEyNSA1NS44MjgxMjUgTCAzNiA0MS42NTYyNSBMIDUwLjE3MTg3NSA1NS44MjgxMjUgQyA1MS43MzE4NzUgNTcuMzkwMTI1IDU0LjI2NzEyNSA1Ny4zOTAxMjUgNTUuODI4MTI1IDU1LjgyODEyNSBDIDU3LjM5MTEyNSA1NC4yNjUxMjUgNTcuMzkxMTI1IDUxLjczNDg3NSA1NS44MjgxMjUgNTAuMTcxODc1IEwgNDEuNjU2MjUgMzYgTCA1NS44MjgxMjUgMjEuODI4MTI1IEMgNTcuMzkwMTI1IDIwLjI2NjEyNSA1Ny4zOTAxMjUgMTcuNzMzODc1IDU1LjgyODEyNSAxNi4xNzE4NzUgQyA1NC4yNjgxMjUgMTQuNjEwODc1IDUxLjczMTg3NSAxNC42MDk4NzUgNTAuMTcxODc1IDE2LjE3MTg3NSBMIDM2IDMwLjM0Mzc1IEwgMjEuODI4MTI1IDE2LjE3MTg3NSBDIDIxLjA0ODEyNSAxNS4zOTE4NzUgMjAuMDIzIDE1IDE5IDE1IHoiLz48L3N2Zz4=);
}
</style>

View File

@@ -0,0 +1,489 @@
<script setup>
import { ref, computed, watch, onMounted } from "vue"
import { useRouter, useRoute } from 'vue-router'
import positionMaps from '../static/positionMaps.js'
import imagePositionMaps from '../static/imagePositionMaps.js'
import { RequestImg, Storage } from "../libs/utils"
defineProps({
show: true
})
onMounted(() => {
})
import { ElMessage } from 'element-plus';
import { Plus } from '@element-plus/icons-vue';
const imageUrl1 = ref('');
// 上传前的校验
// const beforeUpload = (file) => {
// const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png';
// if (!isJpgOrPng) {
// ElMessage.error('只能上传JPG或PNG格式的图片!');
// return false; // 返回false阻止上传
// }
// const isLt2M = file.size / 1024 / 1024 < 2;
// if (!isLt2M) {
// ElMessage.error('图片大小不能超过2MB!');
// return false;
// }
// return true; // 返回true继续上传
// };
// 上传成功回调
// const handleSuccess = (response, index) => {
// console.log(index)
// imageUrl1.value = response.url;
// ElMessage.success('上传成功!');
// };
// 上传失败回调
// const handleError = (error) => {
// // console.error('上传失败:', error);
// // ElMessage.error('上传失败,请重试!');
// uploadItems.value[0].imageUrl = "src/assets/images/demo.png";
// uploadItems.value[1].imageUrl = "src/assets/images/demo.png";
// uploadItems.value[2].imageUrl = "src/assets/images/demo.png";
// uploadItems.value[3].imageUrl = "src/assets/images/demo.png";
// uploadItems.value[4].imageUrl = "src/assets/images/demo.png";
// ElMessage.success('上传成功!');
// };
// 根据imageUrl的结尾编号确定需要多少个上传项
const getUploadItemsCount = () => {
if (!imageUrl.value) return 2 // 默认值
const url = imageUrl.value.toString()
if (url.includes('xianxia') && url.endsWith('_3.png')) {
return 3
}
if (url.includes('paidui') && url.endsWith('_5.png')) {
return 3
}
if (url.endsWith('_1.png') || url.endsWith('_2.png') || url.endsWith('_3.png')) {
return 2
} else if (url.endsWith('_4.png')) {
return 3
} else if (url.endsWith('_5.png')) {
return 4
} else if (url.endsWith('_6.png')) {
return 5
}
return 2 // fallback
}
const uploadItems = ref([])
const initializeUploadItems = () => {
const count = getUploadItemsCount()
const items = Array(count).fill(null).map(() => ({
imageUrl: '',
uploadData: {}
}))
uploadItems.value = items
}
const route = useRoute()
const imageUrl = computed(() => route.query.imageUrl)
watch(imageUrl, () => {
initializeUploadItems()
}, { immediate: true })
const uploadRefs = ref([]);
const clearUploadFile = (index) => {
if (uploadRefs.value[index]) {
uploadRefs.value[index].clearFiles();
uploadItems.value[index].imageUrl = '';
}
};
const generateImage = () => {
}
const router = useRouter();
const goBack = () => {
router.back();
};
const currentVersion = computed(() => {
const url = imageUrl.value;
const match = url.match(/(shenxian|xianxia|fugu|xinzhongshi|luying|paidui)_(\d)/);
if (!match) return positionMaps.shenxian[1];
const [_, category, version] = match;
return positionMaps[category]?.[version] || positionMaps.shenxian[1];
});
const buttonPosition = (index) => {
const versionData = currentVersion.value;
if (!versionData || !versionData.positions[index - 1]) {
return {};
}
const pos = versionData.positions[index - 1];
return {
top : pos.top,
left : pos.left,
"--item-width": pos.width
};
};
const buttonUploadedPosition = (index) => {
const versionData = currentVersion.value;
if (!versionData?.positions?.[index - 1]) {
return {};
}
const pos = versionData.positions[index - 1];
const url = imageUrl.value;
const match = url.match(/(shenxian|xianxia|fugu|xinzhongshi|luying|paidui)_(\d)/);
if (!match) {
return {
top: pos.top,
left: pos.left,
"--item-width": pos.width
};
}
const [_, category, versionStr] = match;
const version = parseInt(versionStr);
const originalTop = parseFloat(pos.top);
if (isNaN(originalTop)) {
return {
top: pos.top,
left: pos.left,
"--item-width": pos.width
};
}
const ADJUSTMENT_CONFIG = {
shenxian: {
1:8, 2:6, 3:6, 4:5, 5:5, 6:4
},
xianxia: {
1:8, 2:6, 3:6, 4:5, 5:5, 6:4
},
fugu: {
1:6, 2:7, 3:7, 4:5, 5:5, 6:4
},
xinzhongshi: {
1:6, 2:7, 3:4, 4:5, 5:3, 6:4
},
luying: {
1:6, 2:7, 3:4, 4:5, 5:3, 6:4
},
paidui:{
1:6, 2:7, 3:6, 4:5, 5:6, 6:4
}
};
let adjustedTop = originalTop;
if (ADJUSTMENT_CONFIG[category]?.[version] !== undefined) {
adjustedTop -= ADJUSTMENT_CONFIG[category][version];
}
return {
top:`${adjustedTop}%`,
left :pos.left ,
"--item-width" :pos.width
};
};
const currentImgVersion = computed(() => {
const url = imageUrl.value;
const match = url.match(/(shenxian|xianxia|fugu|xinzhongshi|luying|paidui)_(\d)/);
if (!match) return imagePositionMaps.shenxian[1];
const [_, category, version] = match;
return imagePositionMaps[category]?.[version] || imagePositionMaps.shenxian[1];
});
const imagePosition = (index) => {
const versionData = currentImgVersion.value;
if (!versionData || !versionData.positions[index - 1]) {
return {};
}
const pos = versionData.positions[index - 1];
return {
top : pos.top,
left : pos.left,
width: pos.width
};
};
// 自定义上传方法
const customUpload = async (options) => {
const { file, data, onProgress, onSuccess, onError } = options
try {
// FormData对象用于构建表单数据
const formData = new FormData()
formData.append('type', 'ali-face')
formData.append('image', file)
const config = {
headers: {
'Content-Type': 'multipart/form-data'
},
onUploadProgress: (progressEvent) => {
if (progressEvent.lengthComputable) {
const percent = Math.round((progressEvent.loaded / progressEvent.total) * 100)
onProgress({ percent }) // 更新上传进度
}
}
}
// 使用你封装的Request方法调用API
const result = await RequestImg('upload/image', formData)
// API调用成功处理
if (result.code === 'success') { // code字段需要根据你的API实际返回调整
onSuccess(result.data) // result.data包含服务器返回的数据
} else {
onError(new Error(result.message || '上传失败'))
}
return result
} catch (error) {
onError(error)
throw error
}
}
</script>
<template>
<div :show="show">
<div class="home-wrapper" style="z-index: 9;">
<div class="scene-item item-1">
<img src="../assets/images/back-btn.png" @click="goBack" alt="后退">
</div>
<div class="scene-item img-from-template scene-item-img" style="transition: none !important;">
<img :src="imageUrl" alt="模板图片">
</div>
<div class="scene-item item-2" @click="generateImage">
<img src="../assets/images/generate-btn.png" alt="开始合成">
</div>
<div class="upload-container">
<div v-for="(item, index) in uploadItems" :key="index" class="upload-item-wrapper">
<el-upload
:ref="(el) => (uploadRefs[index] = el)"
:http-request="customUpload"
:show-file-list="false"
:before-upload="beforeUpload"
:on-success="(res) => handleSuccess(res, index)"
:on-error="handleError"
:data="item.uploadData"
accept="image/*"
>
<el-button class="upload-img-wrapper upload-btn" :style="buttonPosition(index + 1)">
<div class="scene-item item scene-item-img" :class="{ uploaded: item.imageUrl }"
:style="{ width: buttonPosition(index + 1)['--item-width'] }">
<img
v-if="!item.imageUrl"
src="../assets/images/upload-img.png"
alt="上传图片"
>
<div v-if="!item.imageUrl"></div>
</div>
</el-button>
</el-upload>
<!-- 图片预览 -->
<div v-if="item.imageUrl" class="preview-container">
<img
:src="item.imageUrl"
alt="预览图"
class="preview-image upload-btn"
:style="imagePosition(index + 1)"
/>
<button @click.stop.prevent="clearUploadFile(index)" :style="buttonUploadedPosition(index + 1)" style="position: absolute;">
<div :style="{ width: buttonUploadedPosition(index + 1)['--item-width'] }" >
<img src="../assets/images/img-uploaded.png" class="delete-btn upload-img-wrapper" alt="删除图片">
</div>
</button>
</div>
<template v-else>
<Plus />
</template>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.scene-item-img {
transition: none !important;
}
.preview-image {
z-index: 999;
}
.delete-btn {
position: relative;
width: 100%;
z-index: 999;
}
.uploaded {
margin-top: -18px;
}
.upload-btn {
position: absolute;
}
.remove-img-1 {
top: 31.6%;
left: 24%;
position: absolute;
}
.remove-img-2 {
top: 30.4%;
left: 44.2%;
position: absolute;
}
.remove-img-3 {
top: 39.4%;
left: 57.2%;
position: absolute;
}
.remove-img-4 {
top: 34.4%;
left: 72.2%;
position: absolute;
}
.remove-img-5 {
top: 48.4%;
left: 37.2%;
position: absolute;
}
.upload-img-wrapper.el-button {
background: transparent !important;
background-color: transparent !important;
border: none !important;
border-color: transparent !important;
padding: 0 !important;
margin: 0 !important;
color: inherit !important;
box-shadow: none !important;
text-shadow: none !important;
cursor: pointer;
outline: none !important;
border-radius: 0 !important;
height: auto !important;
line-height: inherit !important;
width: auto !important;
min-width: auto !important;
display: inline-block !important;
justify-content: inherit !important;
align-items: inherit !important;
transition: none !important;
}
.upload-img-wrapper.el-button:hover,
.upload-img-wrapper.el-button:focus,
.upload-img-wrapper.el-button:active {
background: transparent !important;
background-color: transparent !important;
border: none !important;
border-color: transparent !important;
box-shadow: none !important;
color: inherit !important;
outline: none !important;
}
.home-wrapper {
width: 100%;
height: 92vh;
background-image: url('src/assets/images/generate-img-bg.png');
background-size: cover;
background-repeat: no-repeat;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
position: relative;
min-height: -webkit-fill-available;
}
.img-from-template {
width: 322px;
height: 436px;
margin-top: -28px;
border-radius: 16px !important;
margin-left: 2px;
}
.scene-item {
position: absolute;
z-index: 2;
cursor: pointer;
border-radius: 8px;
transition: all 0.4s ease;
overflow: hidden;
border: 3px solid transparent;
animation: float 4s ease-in-out infinite;
}
.scene-item:hover {
transform: scale(1.05);
z-index: 10;
}
.scene-item img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.item-1 {
width: 20px;
top: 6.7%;
left: 5%;
animation-delay: 0s;
}
.item-2 {
width: 240px;
bottom: 28px;
animation-delay: 0s;
}
.item {
width: 48px;
}
.item-4 {
width: 48px;
top: 30.6%;
left: 44.5%;
}
.item-5 {
width: 48px;
top: 34%;
left: 72%;
}
.item-6 {
width: 48px;
top: 49.4%;
left: 36.6%;
}
.item-7 {
width: 48px;
top: 40%;
left: 57.5%;
}
</style>

View File

@@ -0,0 +1,85 @@
<script setup>
import { ref, computed, watch, onMounted } from "vue"
import { useRouter } from 'vue-router'
defineProps({
show: true
})
onMounted(() => {
})
const router = useRouter();
const goToGenerateImgPage = () => {
router.push({
name: 'generateImg'
})
}
</script>
<template>
<div :show="show">
<div class="home-wrapper" style="z-index: 9;">
<div class="scene-item item-1" @click="closeTodoList">
<img src="../assets/images/close-btn.png" alt="关闭按钮">
</div>
<div class="scene-item item-2" @click="goToGenerateImgPage">
<img src="../assets/images/confirm-btn.png" alt="我知道了">
</div>
</div>
</div>
</template>
<style scoped>
.home-wrapper {
width: 100%;
height: 92vh;
background-image: url('src/assets/images/generate-img-confirm.png');
background-size: cover;
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;
z-index: 2;
cursor: pointer;
border-radius: 8px;
transition: all 0.4s ease;
overflow: hidden;
border: 3px solid transparent;
animation: float 4s ease-in-out infinite;
}
.scene-item:hover {
transform: scale(1.05);
z-index: 10;
}
.scene-item img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.item-1 {
width: 40px;
top: 15%;
right: 4%;
animation-delay: 0s;
}
.item-2 {
width: 246px;
bottom: 28px;
animation-delay: 0s;
}
</style>

View File

@@ -0,0 +1,105 @@
<script setup>
import { ref, computed, watch, onMounted } from "vue"
import { useRouter } from 'vue-router'
defineProps({
show: true
})
onMounted(() => {
})
const router = useRouter();
const navigateTodoList = () => {
router.push({
name: 'home'
})
}
</script>
<template>
<div :show="show">
<div class="home-wrapper" style="z-index: 9;">
<div class="scene-item item-1">
<img src="../assets/images/back-btn.png" @click="navigateTodoList" alt="后退">
</div>
</div>
</div>
</template>
<style scoped>
.home-wrapper {
width: 100%;
height: 92vh;
background-image: url('src/assets/images/generate-loading.png');
background-size: cover;
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;
z-index: 2;
cursor: pointer;
border-radius: 8px;
transition: all 0.4s ease;
overflow: hidden;
border: 3px solid transparent;
animation: float 4s ease-in-out infinite;
}
.scene-item:hover {
transform: scale(1.05);
z-index: 10;
}
.scene-item img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.item-1 {
width: 20px;
top: 6.7%;
left: 5%;
animation-delay: 0s;
}
.item-2 {
width: 240px;
bottom: 28px;
animation-delay: 0s;
}
.item {
width: 48px;
}
.item-4 {
width: 48px;
top: 30.6%;
left: 44.5%;
}
.item-5 {
width: 48px;
top: 34%;
left: 72%;
}
.item-6 {
width: 48px;
top: 49.4%;
left: 36.6%;
}
.item-7 {
width: 48px;
top: 40%;
left: 57.5%;
}
</style>

242
src/components/HomePage.vue Normal file
View File

@@ -0,0 +1,242 @@
<script setup>
import { ref, computed, watch, onMounted } from "vue"
import { useRouter } from 'vue-router'
import { Request, Storage } from "../libs/utils"
import faceFamily from "../assets/audio/faceFamily.mp3"
defineProps({
show: true
})
const isMusicOn = ref(false);
const audioElement = ref(null);
onMounted(() => {
// 创建音频
audioElement.value = new Audio(faceFamily);
// 尝试自动播放
tryAutoPlay();
})
// 尝试自动播放
const tryAutoPlay = () => {
// 先加载音频
audioElement.value.load();
// 尝试播放
const playPromise = audioElement.value.play();
if (playPromise !== undefined) {
playPromise.then(() => {
// 自动播放成功
isMusicOn.value = true;
console.log("自动播放成功");
})
.catch(error => {
// 自动播放被阻止
console.log("自动播放被阻止,需要用户交互:", error);
isMusicOn.value = false;
audioElement.value.pause();
});
}
};
// 播放/暂停切换
const toggleMusicState = () => {
isMusicOn.value = !isMusicOn.value;
if (!isMusicOn.value) {
audioElement.value.pause();
} else {
audioElement.value.play().catch(error => {
console.log("播放失败:", error);
});
}
};
const router = useRouter();
const navigateSelectTemplatePage = () => {
router.push({
name: 'selectTemplateV2'
})
}
</script>
<template>
<div :show="show">
<div class="home-wrapper">
<div class="scene-item item-1">
<img src="../assets/images/lottery.png" alt="抽奖">
<div class="lottery-main">
<p class="lottery-value">3</p>
</div>
</div>
<div class="scene-item item-2" @click="navigateSelectTemplatePage">
<img src="../assets/images/join.png" alt="立即参与">
<div class="join-main">
<p class="join-value">2</p>
</div>
</div>
<div class="scene-item item-3" @click="navigateTodoList">
<img src="../assets/images/task.png" alt="任务">
</div>
<div @click="toggleMusicState">
<div v-if="isMusicOn" key="on" class="scene-item item-4">
<img src="../assets/images/music-on.png" alt="音乐开">
</div>
<div v-else key="off" class="scene-item item-5">
<img src="../assets/images/music-off.png" alt="音乐关">
</div>
</div>
<div class="scene-item item-6">
<img src="../assets/images/rule.png" alt="规则">
</div>
<div class="scene-item item-7">
<img src="../assets/images/award.png" alt="奖励">
</div>
<div class="scene-item item-8">
<img src="../assets/images/my-photo.png" alt="我的照片">
</div>
<div class="scene-item item-9">
<img src="../assets/images/photos.png" alt="照片广场">
</div>
</div>
</div>
</template>
<style scoped>
.home-wrapper {
width: 100%;
height: 92vh;
background-image: url('../assets/images/home-bg.png');
background-size: cover;
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;
z-index: 2;
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);
z-index: 10;
}
.scene-item img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.item-1 {
width: 90px;
bottom: 0;
left: 0;
animation-delay: 0s;
}
.lottery-main {
display: flex;
justify-content: center;
align-items: center;
width: 18px;
height: 16px;
position: absolute;
top: 7px;
right: 28px;
color: #fff;
}
.lottery-main .lottery-value {
font-size: 10px;
}
.item-2 {
width: 200px;
bottom: 0;
animation-delay: 0s;
}
.join-main {
position: absolute;
top: 16px;
right: 8px;
width: 30px;
height: 30px;
line-height: 30px;
text-align: left;
}
.join-main .join-value {
margin: 0;
color: white;
text-stroke: 4px #ff0000;
-webkit-text-stroke: 1px #ff0000;
font-size: 24px;
font-weight: 900;
}
.item-3 {
width: 90px;
bottom: 0;
right: 0;
animation-delay: 0s;
}
.item-4 {
width: 46px;
top: 1.5%;
right: 1.5%;
animation-delay: 0s;
}
.item-5 {
width: 46px;
top: 1.5%;
right: 1.5%;
animation-delay: 0s;
}
.item-6 {
width: 62px;
top: 8%;
right: 0;
animation-delay: 0s;
}
.item-7 {
width: 62px;
top: 13.5%;
right: 0;
animation-delay: 0s;
}
.item-8 {
width: 50px;
bottom: 32%;
right: 1%;
animation-delay: 0s;
}
.item-9 {
width: 50px;
bottom: 23%;
right: 1%;
animation-delay: 0s;
}
@keyframes loginloading {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>

282
src/components/Login.vue Normal file
View File

@@ -0,0 +1,282 @@
<script setup>
import { ref, computed, watch } from "vue"
import { Request, Storage } from "../libs/utils"
import ModalTransition from "./ModalTransition.vue"
import Agreement from "./Agreement.vue"
defineProps({
show: false
})
const emit = defineEmits(['loginSuccess'])
const phone = ref('')
const code = ref('')
const timedown = ref(60)
const codeText = ref('获取验证码')
const selected = ref(false)
const agreementShow = ref(false)
const subloading = ref(false)
const codeStyleDisable = ref(false)
watch(phone, (newPhone) => {
phone.value = newPhone.replace(/\D/g, '').slice(0, 11).replace(/(\d{3})(\d{4})(\d{4})/, '$1 $2 $3')
})
const codelock = computed(() => { return !/^\d{11}$/.test(phone.value.replace(/\s+/g, '')) })
const selectChange = () => { selected.value = !selected.value }
const loginBtnClass = computed(() => ({
disable: codelock.value || code.value === '' || !selected.value, subloading: subloading.value
}))
const checkData = () => {
if (codelock.value) {
weui.alert("请输入正确的手机号码")
return false
} else if (code.value === '') {
weui.alert("请输入验证码")
return false
} else if (!selected.value) {
weui.alert("请阅读并同意协议")
return false
} else if (subloading.value) {
return false
} else {
return true
}
}
const countDown = () => {
const timer = setTimeout(() => {
if (timedown.value <= 0) {
timedown.value = 60
codeText.value = '获取验证码'
codeStyleDisable.value = false
} else {
timedown.value = timedown.value - 1
codeText.value = `${timedown.value}秒后重新获取`
countDown()
}
clearTimeout(timer)
}, 1000)
}
const handleCode = async () => {
if (codelock.value) {
weui.alert("请输入正确的手机号码")
return
}
if (codeStyleDisable.value) {
return
}
codeStyleDisable.value = true
const result = await Request("sms/sendCode", { phone: phone.value.replace(/\s+/g, '') })
if (result.res.status === 200) {
timedown.value = 60
codeText.value = `${timedown.value}秒后重新获取`
countDown()
} else {
codeStyleDisable.value = false
}
}
const emitAgreementClose = () => {
agreementShow.value = false
}
const loginSubmit = async () => {
if (!checkData()) {
return
}
subloading.value = true
const result = await Request("sms/authPhone", { code: code.value, phone: phone.value.replace(/\s+/g, '') })
if (result.res.status === 200) {
const userData = Object.assign({}, Storage.get("userinfos"), { ...result.json })
Storage.set("userinfos", userData)
emit("loginSuccess", userData)
} else {
subloading.value = false
}
}
</script>
<template>
<ModalTransition class="login" :show="show">
<div class="login-modal-content">
<div class="login-wrapper">
<div class="login-title">泸州老窖会员登录</div>
<div class="login-content">
<div class="login-item">
<input type="tel" class="i-phone" v-model="phone" placeholder="请输入手机号">
</div>
<div class="login-item login-code">
<input type="tel" class="i-code" v-model="code" placeholder="请输入验证码" />
<div class="login-code-send" :class="{ lock: codelock, disable: codeStyleDisable }" @click="handleCode">
{{
codeText }}</div>
</div>
<div class="login-agreement">
<div class="login-agreement-select" :class="{ selected: selected }" @click="selectChange"></div>
<div class="login-agreement-text">我已查看并同意<span class="login-agreement-link"
@click="() => agreementShow = true">泸州老窖会员中心隐私协议</span></div>
</div>
</div>
<div class="btn-login" :class="loginBtnClass" @click="loginSubmit">点击登录</div>
</div>
</div>
</ModalTransition>
<Agreement v-if="agreementShow" @close="emitAgreementClose"></Agreement>
</template>
<style scoped>
.login-modal-content {
width: 100%;
background-color: #f2f3f8;
border-radius: 2vw 2vw 0 0;
animation: transitionIn ease 0.3s forwards;
position: relative;
}
.login-wrapper {
width: calc(100% - 8vw);
margin-left: 4vw;
padding-bottom: 14vw;
}
.login-item {
display: flex;
}
.login-title {
font-size: 3.703704vw;
font-weight: 700;
padding: 4vw 0;
}
.login-item {
margin-top: 3vw;
position: relative;
}
.login-content input {
border-radius: 1vw;
background-color: #fff;
font-size: 5.555556vw;
padding: 3vw;
font-weight: 700;
color: #000;
}
.login-content input::placeholder {
color: #9e9e9e;
font-size: 3.703704vw;
line-height: 1;
transform: translateY(-.5vw);
}
.login-code-send {
position: absolute;
right: 0;
top: 0;
height: 100%;
display: flex;
align-items: center;
padding: 0 3vw;
font-size: 3.703704vw;
line-height: 1;
}
.login-code-send.lock,
.login-code-send.disable {
color: #9e9e9e;
}
.login-agreement {
display: flex;
flex-direction: row;
font-size: 2.777778vw;
margin-top: 4vw;
color: #9e9e9e;
align-items: center;
}
.login-agreement-select {
width: 8vw;
height: 8vw;
position: relative;
}
.login-agreement-select::after {
content: "";
position: absolute;
width: 4vw;
height: 4vw;
border-radius: 50%;
border: 1px solid #000;
top: 50%;
transform: translate3d(0, -50%, 0);
}
.login-agreement-select::before {
content: "";
position: absolute;
width: 4vw;
height: 4vw;
top: 50%;
transform: translate3d(0, -50%, 0) scale(0);
background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciICB2aWV3Qm94PSIwIDAgNTAgNTAiIHdpZHRoPSI1MHB4IiBoZWlnaHQ9IjUwcHgiPjxwYXRoIGQ9Ik0gNDEuOTM3NSA4LjYyNSBDIDQxLjI3MzQzOCA4LjY0ODQzOCA0MC42NjQwNjMgOSA0MC4zMTI1IDkuNTYyNSBMIDIxLjUgMzguMzQzNzUgTCA5LjMxMjUgMjcuODEyNSBDIDguNzg5MDYzIDI3LjI2OTUzMSA4LjAwMzkwNiAyNy4wNjY0MDYgNy4yODEyNSAyNy4yOTI5NjkgQyA2LjU2MjUgMjcuNTE1NjI1IDYuMDI3MzQ0IDI4LjEyNSA1LjkwMjM0NCAyOC44NjcxODggQyA1Ljc3NzM0NCAyOS42MTMyODEgNi4wNzgxMjUgMzAuMzYzMjgxIDYuNjg3NSAzMC44MTI1IEwgMjAuNjI1IDQyLjg3NSBDIDIxLjA2MjUgNDMuMjQ2MDk0IDIxLjY0MDYyNSA0My40MTAxNTYgMjIuMjA3MDMxIDQzLjMyODEyNSBDIDIyLjc3NzM0NCA0My4yNDIxODggMjMuMjgxMjUgNDIuOTE3OTY5IDIzLjU5Mzc1IDQyLjQzNzUgTCA0My42ODc1IDExLjc1IEMgNDQuMTE3MTg4IDExLjEyMTA5NCA0NC4xNTIzNDQgMTAuMzA4NTk0IDQzLjc4MTI1IDkuNjQ0NTMxIEMgNDMuNDEwMTU2IDguOTg0Mzc1IDQyLjY5NTMxMyA4LjU4OTg0NCA0MS45Mzc1IDguNjI1IFoiLz48L3N2Zz4=");
background-size: 80%;
background-position: 60% center;
transition: transform .3s;
}
.login-agreement-select.selected::before {
transform: translate3d(0, -50%, 0) scale(1);
transition: transform .3s ease-in;
}
.login-agreement-link {
color: #000;
border-bottom: 1px solid #000;
}
.btn-login {
text-align: center;
border-radius: 1vw;
background-color: #70b2e2;
margin-top: 4vw;
padding: 3vw;
color: #fff;
position: relative;
}
.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>

321
src/components/Lottery.vue Normal file
View File

@@ -0,0 +1,321 @@
<template>
<ModalTransition class="lottery" :show="show" name="opacity" position="center">
<div class="lottery-wrapper">
<div class="laohuji">
<div class="laohuji-temp" ref="tempRef"></div>
<div class="laohuji-bottom">
<div class="laohuji-list">
<div class="laohuji-item a" ref="itemaRef"></div>
<div class="laohuji-item b" ref="itembRef"></div>
<div class="laohuji-item c" ref="itemcRef"></div>
</div>
</div>
<div class="btn-kaixin" ref="btnRef" @click="handleGetLottery"></div>
<div class="laohuji-kapian" ref="kapianRef">
<div class="laohuji-kapian-bg" ref="kapianBgRef">
<div class="laohuji-kapian-cover" :class="kapianCover"></div>
</div>
</div>
<div class="laohuji-top"></div>
</div>
</div>
</ModalTransition>
</template>
<script setup>
import { gsap } from "gsap";
import Phaser from "phaser"
import { ref, watch, onMounted, onUnmounted } from "vue"
import ModalTransition from "./ModalTransition.vue"
import confetti from "canvas-confetti";
import { Howl } from 'howler';
import { Request } from "../libs/utils"
import lotterySoundUrl from '../assets/audio/lottery.mp3'
import laohujiSoundUrl from '../assets/audio/laohuji.mp3'
import { globalStore } from "@/globalstore";
const laohujiSound = new Howl({
src: [laohujiSoundUrl]
});
const lotterySoundSuccess = new Howl({
src: [lotterySoundUrl]
});
gsap.registerPlugin()
const props = defineProps({
show: false,
})
const emit = defineEmits(['close', 'address'])
const kapianRef = ref(null)
const tempRef = ref(null)
const btnRef = ref(null)
const itemaRef = ref(null)
const itembRef = ref(null)
const itemcRef = ref(null)
const kapianCover = ref('')
const kapianBgRef = ref(null)
const PRIZE_LIST = ['TJGJ','XINCHUN_WEIZUN', 'TEQU_JL_52_60Y_100ML_2', 'LZLJ_TEQU_LZH_52_100ML_2', 'HEIGAI_42_GPJ_500ML', 'DZCZ', 'DZSCZ', '66_POINTS', 'NO']
// 滚动奖品图片的高度(所有奖品)
const GRID = 165.833333
let PRIZEDATA = null
let interval = null
let gsapCtx = null
onMounted(() => {
initAnimateStyle()
})
const initAnimateStyle = () => {
interval && clearInterval(interval)
interval = null
const list = [itemaRef.value, itembRef.value, itemcRef.value]
const randomHeight = [Phaser.Math.Between(3, 8), Phaser.Math.Between(3, 8), Phaser.Math.Between(3, 8)]
gsap.set(kapianBgRef.value, { y: '-72vw' })
gsap.set(btnRef.value, { scale: 0, display: 'none' })
gsap.set(tempRef.value, { height: '63.518519vw' })
list.forEach((v, idx) => {
gsap.set(v, { y: `-${randomHeight[idx] * GRID - GRID / PRIZE_LIST.length}vw`, height: `${randomHeight[idx] * GRID}vw` })
})
}
onMounted(() => {
gsap.set(btnRef.value, { display: "none", scale: 0 })
})
watch(() => props.show, async (newVal) => {
if (!newVal) {
return
}
const lottteryResult = await Request("lottery/draw", { pool: 'game', consume_type: 'points' })
// const lottteryResult = {
// res: { status: 200 },
// json: {
// code: "HEIGAI_42_GPJ_500ML",
// prize_code: 'HEIGAI_42_GPJ_500ML'
// }
// }
if (lottteryResult.res.status !== 200) {
emit('close')
return
} else {
globalStore.consumeBingyin(globalStore.CONSUME_POINT_1_PER_DRAW)
PRIZEDATA = lottteryResult.json
// 未中奖的情况
if (lottteryResult.json.code === 204) {
PRIZEDATA = { prize_code: 'NO', coupon_type: 'NO' }
}
kapianCover.value = PRIZEDATA.prize_code === '66_POINTS' ? 'POINTS' : PRIZEDATA.prize_code
}
gsapCtx = gsap.context(() => {
initAnimateStyle()
const prizeIndex = PRIZE_LIST.findIndex(v => v === PRIZEDATA.prize_code)
const durationArr = [Phaser.Math.Between(3, 9), Phaser.Math.Between(3, 9), Phaser.Math.Between(3, 9)]
gsap.to([itemaRef.value, itembRef.value, itemcRef.value], {
y: `-${GRID / PRIZE_LIST.length * prizeIndex}vw`,
ease: "expo.out",
duration: (idx) => {
return durationArr[idx]
},
onStart: () => {
laohujiSound.play()
laohujiSound.fade(1, 0, Math.max(...durationArr)*1000)
},
delay: .4,
onComplete: () => {
lotterySoundSuccess.play()
var duration = 4 * 1000;
var animationEnd = Date.now() + duration;
var defaults = { startVelocity: 30, spread: 360, ticks: 60, zIndex: 0 };
function randomInRange (min, max) {
return Math.random() * (max - min) + min;
}
interval = setInterval(function () {
var timeLeft = animationEnd - Date.now();
if (timeLeft <= 0) {
return clearInterval(interval);
}
var particleCount = 50 * (timeLeft / duration);
confetti({ ...defaults, particleCount, origin: { x: randomInRange(0.1, 0.3), y: Math.random() - 0.2 } });
confetti({ ...defaults, particleCount, origin: { x: randomInRange(0.7, 0.9), y: Math.random() - 0.2 } });
}, 250);
gsap.to(kapianBgRef.value, { y: '0', duration: 4, ease: "sine.inOut", })
gsap.to(tempRef.value, {
height: '+=90vw', duration: 4, ease: "sine.inOut", onComplete: () => {
gsap.set(btnRef.value, { display: "block" })
gsap.to(btnRef.value, { scale: 1, ease: "sine.inOut", duration: .3 })
}
})
}
})
})
})
const handleGetLottery = () => {
emit('close', { coupon_type: PRIZEDATA.coupon_type })
initAnimateStyle()
if (PRIZEDATA.coupon_type === 'scene') {
emit('address', PRIZEDATA.id)
}
}
onUnmounted(() => {
gsapCtx && gsapCtx.revert()
})
</script>
<style scoped>
.laohuji {
position: relative;
margin-top: -20vw;
}
.laohuji-temp {
width: 80.833333vw;
height: 63.518519vw;
}
.laohuji-top {
width: 80.833333vw;
height: 63.518519vw;
position: absolute;
top: 0;
left: 0;
background-image: url("../assets/images/laohuji-top.webp");
background-repeat: no-repeat;
background-size: 100% 100%;
}
.laohuji-bottom {
width: 80.833333vw;
height: 63.518519vw;
position: absolute;
top: 0;
left: 0;
background-image: url("../assets/images/laohuji-bottom.webp");
background-repeat: no-repeat;
background-size: 100% 100%;
}
.laohuji-kapian {
position: absolute;
overflow: hidden;
top: 58vw;
left: 14.4vw;
}
.laohuji-kapian-bg {
width: 51.851852vw;
clip-path: rect(0px 51.851852vw 74vw 0px);
height: 72vw;
background-image: url("../assets/images/laohuji-kapian.webp");
background-position: left bottom;
background-repeat: no-repeat;
background-size: 100% 73.333333vw;
}
.close {
position: absolute;
width: 8.148148vw;
height: 8.055556vw;
right: 5vw;
top: 0;
background-image: url("../assets/images/icon-close.webp");
background-repeat: no-repeat;
background-size: 100%;
}
.btn-kaixin {
width: 40.092593vw;
height: 15.555556vw;
position: absolute;
bottom: 0;
left: 50%;
margin-left: -20vw;
background-image: url("../assets/images/btn-kaixin.webp");
background-repeat: no-repeat;
background-size: 100%;
}
.laohuji-list {
position: absolute;
top: 31.5vw;
left: 14vw;
width: 53.6vw;
height: 18.425926vw;
background-color: #fff;
display: flex;
justify-content: space-between;
overflow: hidden;
}
.laohuji-item {
width: 16.296296vw;
height: 18.425926vw;
background-color: #ccc;
background-image: url("../assets/images/laohuji-item-1.webp");
background-repeat: repeat-y;
background-size: 16.296296vw 165.833333vw;
}
.laohuji-kapian-cover {
position: relative;
left: 3vw;
top: 13vw;
width: 45.555556vw;
height: 55.185185vw;
background-image: url("../assets/images/NO.webp");
background-repeat: no-repeat;
background-size: 100% 100%;
}
.laohuji-kapian-cover.TJGJ {
background-image: url("../assets/images/TJGJ.webp");
}
.laohuji-kapian-cover.XINCHUN_WEIZUN {
background-image: url("../assets/images/XINCHUN_WEIZUN.webp");
}
.laohuji-kapian-cover.TEQU_JL_52_60Y_100ML_2 {
background-image: url("../assets/images/TEQU_JL_52_60Y_100ML_2.webp");
}
.laohuji-kapian-cover.LZLJ_TEQU_LZH_52_100ML_2 {
background-image: url("../assets/images/LZLJ_TEQU_LZH_52_100ML_2.webp");
}
.laohuji-kapian-cover.HEIGAI_42_GPJ_500ML {
background-image: url("../assets/images/HEIGAI_42_GPJ_500ML.webp");
}
.laohuji-kapian-cover.DZCZ {
background-image: url("../assets/images/DZCZ.webp");
}
.laohuji-kapian-cover.DZSCZ {
background-image: url("../assets/images/DZSCZ.webp");
}
.laohuji-kapian-cover.POINTS {
background-image: url("../assets/images/66_POINTS.webp");
}
</style>

View File

@@ -0,0 +1,121 @@
<template>
<Transition :duration="duration" :name="animateName">
<div class="global-modal" :class="`position-${position}`" v-show="show">
<div class="global-modal-mask"></div>
<div class="global-modal-content">
<div class="global-modal-wrapper">
<slot></slot>
</div>
</div>
</div>
</Transition>
</template>
<script setup>
import { ref } from "vue"
const props = defineProps({
name: {
default:"bottom",
type:String
},
position:{
default:"bottom",
type:String
},
duration: {
default: 300,
type: Number
},
show: false
})
const animateName = ref('')
const directionClass = ref('')
switch (props.name) {
case 'top':
animateName.value = 'transTop'
break;
case 'opacity':
animateName.value = 'opacity'
break;
case 'scale':
animateName.value = 'scale'
break;
default:
animateName.value = 'transBottom'
break;
}
</script>
<style>
.transBottom-enter-active .global-modal-wrapper,
.transBottom-leave-active .global-modal-wrapper {
transform: translate3d(0, 0, 0);
transition: transform .3s ease;
}
.transBottom-enter-from .global-modal-wrapper,
.transBottom-leave-to .global-modal-wrapper {
transform: translate3d(0, 100%, 0);
}
.transBottom-enter-active .global-modal-mask,
.transBottom-leave-active .global-modal-mask {
opacity: 1;
transition: opacity .3s ease;
}
.transBottom-enter-from .global-modal-mask,
.transBottom-leave-to .global-modal-mask {
opacity: 0;
}
.opacity-enter-active .global-modal-wrapper,
.opacity-leave-active .global-modal-wrapper {
opacity:1;
transition: opacity .3s ease;
}
.opacity-enter-from .global-modal-wrapper,
.opacity-leave-to .global-modal-wrapper {
opacity:0;
}
.opacity-enter-active .global-modal-mask,
.opacity-leave-active .global-modal-mask {
opacity: 1;
transition: opacity .3s ease;
}
.opacity-enter-from .global-modal-mask,
.opacity-leave-to .global-modal-mask {
opacity: 0;
}
.scale-enter-active .global-modal-wrapper,
.scale-leave-active .global-modal-wrapper {
opacity:1;
transform: scale(1);
transition: all .6s cubic-bezier(.58,.05,.07,1.4);
}
.scale-enter-from .global-modal-wrapper,
.scale-leave-to .global-modal-wrapper {
opacity:0;
transform: scale(.3);
}
.scale-enter-active .global-modal-mask,
.scale-leave-active .global-modal-mask {
opacity: 1;
transition: opacity .3s ease;
}
.scale-enter-from .global-modal-mask,
.scale-leave-to .global-modal-mask {
opacity: 0;
}
</style>

View File

@@ -0,0 +1,186 @@
<template>
<div>
<ModalTransition :show="show">
<div class="prizelist">
<div class="prizelist-wrapper" v-show="prizelist.length > 0">
<div class="prizelist-wrapper-scroll">
<div class="prizelist-item" v-for="item in prizelist" :key="item.id">
<div class="prizelist-cover" :class="'USER_' + item.prize_code"></div>
<div class="prizelist-title">{{ item.prize_name }}</div>
<div class="prizelist-btngroup">
<template v-if="item.coupon_type === 'scene'">
<div class="btn-goto" :class="{ noaddress: item.pushed !== 1 }" v-html="sceneBtnHtml"
@click="handleItemBtn(item.id, $event)">
</div>
</template>
<template v-else="item.coupon_type === 'coupon'">
<div class="btn-goto" v-html="couponBtnHtml"></div>
</template>
</div>
</div>
</div>
</div>
<div class="prizelist-close" @click="$emit('close')"></div>
</div>
</ModalTransition>
</div>
</template>
<script setup>
import { ref } from "vue"
import { isWeixinPlatform, miniJumpToScene, getMiniPageBtnHack } from "../libs/utils"
import ModalTransition from "./ModalTransition.vue";
const props = defineProps({
show: false,
prizelist: {
type: Array,
default: () => []
}
})
const emit = defineEmits(['address','close'])
const sceneBtnHtml = ref('')
const couponBtnHtml = ref('')
sceneBtnHtml.value = getMiniPageBtnHack("/pages/unify/unify?orgId=200282401019674482&targetUrl=%2Fpages%2Fretail%2Forder%2Forder-list%3Ftab%3DAll%26topTab%3D1")
couponBtnHtml.value = getMiniPageBtnHack("/pages/unify/unify?orgId=200282401019674482&targetUrl=%2Fpages%2Fcoupon%2Fcoupons-list")
const handleItemBtn = (id, event) => {
const target = event.currentTarget
if (target.classList.contains("noaddress")) {
emit("address", id)
} else {
if (isWeixinPlatform()) {
miniJumpToScene()
} else {
weui.alert("请前往「泸州老窖会员中心」小程序进行查询")
}
}
}
</script>
<style scoped>
.prizelist {
position: relative;
width: 100vw;
height: 140.740741vw;
background-image: url("../assets/images/prizelist-bg.webp");
background-repeat: no-repeat;
background-size: 100%;
display: flex;
align-items: flex-end;
}
.prizelist-wrapper {
width: 100%;
height: 90vw;
overflow: auto;
}
.prizelist-wrapper-scroll {
display: flex;
flex-direction: column;
}
.prizelist-item {
display: flex;
margin-bottom: 4vw;
margin-left: 2.5vw;
padding-left: 3vw;
width: 95.092593vw;
height: 19.814815vw;
flex-direction: row;
justify-content: space-between;
align-items: center;
background-image: url("../assets/images/prizelist-item-bg.webp");
background-repeat: no-repeat;
background-size: 100% 100%;
}
.prizelist-cover {
width: 15vw;
margin-right: 3vw;
height: 15vw;
background-size: 100% 100%;
background-repeat: no-repeat;
}
.prizelist-title {
font-size: 4.444444vw;
flex: 1;
display: flex;
font-weight: 700;
color: #09431d;
overflow: hidden;
text-overflow: ellipsis;
-webkit-line-clamp: 2;
display: -webkit-box;
-webkit-box-orient: vertical;
}
.prizelist-close {
position: absolute;
width: 8.148148vw;
height: 8.055556vw;
right: 5vw;
top: 0;
background-image: url("../assets/images/icon-close.webp");
background-repeat: no-repeat;
background-size: 100%;
}
.btn-goto {
margin-right: 4vw;
width: 23.981481vw;
height: 9.074074vw;
background-size: 100% 100%;
background-image: url("../assets/images/btn-gotomember.webp");
background-repeat: no-repeat;
background-size: 100%;
}
wx-open-launch-weapp,
.btn-goto.noaddress wx-open-launch-weapp {
display: none;
}
.btn-goto wx-open-launch-weapp {
display: block;
}
.btn-goto.noaddress {
background-image: url("../assets/images/btn-address.webp");
}
.prizelist-cover.USER_TJGJ {
background-image: url("../assets/images/USER_TJGJ.webp");
}
.prizelist-cover.USER_XINCHUN_WEIZUN {
background-image: url("../assets/images/USER_XINCHUN_WEIZUN.webp");
}
.prizelist-cover.USER_TEQU_JL_52_60Y_100ML_2 {
background-image: url("../assets/images/USER_TEQU_JL_52_60Y_100ML_2.webp");
}
.prizelist-cover.USER_LZLJ_TEQU_LZH_52_100ML_2 {
background-image: url("../assets/images/USER_LZLJ_TEQU_LZH_52_100ML_2.webp");
}
.prizelist-cover.USER_HEIGAI_42_GPJ_500ML {
background-image: url("../assets/images/USER_HEIGAI_42_GPJ_500ML.webp");
}
.prizelist-cover.USER_DZCZ {
background-image: url("../assets/images/USER_DZCZ.webp");
}
.prizelist-cover.USER_DZSCZ {
background-image: url("../assets/images/USER_DZSCZ.webp");
}
.prizelist-cover.USER_66_POINTS {
background-image: url("../assets/images/USER_66_POINTS.webp");
}
</style>

58
src/components/Rule.vue Normal file
View File

@@ -0,0 +1,58 @@
<template>
<ModalTransition class="rule" :show="show">
<div class="rule-wrapper">
<div class="rule-content">
<img src="../assets/images/rule-1.webp" alt="">
<img src="../assets/images/rule-2.webp" alt="">
<img src="../assets/images/rule-3.webp" alt="">
<img src="../assets/images/rule-4.webp" alt="">
<img src="../assets/images/rule-5.webp" alt="">
</div>
<div class="close" @click="emit('close')"></div>
</div>
</ModalTransition>
</template>
<script setup>
import ModalTransition from "./ModalTransition.vue"
const props = defineProps({
show: false,
})
const emit = defineEmits(['close'])
</script>
<style scoped>
.rule-wrapper {
position: relative;
width: 100%;
height: 200vw;
background-image: url("../assets/images/rule-bg.webp");
background-repeat: no-repeat;
background-size: 100%;
}
.rule-content{
position: absolute;
top: 50vw;
left: 10vw;
width: 83.981481vw;
height: 150vw;
overflow: auto;
padding-right: 4vw;
padding-bottom: 10vw;
}
.rule-content img {
width: 100%;
height: auto;
display: block;
}
.close {
position: absolute;
width: 8.148148vw;
height: 8.055556vw;
right: 5vw;
top: 10vw;
background-image: url("../assets/images/icon-close.webp");
background-repeat: no-repeat;
background-size: 100%;
}
</style>

View File

@@ -0,0 +1,294 @@
<script setup>
import { ref, computed, watch, onMounted } from "vue"
import { useRouter } from 'vue-router'
import { Request, Storage } from "../libs/utils"
import faceFamily from "../assets/audio/faceFamily.mp3"
defineProps({
show: true
})
const menuData = ref([
{
title: '神仙',
imgSrc: 'src/assets/images/shenxian.png',
children: [
{
title: '2人的图片',
subMenuImg: 'src/assets/images/2.png'
},
{
title: '3人的图片',
subMenuImg: 'src/assets/images/3.png'
},
{
title: '4人的图片',
subMenuImg: 'src/assets/images/4.png'
},
{
title: '5人的图片',
subMenuImg: 'src/assets/images/5.png'
}
]
},
{
title: '仙侠',
imgSrc: 'src/assets/images/xianxia.png',
children: [
{
title: '2人的图片',
subMenuImg: 'src/assets/images/2.png'
},
{
title: '3人的图片',
subMenuImg: 'src/assets/images/3.png'
},
{
title: '4人的图片',
subMenuImg: 'src/assets/images/4.png'
},
{
title: '5人的图片',
subMenuImg: 'src/assets/images/5.png'
}
]
},
{
title: '唐风',
imgSrc: 'src/assets/images/tangfeng.png',
children: [
{
title: '2人的图片',
subMenuImg: 'src/assets/images/2.png'
},
{
title: '3人的图片',
subMenuImg: 'src/assets/images/3.png'
},
{
title: '4人的图片',
subMenuImg: 'src/assets/images/4.png'
},
{
title: '5人的图片',
subMenuImg: 'src/assets/images/5.png'
}
]
},
{
title: '怀旧',
imgSrc: 'src/assets/images/huaijiu.png',
children: [
{
title: '2人的图片',
subMenuImg: 'src/assets/images/2.png'
},
{
title: '3人的图片',
subMenuImg: 'src/assets/images/3.png'
},
{
title: '4人的图片',
subMenuImg: 'src/assets/images/4.png'
},
{
title: '5人的图片',
subMenuImg: 'src/assets/images/5.png'
}
]
},
{
title: '现代',
imgSrc: 'src/assets/images/xiandai.png',
children: [
{
title: '2人的图片',
subMenuImg: 'src/assets/images/2.png'
},
{
title: '3人的图片',
subMenuImg: 'src/assets/images/3.png'
},
{
title: '4人的图片',
subMenuImg: 'src/assets/images/4.png'
},
{
title: '5人的图片',
subMenuImg: 'src/assets/images/5.png'
}
]
}
]);
onMounted(() => {
})
const router = useRouter();
const navigateTodoList = () => {
router.push({
name: 'home'
})
}
const activeMenu = ref({
level1: 0,
level2: 0
});
const selectMenu = (level, index) => {
// 更新选中菜单
activeMenu.value[`level${level}`] = index;
// 重置下级菜单
if (level < 3) {
for (let i = level + 1; i <= 3; i++) {
activeMenu.value[`level${i}`] = 0;
}
}
// 更新内容显示
if (level === 3) {
const contentId = menuData.value[activeMenu.value.level1]
.children[activeMenu.value.level2]
.children[index].contentId;
activeContent.value = contentId;
}
};
const contentPanels = ref([{}]);
const getMenuLevelTitle = (level) => {
const index = activeMenu.value[`level${level}`];
if (index === null || index === undefined) return '-';
if (level === 1) {
return menuData.value[index]?.title || '-';
} else if (level === 2) {
return menuData.value[activeMenu.value.level1]?.children[index]?.title || '-';
}
return '-';
};
// 计算一级菜单位置
const calculateMenuLevel1Left = (index) => {
return (11.6 + index * 17.4).toFixed(1);
};
// 计算二级菜单位置
const calculateMenuLevel2Left = (index) => {
return (22 + index * 16.6).toFixed(1);
};
</script>
<template>
<div :show="show">
<div class="home-wrapper" style="z-index: 9;">
<div class="scene-item item-1">
<img src="../assets/images/back-btn.png" @click="navigateTodoList" alt="后退">
</div>
<!-- 一级菜单 -->
<div class="menu-level-1">
<div
v-for="(item, index) in menuData"
:key="'level1-' + index"
class="menu-item"
:class="{ active: activeMenu.level1 === index }"
@click="selectMenu(1, index)"
>
<img :src="item.imgSrc" :style="{
position: 'absolute',
width: '44px',
top: '17.3%',
left: calculateMenuLevel1Left(index) + '%'
}" />
</div>
</div>
<!-- 二级菜单 -->
<div class="menu-level-2">
<div
v-for="(item, index) in menuData[activeMenu.level1]?.children"
:key="'level2-' + index"
class="menu-item"
:class="{ active: activeMenu.level2 === index }"
@click="selectMenu(2, index)"
>
<img :src="item.subMenuImg" :style="{
position: 'absolute',
width: '30px',
top: '26%',
left: calculateMenuLevel2Left(index) + '%'
}" />
</div>
</div>
<div class="content-area">
<div
v-for="(item, index) in contentPanels"
:key="'panel-' + index"
class="content-panel"
:class="{ active: activeContent === index }"
>
<div class="content-header">
<div class="breadcrumb">
<span>{{ getMenuLevelTitle(1) }}</span>
<span>{{ getMenuLevelTitle(2) }}</span>
</div>
<h2>{{ item.title }}</h2>
</div>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.home-wrapper {
width: 100%;
height: 92vh;
background-image: url('../assets/images/select-tem.png');
background-size: cover;
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;
z-index: 2;
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);
z-index: 10;
}
.scene-item img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.item-1 {
width: 20px;
top: 6.7%;
left: 5%;
animation-delay: 0s;
}
</style>

View File

@@ -0,0 +1,311 @@
<script setup>
import { ref, onMounted } from "vue"
import { useRouter } from 'vue-router'
defineProps({
show: true
})
const menuData = ref([
{
title: '神仙',
imgSrc: 'src/assets/images/generate/shenxian.png',
activeImgSrc: 'src/assets/images/generate/shenxian-selected.png',
children: [
{ imageUrl: 'src/assets/images/generate/template/shenxian/shenxian_1.png' },
{ imageUrl: 'src/assets/images/generate/template/shenxian/shenxian_2.png' },
{ imageUrl: 'src/assets/images/generate/template/shenxian/shenxian_3.png' },
{ imageUrl: 'src/assets/images/generate/template/shenxian/shenxian_4.png' },
{ imageUrl: 'src/assets/images/generate/template/shenxian/shenxian_5.png' },
{ imageUrl: 'src/assets/images/generate/template/shenxian/shenxian_6.png' }
]
},
{
title: '仙侠',
imgSrc: 'src/assets/images/generate/xianxia.png',
activeImgSrc: 'src/assets/images/generate/xianxia-selected.png',
children: [
{ imageUrl: 'src/assets/images/generate/template/xianxia/xianxia_1.png' },
{ imageUrl: 'src/assets/images/generate/template/xianxia/xianxia_2.png' },
{ imageUrl: 'src/assets/images/generate/template/xianxia/xianxia_3.png' },
{ imageUrl: 'src/assets/images/generate/template/xianxia/xianxia_4.png' },
{ imageUrl: 'src/assets/images/generate/template/xianxia/xianxia_5.png' },
{ imageUrl: 'src/assets/images/generate/template/xianxia/xianxia_6.png' }
]
},
{
title: '复古',
imgSrc: 'src/assets/images/generate/fugu.png',
activeImgSrc: 'src/assets/images/generate/fugu-selected.png',
children: [
{ imageUrl: 'src/assets/images/generate/template/fugu/fugu_1.png' },
{ imageUrl: 'src/assets/images/generate/template/fugu/fugu_2.png' },
{ imageUrl: 'src/assets/images/generate/template/fugu/fugu_3.png' },
{ imageUrl: 'src/assets/images/generate/template/fugu/fugu_4.png' },
{ imageUrl: 'src/assets/images/generate/template/fugu/fugu_5.png' },
{ imageUrl: 'src/assets/images/generate/template/fugu/fugu_6.png' }
]
},
{
title: '新中式',
imgSrc: 'src/assets/images/generate/xinzhongshi.png',
activeImgSrc: 'src/assets/images/generate/xinzhongshi-selected.png',
children: [
{ imageUrl: 'src/assets/images/generate/template/xinzhongshi/xinzhongshi_1.png' },
{ imageUrl: 'src/assets/images/generate/template/xinzhongshi/xinzhongshi_2.png' },
{ imageUrl: 'src/assets/images/generate/template/xinzhongshi/xinzhongshi_3.png' },
{ imageUrl: 'src/assets/images/generate/template/xinzhongshi/xinzhongshi_4.png' },
{ imageUrl: 'src/assets/images/generate/template/xinzhongshi/xinzhongshi_5.png' },
{ imageUrl: 'src/assets/images/generate/template/xinzhongshi/xinzhongshi_6.png' }
]
},
{
title: '露营',
imgSrc: 'src/assets/images/generate/luying.png',
activeImgSrc: 'src/assets/images/generate/luying-selected.png',
children: [
{ imageUrl: 'src/assets/images/generate/template/luying/luying_1.png' },
{ imageUrl: 'src/assets/images/generate/template/luying/luying_2.png' },
{ imageUrl: 'src/assets/images/generate/template/luying/luying_3.png' },
{ imageUrl: 'src/assets/images/generate/template/luying/luying_4.png' },
{ imageUrl: 'src/assets/images/generate/template/luying/luying_5.png' },
{ imageUrl: 'src/assets/images/generate/template/luying/luying_6.png' }
]
},
{
title: '派对',
imgSrc: 'src/assets/images/generate/paidui.png',
activeImgSrc: 'src/assets/images/generate/paidui-selected.png',
children: [
{ imageUrl: 'src/assets/images/generate/template/paidui/paidui_1.png' },
{ imageUrl: 'src/assets/images/generate/template/paidui/paidui_2.png' },
{ imageUrl: 'src/assets/images/generate/template/paidui/paidui_3.png' },
{ imageUrl: 'src/assets/images/generate/template/paidui/paidui_4.png' },
{ imageUrl: 'src/assets/images/generate/template/paidui/paidui_5.png' },
{ imageUrl: 'src/assets/images/generate/template/paidui/paidui_6.png' }
]
}
]);
onMounted(() => {
activeMenu.value = { ...activeMenu.value, level1: 0 };
updateDisplayedImages();
})
const router = useRouter();
const navigateTodoList = () => {
router.push({
name: 'home'
})
}
const activeMenu = ref({
level1: 0
});
const selectMenu = (level, index)=> {
if (level === 1) {
activeMenu.value = { ...activeMenu.value, level1: index };
updateDisplayedImages();
}
}
const updateDisplayedImages = ()=> {
if (activeMenu.value.level1 !== null && menuData.value[activeMenu.value.level1]) {
displayedImages.value = menuData.value[activeMenu.value.level1].children;
} else {
displayedImages.value = [];
}
}
const contentPanels = ref([{}]);
const getMenuLevelTitle = (level) => {
const index = activeMenu.value[`level${level}`];
if (index === null || index === undefined) return '-';
if (level === 1) {
return menuData.value[index]?.title || '-';
}
return '-';
};
const getImageStyle = (index, actived)=> {
console.log(actived)
return {
position: 'absolute',
width: actived? index === 3? '68px' : '54px' : index === 3 ? '66px' : '44px',
top: actived? '14.2%' : '15%',
left: calculateMenuLevel1Left(index, actived) + '%'
}
}
// 计算一级菜单位置
const calculateMenuLevel1Left = (index, actived) => {
const multiplier =
index === 1 ? actived? 12.5 : 13.5 :
index === 2 ? actived? 13 : 13.5 :
index === 3 ? 13.5 :
index === 4 ? actived? 14.6 : 14.8 :
index === 5 ? 14.6 :
13.8;
return (8.4 + index * multiplier).toFixed(1);
};
const displayedImages = ref([]);
const goToGenerateImgPage = (item) => {
const hasVisitedBefore = localStorage.getItem('hasVisitedGenerateImg');
if (!hasVisitedBefore) {
localStorage.setItem('hasVisitedGenerateImg', 'true');
router.push({
name: 'generateImgConfirm',
query: { imageUrl: item.imageUrl }
});
} else {
router.push({
name: 'generateImg',
query: { imageUrl: item.imageUrl }
});
}
}
</script>
<template>
<div :show="show">
<div class="home-wrapper" style="z-index: 9;">
<div class="scene-item item-1">
<img src="../assets/images/back-btn.png" @click="navigateTodoList" alt="后退">
</div>
<!-- 一级菜单 -->
<div class="menu-level-1">
<div
v-for="(item, index) in menuData"
:key="'level1-' + index"
class="menu-item"
:class="{ active: activeMenu.level1 === index }"
@click="selectMenu(1, index)"
>
<img
:src="activeMenu.level1 === index ? item.activeImgSrc : item.imgSrc"
:style="getImageStyle(index, activeMenu.level1 === index)"
/>
</div>
</div>
<div class="images-container">
<div
v-for="(item, index) in displayedImages"
:key="index"
@click="goToGenerateImgPage(item)"
class="mask-image mask-background"
:style="{ 'background-image': `url(${item.imageUrl})` }"
>
<img class="border-img" src="../assets/images/generate/border.png" alt="边框">
</div>
</div>
<div class="content-area">
<div
v-for="(item, index) in contentPanels"
:key="'panel-' + index"
class="content-panel"
:class="{ active: activeContent === index }"
>
<div class="content-header">
<div class="breadcrumb">
<span>{{ getMenuLevelTitle(1) }}</span>
<span>{{ getMenuLevelTitle(2) }}</span>
</div>
<h2>{{ item.title }}</h2>
</div>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.images-container {
display: flex;
flex-wrap: wrap;
justify-content: center;
width: 100%;
gap: 14px;
margin-top: 192px;
overflow: hidden;
overflow-y: auto;
}
.mask-background {
-webkit-mask-image: url("../assets/images/generate/mengban.png");
mask-image: url("../assets/images/generate/mengban.png");
-webkit-mask-size: contain;
mask-size: contain;
-webkit-mask-position: center;
mask-position: center;
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
}
.mask-image {
background-size: cover;
background-repeat: no-repeat;
position: relative;
width: 168px;
height: 224px;
}
.border-img {
width: 170px;
height: 231px;
position: absolute;
top: -2px;
left: 0px;
}
.home-wrapper {
width: 100%;
height: 92vh;
background-image: url('../assets/images/generate/select-template-bg.png');
background-size: cover;
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;
z-index: 2;
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);
z-index: 10;
}
.scene-item img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.item-1 {
width: 20px;
top: 6.7%;
left: 5%;
animation-delay: 0s;
}
</style>

312
src/components/TodoList.vue Normal file
View File

@@ -0,0 +1,312 @@
<template>
<div class="home-wrapper" style="z-index: 9;">
<img src="../assets/images/todo-bg.png" alt="任务列表背景图">
<div class="scene-item item-1" @click="closeTodoList">
<img src="../assets/images/close-btn.png" alt="关闭按钮">
</div>
<div class="scene-item item-2" @click="openQiwei($event)">
<img src="../assets/images/add-wx.png" alt="去添加">
</div>
<div class="scene-item item-3" @click="handleScan($event)">
<img src="../assets/images/scan-code.png" alt="去扫码">
</div>
<div class="scene-item item-4" @click="openHaibao($event)">
<img src="../assets/images/share-link.png" alt="去分享">
</div>
</div>
<ModalTransition class="todolist" :show="show">
<div class="todolist-wrapper">
<div class="btn-group">
<div class="btn-share" :class="globalStore.invitees >= globalStore.MAX_INVITE_DAILY && 'has'"
@click="openHaibao($event)"></div>
<div class="btn-qiwei" :class="globalStore.followed_official && 'has'" @click="openQiwei($event)"></div>
<div class="btn-scan" :class="globalStore.cap_scan >= globalStore.MAX_CAP_SCAN && 'has'"
@click="handleScan($event)"></div>
<div class="btn-peifang" :class="globalStore.game_chances_view_recipes >= globalStore.MAX_VIEW_RECIPES_DAILY && 'has'" @click="openPeifang($event)"></div>
</div>
<div class="close" @click="$emit('close')"></div>
</div>
<div class="fullsection" v-show="haibaoShow">
<div class="haibao">
<img :src="haibaoUrl" alt="">
<div class="close" @click="haibaoShow = false"></div>
</div>
</div>
<div class="fullsection" v-show="qiweiShow">
<div class="qiwei">
<img src="../assets/images/qiwei-bg.webp" alt="">
<div class="close" @click="qiweiShow = false"></div>
</div>
</div>
</ModalTransition>
</template>
<script setup>
import { ref } from "vue"
import { globalStore } from "@/globalstore";
import ModalTransition from "./ModalTransition.vue"
import { Storage, generateQR, isWeixin, isMiniPage } from "../libs/utils"
import Haibao from "@/libs/haibao"
import bg from "../assets/images/haibao-bg.webp"
const props = defineProps({
show: true,
})
const emit = defineEmits(['close','open'])
const shareShow = ref(false)
const qiweiShow = ref(false)
const haibaoShow = ref(false)
const haibaoUrl = ref('')
const handleHaibao = async () => {
if (haibaoUrl.value) {
return
}
const loading = weui.loading()
const infos = Storage.get("userinfos")
const haibao = new Haibao(1080, 2160)
const qrcode = await generateQR(`fromid=${infos.invite_code}&org_id=${infos.org_id}`)
haibao.add(bg, 0, 0)
haibao.add(qrcode, 802, 1908)
haibao.generate().then(url => {
haibaoUrl.value = url
loading.hide()
}).catch(err => {
weui.alert("海报生成失败,请重新生成")
})
}
const openQiwei = (e) => {
const target = e.currentTarget
if (target.classList.contains("has")) {
return
}
qiweiShow.value = true
}
const openHaibao = (e) => {
const target = e.currentTarget
if (target.classList.contains("has")) {
return
}
haibaoShow.value = true
handleHaibao()
}
const openPeifang = (e) => {
const target = e.currentTarget
if (target.classList.contains("has")) {
return
}
emit('open',{ type: 'peifang' })
}
const handleScan = (e) => {
const target = e.currentTarget
if (target.classList.contains("has")) {
return
}
if (!(isWeixin() || isMiniPage())) {
weui.alert("请使用微信打开进行扫码")
return
}
wx.scanQRCode({
needResult: 0, // 默认为0扫描结果由微信处理1则直接返回扫描结果
scanType: ["qrCode", "barCode"], // 可以指定扫二维码还是一维码,默认二者都有
fail (err) {
weui.alert(err.errMsg)
}
});
}
</script>
<style scoped>
.home-wrapper {
width: 100%;
position: relative;
min-height: -webkit-fill-available;
}
.home-wrapper img {
width: 100%;
background-size: cover;
background-repeat: no-repeat;
}
.scene-item {
position: absolute;
z-index: 2;
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);
z-index: 10;
}
.scene-item img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.item-1 {
width: 40px;
bottom: 54%;
right: 3%;
animation-delay: 0s;
}
.item-1 {
width: 40px;
bottom: 54%;
right: 3%;
animation-delay: 0s;
}
.item-2 {
width: 110px;
bottom: 36%;
right: 6%;
animation-delay: 0s;
}
.item-3 {
width: 110px;
bottom: 25%;
right: 6%;
animation-delay: 0s;
}
.item-4 {
width: 110px;
bottom: 14%;
right: 6%;
animation-delay: 0s;
}
.qiwei {
position: relative;
width: 71.851852vw;
height: 89.259259vw;
}
.qiwei img {
width: 100%;
height: 100%;
display: block;
}
.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 .close {
right: -10vw;
}
.qiwei .close {
right: -10vw;
}
.haibao img {
width: 100%;
height: 100%;
display: block;
opacity: 0;
}
.close {
position: absolute;
width: 8.148148vw;
height: 8.055556vw;
right: 5vw;
top: 0;
background-image: url("../assets/images/icon-close.webp");
background-repeat: no-repeat;
background-size: 100%;
}
.todolist-wrapper {
position: relative;
width: 100vw;
height: 136.018519vw;
background-image: url("../assets/images/todo-bg.webp");
background-repeat: no-repeat;
background-size: 100%;
}
.btn-group {
position: absolute;
right: 6vw;
top: 46vw;
width: 23.981481vw;
height: 84vw;
display: flex;
flex-direction: column;
justify-content: space-around;
}
.btn-group div {
width: 23.981481vw;
height: 9.074074vw;
background-repeat: no-repeat;
background-size: 100%;
}
.btn-share {
background-image: url("../assets/images/btn-share.webp");
}
.btn-share.has {
background-image: url("../assets/images/btn-max.webp");
}
.btn-qiwei {
background-image: url("../assets/images/btn-qiwei.webp");
}
.btn-qiwei.has {
background-image: url("../assets/images/btn-added.webp");
}
.btn-scan {
background-image: url("../assets/images/btn-scan.webp");
}
.btn-scan.has {
background-image: url("../assets/images/btn-max.webp");
}
.btn-peifang {
background-image: url("../assets/images/btn-look.webp");
}
.btn-peifang.has {
background-image: url("../assets/images/btn-max.webp");
}
</style>