This commit is contained in:
yixu
2025-12-19 11:21:04 +08:00
commit 258e14e27c
82 changed files with 5371 additions and 0 deletions

28
.gitignore vendored Normal file
View File

@@ -0,0 +1,28 @@
.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
/设计图

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).

108
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>

155
index.html Normal file
View File

@@ -0,0 +1,155 @@
<!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>Ai在一起 金饼送给你</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/faceFamily/"
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%;
}
.preload {
display: none;
}
</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 class="preload">
<img src="./src/assets/images/generate/select-template-bg.webp" alt="">
<img src="./src/assets/images/generate/generate-img-bg.webp" alt="">
<img src="./src/assets/images/generate/photo-squarev2.webp" alt="">
<img src="./src/assets/images/generate/my-photov2.webp" alt="">
</div>
<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/faceFamily/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/faceFamily/",
imgUrl: "https://huodong2.lzlj.com/faceFamily/share.webp"
}
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?eea1d73562644a65f05fa3b1c04c3894";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
</script>
</body>
</html>

29
package.json Normal file
View File

@@ -0,0 +1,29 @@
{
"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",
"canvas-confetti": "^1.9.3",
"face-api.js": "^0.22.2",
"gsap": "^3.13.0",
"howler": "^2.2.4",
"mitt": "^3.0.1",
"qrcode": "^1.5.4",
"swiper": "^12.0.3",
"vite-plugin-vue-devtools": "^8.0.1",
"vue": "^3.5.18",
"vue-virtual-scroller": "2.0.0-beta.8"
},
"devDependencies": {
"@vitejs/plugin-vue": "^6.0.1",
"@vitejs/plugin-vue-jsx": "^3.0.0",
"vite": "^7.1.3"
}
}

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

160
src/App.vue Normal file
View File

@@ -0,0 +1,160 @@
<script setup>
import { Howl, Howler } from 'howler';
import { onMounted, ref, watch } from "vue"
import globalToastEvent, { ToastType } from './globalToastEvent';
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 Ad from "./components/Ad.vue"
import Lottery from './components/Lottery.vue';
import { globalStore } from "./globalstore";
import bgmUrl from "./assets/audio/bgm.mp3"
var bgmSound = new Howl({
src: [bgmUrl],
loop: true
});
const loginShow = ref(false)
const homePageShow = ref(false)
const todolistShow = ref(false)
const prizeListShow = ref(false)
const ruleShow = ref(false)
const prizelist = ref([])
const addressShow = ref(false)
const activePrizeId = ref(0)
const adShow = ref(false)
const lotteryShow = ref(false)
// 登录状态
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.draw_chances = result.json.draw_chances
globalStore.game_chances = result.json.game_chances
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
globalToastEvent.emit(ToastType.MOUNTED)
}
}
const handleLoginSuccess = async () => {
console.log("已登录")
loginShow.value = false
await initUserGameInfos(true, true)
}
// if (isLogin()) {
// handleLoginSuccess()
// } else {
// userStatus(handleLoginSuccess)
// }
const getUserLottery = async () => {
const result = await Request("lottery", { pool: "all" }, "GET")
if (result.res.status === 200) {
prizelist.value = result.json.lottery_logs.length > 0 ? result.json.lottery_logs : []
}
//TODO 上线换成上面的
// prizelist.value = [
// { id: 1, prize_code: "FIRST", prize_name: "一等奖", coupon_type: "scene", pushed: 0 },
// { id: 2, prize_code: "FIRST1", prize_name: "二等奖", coupon_type: "scene", pushed: 1 }
// ]
}
const handleAddressSubmitAfter = (data) => {
const targetItem = prizelist.value.find(item => item.id === data.id)
targetItem.pushed = 1
addressShow.value = false
}
const handleAddress = (id) => {
activePrizeId.value = id
addressShow.value = true
}
globalToastEvent.on(ToastType.SHOW_TODO, () => {
todolistShow.value = true
})
globalToastEvent.on(ToastType.SHOW_PRIZELIST, async () => {
await getUserLottery()
prizeListShow.value = true
})
globalToastEvent.on(ToastType.SHOW_RULE, () => {
ruleShow.value = true
})
globalToastEvent.on(ToastType.SHOW_AD, () => {
adShow.value = true
})
globalToastEvent.on(ToastType.SHOW_LOTTERY, () => {
lotteryShow.value = true
})
globalToastEvent.on(ToastType.SHOW_ADDRESS, (id) => {
activePrizeId.value = id
addressShow.value = true
})
</script>
<template>
<PrizeList :show="prizeListShow" @close="prizeListShow = false" :prizelist="prizelist" @address="handleAddress">
</PrizeList>
<Todolist :show="todolistShow" @close="todolistShow = false"></Todolist>
<Rule :show="ruleShow" @close="ruleShow = false"></Rule>
<Address :show="addressShow" :prizeId="activePrizeId" @address-submit="handleAddressSubmitAfter"
@address-close="addressShow = false"></Address>
<Ad :show="adShow" @close="adShow = false"></Ad>
<!-- <Lottery :show="lotteryShow" @close="lotteryShow = false"></Lottery> -->
</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);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 348 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 321 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 321 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 716 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 323 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 732 KiB

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

@@ -0,0 +1,47 @@
@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, .7);
}
.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;
}
.vue-recycle-scroller__item-wrapper {
width: 100%;
display: flex;
flex-flow: wrap;
}
.vue-recycle-scroller.ready.direction-vertical .vue-recycle-scroller__item-view {
width: 42vw !important;
position: relative !important;
}

Binary file not shown.

71
src/components/Ad.vue Normal file
View File

@@ -0,0 +1,71 @@
<template>
<ModalTransition class="ad" :show="show" position="center">
<div class="ad-wrapper">
<div class="btn-ad" v-html="adHtml" @click="adGoto"></div>
<div class="close" @click="emit('close')"></div>
</div>
</ModalTransition>
</template>
<script setup>
import ModalTransition from "./ModalTransition.vue"
import { ref } from "vue"
import { isWeixinPlatform, miniJumpToActive, getMiniPageBtnHack } from "../libs/utils"
const props = defineProps({
show: false,
})
const emit = defineEmits(['close'])
const adHtml = ref('')
//TODO: 这个地址后面要改的
adHtml.value = getMiniPageBtnHack("/pages/retail-act/landing-page/ordinary?id=897432916524553363&orgId=200282401019674482&programId=84796583983972352")
const adGoto = () => {
if (!isWeixinPlatform()) {
weui.alert("请前往「泸州老窖会员中心」小程序进行查询")
}else{
miniJumpToActive()
}
}
</script>
<style>
.ad-wrapper {
position: relative;
width: 100vw;
height: 190vw;
background-image: url("../assets/images/ad-bg.webp");
background-repeat: no-repeat;
background-size: 100%;
}
.btn-ad {
width: 54.351852vw;
height: 20.092593vw;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, 180%);
background-image: url("../assets/images/btn-ad.webp");
background-repeat: no-repeat;
background-size: 100%;
}
.btn-ad wx-open-launch-weapp#launch-btn{
width: 100%;
height: 100%;
display: block !important;
}
.ad .close {
position: absolute;
width: 8.148148vw;
height: 8.148148vw;
top: 50%;
left: 50%;
transform: translate(-50%, 0);
margin-top: 58vw;
background-image: url("../assets/images/close-btn.webp");
background-repeat: no-repeat;
background-size: 100%;
}
</style>

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

@@ -0,0 +1,255 @@
<template>
<ModalTransition :show="show">
<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>

133
src/components/GameDemo.vue Normal file
View File

