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

29
.gitignore vendored Normal file
View File

@@ -0,0 +1,29 @@
.DS_Store
node_modules
/dist
.next
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
/__MACOSX
/node_modules
/设计图
/src/assets/images

5
README.md Normal file
View File

@@ -0,0 +1,5 @@
# Vue 3 + Vite
This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
Learn more about IDE Support for Vue in the [Vue Docs Scaling up Guide](https://vuejs.org/guide/scaling-up/tooling.html#ide-support).

145
index.html Normal file
View File

@@ -0,0 +1,145 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="UTF-8" />
<meta http-equiv="Cache-control" content="no-cache">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<meta content="telephone=no,email=no" name=format-detection>
<meta name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no, minimal-ui" />
<title>basecode</title>
<script>
const isWeixinPlatform = /MicroMessenger/i.test(window.navigator.userAgent) || window.__wxjs_environment === 'miniprogram'
if (isWeixinPlatform) {
document.querySelector("html").classList.add("weixinPlatform")
}
const isLogin = !!(JSON.parse(localStorage.getItem("userinfos")) && JSON.parse(localStorage.getItem("userinfos")).phone)
window.getParam = (name) => {
if ('URLSearchParams' in window) {
var params = new URLSearchParams(window.location.search)
return params.get(name) ? params.get(name) : null
} else {
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i'),
r = window.location.search.substr(1).match(reg)
if (r != null) return unescape(r[2])
return null
}
}
const source = getParam("source")
const fromid = getParam("fromid")
const org_id = getParam("org_id")
const code = getParam("code")
const appid = "wx718c4a40911b7837"
const host = "https://huodong2.lzlj.com/iceSip/"
// if (isWeixinPlatform && !isLogin && !code) {
// let url = encodeURIComponent(host)
// if (fromid) {
// url = encodeURIComponent(host + "?fromid=" + fromid)
// }
// window.location.href = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + appid + "&redirect_uri=" + url + "&response_type=code&scope=snsapi_userinfo#wechat_redirect"
// }
</script>
<script type="text/javascript">
(function () {
window._fmOpt = {
partner: 'lzlj',
success: function (data) {
localStorage.setItem("blackbox", JSON.stringify(data))
}
};
var fm = document.createElement('script');
fm.type = 'text/javascript'; fm.async = true;
fm.src = 'https://static.trustdecision.com/tdfp/cn/5bfdf55bf7ebc59ca8c07c8c7bd88e9a/fm.js' + '?t=' + (new Date().getTime() / 3600000).toFixed(0);
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(fm, s);
})();
</script>
<style>
.hackpreload {
display: none;
}
body {
width: 100vw;
height: 100vh;
overflow: hidden;
background: url("data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg width='80px' height='80px' viewBox='0 0 80 80' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Ctitle%3Eloading%3C/title%3E%3Cdefs%3E%3ClinearGradient x1='94.0869141%25' y1='0%25' x2='94.0869141%25' y2='90.559082%25' id='linearGradient-1'%3E%3Cstop stop-color='%23ededed' stop-opacity='0' offset='0%25'%3E%3C/stop%3E%3Cstop stop-color='%23ededed' stop-opacity='0.3' offset='100%25'%3E%3C/stop%3E%3C/linearGradient%3E%3ClinearGradient x1='100%25' y1='8.67370605%25' x2='100%25' y2='90.6286621%25' id='linearGradient-2'%3E%3Cstop stop-color='%23ededed' offset='0%25'%3E%3C/stop%3E%3Cstop stop-color='%23ededed' stop-opacity='0.3' offset='100%25'%3E%3C/stop%3E%3C/linearGradient%3E%3C/defs%3E%3Cg stroke='none' stroke-width='1' fill='none' fill-rule='evenodd' opacity='0.9'%3E%3Cg%3E%3Cpath d='M40,0 C62.09139,0 80,17.90861 80,40 C80,62.09139 62.09139,80 40,80 L40,73 C58.2253967,73 73,58.2253967 73,40 C73,21.7746033 58.2253967,7 40,7 L40,0 Z' fill='url(%23linearGradient-1)'%3E%3C/path%3E%3Cpath d='M40,0 L40,7 C21.7746033,7 7,21.7746033 7,40 C7,58.2253967 21.7746033,73 40,73 L40,80 C17.90861,80 0,62.09139 0,40 C0,17.90861 17.90861,0 40,0 Z' fill='url(%23linearGradient-2)'%3E%3C/path%3E%3Ccircle id='Oval' fill='%23ededed' cx='40.5' cy='3.5' r='3.5'%3E%3C/circle%3E%3C/g%3E%3CanimateTransform attributeName='transform' begin='0s' dur='1s' type='rotate' values='0 40 40;360 40 40' repeatCount='indefinite'/%3E%3C/g%3E%3C/svg%3E%0A") no-repeat center center;
background-size: 10% 10%;
}
</style>
<link rel="stylesheet" href="https://res.wx.qq.com/t/wx_fed/weui-source/res/2.6.16/weui.min.css">
</head>
<body>
<div id="app"></div>
<script type="text/javascript" src="https://res2.wx.qq.com/open/js/jweixin-1.6.2.js"></script>
<script type="text/javascript" src="https://res.wx.qq.com/t/wx_fed/weui.js/res/1.2.19/weui.min.js"></script>
<script type="module" src="/src/main.js"></script>
<script>
window.onload = async function () {
if (/MicroMessenger/i.test(window.navigator.userAgent)) {
try {
const response = await fetch(`https://huodong2.lzlj.com/api/iceSip/wechat/jssdk`, {
method: "POST",
headers: new Headers({
'Content-Type': 'application/json',
Accept: "application/json",
}),
body: JSON.stringify({
url: window.location.href, jsApiList: ["updateAppMessageShareData", "updateTimelineShareData", "scanQRCode"]
})
})
const result = await response.json()
if (response.status == 200) {
wx.config({
debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来若要查看传入的参数可以在pc端打开参数信息会通过log打出仅在pc端时才会打印。
appId: result.appId, // 必填,公众号的唯一标识
timestamp: result.timestamp, // 必填,生成签名的时间戳
nonceStr: result.nonceStr, // 必填,生成签名的随机串
signature: result.signature,// 必填,签名
jsApiList: ["updateAppMessageShareData", "updateTimelineShareData", "scanQRCode"], // 必填需要使用的JS接口列表
openTagList: ["wx-open-launch-weapp"]
});
wx.ready(function () {
const shareData = {
title: document.title,
desc: document.title,
link: "https://huodong2.lzlj.com/iceSip/",
imgUrl: "https://huodong2.lzlj.com/iceSip/share.png"
}
wx.updateAppMessageShareData({
title: shareData.title, // 分享标题
desc: shareData.desc, // 分享描述
link: shareData.link, // 分享链接该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
imgUrl: shareData.imgUrl, // 分享图标
})
wx.updateTimelineShareData({
title: shareData.desc, // 分享标题
link: shareData.link, // 分享链接该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
imgUrl: shareData.imgUrl, // 分享图标
})
})
}
} catch (err) {
console.log(err)
}
}
}
</script>
<script>
var _hmt = _hmt || [];
(function () {
var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?71eeb5d8fcd3c242670077b6398b3a0d";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
</script>
</body>
</html>

25
package.json Normal file
View File

@@ -0,0 +1,25 @@
{
"name": "basecode",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@element-plus/icons-vue": "^2.3.2",
"element-plus": "^2.11.1",
"howler": "^2.2.4",
"qrcode": "^1.5.4",
"vite-plugin-vue-devtools": "^8.0.1",
"vue": "^3.5.18",
"vue-router": "^4.5.1"
},
"devDependencies": {
"@vitejs/plugin-vue": "^6.0.1",
"@vitejs/plugin-vue-jsx": "^3.0.0",
"vite": "^7.1.3"
}
}

2216
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

1
public/vite.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

134
src/App.vue Normal file
View File

@@ -0,0 +1,134 @@
<script setup>
import { Howl, Howler } from 'howler';
import { onMounted, ref } from "vue"
import { isWeixin, isLogin, getParam, Storage, Request } from "./libs/utils"
import Login from './components/Login.vue'
import HomePage from './components/HomePage.vue'
import Address from "./components/Address.vue"
import PrizeList from "./components/PrizeList.vue"
import Todolist from "./components/TodoList.vue";
import Rule from "./components/Rule.vue";
import SelectTemplate from './components/SelectTemplate.vue'
import GenerateLoading from './components/GenerateLoading.vue'
import SelectTemplateV2 from './components/SelectTemplateV2.vue'
import { globalStore } from "./globalstore";
import bgmUrl from "./assets/audio/bgm.mp3"
import GenerateImg from './components/GenerateImg.vue';
var bgmSound = new Howl({
src: [bgmUrl],
loop: true
});
const loginShow = ref(false)
const homePageShow = ref(false)
const todolistShow = ref(true)
//分享进来
const fromShare = async () => {
const fromId = getParam("fromid")
if (!fromId) {
return
}
const userinfos = Storage.get("userinfos")
if (fromId === userinfos.invite_code) {
return
}
const inviteInfos = await Request("invite/info", { invite_code: fromId, type: "iceSpr" }, "GET")
if (inviteInfos.res.status == 200) {
const isHelp = inviteInfos.json.helps.find(v => v.id === inviteInfos.json.id)
if (!isHelp) {
const result = await Request("invite/help", { invite_code: fromId, type: "iceSpr" })
if (result.res.status == 200) {
weui.alert("受邀成功!")
}
} else {
weui.alert("您已接受过其他好友邀请,每人只能受邀一次哦~")
}
}
}
// 登录状态
const userStatus = async (callback) => {
const pageCode = getParam("code")
const userinfos = Storage.get("userinfos")
if (isWeixin()) {
if (!pageCode) {
weui.alert("未获取到code")
return
}
const weixinResult = await Request("wechat/login", {
code: pageCode
})
Storage.set("userinfos", weixinResult.json)
if (weixinResult.json && weixinResult.json.phone) {
loginShow.value = false
callback && callback()
} else {
loginShow.value = true
}
} else {
loginShow.value = true
}
}
//初始化持久数据
const initUserGameInfos = async (refresh_official, refresh_cap_scan) => {
const result = await Request('game/info', { refresh_official: refresh_official, refresh_cap_scan: refresh_cap_scan }, "GET")
if (result.res.status === 200) {
globalStore.invitees = result.json.invitees
globalStore.followed_official = result.json.followed_official
globalStore.cap_scan = result.json.cap_scan
globalStore.game_chances_view_recipes = result.json.game_chances_view_recipes
globalStore.MAX_VIEW_RECIPES_DAILY = result.json.constants.MAX_VIEW_RECIPES_DAILY
globalStore.CONSUME_POINT_1_PER_DRAW = result.json.constants.CONSUME_POINT_1_PER_DRAW
globalStore.MAX_CAP_SCAN = result.json.constants.MAX_CAP_SCAN
globalStore.MAX_INVITE_DAILY = result.json.constants.MAX_INVITE_DAILY
}
}
const handleLoginSuccess = async () => {
console.log("已登录")
loginShow.value = false
await initUserGameInfos(true, true)
}
if (isLogin()) {
handleLoginSuccess()
} else {
userStatus(handleLoginSuccess)
}
</script>
<template>
<router-view />
<Login :show="loginShow" @login-success="handleLoginSuccess" />
<!-- <HomePage :show="homePageShow" /> -->
<!-- <Todolist /> -->
<!-- <SelectTemplateV2 /> -->
<!-- <GenerateImg /> -->
<!-- <GenerateLoading /> -->
<!-- <GenerateImgConfirm /> -->
</template>
<style scoped>
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
filter: drop-shadow(0 0 2em #42b883aa);
}
</style>

108
src/agreement.html Normal file
View File

@@ -0,0 +1,108 @@
<h3 style="font-size:14px;margin-bottom:10px;text-align:center;">泸州老窖会员中心隐私条款</h3>
<h4 style="font-size:14px;margin-bottom:10px;text-align:center;">用户协议与隐私条款</h4>
<p style="font-size:12px;margin-bottom:10px;">尊敬的客户,在您使用我们的服务前,请仔细阅读本协议与隐私条款(以下简称协议)。</p>
<p style="font-size:12px;margin-bottom:10px;">
本协议是您与泸州老窖股份有限公司就泸州老窖会员中心相关事宜共同定立的契约。本协议经您通过点击同意即构成对本协议平等主体有约束力、执行力的法律文件。本协议可由我们不时作出修订,且构成您与我们之间达成的有关会员服务使用、具有约束力的协议。您在本协议修订发布后继续使用泸州老窖会员服务,即视为您已接受了修订。如果您不同意本协议或相关修订,则应立即停止注册程序及使用、享受泸州老窖会员中心服务。
</p>
<p style="font-size:12px;margin-bottom:10px;">
如果您不满十八18周岁或其他无民事行为能力/限制民事行为能力的情形,您应确保与您的父母或监护人共同阅读本协议,以便您的父母或监护人理解并同意本协议的内容。您点击同意本协议或使用泸州老窖会员服务后,即视为您确认本人具有享受泸州老窖会员中心服务、下单购物等相应的权利能力和行为能力,能够独立承担法律责任。若您不具备前述行为相适应的民事行为能力,则您与您父母或监护人应依照法律规定承担因此而导致的一切后果。
</p>
<p style="font-size:12px;margin-bottom:10px;">泸州老窖会员中心保留在法律允许的范围内独自决定拒绝服务、关闭用户账户、清除或编辑内容、取消订单的权利。</p>
<p style="font-size:12px;margin-bottom:10px;"><strong>一、用户协议</strong></p>
<p style="font-size:12px;margin-bottom:10px;">
1泸州老窖会员中心可在加以或不加以通知的情况下随时终止会员计划。从宣布计划终止之日起会员将在规定的时间内继续累积积分并兑换期望的奖励。在泸州老窖宣布计划终止十二个月后无论您在参加计划期间所累积的积分数额如何泸州老窖均有权终止您累积积分和兑换奖励的权利。
</p>
<p style="font-size:12px;margin-bottom:10px;">
2. 泸州老窖会员中心保留在加以或不加以通知的情况下,全权决定添加、修改、删除或以其他方式调整与计划相关的任何规则、程序、条件、优惠或奖励的权利,即使这些修改可能会影响已累积的积分、奖励礼券或其他的具体价值。即泸州老窖会员中心可以做出影响范围不限于如下对象的修改:积分的赚取和兑换规则、奖励积分的使用规则和程序、奖励是否持续提供、奖励类型和特别优惠。
</p>
<p style="font-size:12px;margin-bottom:10px;">
3泸州老窖会员身份将赋予会员赚取积分的权利而积分可参照本协议适用条款兑换奖励。我们将竭诚为会员提供优惠和奖励但若为会员居住国的法律或法规所禁止则将无法提供。</p>
<p style="font-size:12px;margin-bottom:10px;">
4. 若会员以违反本协议适用条款的规定或初衷或任意规定(包括但不限于奖励积分兑换活奖励礼券的使用)的方式使用本计划时,泸州老窖会员中心会保留暂停或终止其会籍的权利。此外,泸州老窖会员中心也保留权利,若怀疑会员有以下行为,可自行全权判断并终止其会籍:
</p>
<p style="font-size:12px;margin-bottom:10px;">a违反相关法律、法规或条例的行为</p>
<p style="font-size:12px;margin-bottom:10px;">b违背或违反本协议的适用条款</p>
<p style="font-size:12px;margin-bottom:10px;">c任何欺诈或不诚实的行为、盗窃或与会员账户相关包括但不限于奖励兑换、礼券使用或其他会员礼遇的不当或不道德行为
由于以上原因导致会籍终止,可能会导致所有的已累计积分丢失,且对应礼券和礼遇、等级被取消。除终止会籍外,泸州老窖会员中心还有权视必要程度,采取适当的行政和法律措施,包括但不限于刑事诉讼。</p>
<p style="font-size:12px;margin-bottom:10px;">
5、在未经泸州老窖会员中心许可的情况下不可对任何泸州老窖会员的积分、奖励礼券或泸州老窖会员中心的其他礼遇进行销售和交易。任何被泸州老窖单方面确认的任何以违反本协议适用条款的方式转让、销售或分配的积分、礼券或礼遇均可能会被没收或取消。
</p>
<p style="font-size:12px;margin-bottom:10px;"><strong>二、隐私条款</strong></p>
<p style="font-size:12px;margin-bottom:10px;">1. 用户许可</p>
<p style="font-size:12px;margin-bottom:10px;">1.1  我们允许您在本协议范围内享有个人的、非排他性的、不可转让的使用泸州老窖会员中心的权利。但您不得有以下行为:</p>
<p style="font-size:12px;margin-bottom:10px;">
1.1.1 以任何方式出售、转让、分发、修改或传播泸州老窖会员中心或与泸州老窖会员中心有关的文字、图片、音乐、条形码、视频、数据、超链接、展示及其他内容(“内容”);</p>
<p style="font-size:12px;margin-bottom:10px;">1.1.2 储存或发送淫秽、诽谤、暴力或者其他非法资料;</p>
<p style="font-size:12px;margin-bottom:10px;">1.1.3 储存或发送包含软件病毒或其他有害的计算机的程序资料;</p>
<p style="font-size:12px;margin-bottom:10px;">1.1.4 利用服务或泸州老窖会员中心小程序进行违法犯罪活动;</p>
<p style="font-size:12px;margin-bottom:10px;">
<p style="font-size:12px;margin-bottom:10px;">1.1.5 利用服务或泸州老窖会员中心小程序侵犯他人合法权益;</p>
<p style="font-size:12px;margin-bottom:10px;">1.1.6 利用服务或泸州老窖会员中心小程序影响网络的正常运行;</p>
<p style="font-size:12px;margin-bottom:10px;">1.1.7 损害泸州老窖会员中心小程序数据的完整性或性能。</p>
<p style="font-size:12px;margin-bottom:10px;">本用户许可同样适用于泸州老窖会员服务的任何更新、补充或替代产品,但相关更新、补充或替代产品中有相反规定的除外。</p>
<p style="font-size:12px;margin-bottom:10px;">本用户许可同样适用于泸州老窖会员服务的任何更新、补充或替代产品,但相关更新、补充或替代产品中有相反规定的除外。</p>
<p style="font-size:12px;margin-bottom:10px;">2. 注册账号、密码及安全性</p>
<p style="font-size:12px;margin-bottom:10px;">2.1 注册资格 您承诺:您具有完全民事权利能力和兴文能力或不具有完全民事权利能力和行为能力但经您的法定代理人同意并由其代理注册泸州老窖会员服务。
</p>
<p style="font-size:12px;margin-bottom:10px;">2.2 注册流程:您同意根据泸州老窖会员注册页面的要求提供手机号码并通过认证程序注册账号,或者通过微信授权快速注册账号。您将对账号安全负全部责任。
</p>
<p style="font-size:12px;margin-bottom:10px;">2.3 您成功注册后,您同意接受我们发送的与泸州老窖会员管理、运营相关的微信订阅号/微信服务号/或微信服务信息/或电子邮件/或短消息。</p>
<p style="font-size:12px;margin-bottom:10px;">
2.4 注册成功后,您有权根据泸州老窖会员中心相关页面公示的服务规则获取泸州老窖的相关自安息、发送订单、授权我们通过您指定的账户划扣服务费用并享受其他相关服务。</p>
<p style="font-size:12px;margin-bottom:10px;">2.5 您若发现任何非法使用用户账号或存在安全漏洞的情况,请立即反馈给我们。</p>
<p style="font-size:12px;margin-bottom:10px;">3. 用户信息保护</p>
<p style="font-size:12px;margin-bottom:10px;">
3.1 您同意向泸州老窖会员中心提供某些信息包括但不限于身份识别信息、平台操作信息、位置信息、支付信息、个人信用信息以及其他根据我们的产品和需要而手机的您的个人信息(“用户信息”)。</p>
<p style="font-size:12px;margin-bottom:10px;">3.2 您同意向我们传输用户信息,并授权我们在泸州老窖会员功能所需的范围内以本协议所述的目的记录、处理及存储上述用户信息。</p>
<p style="font-size:12px;margin-bottom:10px;">3.3 我们通过以下方式收集、分享、使用您的用户信息:</p>
<p style="font-size:12px;margin-bottom:10px;">
3.3.1 为了向您提供更加便捷、人性化的服务,我们可能需要您提供一些个人信息,包括:姓名、地址、电话号码等个人资料。为了保证您能够享受更好的服务,您需要及时更新上述注册资料,否则我们可能因为您资料不准确而无法提供服务。</p>
<p style="font-size:12px;margin-bottom:10px;">3.3.2 如果您将泸州老窖会员中心与您第三方支付平台的账户关联,您需要授权我们通过您指定的账户划扣服务费。</p>
<p style="font-size:12px;margin-bottom:10px;">3.3.3 为了向您提供更便捷的售前服务(如查询附近门店)以及售后服务等,泸州老窖会员中心小程序将会使用、保存您的位置信息、交易信息、操作信息等。
</p>
<p style="font-size:12px;margin-bottom:10px;">3.3.4 为了改进服务质量,我们会向您征集反馈意见,包括您参与的问卷调查、在线意见反馈、客户投诉等。</p>
<p style="font-size:12px;margin-bottom:10px;">3.3.5 以其他方式收集用户信息。</p>
<p style="font-size:12px;margin-bottom:10px;">
3.4 除手机号码外,您的头像、姓名、性别、生日、邮箱等信息均为选填信息,您可以选择是否填写。如您填写上述信息,我们也仅会从为您提供更好的服务的角度使用上述信息。</p>
<p style="font-size:12px;margin-bottom:10px;">
3.5 我们将采取技术措施和其他必要措施,确保用户信息安全,防止在本服务中收集的用户信息泄露。在发生前述情形或我们发现存在前述情形的可能时将及时采取措施补救。</p>
<p style="font-size:12px;margin-bottom:10px;">3.6 我们承诺未经过您的同意不将您的个人信息任意披露。但在以下情形下,我们将无法做出前述保证并披露您的相关信息。这些情形包括但不限于:</p>
<p style="font-size:12px;margin-bottom:10px;">
3.6.1 为了您的服务、交易顺利完成,我们不得不把您的某些用户信息,如您的姓名、电话、配送地址等提供给相关服务方,以便于他们及时与您取得联系,提供服务;</p>
<p style="font-size:12px;margin-bottom:10px;">
3.6.2 当您在泸州老窖会员中心小程序的行为违反服务条款,或可能损害他人权益或导致他人遭受损害,只要我们相信披露您的用户信息是为了辨识、联络或采取法律行动所必须的行动时;</p>
<p style="font-size:12px;margin-bottom:10px;">3.6.3 根据法律法规规定必须披露或公开的用户信息;</p>
<p style="font-size:12px;margin-bottom:10px;">3.6.4 有权机关依法执行公务,要求提供特定用户信息;</p>
<p style="font-size:12px;margin-bottom:10px;">3.6.5 您与我们及合作单位之间就用户信息的使用公开达成约定,我们因此向合作单位公开用户信息;</p>
<p style="font-size:12px;margin-bottom:10px;">3.6.6 任何由于黑客攻击电脑病毒侵入或其他不可抗力事件导致用户信息的泄露。</p>
<p style="font-size:12px;margin-bottom:10px;">3.7 您同意我们在以下事项中使用用户信息:</p>
<p style="font-size:12px;margin-bottom:10px;">3.7.1 向您及时发送重要的通知,如软件更新、本协议条款变更;</p>
<p style="font-size:12px;margin-bottom:10px;">3.7.2 进行审计数据分析和研究等,以改进产品服务及与用户之间的沟通;</p>
<p style="font-size:12px;margin-bottom:10px;">3.7.3 依照本协议约定,我们管理、审查用户信息及采取处理的措施;</p>
<p style="font-size:12px;margin-bottom:10px;">3.7.4 适用法律法规规定的其他事项。</p>
<p style="font-size:12px;margin-bottom:10px;">除上述事项外,未取得您事先同意,我们不会将用户信息用于其他任何用途。</p>
<p style="font-size:12px;margin-bottom:10px;">4. 责任</p>
<p style="font-size:12px;margin-bottom:10px;">
4.1 我们向您提供的预定信息仅供您参考并不构成保证。我们将在合理的范围内尽力保证该等信息准确但无法保证其中没有任何错误。由于会员中心小程序的功能受到客观技术条件的影响您理解并同意小程序在起使用过程中发生暂时中断是正常的现象。我们不能保证小程序的功能不会发生中断或不出现差错也不能保证CRM的网站或服务器不存在病毒或其他有害成分。
</p>
<p style="font-size:12px;margin-bottom:10px;">4.2 您通过小程序请求的产品和服务质量有泸州老窖负责控制和监督。</p>
<p style="font-size:12px;margin-bottom:10px;">4.3 我们将不承担任何基于不可抗力等非人为因素给您造成的损失。不可抗力包括但不限于:</p>
<p style="font-size:12px;margin-bottom:10px;">4.3.1 大雾、雷雨、沙尘、道路积雪、结冰、低能见度等危及行车安全的恶劣气象条件;</p>
<p style="font-size:12px;margin-bottom:10px;">4.3.2 洪涝、台风、冰雹、暴雪、地震、山地崩塌、滑坡、泥石流、海啸等自然灾害;</p>
<p style="font-size:12px;margin-bottom:10px;">4.3.3 公安机关交通部门在集会游行、大型运动会、道路桥梁建设、救灾抢险等情形下实施的交通管制等措施;</p>
<p style="font-size:12px;margin-bottom:10px;">4.3.4 黑客攻击、电信部门技术调整导致之重大影响、因政府管制二造成之暂时关闭、病毒侵袭等事件。</p>
<p style="font-size:12px;margin-bottom:10px;">4.4 我们对于您的以下行为引起的直接、间接、偶然及继起的损害不负责任:</p>
<p style="font-size:12px;margin-bottom:10px;">4.4.1 不正当使用服务;</p>
<p style="font-size:12px;margin-bottom:10px;">4.4.2 故意隐瞒重要信息;</p>
<p style="font-size:12px;margin-bottom:10px;">4.4.3 传送的信息不符合规定等。</p>
<p style="font-size:12px;margin-bottom:10px;">4.5 在适用法律允许的最大范围内,在任何情况下,</p>
<p style="font-size:12px;margin-bottom:10px;">a.我们均不就小程序的使用对用户承担责任;</p>
<p style="font-size:12px;margin-bottom:10px;">b.不就任何间接的、特殊的、偶然的、附随的或惩罚性的损害对您承担责任,包括但不限于商誉损失、利润损失、用户信息丢失、失窃</p>
<p style="font-size:12px;margin-bottom:10px;">5. 知识产权</p>
<p style="font-size:12px;margin-bottom:10px;">
泸州老窖标识以及与泸州老窖会员相关的其他泸州老窖商标、服务标志、图像和标识均为泸州老窖的商标或注册商标。与泸州老窖会员相关的其他商标、服务标志、图像和标识为其各自所有人的商标。未经泸州老窖或相关商标持有人的事先书面许可,用户不得全部或部门复制、模仿或使用泸州老窖标志或第三方标志。小程序和内容受到与著作权、商标、专利、商业秘密及其他专有权利相关的国际条约、法律、法规、行政规章等规定的保护,您可在泸州老窖或内容所有人授权的情况下使用包含数字信息的安全组件。
</p>
<p style="font-size:12px;margin-bottom:10px;">6. 合约期限及变更、终止:我们和您订立的这份协议是无固定期间协议。</p>
<p style="font-size:12px;margin-bottom:10px;">7. 其他</p>
<p style="font-size:12px;margin-bottom:10px;">7.1 本用户协议部分条款或附件无效或终止的,我们有权根据其他情况选择是否继续履行其他条款。</p>
<p style="font-size:12px;margin-bottom:40px;">7.2 本协议适用中国法律。本协议履行中发生的任何争议,由泸州老窖股份有限公司所在地人民法院管辖。</p>

BIN
src/assets/audio/bgm.mp3 Normal file

Binary file not shown.

Binary file not shown.

65
src/assets/base.css Normal file
View File

@@ -0,0 +1,65 @@
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
padding: 0;
margin: 0;
}
* {
box-sizing: border-box;
}
:not(img, input, textarea) {
-webkit-user-drag: none;
-webkit-touch-callout: none;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
:not(input, img, textarea) {
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
input,
textarea {
padding: 0;
border: none;
width: 100%;
height: 100%;
background: none;
outline: none;
}
input::placeholder,
textarea::placeholder {
color: rgba(99, 54, 16, .5);
font-weight: 700;
}
input:focus,
textarea:focus {
border: none;
}
body {
margin: 0;
padding: 0;
background-color: #000;
}
@keyframes loginloading {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}

37
src/assets/main.css Normal file
View File

@@ -0,0 +1,37 @@
@import './base.css';
#app{
width: 100vw;
height: 100vh;
}
.global-modal-mask {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, .85);
}
.global-modal-content {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
}
.global-modal.position-bottom .global-modal-content {
justify-content: flex-end;
}
.global-modal.position-top .global-modal-content {
justify-content: flex-start;
}
.global-modal.position-center .global-modal-content {
justify-content: center;
align-items: center;
}

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>

17
src/globalstore.js Normal file
View File

@@ -0,0 +1,17 @@
import { reactive } from "vue"
export const globalStore = reactive({
invitees: 0,
followed_official: false,
cap_scan: 0,
game_chances_view_recipes: 0,
POINT_1_ICESIP: 100,
POINT_1_ICON: 100,
MAX_CAP_SCAN: 5,
CONSUME_POINT_1_PER_DRAW: 1000,
MAX_INVITE_DAILY: 5,
MAX_VIEW_RECIPES_DAILY: 3,
updateViewRecipesCount () {
this.game_chances_view_recipes = this.game_chances_view_recipes >= this.MAX_VIEW_RECIPES_DAILY ? this.MAX_VIEW_RECIPES_DAILY : this.game_chances_view_recipes + 1
}
})

1
src/libs/area.js Normal file

File diff suppressed because one or more lines are too long

93
src/libs/haibao.js Normal file
View File

@@ -0,0 +1,93 @@
export default class Haibao {
constructor(width, height, color = '#fff') {
this.canvas = document.createElement('canvas');
this.ctx = this.canvas.getContext('2d', { alpha: false });
this.canvas.width = width;
this.canvas.height = height;
this.ctx.fillStyle = color;
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height)
this.zIndex = 0
this.imagePromises = []
this.images = []; // 存储待合成的图片信息
}
/**
* 添加图片到合成队列现在返回Promise
* @param {string|HTMLImageElement} image 图片URL或Image对象
* @param {number} x 绘制X坐标
* @param {number} y 绘制Y坐标
* @returns {Promise} 图片加载完成的Promise
*/
add (image, x, y, index) {
this.zIndex++
const zIndex = index ? index : this.zIndex
const loadPromise = this._createLoadPromise(image).then(img => {
this.images.push({ img, x, y, zIndex });
});
this.imagePromises.push(loadPromise);
return loadPromise;
}
/**
* 生成合成后的图片返回Promise
* @returns {Promise<string>} 合成后的Base64图片数据
*/
async generate (mimeType = 'image/jpeg', quality = .8) {
const validTypes = ['image/png', 'image/jpeg', 'image/webp'];
mimeType = validTypes.includes(mimeType) ? mimeType : 'image/png';
quality = Math.min(1, Math.max(0, Number(quality))) || .8;
await Promise.all(this.imagePromises);
// 清空画布
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
// 绘制所有图片
this.images.sort((a, b) => a.zIndex - b.zIndex)
this.images.forEach(({ img, x, y, zIndex }) => {
this.ctx.drawImage(img, x, y);
});
const b64 = this.canvas.toDataURL(mimeType, quality)
this.images.length = 0;
this.imagePromises.length = 0;
return b64
}
// 创建加载Promise私有方法
_createLoadPromise (image) {
if (typeof image === 'string') {
return this._loadImageFromUrl(image);
} else if (image instanceof HTMLImageElement) {
return this._handleExistingImage(image);
}
return Promise.reject(new Error('Invalid image type'));
}
// 从URL加载图片
_loadImageFromUrl (url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.crossOrigin = 'anonymous';
img.src = url;
img.onload = () => resolve(img);
img.onerror = reject;
});
}
// 处理已存在的Image对象
_handleExistingImage (img) {
return new Promise((resolve, reject) => {
if (img.complete && img.naturalHeight !== 0) {
resolve(img);
} else {
img.onload = () => resolve(img);
img.onerror = reject;
}
});
}
}

