init
This commit is contained in:
255
src/components/Address.vue
Normal file
255
src/components/Address.vue
Normal 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>
|
||||
67
src/components/Agreement.vue
Normal file
67
src/components/Agreement.vue
Normal 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>
|
||||
489
src/components/GenerateImg.vue
Normal file
489
src/components/GenerateImg.vue
Normal 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>
|
||||
85
src/components/GenerateImgConfirm.vue
Normal file
85
src/components/GenerateImgConfirm.vue
Normal 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>
|
||||
105
src/components/GenerateLoading.vue
Normal file
105
src/components/GenerateLoading.vue
Normal 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
242
src/components/HomePage.vue
Normal 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
282
src/components/Login.vue
Normal 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
321
src/components/Lottery.vue
Normal 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>
|
||||
121
src/components/ModalTransition.vue
Normal file
121
src/components/ModalTransition.vue
Normal 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>
|
||||
186
src/components/PrizeList.vue
Normal file
186
src/components/PrizeList.vue
Normal 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
58
src/components/Rule.vue
Normal 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>
|
||||
294
src/components/SelectTemplate.vue
Normal file
294
src/components/SelectTemplate.vue
Normal 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>
|
||||
311
src/components/SelectTemplateV2.vue
Normal file
311
src/components/SelectTemplateV2.vue
Normal 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
312
src/components/TodoList.vue
Normal 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>
|
||||
Reference in New Issue
Block a user