@@ -0,0 +1,133 @@
<script setup>
import { Request, Storage } from "../libs/utils"
import ModalTransition from "./ModalTransition.vue"
import { globalStore } from "../globalstore.js";
defineProps({
show: false
})
const emit = defineEmits(['close'])
// const emit = defineEmits(['update:show']);
const cancelBtn = () => {
emit('close')
}
</script>
<template>
<ModalTransition class="popup" :show="show">
<div class="home-bg">
<div class="popup-bg">
<div class="scene-item item-1">
<img src="../assets/images/new/start.png" @click="cancelBtn" alt="开始">
</div>
<div class="scene-item item-2">
<img src="../assets/images/new/start-1.png" @click="cancelBtn" alt="立即挑战">
</div>
<div class="scene-item item-3">
<img src="../assets/images/new/close-btn.png" @click="cancelBtn" alt="关闭">
</div>
</div>
</div>
</ModalTransition>
</template>
<style scoped>
.home-bg {
position: absolute;
top: 0;
left: 0;
}
.message {
width: 60vw;
position: absolute;
top: 42%;
color: #774107;
text-align: center;
}
.popup-bg {
width: 100vw;
height: 204vw;
background-image: url('../assets/images/new/demo-popup.png');
background-size: 100%;
background-repeat: no-repeat;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
position: relative;
}
.scene-item {
position: absolute;
cursor: pointer;
transition: all 0.4s ease;
border-radius: 8px;
overflow: hidden;
border: 3px solid transparent;
animation: float 4s ease-in-out infinite;
}
.scene-item:hover {
transform: scale(1.05);
}
.btn-login {
text-align: center;
border-radius: 1vw;
background-color: #70b2e2;
margin-top: 4vw;
padding: 3vw;
color: #fff;
position: relative;
}
.item-1 {
top: 114vw;
width: 20vw;
}
.item-2 {
top: 134vw;
width: 54vw;
}
.item-3 {
top: 152vw;
width: 12vw;
}
.scene-item img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.btn-login.disable {
opacity: .6;
}
.btn-login.subloading {
opacity: .6;
}
.btn-login.subloading::before {
content: "";
position: absolute;
border: .5vw solid #fff;
border-color: rgba(255, 255, 255, .8) transparent transparent transparent;
border-radius: 50%;
width: 3vw;
height: 3vw;
top: 30%;
left: calc(50% - 14vw);
animation: loginloading 1s linear infinite;
}
@keyframes loginloading {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>

363
src/components/GamePage.vue Normal file
View File

@@ -0,0 +1,363 @@
<template>
<ModalTransition class="game-swiper" :show="show">
<div class="scene-item item-1" @click="handleGoBack">
<img src="../assets/images/new/back.png" alt="后退">
</div>
<div class="scene-item item-2">
<img src="../assets/images/new/music.png" alt="音乐">
</div>
<!-- 隐藏的圆形点击区域 -->
<div
class="hidden-circular-area"
@click="handleShowLottery"
:style="{
top: circularArea.top,
left: circularArea.left,
width: circularArea.size,
height: circularArea.size
}"
></div>
<div class="game-page">
</div>
</ModalTransition>
<Lottery :show="lotteryShow" @close="lotteryShow = false" :type="lotteryType" :data="lotteryNoticeData" />
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import ModalTransition from "./ModalTransition.vue"
import Lottery from './Lottery.vue'
import globalToastEvent, { ToastType } from '../globalToastEvent';
const props = defineProps({
show: false,
})
const emit = defineEmits(['close'])
// const emit = defineEmits(['update:show'])
// Lottery 相关状态
const lotteryShow = ref(false)
const lotteryType = ref('draw')
const lotteryNoticeData = ref(null)
// 隐藏圆形点击区域的配置(可调整坐标和大小)
const circularArea = ref({
top: '78vw', // 可配置:距离顶部的位置
left: '50vw', // 可配置:距离左边的位置
size: '25vw' // 可配置:圆形区域的大小
})
// const handleGoHome = () => {
// emit('update:show', false)
// }
const handleGoBack = () => {
emit('close')
}
// 点击隐藏区域显示抽奖弹窗
const handleShowLottery = () => {
globalToastEvent.emit(ToastType.SHOW_LOTTERY)
}
// 轮播图数据
const slides = ref([
{
image: new URL('../assets/images/new/slider1.png', import.meta.url).href,
title: '轮播图1',
link: '/home' // 点击跳转的路由
},
{
image: new URL('../assets/images/new/slider1.png', import.meta.url).href,
title: '轮播图2',
link: '/lottery'
},
{
image: new URL('../assets/images/new/slider1.png', import.meta.url).href,
title: '轮播图3',
link: '/address'
}
])
const currentIndex = ref(0)
const startX = ref(0)
const moveX = ref(0)
const isDragging = ref(false)
const isTransitioning = ref(true)
let autoPlayTimer = null
// 触摸开始
const handleTouchStart = (e) => {
startX.value = e.touches[0].clientX
isDragging.value = true
isTransitioning.value = false
stopAutoPlay()
}
// 触摸移动
const handleTouchMove = (e) => {
if (!isDragging.value) return
moveX.value = e.touches[0].clientX - startX.value
}
// 触摸结束
const handleTouchEnd = () => {
if (!isDragging.value) return
isTransitioning.value = true
const threshold = 50 // 滑动阈值
if (moveX.value > threshold) {
// 向右滑动 - 上一张
prevSlide()
} else if (moveX.value < -threshold) {
// 向左滑动 - 下一张
nextSlide()
}
isDragging.value = false
moveX.value = 0
// startAutoPlay()
}
// 上一张
const prevSlide = () => {
currentIndex.value = currentIndex.value > 0
? currentIndex.value - 1
: slides.value.length - 1
}
// 下一张
const nextSlide = () => {
currentIndex.value = currentIndex.value < slides.value.length - 1
? currentIndex.value + 1
: 0
}
// 跳转到指定幻灯片
const goToSlide = (index) => {
isTransitioning.value = true
currentIndex.value = index
stopAutoPlay()
startAutoPlay()
}
// 点击幻灯片处理
const handleSlideClick = (slide) => {
}
// 自动播放
const startAutoPlay = () => {
autoPlayTimer = setInterval(() => {
nextSlide()
}, 3000) // 每3秒切换一次
}
// 停止自动播放
const stopAutoPlay = () => {
if (autoPlayTimer) {
clearInterval(autoPlayTimer)
autoPlayTimer = null
}
}
// 生命周期
onMounted(() => {
// startAutoPlay()
})
onUnmounted(() => {
stopAutoPlay()
})
</script>
<style scoped>
.scene-item {
position: fixed;
cursor: pointer;
transition: all 0.4s ease;
overflow: hidden;
animation: float 4s ease-in-out infinite;
}
.scene-item:hover {
transform: scale(1.05);
}
.scene-item img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.item-1 {
width: 14vw;
height: 14vw;
top: 8vw;
left: 2vw;
animation-delay: 0s;
}
.item-2 {
width: 12vw;
height: 12vw;
top: 8vw;
right: 2vw;
animation-delay: 0s;
}
.item-3 {
width: 46vw;
bottom: 14vw;
animation-delay: 0s;
}
.confirm-layout {
display: flex;
justify-content: center;
}
/* 隐藏的圆形点击区域 */
.hidden-circular-area {
position: fixed;
border-radius: 50%;
cursor: pointer;
transform: translate(-50%, -50%);
/* 隐藏样式 - 完全透明且不可见 */
background: transparent;
opacity: 0;
z-index: 100;
/* 开发调试时可以取消注释下面这行来查看区域位置 */
/* background: rgba(255, 0, 0, 0.3); */
}
.game-page {
width: 100%;
min-height: 100vh;
background: #f5f5f5;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
box-sizing: border-box;
background-image: url('../assets/images/new/game-1-bg.png');
background-size: cover;
background-repeat: no-repeat;
background-position: center;
}
.carousel-container {
width: 100%;
max-width: 100vw;
position: relative;
padding: 0 20px;
box-sizing: border-box;
}
.carousel-wrapper {
width: 80vw;
overflow: visible;
position: relative;
touch-action: pan-y;
}
.carousel-track {
display: flex;
flex-direction: row;
width: 100%;
padding: 20px 0;
}
.carousel-slide {
min-width: calc(100% - 160px);
width: calc(100% - 160px);
margin: 0 20px;
flex-shrink: 0;
cursor: pointer;
user-select: none;
transition: all 0.3s ease;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
transform: scale(0.85);
opacity: 0.5;
filter: grayscale(60%) brightness(0.7);
}
.carousel-slide.active {
transform: scale(1);
opacity: 1;
filter: grayscale(0%) brightness(1);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
z-index: 2;
}
.carousel-slide img {
width: 100%;
height: auto;
display: block;
object-fit: cover;
pointer-events: none;
}
/* 指示器 */
.carousel-indicators {
position: absolute;
bottom: -8vw;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 2vw;
z-index: 10;
padding: 2vw 4vw;
background: rgba(0, 0, 0, 0.3);
border-radius: 4vw;
backdrop-filter: blur(4px);
}
.indicator {
width: 10px;
height: 10px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.6);
cursor: pointer;
transition: all 0.3s ease;
border: 2px solid transparent;
}
.indicator:hover {
background: rgba(255, 255, 255, 0.8);
transform: scale(1.2);
}
.indicator.active {
background: #fff;
width: 28px;
border-radius: 5px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
/* 移动端适配 */
@media (max-width: 768px) {
.game-page {
padding: 10px;
}
.carousel-container {
padding: 0 10px;
}
.carousel-slide {
min-width: calc(100% - 12vw);
width: calc(100% - 12vw);
margin: 0 10px;
border-radius: 8px;
}
.carousel-track {
padding: 10px 0;
}
}
</style>

View File

@@ -0,0 +1,359 @@
<template>
<ModalTransition class="game-swiper" :show="show">
<div class="scene-item item-1" @click="handleGoHome">
<img src="../assets/images/new/go-home.png" alt="回到首页">
</div>
<div class="scene-item item-2">
<img src="../assets/images/new/music.png" alt="音乐">
</div>
<div class="confirm-layout">
<div class="scene-item item-3" @click="handleConfirmClick">
<img src="../assets/images/new/confirm-btn.png" alt="确定">
</div>
</div>
<div class="swiper-page">
<div class="carousel-container">
<div
class="carousel-wrapper"
@touchstart="handleTouchStart"
@touchmove="handleTouchMove"
@touchend="handleTouchEnd"
>
<div
class="carousel-track"
:style="{ transform: `translateX(calc(-${currentIndex * 100}% + ${currentIndex * 60}px))`, transition: isTransitioning ? 'transform 0.3s ease' : 'none' }"
>
<div
v-for="(slide, index) in slides"
:key="index"
:class="['carousel-slide', { active: currentIndex === index }]"
>
<img :src="slide.image" :alt="slide.title" />
</div>
</div>
</div>
<!-- 指示器 -->
<div class="carousel-indicators">
<span
v-for="(slide, index) in slides"
:key="index"
:class="['indicator', { active: currentIndex === index }]"
@click="goToSlide(index)"
></span>
</div>
</div>
</div>
</ModalTransition>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import ModalTransition from "./ModalTransition.vue"
import globalToastEvent, { ToastType } from '../globalToastEvent';
const props = defineProps({
show: false,
})
const emit = defineEmits(['update:show'])
const handleGoHome = () => {
emit('close')
}
// 轮播图数据
const slides = ref([
{
image: new URL('../assets/images/new/slider1.png', import.meta.url).href,
title: '轮播图1',
link: '/home' // 点击跳转的路由
},
{
image: new URL('../assets/images/new/slider1.png', import.meta.url).href,
title: '轮播图2',
link: '/lottery'
},
{
image: new URL('../assets/images/new/slider1.png', import.meta.url).href,
title: '轮播图3',
link: '/address'
}
])
const currentIndex = ref(0)
const startX = ref(0)
const moveX = ref(0)
const isDragging = ref(false)
const isTransitioning = ref(true)
let autoPlayTimer = null
// 触摸开始
const handleTouchStart = (e) => {
startX.value = e.touches[0].clientX
isDragging.value = true
isTransitioning.value = false
stopAutoPlay()
}
// 触摸移动
const handleTouchMove = (e) => {
if (!isDragging.value) return
moveX.value = e.touches[0].clientX - startX.value
}
// 触摸结束
const handleTouchEnd = () => {
if (!isDragging.value) return
isTransitioning.value = true
const threshold = 50 // 滑动阈值
if (moveX.value > threshold) {
// 向右滑动 - 上一张
prevSlide()
} else if (moveX.value < -threshold) {
// 向左滑动 - 下一张
nextSlide()
}
isDragging.value = false
moveX.value = 0
// startAutoPlay()
}
// 上一张
const prevSlide = () => {
currentIndex.value = currentIndex.value > 0
? currentIndex.value - 1
: slides.value.length - 1
}
// 下一张
const nextSlide = () => {
currentIndex.value = currentIndex.value < slides.value.length - 1
? currentIndex.value + 1
: 0
}
// 跳转到指定幻灯片
const goToSlide = (index) => {
isTransitioning.value = true
currentIndex.value = index
stopAutoPlay()
startAutoPlay()
}
const hasVisitedBefore = localStorage.getItem('hasVisitedGameSwiper');
const handleConfirmClick = () => {
const slide = slides.value?.[currentIndex.value]
if (!slide) return
handleSlideClick(slide)
}
const handleSlideClick = (slide) => {
if (!hasVisitedBefore) {
localStorage.setItem('hasVisitedGameSwiper', 'true');
globalToastEvent.emit(ToastType.SHOW_GAMEDEMO)
globalToastEvent.emit(ToastType.SHOW_GAMEPAGE)
} else {
globalToastEvent.emit(ToastType.SHOW_GAMEPAGE)
}
}
// 自动播放
const startAutoPlay = () => {
autoPlayTimer = setInterval(() => {
nextSlide()
}, 3000) // 每3秒切换一次
}
// 停止自动播放
const stopAutoPlay = () => {
if (autoPlayTimer) {
clearInterval(autoPlayTimer)
autoPlayTimer = null
}
}
// 生命周期
onMounted(() => {
// startAutoPlay()
})
onUnmounted(() => {
stopAutoPlay()
})
</script>
<style scoped>
.scene-item {
position: fixed;
cursor: pointer;
transition: all 0.4s ease;
overflow: hidden;
animation: float 4s ease-in-out infinite;
}
.scene-item:hover {
transform: scale(1.05);
}
.scene-item img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.item-1 {
width: 14vw;
height: 14vw;
top: 8vw;
left: 2vw;
animation-delay: 0s;
}
.item-2 {
width: 12vw;
height: 12vw;
top: 8vw;
right: 2vw;
animation-delay: 0s;
}
.item-3 {
width: 46vw;
bottom: 14vw;
animation-delay: 0s;
}
.confirm-layout {
display: flex;
justify-content: center;
}
.swiper-page {
width: 100%;
min-height: 100vh;
background: #f5f5f5;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
box-sizing: border-box;
background-image: url('../assets/images/new/swiper-page-bg.png');
background-size: cover;
background-repeat: no-repeat;
background-position: center;
}
.carousel-container {
width: 100%;
max-width: 100vw;
position: relative;
padding: 0 20px;
box-sizing: border-box;
}
.carousel-wrapper {
width: 80vw;
overflow: visible;
position: relative;
touch-action: pan-y;
}
.carousel-track {
display: flex;
flex-direction: row;
width: 100%;
padding: 20px 0;
}
.carousel-slide {
min-width: calc(100% - 160px);
width: calc(100% - 160px);
margin: 0 20px;
flex-shrink: 0;
cursor: pointer;
user-select: none;
transition: all 0.3s ease;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
transform: scale(0.85);
opacity: 0.5;
filter: grayscale(60%) brightness(0.7);
}
.carousel-slide.active {
transform: scale(1);
opacity: 1;
filter: grayscale(0%) brightness(1);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
z-index: 2;
}
.carousel-slide img {
width: 100%;
height: auto;
display: block;
object-fit: cover;
pointer-events: none;
}
/* 指示器 */
.carousel-indicators {
position: absolute;
bottom: -8vw;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 2vw;
padding: 2vw 4vw;
background: rgba(0, 0, 0, 0.3);
border-radius: 4vw;
backdrop-filter: blur(4px);
}
.indicator {
width: 10px;
height: 10px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.6);
cursor: pointer;
transition: all 0.3s ease;
border: 2px solid transparent;
}
.indicator:hover {
background: rgba(255, 255, 255, 0.8);
transform: scale(1.2);
}
.indicator.active {
background: #fff;
width: 28px;
border-radius: 5px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
/* 移动端适配 */
@media (max-width: 768px) {
.game-page {
padding: 10px;
}
.carousel-container {
padding: 0 10px;
}
.carousel-slide {
min-width: calc(100% - 12vw);
width: calc(100% - 12vw);
margin: 0 10px;
border-radius: 8px;
}
.carousel-track {
padding: 10px 0;
}
}
</style>

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

@@ -0,0 +1,663 @@
<script setup>
import { ref, computed, watch, onMounted } from "vue"
import bgm from "../assets/audio/bgm.mp3"
import globalToastEvent, { ToastType } from '../globalToastEvent';
import { globalStore } from "../globalstore.js";
import Login from '../components/Login.vue'
import { isWeixin, isLogin, getParam, Storage, Request } from "../libs/utils"
import Lottery from '../components/Lottery.vue';
import PrizeList from "./PrizeList.vue"
import Rule from "./Rule.vue";
import GameSwiper from "./GameSwiper.vue";
import Todolist from "./TodoList.vue";
import SharePage from "./SharePage.vue";
import GamePage from "./GamePage.vue";
import GameDemo from "./GameDemo.vue";
import Address from "./Address.vue";
import PopupMore from "./PopupMore.vue";
const props = defineProps({
show: true
})
const isMusicOn = ref(false);
const audioElement = ref(null);
const videoElement = ref(null);
const videoLoaded = ref(false);
const videoError = ref(false);
const lotteryShow = ref(false)
const lotteryType = ref("draw")
const lotteryNoticeData = ref(null)
// 初始化全局音频实例
const initGlobalAudio = () => {
if (!globalStore.globalAudio) {
globalStore.globalAudio = new Audio(bgm);
globalStore.globalAudio.loop = true; // 设置循环播放
globalStore.globalAudio.preload = 'auto';
// 监听音频事件
globalStore.globalAudio.addEventListener('play', () => {
isMusicOn.value = true;
});
globalStore.globalAudio.addEventListener('pause', () => {
isMusicOn.value = false;
});
globalStore.globalAudio.addEventListener('ended', () => {
isMusicOn.value = false;
});
}
audioElement.value = globalStore.globalAudio;
// 同步当前音乐状态
isMusicOn.value = !globalStore.globalAudio.paused;
};
onMounted(() => {
initGlobalAudio();
initVideo();
checkAndPlayAudio();
})
// 检查并播放音频
const checkAndPlayAudio = () => {
if (!audioElement.value) return;
// 如果用户曾经手动静音,则不自动播放
if (globalStore.userMutedMusic) {
console.log('用户曾经手动静音,不自动播放音乐');
isMusicOn.value = false;
return;
}
// 检查音频是否已在播放
if (!audioElement.value.paused) {
console.log('音频已在播放,跳过重复播放');
isMusicOn.value = true;
return;
}
// 只在首次访问或音乐没有被用户手动静音时才尝试自动播放
if (globalStore.isFirstVisitHomePage || !globalStore.userMutedMusic) {
const playPromise = audioElement.value.play();
if (playPromise !== undefined) {
playPromise.then(() => {
// 自动播放成功
isMusicOn.value = true;
globalStore.isFirstVisitHomePage = false;
console.log('自动播放成功');
})
.catch(error => {
// 自动播放被阻止
console.log('自动播放被阻止,需要用户交互:', error);
isMusicOn.value = false;
audioElement.value.pause();
});
}
} else {
// 非首次访问且用户没有手动静音,但音乐当前暂停,则保持暂停状态
isMusicOn.value = false;
}
};
// 初始化视频
const initVideo = () => {
document.addEventListener('WeixinJSBridgeReady',()=>{
videoElement.value && videoElement.value.play()
})
setTimeout(() => {
if (videoElement.value) {
const video = videoElement.value;
// 移动设备视频优化设置
video.muted = true;
video.loop = true;
video.autoplay = true;
video.playsInline = true;
video.setAttribute('webkit-playsinline', 'true');
video.setAttribute('playsinline', 'true');
video.setAttribute('x5-video-player-type', 'h5');
video.setAttribute('x5-video-player-fullscreen', 'false');
// 移动设备特殊处理
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
if (isMobile) {
video.preload = 'metadata'; // 移动设备使用较小的预加载
// 尝试在用户交互后播放
const playVideo = () => {
video.play().then(() => {
console.log('移动设备视频播放成功');
videoLoaded.value = true;
}).catch(error => {
console.log('移动设备视频播放失败:', error);
videoError.value = true;
});
};
// 在用户点击时尝试播放视频
document.addEventListener('touchstart', playVideo, { once: true });
document.addEventListener('click', playVideo, { once: true });
}
// 按钮交互触发视频播放(用于 iOS 等严格的自动播放限制)
const buttons = document.querySelectorAll('.scene-item');
buttons.forEach(button => {
button.addEventListener('click', () => {
if (video.paused && !videoError.value) {
video.play().catch(error => {
console.log('点击后视频播放失败:', error);
});
}
}, { once: true });
});
// 监听视频事件
video.addEventListener('loadedmetadata', () => {
console.log('视频元数据加载完成');
if (!isMobile) {
video.play().catch(error => {
console.log('PC设备视频自动播放失败:', error);
videoError.value = true;
});
}
});
video.addEventListener('canplay', () => {
videoLoaded.value = true;
videoError.value = false;
});
}
}, 200);
};
// 播放/暂停切换
const toggleMusicState = () => {
if (!audioElement.value) {
initGlobalAudio();
}
if (isMusicOn.value) {
// 当前在播放,用户点击静音
audioElement.value.pause();
globalStore.userMutedMusic = true; // 记录用户手动静音
isMusicOn.value = false;
console.log('用户手动静音音乐');
} else {
// 当前暂停,用户点击播放
const playPromise = audioElement.value.play();
if (playPromise !== undefined) {
playPromise.then(() => {
globalStore.userMutedMusic = false; // 用户重新开启音乐,清除静音标记
isMusicOn.value = true;
console.log('用户手动开启音乐');
})
.catch(error => {
console.log('播放失败:', error);
isMusicOn.value = false;
});
}
}
};
// 视频事件处理
const onVideoLoadStart = () => {
console.log('视频开始加载');
};
const onVideoCanPlay = () => {
console.log('视频可以播放');
videoLoaded.value = true;
videoError.value = false;
};
const onVideoLoaded = () => {
console.log('视频数据加载完成');
videoLoaded.value = true;
};
const onVideoError = (event) => {
console.error('视频加载错误:', event);
videoError.value = true;
videoLoaded.value = false;
};
const navigateTodoList = () => {
globalToastEvent.emit(ToastType.SHOW_TODO)
}
const popupMore = async () => {
globalToastEvent.emit(ToastType.SHOW_POPUPMORE)
}
const handleRule = () => {
globalToastEvent.emit(ToastType.SHOW_RULE)
}
const handleLottery = () => {
// if (globalStore.draw_chances <= 0) {
// return weui.alert("还没有抽奖机会,快去参加活动吧")
// };
lotteryType.value = 'draw'
lotteryNoticeData.value = null
globalToastEvent.emit(ToastType.SHOW_LOTTERY);
}
const loginShow = ref(false)
// 登录状态
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.draw_chances = result.json.draw_chances
globalStore.game_chances = result.json.game_chances
globalStore.first_share_today = result.json.first_share_today
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
globalToastEvent.emit(ToastType.MOUNTED)
}
}
const getNotice = async () => {
const result = await Request('notice/latest', {}, "GET")
if (result?.json?.notification) {
lotteryType.value = 'notice'
lotteryNoticeData.value = result.json.notification
lotteryShow.value = true
}
}
const handleLoginSuccess = async () => {
console.log("已登录")
loginShow.value = false
await initUserGameInfos(true, true)
await getNotice()
}
if (isLogin()) {
handleLoginSuccess()
} else {
userStatus(handleLoginSuccess)
}
let mergeId = '';
const urlParams = new URLSearchParams(window.location.search);
mergeId = urlParams.get('merge_id');
watch(() => mergeId, async (newVal) => {
if (!newVal) {
return
}
if (newVal) {
isPhotoSquareVisible.value = true;
}
}, { immediate: true })
globalToastEvent.on(ToastType.SHOW_LOTTERY, async () => {
// await initUserGameInfos(false, false);
lotteryShow.value = true
})
globalToastEvent.on(ToastType.INFO_UPDATE, async () => {
initUserGameInfos(false, false)
})
watch(() => lotteryShow.value, async (newVal) => {
initUserGameInfos(true, true);
}, { immediate: true })
const gameSwiperShow = ref(false);
const navigateGamePage = () => {
gameSwiperShow.value = true;
}
globalToastEvent.on(ToastType.SHOW_SWIPER, () => {
gameSwiperShow.value = true;
})
const ruleShow = ref(false)
globalToastEvent.on(ToastType.SHOW_RULE, () => {
ruleShow.value = true
})
const prizeListShow = ref(false)
const handlePrizeList = async () => {
await getUserLottery()
prizeListShow.value = true
}
const prizelist = ref([])
const getUserLottery = async () => {
const result = await Request("lottery", { pool: "all" }, "GET")
if (result.res.status === 200) {
prizelist.value = result.json.lottery_logs.length > 0 ? result.json.lottery_logs : []
}
//TODO 上线换成上面的
// prizelist.value = [
// { id: 1, prize_code: "FIRST", prize_name: "一等奖", coupon_type: "scene", pushed: 0 },
// { id: 2, prize_code: "FIRST1", prize_name: "二等奖", coupon_type: "scene", pushed: 1 }
// ]
}
globalToastEvent.on(ToastType.SHOW_PRIZELIST, async () => {
await getUserLottery()
prizeListShow.value = true
})
const todolistShow = ref(false)
globalToastEvent.on(ToastType.SHOW_TODO, () => {
todolistShow.value = true
})
const sharePageShow = ref(false)
globalToastEvent.on(ToastType.SHOW_SHAREPAGE, () => {
sharePageShow.value = true
})
const popupMoreShow = ref(false)
globalToastEvent.on(ToastType.SHOW_POPUPMORE, () => {
popupMoreShow.value = true
})
const gamePageShow = ref(false)
globalToastEvent.on(ToastType.SHOW_GAMEPAGE, () => {
gamePageShow.value = true
})
const gameDemoShow = ref(false)
globalToastEvent.on(ToastType.SHOW_GAMEDEMO, () => {
gameDemoShow.value = true
})
globalToastEvent.on(ToastType.CLOSE_ALL, () => {
gameSwiperShow.value = false;
gamePageShow.value = false;
gameDemoShow.value = false;
lotteryShow.value = false;
})
const activePrizeId = ref(0)
const addressShow = ref(false)
const handleAddressSubmitAfter = (data) => {
const targetItem = prizelist.value.find(item => item.id === data.id)
targetItem.pushed = 1
addressShow.value = false
}
const handleAddress = (id) => {
activePrizeId.value = id
addressShow.value = true
}
</script>
<template>
<div :show="show" class="main">
<div class="home-wrapper">
<div class="fallback-background"></div>
<div class="scene-item item-1" @click="handleLottery" :class="{ 'disabled': globalStore.draw_chances <= 0 }">
<img src="../assets/images/new/lottery.png" alt="抽奖">
</div>
<div class="scene-item item-2" @click="navigateGamePage">
<img src="../assets/images/new/start-btn.png" alt="开始探索">
</div>
<div class="scene-item item-3" @click="navigateTodoList">
<img src="../assets/images/new/task.png" alt="任务">
</div>
<div @click="toggleMusicState">
<div v-if="isMusicOn" key="on" class="scene-item item-4">
<img src="../assets/images/new/music.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" @click="handleRule">
<img src="../assets/images/new/rule.png" alt="规则">
</div>
<div class="scene-item item-7" @click="handlePrizeList">
<img src="../assets/images/new/award.png" alt="奖励">
</div>
<div class="scene-item item-8" @click="popupMore">
<img src="../assets/images/new/learn-more.png" alt="更多金喜">
</div>
<Rule :show="ruleShow" @close="ruleShow = false"></Rule>
<PrizeList :show="prizeListShow" @close="prizeListShow = false" :prizelist="prizelist" @address="handleAddress">
</PrizeList>
<Address :show="addressShow" :prizeId="activePrizeId" @address-submit="handleAddressSubmitAfter"
@address-close="addressShow = false"></Address>
</div>
</div>
<Login :show="loginShow" @login-success="handleLoginSuccess" />
<GameSwiper v-model:show="gameSwiperShow" @close="gameSwiperShow = false" />
<Todolist :show="todolistShow" @close="todolistShow = false"></Todolist>
<GamePage :show="gamePageShow" @close="gamePageShow = false" />
<GameDemo :show="gameDemoShow" @close="gameDemoShow = false" />
<Lottery :show="lotteryShow" @close="lotteryShow = false" :type="lotteryType" :data="lotteryNoticeData"></Lottery>
<SharePage :show="sharePageShow" @close="sharePageShow = false"></SharePage>
<PopupMore :show="popupMoreShow" @close="popupMoreShow = false"></PopupMore>
</template>
<style scoped>
.logo {
top: 4vw;
width: 24vw;
left: 4vw;
}
.slogan {
top: 20vw;
width: 76vw;
}
.home-title {
width: 72vw;
top: 144vw;
}
.main {
height: 100%;
overflow-y: auto;
}
.home-wrapper {
width: 100vw;
height: 200vw;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
position: relative;
min-height: -webkit-fill-available;
overflow: hidden;
/* 防止视频溢出 */
}
/* 视频背景样式 */
.background-video {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
/* 保持视频比例并填满容器 */
z-index: 0;
/* 置于最底层 */
pointer-events: none;
/* 禁止视频交互,避免影响按钮点击 */
}
/* fallback 背景图 */
.fallback-background {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: url('../assets/images/new/home-bg.png');
background-size: cover;
background-repeat: no-repeat;
background-position: center;
z-index: -1;
/* 置于视频下方 */
}
.scene-item {
position: fixed;
cursor: pointer;
transition: all 0.4s ease;
overflow: hidden;
animation: float 4s ease-in-out infinite;
}
.scene-item:hover {
transform: scale(1.05);
}
.scene-item img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.item-1 {
width: 21vw;
bottom: 7vw;
left: 0;
animation-delay: 0s;
}
.lottery-main {
display: flex;
justify-content: center;
align-items: center;
width: 4vw;
height: 3.5vw;
position: absolute;
top: 1.7vw;
right: 7.3vw;
color: #fff;
}
.lottery-main .lottery-value {
font-size: 4vw;
}
.item-2 {
width: 48vw;
bottom: 5vw;
animation-delay: 0s;
}
.join-main {
position: absolute;
top: 3.4vw;
right: -3.5vw;
width: 13vw;
}
.join-main .join-value {
margin: 0;
color: white;
text-shadow: -1px -1px 0 #ff0000, 1px -1px 0 #ff0000, -1px 1px 0 #ff0000, 1px 1px 0 #ff0000, 0 0 2px #ff0000;
font-size: 5.4vw;
font-weight: 900;
}
.item-3 {
width: 21vw;
bottom: 7vw;
right: 0;
animation-delay: 0s;
}
.item-4 {
width: 11vw;
top: 1.5%;
right: 7%;
animation-delay: 0s;
}
.item-5 {
width: 11vw;
top: 1.5%;
right: 1.5%;
animation-delay: 0s;
}
.item-6 {
width: 14.5vw;
top: 8%;
right: 0;
animation-delay: 0s;
}
.item-7 {
width: 14.5vw;
top: 12%;
right: 0;
animation-delay: 0s;
}
.item-8 {
width: 27vw;
bottom: 27vw;
right: 1vw;
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, ''), test: 'uNeverN0WhatTh1s' })
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, ''), agree: 1 })
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>

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