305
src/libs/utils.js Normal file
View File

@@ -0,0 +1,305 @@
import QRCode from "qrcode"
export const HOST = "https://huodong2.lzlj.com"
export const DIR = "faceFamily"
export const API = `${HOST}/api/${DIR}/`
export const debuglog = (text) => {
console.log(`%c -- ${text} `, "background:green;color:#fff")
}
export const Storage = {
set (name, data) {
localStorage.setItem(name, JSON.stringify(data))
},
get (name) {
return JSON.parse(localStorage.getItem(name))
},
clear () {
localStorage.clear()
},
remove (name) {
localStorage.removeItem(name)
}
}
export const Sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
export const isLogin = () => {
return !!(Storage.get("userinfos") && Storage.get("userinfos").phone)
// return true
}
export const isBaseLogin = () => {
return Storage.get("userinfos")
// return true
}
export const generateQR = async (text) => {
return await QRCode.toDataURL(`${HOST}/${DIR}/?${text}`, {
errorCorrectionLevel: 'H',
width: 160,
height: 160,
margin:1,
colorDark: "#000000",
colorLight: "#e8e2cc",
})
}
export const createPop = (id, classname) => {
const wrapper = document.createElement("div")
wrapper.id = id
wrapper.classList.add(classname)
document.querySelector("body").appendChild(wrapper)
return wrapper
}
export const isIos = () => {
return /(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent)
}
export const isWeixin = () => {
return /MicroMessenger/i.test(window.navigator.userAgent)
}
export const isWeixinPlatform = () => {
return /MicroMessenger/i.test(window.navigator.userAgent) || window.__wxjs_environment === 'miniprogram'
}
export const isWeibo = () => {
return /Weibo/i.test(window.navigator.userAgent)
}
export const isDouyin = () => {
return /aweme/i.test(window.navigator.userAgent)
}
export const isWebp = () => {
return !![].map && document.createElement('canvas').toDataURL('image/webp').indexOf('data:image/webp') == 0
}
export const isMiniPage = () => {
return window.__wxjs_environment === 'miniprogram'
// return true
}
export const webpAsPng = (url) => {
const nowebp = document.querySelector("html").classList.value.indexOf("nowebp") > -1
return nowebp ? url.replace(".webp", ".png").replace("images/", "images/png/") : url
}
export const getUserBrowersName = () => {
const ua = navigator.userAgent
if (isWeixin()) {
return "weixin"
} else if (isWeibo()) {
return "weibo"
} else if (isDouyin()) {
return "douyin"
} else if (isMiniPage()) {
return "miniprogram"
} else {
return "other"
}
}
export const getMiniPageBtnHack = (url) => {
//TODO确认小程序appid
let html = ''
html += `<wx-open-launch-weapp id="launch-btn" appid="wxc911dd6c6bc128de" path="${url}"><template>`
html += '<style>.open-btn {position:absolute;width:100%;height:100%;opacity:0}</style>'
html += '<button class="open-btn">打开小程序</button>'
html += '</template></wx-open-launch-weapp>'
return html
}
export const miniJumpToScene = () => {
wx.miniProgram.navigateTo({ url: '/pages/unify/unify?orgId=200282401019674482&targetUrl=%2Fpages%2Fretail%2Forder%2Forder-list%3Ftab%3DAll%26topTab%3D1' })
}
export const miniJumpToCouopon = () => {
wx.miniProgram.navigateTo({
url: '/pages/unify/unify?orgId=200282401019674482&targetUrl=%2Fpages%2Fcoupon%2Fcoupons-list'
})
}
export const miniJumpToCenter = () => {
wx.miniProgram.navigateTo({ url: '/pages/unify/unify?currentCode=my&orgId=200282401019674482' })
}
//[ ]
// export const miniJumpToCouopon = () => {
// wx.miniProgram.navigateTo({ url: 'pages/unify/unify?orgId=200282401019674482&targetUrl=%2Fpages%2Fcoupon%2Fcoupons-list' })
// }
export const getParam = (name) => {
if ('URLSearchParams' in window) {
var params = new URLSearchParams(window.location.search)
return params.get(name) ? params.get(name) : null
} else {
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i'),
r = window.location.search.substr(1).match(reg)
if (r != null) return unescape(r[2])
return null
}
}
export const Request = async (url, data, type, noloading, noerror) => {
let headers = {
'Content-Type': 'application/json',
Accept: "application/json",
"Source": "iceSip",
'App-Channel': getUserBrowersName(),
refer: document.referrer,
blackbox: Storage.get("blackbox") ? Storage.get("blackbox") : false
}
if (url != "sms/sendCode" && url != "sms/authPhone" && url != "wechat/login" && url != "wechat/jssdk") {
if (isLogin()) {
headers.Authorization = `Bearer ${Storage.get("userinfos").api_token}`
} else {
Storage.clear()
weui.alert("错误的请求")
window.location.reload()
return
}
}
if (isBaseLogin() && url == "sms/authPhone") {
headers.Authorization = `Bearer ${Storage.get("userinfos").api_token}`
}
let loading = false
if (!noloading) {
loading = weui.loading()
}
let message = "请求失败,请重试"
let fetchData = {
method: type || 'POST',
headers: new Headers(headers),
}
if (fetchData.method == "POST") {
fetchData.body = JSON.stringify(data)
}
let requrl = `${API}${url}`
if (type === "GET") {
let paramArr = []
Object.keys(data).forEach(v => {
paramArr.push(`${v}=${data[v]}`)
})
requrl = paramArr.length === 0 ? `${requrl}` : `${requrl}?${paramArr.join("&")}`
}
try {
const response = await fetch(`${requrl}`, fetchData)
const result = await response.json()
message = result.message || message
if (getParam("debug")) {
console.log("url:", url)
console.log("data:", data)
console.log(response)
console.log(result)
}
loading && loading.hide()
if (response.status == 200 || response.status == 201) {
return { res: response, json: result }
} else if (response.status == 401) {
Storage.clear()
weui.alert("错误的请求")
window.location.reload()
return
} else {
if (!noerror) {
weui.alert(message)
}
return { res: response, json: result }
}
} catch (error) {
if (!noerror) {
weui.alert(message)
}
loading && loading.hide()
}
}
export const RequestImg = async (url, data, type, noloading, noerror) => {
let headers = {
'Content-Type': 'multipart/form-data',
Accept: "application/json",
"Source": "faceFamily",
'App-Channel': getUserBrowersName(),
refer: document.referrer,
blackbox: Storage.get("blackbox") ? Storage.get("blackbox") : false
}
if (url != "sms/sendCode" && url != "sms/authPhone" && url != "wechat/login" && url != "wechat/jssdk") {
if (isLogin()) {
headers.Authorization = `Bearer ${Storage.get("userinfos").api_token}`
} else {
Storage.clear()
weui.alert("错误的请求")
window.location.reload()
return
}
}
if (isBaseLogin() && url == "sms/authPhone") {
headers.Authorization = `Bearer ${Storage.get("userinfos").api_token}`
}
let loading = false
if (!noloading) {
loading = weui.loading()
}
let message = "请求失败,请重试"
let fetchData = {
method: type || 'POST',
headers: new Headers(headers),
}
if (fetchData.method == "POST") {
fetchData.body = JSON.stringify(data)
}
let requrl = 'https://huodong2.lzlj.com/api/faceFamily/upload/image'
if (type === "GET") {
let paramArr = []
Object.keys(data).forEach(v => {
paramArr.push(`${v}=${data[v]}`)
})
requrl = paramArr.length === 0 ? `${requrl}` : `${requrl}?${paramArr.join("&")}`
}
try {
const response = await fetch(`${requrl}`, fetchData)
const result = await response.json()
message = result.message || message
if (getParam("debug")) {
console.log("url:", url)
console.log("data:", data)
console.log(response)
console.log(result)
}
loading && loading.hide()
if (response.status == 200 || response.status == 201) {
return { res: response, json: result }
} else if (response.status == 401) {
Storage.clear()
weui.alert("错误的请求")
window.location.reload()
return
} else {
if (!noerror) {
weui.alert(message)
}
return { res: response, json: result }
}
} catch (error) {
if (!noerror) {
weui.alert(message)
}
loading && loading.hide()
}
}
// let str = ''
// let s = ''
// for (let index = 0; index < 60; index++) {
// str += `import denglong2${index} from "./images/frame/denglong2/denglong2_${index}.webp";`
// s+=`denglong2${index},`
// }

8
src/main.js Normal file
View File

@@ -0,0 +1,8 @@
import { createApp } from 'vue'
import './assets/main.css'
import App from './App.vue'
import router from './router'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
createApp(App).use(router).use(ElementPlus).mount('#app')

14
src/router/index.js Normal file
View File

@@ -0,0 +1,14 @@
import { createRouter, createWebHistory } from 'vue-router'
import routes from './routes' // 导入路由定义
const router = createRouter({
history: createWebHistory(),
routes
})
// 全局前置守卫
router.beforeEach((to, from, next) => {
next()
})
export default router

42
src/router/routes.js Normal file
View File

@@ -0,0 +1,42 @@
import HomePage from '../../src/components/HomePage.vue'
import TodoList from '../../src/components/HomePage.vue'
import GenerateImg from '../../src/components/GenerateImg.vue'
import SelectTemplateV2 from '../../src/components/SelectTemplateV2.vue'
import GenerateImgConfirm from '../../src/components/GenerateImgConfirm.vue'
export default [
{
path: '/',
name: 'home',
component: HomePage
},
{
path: '/todoList',
name: 'todoList',
component: TodoList
},
{
path: '/selectTemplateV2',
name: 'selectTemplateV2',
component: SelectTemplateV2
},
{
path: '/generateImg',
name: 'generateImg',
component: GenerateImg
},
{
path: '/generateImgConfirm',
name: 'generateImgConfirm',
component: GenerateImgConfirm
},
// {
// path: '/todoList',
// name: 'todo',
// component: () => import('../../src/components/TodoList.vue')
// },
{
path: '/:pathMatch(.*)*', // 404 路由
redirect: '/'
}
]