@@ -0,0 +1,642 @@
<template>
<ModalTransition class="lottery" :show="show" name="opacity" position="center">
<div class="lottery-wrapper">
<div class="guang" ref="guangRef"></div>
<div class="guangyun" ref="guangyunRef"></div>
<div class="lottery-scene" ref="sceneRef">
<div class="lottery-card" ref="cardRef">
<div class="lottery-prize">
<div class="lottery-layer" ref="layerRef">
<!-- 底层中奖图片被遮罩层盖住主要用于语义与对齐 -->
<div class="lottery-image" :class="'USER_' + activePrizeData.prize_code" />
<!-- 中层遮罩层背景默认完全遮住中奖图 -->
<div class="lottery-mask-image" ref="maskBgRef" />
<!-- 顶层通过 mask-img.png 显示中奖图片的一小块并自动放大揭晓 -->
<div
class="lottery-reveal lottery-image"
:class="[
'USER_' + activePrizeData.prize_code,
revealDone ? 'reveal-done' : ''
]"
ref="revealRef"
v-show="activePrizeData.prize_code !== 'NO'"
/>
<!-- 可视化的 mask否则用户看不到 mask-img.png 本身 -->
<div
class="lottery-mask-visual"
:class="revealDone ? 'reveal-done' : ''"
v-show="activePrizeData.prize_code !== 'NO'"
/>
</div>
<!-- 标题保持原逻辑 -->
<div
class="lottery-title"
:class="props.type === 'notice' && 'notice'"
ref="titleRef"
v-show="activePrizeData.prize_code !== 'NO'"
></div>
<div class="lottery-btngroup" v-show="btngroupShow">
<div class="btn-more" @click="navigatePopupMore"></div>
<div class="btn-kaixin" @click="handleLotteryAction" v-if="activePrizeData.prize_code !== 'NO'"></div>
</div>
</div>
</div>
</div>
</div>
</ModalTransition>
</template>
<script setup>
import { gsap } from "gsap";
import { ref, watch, onMounted, onUnmounted } from "vue"
import globalToastEvent, { ToastType } from "../globalToastEvent";
import ModalTransition from "./ModalTransition.vue"
import confetti from "canvas-confetti";
import { Request, Sleep } from "../libs/utils"
import { globalStore } from "@/globalstore";
import getUserPicture from "../libs/getUserPicture";
const navigatePopupMore = () => {
globalToastEvent.emit(ToastType.SHOW_POPUPMORE)
// globalToastEvent.emit(ToastType.SHOW_SHAREPAGE)
}
// const lotteryFaceUrl = ref('')
const cardRef = ref(null)
const sceneRef = ref(null)
const guangRef = ref(null)
const guangyunRef = ref(null)
// 旧的 lotteryImageRef 仍保留(避免影响其他逻辑),但遮罩/揭晓现在用下面两个 ref
const lotteryImageRef = ref(null)
const maskBgRef = ref(null)
const layerRef = ref(null)
const revealRef = ref(null)
const titleRef = ref(null)
const activePrizeData = ref({})
const btngroupShow = ref(false)
const revealDone = ref(false)
gsap.registerPlugin()
const props = defineProps({
show: false,
type: "draw",
data: null
})
const emit = defineEmits(['close'])
let interval = null
let gsapCtx = null
let PRIZEDATA = null
const closeThis = () => {
initAnimateStyle()
emit("close")
}
// const handleBtnMore = () => {
// closeThis()
// globalToastEvent.emit(ToastType.SHOW_AD)
// }
const readNotice = async () => {
await Request(`notice/read`, { id: activePrizeData.value.noticeId })
}
const handleLotteryAction = async () => {
//实物
// closeThis()
globalToastEvent.emit(ToastType.CLOSE_ALL);
if (props.type !== 'draw') {
await readNotice()
}
if (activePrizeData.value.coupon_type === 'scene') {
globalToastEvent.emit(ToastType.SHOW_ADDRESS, activePrizeData.value.id)
}
}
onMounted(() => {
gsap.registerPlugin()
// initAnimateStyle()
})
const initAnimateStyle = () => {
interval && clearInterval(interval)
interval = null
let keyframes = [];
const frames = 20; // 关键帧数量
const strength = 5; // 抖动强度
// 生成关键帧
for (let i = 0; i < frames; i++) {
keyframes.push({
x: gsap.utils.random(-strength, strength),
y: gsap.utils.random(-strength, strength),
duration: 0.05 // 每帧的持续时间
});
}
revealDone.value = false
btngroupShow.value = false
// 重置遮罩层/揭晓层的初始状态
if (maskBgRef.value) {
gsap.set(maskBgRef.value, { opacity: 1 })
}
// 以 50x50px 为起点(按你的假设),把变量放在容器上,便于同时驱动 reveal + 可视化 mask
if (layerRef.value) {
gsap.set(layerRef.value, { '--maskSize': '50px' })
}
}
watch(() => props.show, async (newVal) => {
if (!newVal) {
return
}
const loading = weui.loading()
let lottteryResult = null
// if (props.type === 'draw') {
// lottteryResult = await Request("lottery/draw", { pool: 'game', consume_type: 'points' })
// // 监听请求失败,关闭组件
// if (!lottteryResult || !lottteryResult.res || (lottteryResult.res.status !== 200 && lottteryResult.res.status !== 201)) {
// emit('close')
// loading.hide()
// return
// }
// } else {
// if (!props.data) {
// weui.alert("获取排名奖励出错,请刷新页面重试")
// emit('close')
// loading.hide()
// return
// }
// lottteryResult = {
// res: { status: 200 },
// json: {
// noticeId: props.data.id,
// id: props.data.data.prize.id,
// prize_code: props.data.data.prize.prize_code,
// prize_name: props.data.data.prize.name,
// coupon_type: props.data.data.prize.scene,
// rank: props.data.data.rank
// }
// }
// }
// const userPicture = await getUserPicture()
// const preimg = async() => {
// new Promise((resolve) => {
// const img = new Image()
// img.onload = () => resolve(img)
// img.src = userPicture
// })
// }
// await preimg()
// lotteryFaceUrl.value = userPicture
// TODO: 延时测试,上限删掉
// await Sleep(20000)
// TODO: 测试数据
lottteryResult = {
res: { status: 200 },
json: {
id: 0,
code: "xxx",
// NOTE: 测试用,确保能命中现有的样式映射,便于看到揭晓效果
prize_code: 'LZ_ZQ_DZJ',
coupon_type: "scene",
name: "泸州老窖的一瓶酒"
}
}
loading.hide()
if (lottteryResult.res.status !== 200) {
emit('close')
return
} else {
globalStore.lotteryCut()
activePrizeData.value = lottteryResult.json
// 未中奖的情况
if (lottteryResult.json.code === 204) {
activePrizeData.value = { prize_code: 'NO', coupon_type: 'NO' }
}
// kapianCover.value = PRIZEDATA.prize_code === '66_POINTS' ? 'POINTS' : PRIZEDATA.prize_code
}
gsapCtx = gsap.context(() => {
initAnimateStyle()
const prizeshowTime = gsap.timeline({
onComplete: () => {
btngroupShow.value = true
}
});
prizeshowTime.to([sceneRef.value], {
duration: 2.5,
scale: 1,
opacity: 1,
ease: "power4.inOut"
})
// 中奖揭晓:先显示 2 秒,再用更慢、线性的放大过程(更清晰地看到“逐渐放大”)
if (layerRef.value && activePrizeData.value.prize_code !== 'NO') {
// 保证起始大小50x50px
gsap.set(layerRef.value, { '--maskSize': '50px' })
// 先停留 2 秒,让用户看清初始“窗口”
prizeshowTime.to({}, { duration: 0.5 })
// 再逐渐放大到 50 倍2500px
prizeshowTime.to(layerRef.value, {
duration: 5.0,
ease: "none",
'--maskSize': '2500px',
onComplete: () => {
revealDone.value = true
// 动画结束后把遮罩背景淡出,让中奖图完全可见
maskBgRef.value && gsap.to(maskBgRef.value, { duration: 0.3, opacity: 0 })
}
})
}
// 兼容旧动画(如果外部还在依赖 lotteryImageRef
if (lotteryImageRef.value) {
prizeshowTime.to(lotteryImageRef.value, {
duration: 1,
scale: 1,
ease: "power4.inOut"
}, "-=1.5")
}
prizeshowTime.to(guangyunRef.value, {
duration: 1.5,
scale: 2,
}, "-=1")
prizeshowTime.to(titleRef.value, {
scaleY: 1,
ease: "elastic.out(1,0.3)"
})
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);
})
})
onUnmounted(() => {
gsapCtx && gsapCtx.revert()
})
</script>
<style scoped>
.lottery-layer {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
/* 叠加区域尺寸:按需求宽度 80vw并水平/垂直居中 */
width: 80vw;
height: 80vw;
max-height: 80vh;
}
/* 中层遮罩背景(默认完全盖住中奖图) */
.lottery-mask-image {
height: 110vw;
width: 82vw;
margin-top: -30vw;
position: absolute;
inset: 0;
background-image: url("../assets/images/new/lottery-mask-bg.png");
background-repeat: no-repeat;
/* 保持图片比例:区域 80vw 内居中显示 */
background-size: contain;
background-position: center;
z-index: 2;
}
/* 底层/顶层复用:中奖图 */
.lottery-image {
height: 110vw;
width: 82vw;
margin-top: -30vw;
position: absolute;
inset: 0;
background-position: center;
background-repeat: no-repeat;
/* 保持图片比例:区域 80vw 内居中显示 */
background-size: contain;
z-index: 1;
}
/* 顶层:通过 mask-img.png 露出中奖图,并放大 mask 直到全露出 */
.lottery-reveal {
z-index: 3;
/* CSS MaskWebKit + 标准) */
/* -webkit-mask-image: url("../assets/images/new/mask-img.png");
-webkit-mask-repeat: no-repeat;
-webkit-mask-position: center;
-webkit-mask-size: var(--maskSize) var(--maskSize);
mask-image: url("../assets/images/new/mask-img.png");
mask-repeat: no-repeat;
mask-position: center;
mask-size: var(--maskSize) var(--maskSize); */
-webkit-clip-path: circle(calc(var(--maskSize) * 0.5) at 50% 50%);
clip-path: circle(calc(var(--maskSize) * 0.5) at 50% 50%);
}
/* 可视化的 mask-img.png让用户能看到“遮罩在放大”的过程 */
.lottery-mask-visual {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: var(--maskSize);
height: var(--maskSize);
/* background-image: url("../assets/images/new/mask-img.png"); */
background-repeat: no-repeat;
background-size: 100% 100%;
background-position: center;
z-index: 4;
pointer-events: none;
}
.lottery-reveal.reveal-done {
-webkit-clip-path: none;
clip-path: none;
}
.lottery-mask-visual.reveal-done {
opacity: 0;
}
/* 动画结束后:移除 mask避免某些机型在超大 mask-size 时渲染抖动 */
.lottery-reveal.reveal-done {
-webkit-mask-image: none;
mask-image: none;
}
.lottery-image.USER_LZ_ZQ_DZJ {
background-image: url("../assets/images/new/zhongjiang1.png");
}
.lottery-image.USER_HG_42_GPJ_500ML {
background-image: url("../assets/images/USER_HG_42_GPJ_500ML.webp");
}
.lottery-image.USER_QLL_MH {
background-image: url("../assets/images/USER_QLL_MH.webp");
}
.lottery-image.USER_DZSKSJ {
background-image: url("../assets/images/USER_DZSKSJ.webp");
}
.lottery-image.USER_LZ_ZQ_DZJ_LJW {
background-image: url("../assets/images/USER_LZ_ZQ_DZJ_LJW.webp");
}
.lottery-image.USER_NO {
background-size: 60% auto;
background-position: center center;
background-image: url("../assets/images/USER_NO.webp");
}
.lottery-image.USER_LZ_XS_SPZ_30ML {
background-image: url("../assets/images/USER_LZ_XS_SPZ_30ML.webp");
}
.lottery-image.USER_LZ_DZ_JBGJ {
background-image: url("../assets/images/USER_LZ_DZ_JBGJ.webp");
}
.lottery-image.USER_LZ_DZ_JBGJ_LZ_ZQ_DZJ {
background-image: url("../assets/images/USER_LZ_DZ_JBGJ_LZ_ZQ_DZJ.webp");
}
.guang,
.guangyun {
position: absolute;
left: 50%;
margin-left: -66.2vw;
top: 50%;
margin-top: -64.5vw;
width: 129.62963vw;
height: 129.62963vw;
background-repeat: no-repeat;
background-size: 100%;
}
.guang {
background-image: url("../assets/images/guang.webp");
animation: rotateGuang 20s linear infinite;
}
.guangyun {
background-image: url("../assets/images/guangyun.webp");
/* animation: rotateGuangyun 3s linear infinite; */
}
.lottery-title {
position: absolute;
top: 24vw;
left: 50%;
transform: translateX(-50%) !important;
width: 65.462963vw;
height: 19.907407vw;
background-image: url("../assets/images/new/lottery-title.png");
background-repeat: no-repeat;
background-size: 100%;
z-index: 10;
}
.lottery-title.notice {
width: 39.259259vw;
height: 7.12963vw;
background-image: url("../assets/images/notice-title.webp");
}
.lottery-desc {
position: absolute;
top: 32vw;
left: 50%;
transform: translateX(-50%) !important;
width: 81vw;
font-size: 5vw;
font-weight: 700;
text-align: center;
color: rgb(255, 255, 255);
text-shadow:
0 0 0.37037vw #ff0000,
0 0 0.740741vw #ff0000,
0 0 1.111111vw #ff0000,
0.092593vw 0.092593vw 0 #ff0000,
-0.092593vw -0.092593vw 0 #ff0000;
}
.lottery-name {
position: absolute;
bottom: 18vw;
text-align: center;
width: 80%;
left: 10%;
font-size: 4.3vw;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-weight: 700;
color: rgb(255, 255, 255);
text-shadow:
0 0 0.37037vw #ff0000,
0 0 0.740741vw #ff0000,
0 0 1.111111vw #ff0000,
0.092593vw 0.092593vw 0 #ff0000,
-0.092593vw -0.092593vw 0 #ff0000;
}
.lottery-btngroup {
position: absolute;
bottom: 56vw;
width: 100%;
display: flex;
justify-content: space-evenly;
z-index: 10;
}
.btn-more {
width: 48.703704vw;
height: 18.240741vw;
background-image: url("../assets/images/new/more-btn.png");
background-repeat: no-repeat;
background-size: 100%;
}
.btn-kaixin {
width: 48.703704vw;
height: 18.240741vw;
background-image: url("../assets/images/new/btn-kaixin.png");
background-repeat: no-repeat;
background-size: 100%;
}
@keyframes rotateGuangyun {
0% {
transform: scale(.3);
opacity: 0;
}
50% {
transform: scale(1);
opacity: 1;
}
100% {
transform: scale(1.4);
opacity: 0;
}
}
@keyframes rotateGuang {
from {
transform: rotate(0deg);
}
to {
transform: rotate(-360deg);
}
}
.lottery-scene {
position: relative;
width: 88.055556vw;
height: 148.796296vw;
}
.lottery-card {
position: absolute;
width: 100%;
height: 100%;
top: -14vw;
}
.lottery-face {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
backface-visibility: hidden;
}
.lottery-card img {
width: 100%;
display: block;
}
.lottery-prize {
position: absolute;
/* top: 0;
left: 0;
width: 100%;
height: 100%; */
top: -20vw;
left: -6vw;
width: 100vw;
height: 244vw;
background-image: url("../assets/images/new/lottery-bg.png");
background-repeat: no-repeat;
background-size: 100%;
}
.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%;
}
</style>