View File

@@ -0,0 +1,664 @@
const imagePositionMaps = {
shenxian: {
1: {
positions: [{
top: '41%',
left: '38%',
width: '55px'
},
{
top: '56%',
left: '57%',
width: '55px'
}
]
},
2: {
positions: [{
top: '43%',
left: '39%',
width: '55px'
},
{
top: '56%',
left: '60%',
width: '55px'
}
]
},
3: {
positions: [{
top: '49%',
left: '30%',
width: '55px'
},
{
top: '47%',
left: '53%',
width: '55px'
}
]
},
4: {
positions: [{
top: '40%',
left: '31%',
width: '50px'
},
{
top: '36%',
left: '61%',
width: '50px'
},
{
top: '62%',
left: '40%',
width: '50px'
}
]
},
5: {
positions: [{
top: '39%',
left: '32%',
width: '44px'
},
{
top: '36%',
left: '54%',
width: '44px'
},
{
top: '60%',
left: '43%',
width: '44px'
},
{
top: '55%',
left: '68%',
width: '44px'
}
]
},
6: {
positions: [{
top: '42%',
left: '26%',
width: '40px'
},
{
top: '39%',
left: '43%',
width: '40px'
},
{
top: '39%',
left: '59%',
width: '40px'
},
{
top: '44%',
left: '68%',
width: '40px'
},
{
top: '53%',
left: '41%',
width: '40px'
}
]
},
},
xianxia: {
1: {
positions: [{
top: '46%',
left: '33%',
width: '55px'
},
{
top: '46%',
left: '61%',
width: '55px'
}
]
},
2: {
positions: [{
top: '41%',
left: '37%',
width: '55px'
},
{
top: '55%',
left: '55%',
width: '55px'
}
]
},
3: {
positions: [{
top: '40%',
left: '34%',
width: '55px'
},
{
top: '44%',
left: '57%',
width: '55px'
},
{
top: '61%',
left: '38%',
width: '55px'
}
]
},
4: {
positions: [{
top: '43%',
left: '37%',
width: '50px'
},
{
top: '45%',
left: '58%',
width: '50px'
},
{
top: '61%',
left: '42%',
width: '50px'
}
]
},
5: {
positions: [{
top: '43%',
left: '28%',
width: '44px'
},
{
top: '41%',
left: '55%',
width: '44px'
},
{
top: '55%',
left: '46%',
width: '44px'
},
{
top: '47%',
left: '68%',
width: '44px'
}
]
},
6: {
positions: [{
top: '42%',
left: '35%',
width: '40px'
},
{
top: '37%',
left: '53%',
width: '40px'
},
{
top: '40%',
left: '71%',
width: '40px'
},
{
top: '52%',
left: '27%',
width: '40px'
},
{
top: '55%',
left: '54%',
width: '40px'
}
]
},
},
fugu: {
1: {
positions: [{
top: '35%',
left: '39%',
width: '55px'
},
{
top: '41%',
left: '56%',
width: '55px'
}
]
},
2: {
positions: [{
top: '45%',
left: '34%',
width: '55px'
},
{
top: '45%',
left: '53%',
width: '55px'
}
]
},
3: {
positions: [{
top: '43%',
left: '40%',
width: '55px'
},
{
top: '51%',
left: '61%',
width: '55px'
}
]
},
4: {
positions: [{
top: '46%',
left: '29%',
width: '50px'
},
{
top: '36%',
left: '55%',
width: '50px'
},
{
top: '646%',
left: '69%',
width: '50px'
}
]
},
5: {
positions: [{
top: '34%',
left: '35%',
width: '44px'
},
{
top: '38%',
left: '54%',
width: '44px'
},
{
top: '49%',
left: '32%',
width: '44px'
},
{
top: '53%',
left: '59%',
width: '44px'
}
]
},
6: {
positions: [{
top: '33%',
left: '49%',
width: '40px'
},
{
top: '36%',
left: '70%',
width: '40px'
},
{
top: '49%',
left: '34%',
width: '40px'
},
{
top: '50%',
left: '49%',
width: '40px'
},
{
top: '56%',
left: '62%',
width: '40px'
}
]
},
},
xinzhongshi: {
1: {
positions: [{
top: '51%',
left: '31%',
width: '60px'
},
{
top: '46%',
left: '48%',
width: '60px'
}
]
},
2: {
positions: [{
top: '58%',
left: '29%',
width: '60px'
},
{
top: '39%',
left: '47%',
width: '60px'
}
]
},
3: {
positions: [{
top: '45.5%',
left: '34%',
width: '40px'
},
{
top: '47.5%',
left: '59.5%',
width: '40px'
}
]
},
4: {
positions: [{
top: '39%',
left: '37.5%',
width: '50px'
},
{
top: '42.5%',
left: '59%',
width: '50px'
},
{
top: '59%',
left: '50%',
width: '50px'
}
]
},
5: {
positions: [{
top: '40.5%',
left: '46%',
width: '44px'
},
{
top: '41%',
left: '66%',
width: '44px'
},
{
top: '47.5%',
left: '30.5%',
width: '44px'
},
{
top: '53%',
left: '59%',
width: '44px'
}
]
},
6: {
positions: [{
top: '41%',
left: '26%',
width: '36px'
},
{
top: '41%',
left: '47.6%',
width: '36px'
},
{
top: '52.5%',
left: '26.4%',
width: '36px'
},
{
top: '58%',
left: '44.5%',
width: '36px'
},
{
top: '49%',
left: '67%',
width: '36px'
}
]
},
},
luying: {
1: {
positions: [{
top: '49%',
left: '33%',
width: '60px'
},
{
top: '53%',
left: '50%',
width: '60px'
}
]
},
2: {
positions: [{
top: '48%',
left: '33%',
width: '60px'
},
{
top: '57%',
left: '58%',
width: '60px'
}
]
},
3: {
positions: [{
top: '53%',
left: '39%',
width: '60px'
},
{
top: '53.5%',
left: '57.2%',
width: '60px'
}
]
},
4: {
positions: [{
top: '45%',
left: '29.5%',
width: '50px'
},
{
top: '44.5%',
left: '57.6%',
width: '50px'
},
{
top: '53.4%',
left: '45%',
width: '50px'
}
]
},
5: {
positions: [{
top: '49.7%',
left: '24%',
width: '44px'
},
{
top: '47%',
left: '61%',
width: '44px'
},
{
top: '57%',
left: '34.5%',
width: '44px'
},
{
top: '55%',
left: '49%',
width: '44px'
}
]
},
6: {
positions: [{
top: '41%',
left: '42%',
width: '40px'
},
{
top: '43%',
left: '63.6%',
width: '40px'
},
{
top: '50.5%',
left: '28%',
width: '40px'
},
{
top: '51.4%',
left: '51%',
width: '40px'
},
{
top: '57.5%',
left: '68%',
width: '40px'
}
]
},
},
paidui: {
1: {
positions: [{
top: '46%',
left: '31%',
width: '60px'
},
{
top: '42%',
left: '48%',
width: '60px'
}
]
},
2: {
positions: [{
top: '49%',
left: '35%',
width: '60px'
},
{
top: '48%',
left: '54%',
width: '60px'
}
]
},
3: {
positions: [{
top: '57%',
left: '30%',
width: '50px'
},
{
top: '41%',
left: '51.2%',
width: '50px'
}
]
},
4: {
positions: [{
top: '41%',
left: '37.5%',
width: '50px'
},
{
top: '44.4%',
left: '64.6%',
width: '50px'
},
{
top: '56.4%',
left: '48%',
width: '50px'
}
]
},
5: {
positions: [{
top: '43.7%',
left: '29%',
width: '44px'
},
{
top: '40%',
left: '44%',
width: '44px'
},
{
top: '46%',
left: '64.5%',
width: '44px'
}
]
},
6: {
positions: [{
top: '38%',
left: '28%',
width: '40px'
},
{
top: '40%',
left: '56.6%',
width: '40px'
},
{
top: '49.5%',
left: '37%',
width: '40px'
},
{
top: '52%',
left: '53%',
width: '40px'
},
{
top: '53.5%',
left: '71.2%',
width: '40px'
}
]
},
},
};
export default imagePositionMaps