View File

@@ -0,0 +1,130 @@
<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,
noAnimation: {
default: false,
type: Boolean
}
})
const animateName = ref('')
const directionClass = ref('')
if (!props.noAnimation) {
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>
.global-modal-content {
z-index: 10;
}
.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>

159
src/components/Popup.vue Normal file
View File

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

View File

@@ -0,0 +1,161 @@
<script setup>
import { isWeixinPlatform, miniJumpToScene, getMiniPageBtnHack } from "../libs/utils"
import ModalTransition from "./ModalTransition.vue"
import { globalStore } from "../globalstore.js";
defineProps({
show: false
})
const emit = defineEmits(['close'])
// const emit = defineEmits(['update:show']);
const cancelBtn = () => {
emit('close')
}
const startNow = () => {
if (isWeixinPlatform()) {
miniJumpToScene()
} else {
weui.alert("请前往「泸州老窖会员中心」小程序进行查询")
}
}
</script>
<template>
<ModalTransition class="popup" :show="show">
<div class="home-wrapper">
<div class="fullsection">
<div class="haibao">
</div>
</div>
<div class="scene-item item-2">
<img src="../assets/images/new/start-now.png" @click="startNow" alt="立即参与">
</div>
<div class="scene-item item-3">
<img src="../assets/images/new/close-btn.png" @click="cancelBtn" alt="关闭">
</div>
</div>
</ModalTransition>
</template>
<style scoped>
.haibao {
width: 70vw;
height: 119.444444vw;
background-image: url("../assets/images/new/sanchongli.png");
background-repeat: no-repeat;
background-size: 100%;
}
.home-wrapper {
width: 100vw;
height: 217vw;
background-image: url("../assets/images/new/share-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;
}
.home-bg {
position: absolute;
top: 0;
left: 0;
}
.message {
width: 60vw;
position: absolute;
top: 42%;
color: #774107;
text-align: center;
}
.popup-bg {
width: 100vw;
height: 204vw;
/* background-image: url('../assets/images/new/sanchongli.png'); */
background-size: 100%;
background-repeat: no-repeat;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
position: relative;
}
.scene-item {
position: absolute;
cursor: pointer;
transition: all 0.4s ease;
border-radius: 8px;
overflow: hidden;
border: 3px solid transparent;
animation: float 4s ease-in-out infinite;
}
.scene-item:hover {
transform: scale(1.05);
}
.btn-login {
text-align: center;
border-radius: 1vw;
background-color: #70b2e2;
margin-top: 4vw;
padding: 3vw;
color: #fff;
position: relative;
}
.item-1 {
top: 18vw;
width: 86vw;
}
.item-2 {
top: 147vw;
width: 46vw;
}
.item-3 {
top: 162vw;
width: 12vw;
}
.scene-item img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.btn-login.disable {
opacity: .6;
}
.btn-login.subloading {
opacity: .6;
}
.btn-login.subloading::before {
content: "";
position: absolute;
border: .5vw solid #fff;
border-color: rgba(255, 255, 255, .8) transparent transparent transparent;
border-radius: 50%;
width: 3vw;
height: 3vw;
top: 30%;
left: calc(50% - 14vw);
animation: loginloading 1s linear infinite;
}
@keyframes loginloading {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>

View File

@@ -0,0 +1,203 @@
<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, watch } 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>
.prizelist {
position: relative;
width: 100vw;
height: 110vw;
background-image: url("../assets/images/new/prizelist-bg.png");
background-repeat: no-repeat;
background-size: 100%;
display: flex;
align-items: flex-end;
}
.prizelist-wrapper {
width: 100%;
height: 74vw;
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.833333vw;
height: 20.648148vw;
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: #48260a;
overflow: hidden;
text-overflow: ellipsis;
-webkit-line-clamp: 2;
display: -webkit-box;
-webkit-box-orient: vertical;
}
.prizelist-close {
position: absolute;
width: 11vw;
height: 11vw;
right: 3vw;
top: -14vw;
background-image: url("../assets/images/new/close-btn.png");
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/new/btn-goto.png");
background-repeat: no-repeat;
background-size: 100%;
}
wx-open-launch-weapp{
width: 100%;
height: 100%;
}
.btn-goto wx-open-launch-weapp {
display: block;
}
wx-open-launch-weapp,
.btn-goto.noaddress wx-open-launch-weapp {
display: none;
}
.btn-goto.noaddress {
background-image: url("../assets/images/new/btn-address.png");
}
.prizelist-cover.USER_LZ_ZQ_DZJ {
background-image: url("../assets/images/generate/USER_LZ_ZQ_DZJ.webp");
}
.prizelist-cover.USER_HG_42_GPJ_500ML {
background-image: url("../assets/images/USER_HG_42_GPJ_500ML.webp");
}
.prizelist-cover.USER_QLL_MH {
background-image: url("../assets/images/USER_QLL_MH.webp");
}
.prizelist-cover.USER_DZSKSJ {
background-image: url("../assets/images/generate/USER_DZSKSJ.webp");
}
.prizelist-cover.USER_LZ_ZQ_DZJ_LJW {
background-image: url("../assets/images/USER_LZ_ZQ_DZJ_LJW.webp");
}
.prizelist-cover.USER_LZ_XS_SPZ_30ML {
background-image: url("../assets/images/generate/USER_LZ_XS_SPZ_30ML.webp");
}
.prizelist-cover.USER_LZ_DZ_JBGJ {
background-image: url("../assets/images/USER_LZ_DZ_JBGJ.webp");
}
.prizelist-cover.USER_LZ_DZ_JBGJ_LZ_ZQ_DZJ {
background-image: url("../assets/images/generate/USER_LZ_DZ_JBGJ_LZ_ZQ_DZJ.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>

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

@@ -0,0 +1,59 @@
<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'])
//TODO 上线更换rule的图片
</script>
<style scoped>
.rule-wrapper {
position: relative;
width: 100%;
height: 182vw;
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: 11vw;
height: 11vw;
right: 3vw;
top: 11vw;
background-image: url("../assets/images/new/close-btn.png");
background-repeat: no-repeat;
background-size: 100%;
}
</style>

View File

@@ -0,0 +1,207 @@
<script setup>
import { Request, Storage, generateQR } from "../libs/utils.js"
import ModalTransition from "./ModalTransition.vue"
import { ref, onMounted, computed } from "vue"
import Haibao from "../libs/haibao.js"
import ruleBg from "../assets/images/new/rule-share.png"
import globalToastEvent, { ToastType } from '../globalToastEvent';
const props = defineProps({
show: false,
})
const emit = defineEmits(['close'])
// const emit = defineEmits(['update:show'])
const gotoLottery = () => {
globalToastEvent.emit(ToastType.SHOW_LOTTERY);
}
const cancelBtn = () => {
emit('close')
}
// 海报
const haibaoShow = ref(false);
const haibaoUrl = ref('');
const openHaibao = (e) => {
haibaoShow.value = true
handleHaibao()
}
onMounted(openHaibao)
const handleHaibao = async () => {
const loading = weui.loading()
const infos = Storage.get("userinfos")
const haibaoCover = new Haibao(1080, 2160);
haibaoCover.add(ruleBg, 0, 0)
const qrcode = await generateQR(`fromid=${infos.invite_code}&merge_id=${infos.mergeId}`, 200, 200);
haibaoCover.add(qrcode, 742, 1870);
haibaoCover.draw().then(() => {
haibaoCover.generate({ mimeType: 'image/png' }).then(async (url) => {
haibaoUrl.value = url
loading.hide()
}).catch(err => {
console.log(err)
weui.alert("海报生成失败,请重新生成")
loading.hide()
})
})
}
</script>
<template>
<ModalTransition class="popup" :show="show">
<div class="home-wrapper">
<div class="fullsection">
<div class="haibao">
<img :src="haibaoUrl" alt="">
</div>
</div>
<!-- <div class="scene-item item-4">
<img src="../assets/images/new/choujiang.png" @click="gotoLottery" alt="立即抽奖">
</div> -->
<div class="scene-item item-5">
<img src="../assets/images/new/close-btn.png" @click="cancelBtn" alt="关闭">
</div>
</div>
</ModalTransition>
</template>
<style scoped>
.home-wrapper {
width: 100vw;
height: 217vw;
background-image: url("../assets/images/new/share-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;
}
.haibao {
width: 70vw;
height: 119.444444vw;
background-image: url("../assets/images/new/share-popup-demo.png");
background-repeat: no-repeat;
background-size: 100%;
}
.haibao img {
width: 100%;
height: 100%;
display: block;
opacity: 0;
}
.home-bg {
position: absolute;
top: 0;
left: 0;
}
.message {
width: 60vw;
position: absolute;
top: 42%;
color: #774107;
text-align: center;
}
.share-bg {
width: 100vw;
height: 204vw;
background-image: url('../assets/images/new/share-bg.png');
background-size: 100%;
background-repeat: no-repeat;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
position: relative;
}
.scene-item {
position: absolute;
cursor: pointer;
transition: all 0.4s ease;
border-radius: 8px;
overflow: hidden;
border: 3px solid transparent;
animation: float 4s ease-in-out infinite;
}
.scene-item:hover {
transform: scale(1.05);
}
.btn-login {
text-align: center;
border-radius: 1vw;
background-color: #70b2e2;
margin-top: 4vw;
padding: 3vw;
color: #fff;
position: relative;
}
.item-1 {
top: 114vw;
width: 20vw;
}
.item-2 {
top: 134vw;
width: 54vw;
}
.item-3 {
top: 164vw;
width: 13vw;
}
.item-4 {
top: 166vw;
width: 40vw;
}
.item-5 {
top: 166vw;
width: 14vw;
}
.scene-item img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.btn-login.disable {
opacity: .6;
}
.btn-login.subloading {
opacity: .6;
}
.btn-login.subloading::before {
content: "";
position: absolute;
border: .5vw solid #fff;
border-color: rgba(255, 255, 255, .8) transparent transparent transparent;
border-radius: 50%;
width: 3vw;
height: 3vw;
top: 30%;
left: calc(50% - 14vw);
animation: loginloading 1s linear infinite;
}
@keyframes loginloading {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>

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

@@ -0,0 +1,241 @@
<template>
<ModalTransition class="todolist" :show="show">
<div class="todolist-wrapper">
<div class="btn-group">
<div class="btn-share" @click="openHaibao($event)"></div>
<div class="btn-qiwei" :class="globalStore.followed_official && 'has'" @click="openQiwei($event)"></div>
</div>
<div class="close" @click="$emit('close')"></div>
</div>
<div class="fullsection" v-show="haibaoShow">
<div class="haibao" :style="userhaibaoCover">
<img :src="haibaoUrl" alt="">
<div class="close" @click="haibaoShow = false"></div>
</div>
</div>
<div class="fullsection" v-show="qiweiShow">
<div class="qiwei">
<img src="../assets/images/new/qiwei.png" alt="">
<div class="close" @click="qiweiShow = false"></div>
</div>
</div>
</ModalTransition>
</template>
<script setup>
import { ref, computed } from "vue"
import { globalStore } from "@/globalstore";
import ModalTransition from "./ModalTransition.vue";
import globalToastEvent, { ToastType } from '../globalToastEvent';
const props = defineProps({
show: false,
})
const emit = defineEmits(['close', 'open'])
const qiweiShow = ref(false)
const haibaoShow = ref(false)
const haibaoUrl = ref('')
const userHaibaoUrl = ref('')
const userhaibaoCover = computed(() => {
return { backgroundImage: `url(${userHaibaoUrl.value})` }
})
const openHaibao = async (e) => {
globalToastEvent.emit(ToastType.SHOW_SHAREPAGE);
}
const openQiwei = (e) => {
const target = e.currentTarget
if (target.classList.contains("has")) {
return
}
qiweiShow.value = true
}
</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;
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);
}
.scene-item img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.item-1 {
width: 10vw;
bottom: 54%;
right: 3%;
animation-delay: 0s;
}
.item-1 {
width: 10vw;
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: 80.740741vw;
/* 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 img {
width: 100%;
height: 100%;
display: block;
opacity: 0;
}
.close {
position: absolute;
width: 11vw;
height: 11vw;
right: 3vw;
top: -14vw;
background-image: url("../assets/images/new/close-btn.png");
background-repeat: no-repeat;
background-size: 100%;
}
.haibao .close {
left: 50%;
bottom: -5vw;
top: initial;
transform: translate3d(-50%, 0, 0);
}
.qiwei .close {
right: 50%;
top: 126vw;
margin-right: -6vw;
}
.todolist-wrapper {
position: relative;
width: 100vw;
height: 94vw;
background-image: url("../assets/images/new/todo-bg.png");
background-repeat: no-repeat;
background-size: 100%;
}
.btn-group {
position: absolute;
right: 6vw;
top: 35vw;
width: 24.907407vw;
height: 46vw;
display: flex;
flex-direction: column;
justify-content: space-around;
}
.btn-group div {
width: 24.907407vw;
height: 10vw;
background-repeat: no-repeat;
background-size: 100%;
}
.btn-share {
background-image: url("../assets/images/new/share-link.png");
}
.btn-share.has {
background-image: url("../assets/images/new/limit-btn.png");
}
.btn-qiwei {
background-image: url("../assets/images/new/add-wx.png");
}
.btn-qiwei.has {
background-image: url("../assets/images/btn-added.webp");
}
.btn-scan {
background-image: url("../assets/images/scan-code.webp");
}
.btn-scan.has {
background-image: url("../assets/images/new/limit-btn.png");
}
</style>

21
src/globalToastEvent.js Normal file
View File

@@ -0,0 +1,21 @@
import mitt from 'mitt';
const globalToastEvent = mitt();
export const ToastType = {
SHOW_TODO: "show_todo",
SHOW_RULE: "show_rule",
SHOW_PRIZELIST: "show_prizelist",
SHOW_LOTTERY: "show_lottery",
SHOW_AD: "show_ad",
SHOW_ADDRESS: "show_address",
MOUNTED: "mounted",
INFO_UPDATE: "info_update",
SHOW_SWIPER: "game_swiper",
SHOW_GAMEPAGE: "game_page",
SHOW_GAMEDEMO: "game_demo",
SHOW_SHAREPAGE: "share_page",
SHOW_POPUPMORE: "popup_more",
CLOSE_ALL: "close_all"
};
export default globalToastEvent;

39
src/globalstore.js Normal file
View File

@@ -0,0 +1,39 @@
import { reactive } from "vue"
export const globalStore = reactive({
first_share_today:false,
globalAudio: null,
userMutedMusic: false, // 用户是否手动静音了音乐
isFirstVisitHomePage: true, // 是否首次访问首页
select_template: '',
people_count: 0,
draw_chances: 0,
game_chances: 0,
lotteryCount: 0,
result_url: '',
mergeId: 61,
generateStatus: false,
generateImgTemplates: [],
chartsBattle: false,
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,
reducerDrawChances () {
this.draw_chances--
},
reducerGameChances () {
this.game_chances--
},
lotteryCut () {
this.lotteryCount--
},
lotteryAdd () {
this.lotteryCount++
},
})

1
src/libs/area.js Normal file

File diff suppressed because one or more lines are too long

204
src/libs/faceValidator.js Normal file
View File

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

View File

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

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

@@ -0,0 +1,119 @@
export default class Haibao {
constructor(width, height, color = '#fff') {
this.canvas = document.createElement('canvas');
this.ctx = this.canvas.getContext('2d', { alpha: true });
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, width = 'auto', height = 'auto', index) {
this.zIndex++
const zIndex = index ? index : this.zIndex
const loadPromise = this._createLoadPromise(image).then(img => {
this.images.push({ img, x, y, width, height, zIndex });
});
this.imagePromises.push(loadPromise);
return loadPromise;
}
text (text, x, y, opts) {
const { font = '16px sans-serif', color = '#000', align = 'center' } = opts || {};
this.ctx.font = font;
this.ctx.fillStyle = color;
const drawText = (text, x, y) => {
this.ctx.fillText(text, x, y);
};
const totalWidth = this.ctx.measureText(text).width;
if (align === 'center') {
x -= totalWidth / 2;
}
drawText(text, x, y);
return this
}
async draw (mask = 'source-over') {
await Promise.all(this.imagePromises);
// 绘制所有图片
this.images.sort((a, b) => a.zIndex - b.zIndex)
this.images.forEach(({ img, x, y, width, height, zIndex }, idx) => {
const w = width === 'auto' ? img.width : width
const h = height === 'auto' ? img.height : height
this.ctx.drawImage(img, x, y, w, h);
if (idx === 0) {
this.ctx.globalCompositeOperation = mask
} else {
this.ctx.globalCompositeOperation = 'source-over'
}
});
return this;
}
/**
* 生成合成后的图片返回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;
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;
}
});
}
}

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

@@ -0,0 +1,221 @@
import QRCode from "qrcode"
export const HOST = "https://huodong2.lzlj.com"
export const DIR = "zhaoma"
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, width = 160, height = 160) => {
return await QRCode.toDataURL(`${HOST}/${DIR}/?${text}`, {
errorCorrectionLevel: 'H',
width: width,
height: height,
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", ".webp").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 miniJumpToActive = () => {
wx.miniProgram.navigateTo({ url: '/pages/retail-act/landing-page/ordinary?id=897432916524553363&orgId=200282401019674482&programId=84796583983972352' })
}
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()
}
}
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()
}
}
// 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 HomePage from './components/HomePage.vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import VueVirtualScroller from 'vue-virtual-scroller'
createApp(HomePage).use(VueVirtualScroller).use(ElementPlus).mount('#app')

28
vite.config.js Normal file
View File

@@ -0,0 +1,28 @@
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: process.env.NODE_ENV === 'production' ? 'https://huodong2.lzlj.com/faceFamily/' : '/zhaoMa/',
plugins: [
vue({
template: {
compilerOptions: {
isCustomElement: (tag) => tag.includes('swiper')
}
}
}),
vueDevTools(),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
},
},
define: {
'__APP_VERSION__': JSON.stringify(packageJson.version)
},
})