664
src/static/positionMaps.js Normal file
View File

@@ -0,0 +1,664 @@
const positionMaps = {
shenxian: {
1: {
positions: [{
top: '37%',
left: '36%',
width: '76px'
},
{
top: '52%',
left: '55%',
width: '76px'
}
]
},
2: {
positions: [{
top: '38%',
left: '38%',
width: '70px'
},
{
top: '51%',
left: '58%',
width: '70px'
}
]
},
3: {
positions: [{
top: '43%',
left: '28%',
width: '72px'
},
{
top: '41%',
left: '51%',
width: '72px'
}
]
},
4: {
positions: [{
top: '35%',
left: '30%',
width: '60px'
},
{
top: '31%',
left: '59%',
width: '60px'
},
{
top: '57%',
left: '38%',
width: '60px'
}
]
},
5: {
positions: [{
top: '34%',
left: '30%',
width: '58px'
},
{
top: '31%',
left: '52%',
width: '58px'
},
{
top: '55%',
left: '41%',
width: '58px'
},
{
top: '50%',
left: '66%',
width: '58px'
}
]
},
6: {
positions: [{
top: '38%',
left: '25%',
width: '50px'
},
{
top: '35%',
left: '42%',
width: '50px'
},
{
top: '35%',
left: '57%',
width: '50px'
},
{
top: '40%',
left: '67%',
width: '50px'
},
{
top: '49%',
left: '40%',
width: '50px'
}
]
},
},
xianxia: {
1: {
positions: [{
top: '42%',
left: '31%',
width: '76px'
},
{
top: '41%',
left: '58%',
width: '76px'
}
]
},
2: {
positions: [{
top: '36%',
left: '36%',
width: '70px'
},
{
top: '50%',
left: '54%',
width: '70px'
}
]
},
3: {
positions: [{
top: '34%',
left: '32%',
width: '72px'
},
{
top: '38%',
left: '55%',
width: '72px'
},
{
top: '55%',
left: '36%',
width: '72px'
}
]
},
4: {
positions: [{
top: '38%',
left: '36%',
width: '60px'
},
{
top: '40%',
left: '57%',
width: '60px'
},
{
top: '56%',
left: '41%',
width: '60px'
}
]
},
5: {
positions: [{
top: '39%',
left: '27%',
width: '58px'
},
{
top: '36%',
left: '53%',
width: '58px'
},
{
top: '50%',
left: '44%',
width: '58px'
},
{
top: '43%',
left: '67%',
width: '58px'
}
]
},
6: {
positions: [{
top: '38%',
left: '34%',
width: '50px'
},
{
top: '33%',
left: '51.6%',
width: '50px'
},
{
top: '36%',
left: '69%',
width: '50px'
},
{
top: '48%',
left: '26%',
width: '50px'
},
{
top: '51%',
left: '53%',
width: '50px'
}
]
},
},
fugu: {
1: {
positions: [{
top: '30%',
left: '38%',
width: '70px'
},
{
top: '36%',
left: '54%',
width: '70px'
}
]
},
2: {
positions: [{
top: '40%',
left: '32%',
width: '74px'
},
{
top: '40%',
left: '51%',
width: '74px'
}
]
},
3: {
positions: [{
top: '39%',
left: '39%',
width: '72px'
},
{
top: '46%',
left: '59%',
width: '72px'
}
]
},
4: {
positions: [{
top: '41%',
left: '28%',
width: '60px'
},
{
top: '31%',
left: '54%',
width: '60px'
},
{
top: '41%',
left: '68%',
width: '60px'
}
]
},
5: {
positions: [{
top: '30%',
left: '33%',
width: '58px'
},
{
top: '34%',
left: '52%',
width: '58px'
},
{
top: '45%',
left: '30.5%',
width: '58px'
},
{
top: '48.5%',
left: '57%',
width: '58px'
}
]
},
6: {
positions: [{
top: '29%',
left: '48%',
width: '50px'
},
{
top: '32%',
left: '68.6%',
width: '50px'
},
{
top: '44.5%',
left: '33%',
width: '50px'
},
{
top: '46.2%',
left: '47.4%',
width: '50px'
},
{
top: '52%',
left: '61%',
width: '50px'
}
]
},
},
xinzhongshi: {
1: {
positions: [{
top: '46%',
left: '31%',
width: '70px'
},
{
top: '41%',
left: '47%',
width: '70px'
}
]
},
2: {
positions: [{
top: '53%',
left: '28%',
width: '74px'
},
{
top: '34%',
left: '45%',
width: '74px'
}
]
},
3: {
positions: [{
top: '42%',
left: '34%',
width: '48px'
},
{
top: '43.5%',
left: '58.5%',
width: '48px'
}
]
},
4: {
positions: [{
top: '34%',
left: '36.5%',
width: '60px'
},
{
top: '37.5%',
left: '58%',
width: '60px'
},
{
top: '54%',
left: '49%',
width: '60px'
}
]
},
5: {
positions: [{
top: '37%',
left: '47%',
width: '44px'
},
{
top: '36.5%',
left: '66%',
width: '44px'
},
{
top: '43%',
left: '30.5%',
width: '44px'
},
{
top: '49%',
left: '59%',
width: '44px'
}
]
},
6: {
positions: [{
top: '38%',
left: '25%',
width: '48px'
},
{
top: '37%',
left: '46.6%',
width: '48px'
},
{
top: '48.5%',
left: '25%',
width: '48px'
},
{
top: '54%',
left: '43%',
width: '48px'
},
{
top: '45%',
left: '65%',
width: '48px'
}
]
},
},
luying: {
1: {
positions: [{
top: '44%',
left: '33%',
width: '70px'
},
{
top: '48%',
left: '49%',
width: '70px'
}
]
},
2: {
positions: [{
top: '43.5%',
left: '32%',
width: '74px'
},
{
top: '52%',
left: '57%',
width: '74px'
}
]
},
3: {
positions: [{
top: '45%',
left: '38%',
width: '74px'
},
{
top: '45.5%',
left: '55.5%',
width: '74px'
}
]
},
4: {
positions: [{
top: '40%',
left: '28.5%',
width: '60px'
},
{
top: '39.5%',
left: '56%',
width: '60px'
},
{
top: '49%',
left: '44%',
width: '60px'
}
]
},
5: {
positions: [{
top: '45.7%',
left: '24%',
width: '44px'
},
{
top: '43%',
left: '61%',
width: '44px'
},
{
top: '53%',
left: '34.5%',
width: '44px'
},
{
top: '51%',
left: '49%',
width: '44px'
}
]
},
6: {
positions: [{
top: '37.5%',
left: '42%',
width: '48px'
},
{
top: '39%',
left: '62.6%',
width: '48px'
},
{
top: '46.5%',
left: '27%',
width: '48px'
},
{
top: '47.4%',
left: '50%',
width: '48px'
},
{
top: '53.5%',
left: '67%',
width: '48px'
}
]
},
},
paidui: {
1: {
positions: [{
top: '41%',
left: '31%',
width: '70px'
},
{
top: '37%',
left: '47%',
width: '70px'
}
]
},
2: {
positions: [{
top: '44%',
left: '34%',
width: '74px'
},
{
top: '43.5%',
left: '52%',
width: '74px'
}
]
},
3: {
positions: [{
top: '51%',
left: '29%',
width: '60px'
},
{
top: '35.2%',
left: '49.5%',
width: '60px'
}
]
},
4: {
positions: [{
top: '36%',
left: '36.5%',
width: '60px'
},
{
top: '39.5%',
left: '54%',
width: '60px'
},
{
top: '52%',
left: '47%',
width: '60px'
}
]
},
5: {
positions: [{
top: '40%',
left: '27.5%',
width: '60px'
},
{
top: '36.5%',
left: '42%',
width: '60px'
},
{
top: '42%',
left: '62.5%',
width: '60px'
}
]
},
6: {
positions: [{
top: '35%',
left: '28%',
width: '46px'
},
{
top: '37%',
left: '55.6%',
width: '46px'
},
{
top: '46.5%',
left: '36%',
width: '46px'
},
{
top: '48.5%',
left: '52%',
width: '46px'
},
{
top: '50%',
left: '71%',
width: '46px'
}
]
},
},
};
export default positionMaps

49
vite.config.js Normal file
View File

@@ -0,0 +1,49 @@
import { fileURLToPath, URL } from 'node:url'
const packageJson = require('./package.json');
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'
// https://vite.dev/config/
export default defineConfig({
// base: 'https://huodong2.lzlj.com/iceSip/',
plugins: [
vue({
template: {
compilerOptions: {
isCustomElement: (tag) => tag.includes('swiper')
}
}
}),
vueDevTools(),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
},
},
server: {
proxy: {
'/api': {
target: 'https://huodong2.lzlj.com',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}
}
},
build: {
assetsInlineLimit: 0,
rollupOptions: {
output: {
manualChunks: {
phaser: ['phaser']
}
}
},
},
define: {
'__APP_VERSION__': JSON.stringify(packageJson.version)
},
})