You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1344 lines
34 KiB

3 weeks ago
6 days ago
6 days ago
6 days ago
6 days ago
6 days ago
5 days ago
6 days ago
5 days ago
3 weeks ago
6 days ago
3 weeks ago
6 days ago
3 weeks ago
6 days ago
3 weeks ago
6 days ago
5 days ago
6 days ago
6 days ago
3 weeks ago
6 days ago
6 days ago
5 days ago
5 days ago
6 days ago
5 days ago
6 days ago
5 days ago
6 days ago
5 days ago
6 days ago
5 days ago
6 days ago
5 days ago
6 days ago
5 days ago
6 days ago
5 days ago
5 days ago
6 days ago
6 days ago
6 days ago
6 days ago
6 days ago
6 days ago
6 days ago
5 days ago
6 days ago
5 days ago
6 days ago
5 days ago
5 days ago
6 days ago
3 weeks ago
5 days ago
6 days ago
5 days ago
6 days ago
3 weeks ago
5 days ago
3 weeks ago
6 days ago
6 days ago
5 days ago
6 days ago
5 days ago
6 days ago
5 days ago
6 days ago
3 weeks ago
6 days ago
3 weeks ago
6 days ago
6 days ago
6 days ago
5 days ago
6 days ago
3 weeks ago
6 days ago
3 weeks ago
6 days ago
3 weeks ago
6 days ago
3 weeks ago
6 days ago
3 weeks ago
6 days ago
3 weeks ago
6 days ago
5 days ago
6 days ago
5 days ago
6 days ago
5 days ago
6 days ago
5 days ago
6 days ago
5 days ago
6 days ago
5 days ago
6 days ago
3 weeks ago
6 days ago
3 weeks ago
6 days ago
3 weeks ago
6 days ago
3 weeks ago
6 days ago
3 weeks ago
6 days ago
3 weeks ago
6 days ago
3 weeks ago
6 days ago
3 weeks ago
6 days ago
3 weeks ago
6 days ago
5 days ago
3 weeks ago
6 days ago
3 weeks ago
6 days ago
5 days ago
3 weeks ago
6 days ago
3 weeks ago
6 days ago
3 weeks ago
6 days ago
3 weeks ago
6 days ago
3 weeks ago
6 days ago
3 weeks ago
6 days ago
3 weeks ago
  1. <template>
  2. <div class="wheel-container">
  3. <!-- 顶部区域 -->
  4. <div class="top-section">
  5. <div class="expire-time">财富金轮到期时间{{ expireTime }}</div>
  6. <div class="top-right">
  7. <div class="icon-item" @click="toggleHistory">
  8. <img :src="historyImg" class="history-icon" alt="历史记录">
  9. <span class="icon-text">历史记录</span>
  10. </div>
  11. <div class="icon-item" @click="handleRuleClick">
  12. <img :src="ruleImg" class="rule-icon" alt="活动规则">
  13. <span class="icon-text">活动规则</span>
  14. </div>
  15. </div>
  16. </div>
  17. <!-- 主标题图片 -->
  18. <img :src="titleImg" class="title-img" alt="转动财富金轮 开启财富人生">
  19. <!-- 转盘区域 -->
  20. <div class="wheel-section">
  21. <div class="wheel-wrapper">
  22. <!-- 转盘图片尺寸333x333 - 初始旋转22.5 -->
  23. <img
  24. :src="wheelImg"
  25. class="wheel-img"
  26. :class="{ spinning: isSpinning }"
  27. :style="{ transform: `rotate(${currentRotation}deg)` }"
  28. alt="财富金轮"
  29. ref="wheel"
  30. >
  31. <!-- 指针图片尺寸137x137 - 初始旋转22.5 -->
  32. <img :src="pointerImg" class="pointer-img" alt="指针" style="transform: translate(-50%, -50%) rotate(-19deg);">
  33. <!-- 指针盖图片尺寸104x106 -->
  34. <img :src="pointerCoverImg" class="pointer-cover-img" alt="指针盖" style="transform: translate(-50%, -50%) rotate(-25deg);">
  35. </div>
  36. </div>
  37. <!-- 剩余次数 -->
  38. <div class="remaining-section">
  39. <img :src="remainingBgImg" class="remaining-bg" alt="今日剩余">
  40. <span class="remaining-text">今日剩余{{ remainingTimes }}</span>
  41. </div>
  42. <!-- 转动按钮 -->
  43. <button
  44. class="spin-btn"
  45. :disabled="isSpinning || showPrizeModal || showRuleModal || isFirstVisit"
  46. @click="handleSpin"
  47. >
  48. <img
  49. :src="(remainingTimes > 0 && !showPrizeModal && !showRuleModal && !isFirstVisit) ? spinBtnImg : disabledSpinBtnImg"
  50. alt="转动金轮"
  51. class="spin-btn-img"
  52. >
  53. </button>
  54. <!-- 中奖弹窗 -->
  55. <div class="prize-modal" v-if="showPrizeModal">
  56. <div class="modal-overlay" @click="closeModal"></div>
  57. <div class="modal-content">
  58. <img :src="modalBgImg" class="modal-bg" alt="弹窗背景">
  59. <!-- 右上角关闭按钮 -->
  60. <button class="close-modal" @click="closeModal">
  61. <img :src="closeBtnImg" alt="关闭弹窗">
  62. </button>
  63. <div class="prize-info">
  64. <div class="prize-title">{{ prizeMessage }}</div>
  65. <div class="prize-amount-wrapper">
  66. <img :src="prizeAmountImg" class="prize-amount-bg" alt="奖品数量背景">
  67. <div class="prize-amount" :class="{ scrolling: shouldScroll }">
  68. <!-- 关键修改优化滚动实现 -->
  69. <span class="prize-text" v-if="shouldScroll">
  70. {{ prizeAmount }}&nbsp;&nbsp;&nbsp;{{ prizeAmount }}
  71. </span>
  72. <span v-else>{{ prizeAmount }}</span>
  73. </div>
  74. </div>
  75. </div>
  76. </div>
  77. </div>
  78. <!-- 剩余次数为0时的提示弹窗 -->
  79. <div class="no-times-modal" v-if="showNoTimesModal">
  80. <div class="modal-overlay" @click="closeNoTimesModal"></div>
  81. <div class="no-times-content">
  82. <img :src="noTimesImg" class="no-times-bg" alt="今日次数已用完">
  83. </div>
  84. </div>
  85. <!-- 活动规则弹窗 -->
  86. <div class="rule-modal" v-if="showRuleModal">
  87. <div class="modal-overlay" @click="closeRuleModal"></div>
  88. <div class="rule-content">
  89. <img :src="ruleBgImg" class="rule-bg" alt="活动规则背景">
  90. <!-- 右上角关闭按钮 -->
  91. <button class="close-rule-btn" @click="closeRuleModal">
  92. <img :src="closeRuleBtnImg" alt="关闭活动规则">
  93. </button>
  94. <div class="rule-text">
  95. <div class="rule-line">1.在财富金轮使用期限内每天可以转动一次</div>
  96. <div class="rule-line">2.通过财富金轮可以获得免费Tonken和背包礼物等</div>
  97. <div class="rule-line">3.用户通过财富金轮获得的物品以最终展示结果为准</div>
  98. <div class="rule-line">4.Homily Link平台拥有最终解释权</div>
  99. </div>
  100. </div>
  101. </div>
  102. <!-- 首次访问活动规则弹窗 -->
  103. <div class="rule-modal" v-if="isFirstVisit">
  104. <div class="modal-overlay" @click="closeFirstVisitModal"></div>
  105. <div class="rule-content">
  106. <img :src="ruleBgImg" class="rule-bg" alt="活动规则背景">
  107. <!-- 右上角关闭按钮 -->
  108. <button class="close-rule-btn" @click="closeFirstVisitModal">
  109. <img :src="closeRuleBtnImg" alt="关闭活动规则">
  110. </button>
  111. <div class="rule-text">
  112. <div class="rule-line">1.在财富金轮使用期限内每天可以转动一次</div>
  113. <div class="rule-line">2.通过财富金轮可以获得免费Tonken和背包礼物等</div>
  114. <div class="rule-line">3.用户通过财富金轮获得的物品以最终展示结果为准</div>
  115. <div class="rule-line">4.Homily Link平台拥有最终解释权</div>
  116. </div>
  117. </div>
  118. </div>
  119. <!-- 右侧历史记录侧板 -->
  120. <div class="history-record" v-if="historyRecordListVisible">
  121. <!-- 折叠按钮点击再次关闭 -->
  122. <img src="../../assets/img/wealthGoldenWheel/foldUpIcon.png"
  123. class="fold-up" @click="toggleHistory" />
  124. <div class="history-title">中奖记录</div>
  125. <div style="margin-left: 45px; margin-top: 18px;">
  126. <button v-for="item in options" :key="item.value"
  127. :style="{
  128. backgroundColor: selected === item.value ? '#0003bf' : '#7475b2',
  129. borderColor: selected === item.value ? '#0003bf' : '#7475b2',
  130. color: '#fff',
  131. marginRight: '24px',
  132. padding: '6px 18px',
  133. borderRadius: '8px',
  134. border: 'none',
  135. cursor: 'pointer'
  136. }"
  137. @click="changeTypeHistoryList(item)">
  138. {{ item.label }}
  139. </button>
  140. </div>
  141. <div class="history-contents">
  142. <!-- nowMonths当前年 -->
  143. <div v-if="nowMonths && nowMonths.length > 0" style="padding-left:45px;">
  144. <div v-for="m in nowMonths" :key="m.month">
  145. <div class="month-row" @click="toggleMonthNow(m)">
  146. <span class="month-text">{{ m.month }}</span>
  147. <span class="month-caret">{{ m.open ? '▼' : '▲' }}</span>
  148. </div>
  149. <div v-if="m.open">
  150. <div v-if="!m.records || m.records.length === 0" class="empty-month">暂无记录</div>
  151. <div v-for="(item, idx) in m.records" :key="item.id || idx" class="history-record-item">
  152. <div class="left">
  153. <div class="title" v-if="selected == 1">
  154. <span class="circle"></span>财富金轮{{ item.type }}
  155. </div>
  156. <div class="title" v-else-if="selected == 3">
  157. <span class="circle"></span>{{ item.type }}
  158. </div>
  159. <div class="time">{{ item.time }}</div>
  160. </div>
  161. <div class="right">
  162. <span class="token">+ {{ item.amount }}</span>
  163. </div>
  164. </div>
  165. </div>
  166. </div>
  167. </div>
  168. <!-- 历史年列表 -->
  169. <div v-if="years && years.length > 0">
  170. <div v-for="y in years" :key="y.year">
  171. <div class="year-header" @click="toggleYear(y)" style="padding-left:14px;">
  172. <span class="year-text">{{ y.year }}</span>
  173. <span class="year-caret">{{ y.expanded ? '▼' : '▲' }}</span>
  174. </div>
  175. <div v-if="y.expanded" class="months" style="padding-left:45px;">
  176. <div v-for="m in y.months" :key="m.month">
  177. <div class="month-row" @click="toggleMonth(m)">
  178. <span class="month-text">{{ m.month }}</span>
  179. <span class="month-caret">{{ m.open ? '▼' : '▲' }}</span>
  180. </div>
  181. <div v-if="m.open">
  182. <div v-if="!m.records || m.records.length === 0" class="empty-month">暂无记录</div>
  183. <div v-for="(item, idx) in m.records" :key="item.id || idx" class="history-record-item">
  184. <div class="left">
  185. <div class="title" v-if="selected == 1">
  186. <span class="circle"></span>财富金轮{{ item.type }}
  187. </div>
  188. <div class="title" v-else-if="selected == 3">
  189. <span class="circle"></span>{{ item.type }}
  190. </div>
  191. <div class="time">{{ item.time }}</div>
  192. </div>
  193. <div class="right">
  194. <span class="token">+ {{ item.amount }}</span>
  195. </div>
  196. </div>
  197. </div>
  198. </div>
  199. </div>
  200. </div>
  201. </div>
  202. </div>
  203. <!-- 底部装饰 -->
  204. <div class="history-decoration" aria-hidden="true"></div>
  205. </div>
  206. </div>
  207. </template>
  208. <script>
  209. import api from '@/utils/common.js'
  210. import { permissionApi, lotteryApi,historyApi } from '@/api/goldenWheel'
  211. export default {
  212. name: 'FortuneWheel',
  213. data() {
  214. return {
  215. // 图片资源
  216. historyImg: 'https://d31zlh4on95l9h.cloudfront.net/images/aed693de6beb9faae015fd0628c4f052.png',
  217. ruleImg: 'https://d31zlh4on95l9h.cloudfront.net/images/9a3f7680b29e31b60151cf562c0d43cb.png',
  218. titleImg: 'https://d31zlh4on95l9h.cloudfront.net/images/a87d19806fed47b7fdf18d4b5dd70e65.png',
  219. wheelImg: 'https://d31zlh4on95l9h.cloudfront.net/images/caaf77a490be46c56ae824d2d9aa72f4.png',
  220. pointerImg: 'https://d31zlh4on95l9h.cloudfront.net/images/72cda5c3b13823aa6ca87a3065fe2197.png',
  221. pointerCoverImg: 'https://d31zlh4on95l9h.cloudfront.net/images/19f2ea90e2560330089214e88eff04b5.png',
  222. remainingBgImg: 'https://d31zlh4on95l9h.cloudfront.net/images/a43f0c383d55fc56b34768f039a401a8.png',
  223. spinBtnImg: 'https://d31zlh4on95l9h.cloudfront.net/images/11b749e0fd4b08238980b74c6e80b2e6.png',
  224. disabledSpinBtnImg: 'https://d31zlh4on95l9h.cloudfront.net/images/9b7383c1f80bb32a279791879947791a.png',
  225. modalBgImg: 'https://d31zlh4on95l9h.cloudfront.net/images/f75ce9ca2703662fd3176dd0493f6d7b.png',
  226. prizeAmountImg: 'https://d31zlh4on95l9h.cloudfront.net/images/878b8ea0c78bcafdaf4f6eb63a0b2eef.png',
  227. closeBtnImg: 'https://d31zlh4on95l9h.cloudfront.net/images/453475456dad8e6832e9904c901c1274.png',
  228. noTimesImg: 'https://d31zlh4on95l9h.cloudfront.net/images/a67e8b3de6e441af7bcb8fbbb2153ec2.png',
  229. ruleBgImg: 'https://d31zlh4on95l9h.cloudfront.net/images/9f585ee0ab251f348a4568355ad36816.png',
  230. closeRuleBtnImg: 'https://d31zlh4on95l9h.cloudfront.net/images/ca2ddd411ffb85968bc261382477c984.png',
  231. // 数据
  232. token: '',
  233. expireTime: '',
  234. remainingTimes: 0,
  235. isSpinning: false,
  236. showPrizeModal: false,
  237. showNoTimesModal: false,
  238. showRuleModal: false,
  239. isFirstVisit: true,
  240. currentRotation: -23.5, // 初始旋转-22.5度
  241. noTimesTimer: null,
  242. prizeMessage: '',
  243. prizeAmount: '',
  244. textWidth: 0,
  245. historyRecordListVisible: false,
  246. nowMonths: [],
  247. years: [],
  248. selected: 1,
  249. options: [
  250. { label: 'Token', value: 1 },
  251. { label: '卡券', value: 3 }
  252. ],
  253. }
  254. },
  255. computed: {
  256. // 判断是否需要滚动展示
  257. shouldScroll() {
  258. if (!this.prizeAmount) return false;
  259. // 判断中文字符数量
  260. const chineseCharCount = (this.prizeAmount.match(/[\u4e00-\u9fa5]/g) || []).length;
  261. // 判断英文字母数量
  262. const englishCharCount = (this.prizeAmount.match(/[a-zA-Z]/g) || []).length;
  263. // 汉字超过7个或字母超过15个时启用滚动
  264. return chineseCharCount > 7 || englishCharCount > 15;
  265. }
  266. },
  267. mounted() {
  268. this.getTokenFromURL();
  269. this.fetchWheelInfo();
  270. // 预加载图片
  271. this.preloadImages();
  272. api.packageFun('JWsetTitle', function () {}, {
  273. platform: 5,
  274. title: "财富金轮"
  275. })
  276. },
  277. methods: {
  278. toggleHistory() {
  279. this.historyRecordListVisible = !this.historyRecordListVisible
  280. if (this.historyRecordListVisible) {
  281. this.loadHistoryRecord()
  282. }
  283. },
  284. changeTypeHistoryList(item) {
  285. this.selected = item.value
  286. this.loadHistoryRecord()
  287. },
  288. async loadHistoryRecord() {
  289. try {
  290. const res = await historyApi({
  291. token: this.token,
  292. type: this.selected
  293. });
  294. if (res.code === 200) {
  295. const now_year = res.data.now_year || []
  296. const last_year_list = res.data.last_year_list || []
  297. this.nowMonths = (Array.isArray(now_year) ? now_year : []).map((m, idx) => ({
  298. month: m.month,
  299. open: idx === 0,
  300. records: (m.list || []).map((r, ridx) => ({
  301. id: ridx,
  302. time: r.time,
  303. amount: parseFloat(r.num),
  304. type: r.name
  305. }))
  306. }))
  307. this.years = (Array.isArray(last_year_list) ? last_year_list : []).map((y) => ({
  308. year: y.year,
  309. expanded: false,
  310. months: (y.list || []).map((m) => ({
  311. month: m.month,
  312. open: false,
  313. records: (m.list || []).map((r, ridx) => ({
  314. id: ridx,
  315. time: r.time,
  316. amount: parseFloat(r.num),
  317. type: r.name
  318. }))
  319. }))
  320. }))
  321. } else {
  322. this.nowMonths = []
  323. this.years = []
  324. }
  325. } catch (err) {
  326. this.nowMonths = []
  327. this.years = []
  328. }
  329. },
  330. toggleMonthNow(m) {
  331. m.open = !m.open
  332. },
  333. toggleYear(y) {
  334. y.expanded = !y.expanded
  335. },
  336. toggleMonth(m) {
  337. m.open = !m.open
  338. },
  339. // 预加载图片以提高性能
  340. preloadImages() {
  341. const images = [
  342. this.historyImg, this.ruleImg, this.titleImg, this.wheelImg,
  343. this.pointerImg, this.pointerCoverImg, this.remainingBgImg,
  344. this.spinBtnImg, this.disabledSpinBtnImg, this.modalBgImg,
  345. this.prizeAmountImg, this.closeBtnImg, this.noTimesImg,
  346. this.ruleBgImg, this.closeRuleBtnImg
  347. ];
  348. images.forEach(src => {
  349. const img = new Image();
  350. img.src = src;
  351. });
  352. },
  353. getTokenFromURL() {
  354. // 首先尝试从URL获取token
  355. const urlParams = new URLSearchParams(window.location.search);
  356. const tokenParam = urlParams.get('token');
  357. if (tokenParam) {
  358. // 如果URL中有token,保存到localStorage并设置到组件
  359. const decodedToken = decodeURIComponent(tokenParam);
  360. localStorage.setItem('fortuneWheelToken', decodedToken);
  361. this.token = decodedToken;
  362. } else {
  363. // 如果URL中没有token,尝试从localStorage获取
  364. const storedToken = localStorage.getItem('fortuneWheelToken');
  365. if (storedToken) {
  366. this.token = storedToken;
  367. } else {
  368. console.log('URL和localStorage中都没有找到token');
  369. // 这里可以添加处理没有token的情况,比如跳转到登录页
  370. }
  371. }
  372. },
  373. // 调用API获取财富金轮信息
  374. fetchWheelInfo() {
  375. if (!this.token) {
  376. console.error('Token为空,无法调用API');
  377. return;
  378. }
  379. permissionApi({ token: this.token }).then(res => {
  380. if(res.code === 200){
  381. this.expireTime = res.data.deadline;
  382. this.remainingTimes = res.data.count;
  383. } else {
  384. console.error('API返回错误:', res.msg);
  385. }
  386. }).catch(error => {
  387. console.error('API调用失败:', error);
  388. });
  389. },
  390. // 调用抽奖API获取奖品信息
  391. async fetchPrizeInfo() {
  392. if (!this.token) {
  393. console.error('Token为空,无法调用抽奖API');
  394. return false;
  395. }
  396. try {
  397. // 调用抽奖API
  398. const res = await lotteryApi({ token: this.token });
  399. if(res.code === 200){
  400. // 解析API返回的字符串数据
  401. const prizeData = res.data;
  402. // 关键修改:按照第一个空格分割字符串
  403. const firstSpaceIndex = prizeData.indexOf(' ');
  404. if (firstSpaceIndex !== -1) {
  405. // 第一个空格前面的部分作为prizeMessage
  406. this.prizeMessage = prizeData.substring(0, firstSpaceIndex);
  407. // 第一个空格后面的部分作为prizeAmount
  408. this.prizeAmount = prizeData.substring(firstSpaceIndex + 1);
  409. } else {
  410. // 如果没有空格,整个字符串作为prizeMessage,prizeAmount为空
  411. this.prizeMessage = prizeData;
  412. this.prizeAmount = '';
  413. }
  414. // 抽奖API执行完成后,查询最新的剩余次数
  415. await this.fetchWheelInfo();
  416. return true;
  417. } else {
  418. console.error('抽奖API返回错误:', res.msg);
  419. // 设置默认值
  420. this.prizeMessage = '抽奖失败';
  421. this.prizeAmount = '请重试';
  422. // 即使抽奖失败,也查询最新的剩余次数
  423. await this.fetchWheelInfo();
  424. return false;
  425. }
  426. } catch (error) {
  427. console.error('抽奖API调用失败:', error);
  428. // 设置默认值
  429. this.prizeMessage = '网络错误';
  430. this.prizeAmount = '请重试';
  431. // 即使抽奖失败,也查询最新的剩余次数
  432. await this.fetchWheelInfo();
  433. return false;
  434. }
  435. },
  436. // 处理转动
  437. async handleSpin() {
  438. if (this.showRuleModal || this.isFirstVisit || this.isSpinning) return;
  439. // 先查询剩余次数
  440. try {
  441. if (this.remainingTimes <= 0) {
  442. this.showNoTimesModal = true;
  443. if (this.noTimesTimer) {
  444. clearTimeout(this.noTimesTimer);
  445. }
  446. this.noTimesTimer = setTimeout(() => {
  447. this.showNoTimesModal = false;
  448. }, 3000);
  449. return;
  450. }
  451. // 有剩余次数,开始抽奖
  452. this.isSpinning = true;
  453. // 开始旋转动画(从当前-22.5度位置开始旋转)
  454. const newRotation = this.currentRotation + 1440;
  455. this.currentRotation = newRotation;
  456. // 调用抽奖API获取奖品信息
  457. const success = await this.fetchPrizeInfo();
  458. setTimeout(() => {
  459. this.isSpinning = false;
  460. if (success) {
  461. setTimeout(() => {
  462. this.showPrizeModal = true;
  463. }, 500);
  464. }
  465. }, 3000);
  466. } catch (error) {
  467. console.error('查询剩余次数失败:', error);
  468. this.isSpinning = false;
  469. }
  470. },
  471. // 处理活动规则点击
  472. handleRuleClick() {
  473. this.showRuleModal = true;
  474. },
  475. // 关闭弹窗
  476. closeModal() {
  477. this.showPrizeModal = false;
  478. },
  479. // 关闭无次数提示弹窗
  480. closeNoTimesModal() {
  481. this.showNoTimesModal = false;
  482. if (this.noTimesTimer) {
  483. clearTimeout(this.noTimesTimer);
  484. }
  485. },
  486. // 关闭活动规则弹窗
  487. closeRuleModal() {
  488. this.showRuleModal = false;
  489. },
  490. // 关闭首次访问弹窗
  491. closeFirstVisitModal() {
  492. this.isFirstVisit = false;
  493. },
  494. // 跳转到历史记录页面
  495. goToHistory() {
  496. this.$router.push('/history');
  497. },
  498. // 计算文本宽度,动态设置动画
  499. calcTextWidth() {
  500. if (!this.shouldScroll || !this.prizeAmount) return;
  501. // 创建临时元素,用于计算文本宽度
  502. const tempSpan = document.createElement('span');
  503. tempSpan.style.visibility = 'hidden';
  504. tempSpan.style.position = 'absolute';
  505. tempSpan.style.whiteSpace = 'nowrap';
  506. tempSpan.style.fontSize = '25px';
  507. tempSpan.style.fontWeight = 'bold';
  508. tempSpan.style.fontFamily = 'inherit';
  509. tempSpan.textContent = this.prizeAmount;
  510. document.body.appendChild(tempSpan);
  511. // 获取单个文本宽度
  512. this.textWidth = tempSpan.offsetWidth;
  513. document.body.removeChild(tempSpan);
  514. // 移除现有的样式
  515. const existingStyle = document.getElementById('scroll-animation-style');
  516. if (existingStyle) {
  517. existingStyle.remove();
  518. }
  519. // 动态设置滚动动画
  520. const style = document.createElement('style');
  521. style.id = 'scroll-animation-style';
  522. style.textContent = `
  523. @keyframes seamless-scroll {
  524. 0% { transform: translateX(0); }
  525. 100% { transform: translateX(-${this.textWidth + 20}px); }
  526. }
  527. @media (max-width: 380px) {
  528. @keyframes seamless-scroll {
  529. 0% { transform: translateX(0); }
  530. 100% { transform: translateX(-${this.textWidth + 16}px); }
  531. }
  532. }
  533. .prize-amount.scrolling .prize-text {
  534. animation: seamless-scroll ${Math.max(5, this.textWidth / 30)}s linear infinite;
  535. padding-right: 20px;
  536. display: inline-block;
  537. }
  538. `;
  539. document.head.appendChild(style);
  540. }
  541. },
  542. watch: {
  543. // 当奖品金额变化时,重新计算文本宽度
  544. prizeAmount(newVal) {
  545. if (newVal && this.shouldScroll) {
  546. this.$nextTick(() => {
  547. this.calcTextWidth();
  548. });
  549. }
  550. }
  551. }
  552. }
  553. </script>
  554. <style scoped>
  555. .wheel-container {
  556. min-height: 100vh;
  557. background-image: url('https://d31zlh4on95l9h.cloudfront.net/images/ec8f801bf97306aa16155498f831fa68.png');
  558. background-size: cover;
  559. background-position: bottom center;
  560. padding: 15px;
  561. display: flex;
  562. flex-direction: column;
  563. align-items: center;
  564. position: relative;
  565. }
  566. /* 顶部区域样式 */
  567. .top-section {
  568. width: 100%;
  569. display: flex;
  570. justify-content: space-between;
  571. align-items: flex-start;
  572. margin-bottom: 15px;
  573. }
  574. .expire-time {
  575. color: #fff;
  576. font-size: 12px;
  577. font-weight: bold;
  578. text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);
  579. margin: 11px 0 0 12px;
  580. }
  581. .top-right {
  582. display: flex;
  583. gap: 20px;
  584. align-items: center;
  585. }
  586. .icon-item {
  587. display: flex;
  588. flex-direction: column;
  589. align-items: center;
  590. gap: 4px;
  591. cursor: pointer;
  592. }
  593. .history-icon, .rule-icon {
  594. width: 25px;
  595. height: 25px;
  596. cursor: pointer;
  597. }
  598. .icon-text {
  599. color: #fff;
  600. font-size: 11px;
  601. text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.8);
  602. }
  603. /* 主标题图片样式 */
  604. .title-img {
  605. width: 327px;
  606. height: 144px;
  607. margin-bottom: 20px;
  608. }
  609. /* 转盘区域样式 */
  610. .wheel-section {
  611. display: flex;
  612. flex-direction: column;
  613. align-items: center;
  614. margin-bottom: 15px;
  615. }
  616. .wheel-wrapper {
  617. position: relative;
  618. width: 333px;
  619. height: 333px;
  620. }
  621. .wheel-img {
  622. width: 100%;
  623. height: 100%;
  624. transform-origin: center;
  625. will-change: transform;
  626. transition: transform 0.3s ease-out;
  627. backface-visibility: hidden;
  628. transform: translateZ(0) rotate(-22.5deg); /* 初始旋转-22.5度 */
  629. }
  630. .wheel-img.spinning {
  631. animation: optimal-spin 3s cubic-bezier(0.17, 0.67, 0.21, 0.98) forwards;
  632. backface-visibility: hidden;
  633. transform: translateZ(0);
  634. }
  635. @keyframes optimal-spin {
  636. 0% {
  637. transform: rotate(-22.5deg); /* 从-22.5度开始旋转 */
  638. }
  639. 100% {
  640. transform: rotate(1417.5deg); /* 1440 - 22.5 = 1417.5度 */
  641. }
  642. }
  643. /* 指针样式 - 初始旋转-22.5度 */
  644. .pointer-img {
  645. position: absolute;
  646. top: 50%;
  647. left: 50%;
  648. width: 137px;
  649. height: 137px;
  650. transform: translate(-50%, -50%) rotate(-22.5deg);
  651. z-index: 2;
  652. }
  653. /* 指针盖样式 */
  654. .pointer-cover-img {
  655. position: absolute;
  656. top: 50%;
  657. left: 50%;
  658. width: 104px;
  659. height: 106px;
  660. transform: translate(-50%, -50%);
  661. z-index: 3;
  662. }
  663. /* 剩余次数样式 */
  664. .remaining-section {
  665. position: relative;
  666. margin-bottom: 20px;
  667. display: flex;
  668. justify-content: center;
  669. margin-top: -5px;
  670. }
  671. .remaining-bg {
  672. width: 104px;
  673. height: 21.5px;
  674. }
  675. .remaining-text {
  676. position: absolute;
  677. top: 50%;
  678. left: 50%;
  679. transform: translate(-50%, -50%);
  680. color: #fff;
  681. font-size: 12px;
  682. font-weight: bold;
  683. text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.8);
  684. width: 100%;
  685. text-align: center;
  686. white-space: nowrap;
  687. }
  688. /* 转动按钮样式 */
  689. .spin-btn {
  690. background: none;
  691. border: none;
  692. cursor: pointer;
  693. transition: transform 0.2s ease;
  694. padding: 0;
  695. }
  696. .spin-btn:active:not(:disabled) {
  697. transform: scale(0.95);
  698. }
  699. .spin-btn:disabled {
  700. cursor: not-allowed;
  701. opacity: 0.7;
  702. }
  703. .spin-btn-img {
  704. width: 240px;
  705. height: 94px;
  706. }
  707. /* 弹窗样式 */
  708. .prize-modal, .no-times-modal, .rule-modal {
  709. position: fixed;
  710. top: 0;
  711. left: 0;
  712. width: 100%;
  713. height: 100%;
  714. display: flex;
  715. justify-content: center;
  716. align-items: center;
  717. z-index: 1000;
  718. }
  719. .modal-overlay {
  720. position: absolute;
  721. top: 0;
  722. left: 0;
  723. width: 100%;
  724. height: 100%;
  725. background-color: rgba(0, 0, 0, 0.7);
  726. }
  727. /* 中奖弹窗内容区域尺寸为333x377 */
  728. .modal-content {
  729. position: relative;
  730. width: 333px;
  731. height: 377px;
  732. display: flex;
  733. flex-direction: column;
  734. align-items: center;
  735. justify-content: center;
  736. z-index: 1001;
  737. animation: modal-appear 0.3s ease-out;
  738. }
  739. @keyframes modal-appear {
  740. from {
  741. opacity: 0;
  742. transform: scale(0.8) translateY(-20px);
  743. }
  744. to {
  745. opacity: 1;
  746. transform: scale(1) translateY(0);
  747. }
  748. }
  749. .modal-bg {
  750. position: absolute;
  751. width: 100%;
  752. height: 100%;
  753. z-index: -1;
  754. }
  755. /* 无次数提示弹窗样式 */
  756. .no-times-content {
  757. position: relative;
  758. width: 333px;
  759. height: 79px;
  760. display: flex;
  761. justify-content: center;
  762. align-items: center;
  763. z-index: 1001;
  764. margin-top: 250px;
  765. animation: slide-up 0.3s ease-out;
  766. }
  767. @keyframes slide-up {
  768. from {
  769. opacity: 0;
  770. transform: translateY(20px);
  771. }
  772. to {
  773. opacity: 1;
  774. transform: translateY(0);
  775. }
  776. }
  777. .no-times-bg {
  778. position: absolute;
  779. width: 100%;
  780. height: 100%;
  781. z-index: -1;
  782. }
  783. /* 弹窗内容样式 */
  784. .prize-info {
  785. display: flex;
  786. flex-direction: column;
  787. align-items: center;
  788. justify-content: center;
  789. text-align: center;
  790. z-index: 2;
  791. margin-top: 120px;
  792. }
  793. .prize-title {
  794. color: #B82525;
  795. font-size: 25px;
  796. font-weight: bold;
  797. margin-bottom: 35px;
  798. text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);
  799. animation: text-pulse 2s infinite;
  800. }
  801. @keyframes text-pulse {
  802. 0%, 100% {
  803. transform: scale(1);
  804. }
  805. 50% {
  806. transform: scale(1.05);
  807. }
  808. }
  809. /* 奖品数量样式 - 优化后的样式 */
  810. .prize-amount-wrapper {
  811. position: relative;
  812. display: flex;
  813. justify-content: center;
  814. align-items: center;
  815. margin-top: 20px;
  816. width: 272px;
  817. height: 58px;
  818. overflow: hidden;
  819. }
  820. .prize-amount-bg {
  821. position: absolute;
  822. width: 272px;
  823. height: 58px;
  824. z-index: 1;
  825. }
  826. .prize-amount {
  827. position: absolute;
  828. top: 50%;
  829. left: 50%;
  830. transform: translate(-50%, -50%);
  831. color: #FFFFFF;
  832. font-size: 25px;
  833. font-weight: bold;
  834. text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.8);
  835. width: 240px;
  836. text-align: center;
  837. white-space: nowrap;
  838. z-index: 2;
  839. overflow: hidden;
  840. }
  841. /* 滚动状态:限制文本显示区域,确保仅容器内文本可见 */
  842. .prize-amount.scrolling {
  843. width: 170px;
  844. text-align: left;
  845. }
  846. /* 滚动文本容器 */
  847. .prize-amount.scrolling .prize-text {
  848. will-change: transform;
  849. backface-visibility: hidden;
  850. transform: translateZ(0);
  851. }
  852. /* 关闭按钮样式 - 修改位置 */
  853. .close-modal {
  854. position: absolute;
  855. top: 115px;
  856. right: 22px;
  857. background: none;
  858. border: none;
  859. cursor: pointer;
  860. z-index: 1002;
  861. transition: transform 0.2s ease;
  862. padding: 0;
  863. }
  864. .close-modal:hover {
  865. transform: scale(1.1);
  866. }
  867. .close-modal img {
  868. width: 35px;
  869. height: 35px;
  870. }
  871. /* 活动规则弹窗样式 - 整体下移 */
  872. .rule-content {
  873. position: relative;
  874. width: 333px;
  875. height: 239px;
  876. display: flex;
  877. flex-direction: column;
  878. align-items: center;
  879. z-index: 1001;
  880. animation: modal-appear 0.3s ease-out;
  881. margin-top: 80px;
  882. }
  883. .rule-bg {
  884. position: absolute;
  885. width: 100%;
  886. height: 100%;
  887. z-index: -1;
  888. }
  889. /* 活动规则关闭按钮 - 修复:确保按钮层级高于背景 */
  890. .close-rule-btn {
  891. position: absolute;
  892. top: 45.5px;
  893. right: 35.5px;
  894. background: none;
  895. border: none;
  896. cursor: pointer;
  897. z-index: 1002;
  898. transition: transform 0.2s ease;
  899. padding: 0;
  900. width: 30px;
  901. height: 30px;
  902. display: flex;
  903. align-items: center;
  904. justify-content: center;
  905. }
  906. .close-rule-btn:hover {
  907. transform: scale(1.1);
  908. }
  909. .close-rule-btn img {
  910. width: 20px;
  911. height: 20px;
  912. display: block;
  913. }
  914. /* 活动规则文本 */
  915. .rule-text {
  916. position: absolute;
  917. top: 83px;
  918. left: 27px;
  919. display: flex;
  920. flex-direction: column;
  921. gap: 9.15px;
  922. width: calc(100% - 54px);
  923. }
  924. .rule-line {
  925. color: #FFFFFF;
  926. font-size: 12px;
  927. line-height: 1.4;
  928. }
  929. /* 媒体查询 - 适配小屏幕 */
  930. @media (max-width: 380px) {
  931. .title-img {
  932. width: 280px;
  933. height: 123px;
  934. }
  935. .wheel-wrapper {
  936. width: 280px;
  937. height: 280px;
  938. }
  939. .pointer-img {
  940. width: 115px;
  941. height: 115px;
  942. }
  943. .pointer-cover-img {
  944. width: 87px;
  945. height: 89px;
  946. }
  947. .spin-btn-img {
  948. width: 200px;
  949. height: 78px;
  950. }
  951. .top-right {
  952. gap: 15px;
  953. }
  954. .remaining-section {
  955. margin-top: -8px;
  956. }
  957. /* 小屏幕适配中奖弹窗 */
  958. .modal-content {
  959. width: 300px;
  960. height: 340px;
  961. }
  962. .prize-info {
  963. margin-top: 100px;
  964. }
  965. .prize-title {
  966. margin-bottom: 30px;
  967. }
  968. .prize-amount-wrapper {
  969. margin-top: 18px;
  970. width: 240px;
  971. height: 51px;
  972. }
  973. .prize-amount-bg {
  974. width: 240px;
  975. height: 51px;
  976. }
  977. .prize-amount {
  978. font-size: 22px;
  979. width: 210px;
  980. }
  981. .prize-amount.scrolling {
  982. width: 190px;
  983. }
  984. .no-times-content {
  985. width: 280px;
  986. height: 66px;
  987. margin-top: 200px;
  988. }
  989. /* 小屏幕适配关闭按钮位置 */
  990. .close-modal {
  991. top: 100px;
  992. right: 18px;
  993. }
  994. .close-modal img {
  995. width: 30px;
  996. height: 30px;
  997. }
  998. /* 小屏幕适配活动规则弹窗 */
  999. .rule-content {
  1000. width: 300px;
  1001. height: 215px;
  1002. margin-top: 70px;
  1003. }
  1004. .close-rule-btn {
  1005. top: 41px;
  1006. right: 32px;
  1007. }
  1008. .rule-text {
  1009. top: 75px;
  1010. left: 24px;
  1011. width: calc(100% - 48px);
  1012. }
  1013. }
  1014. .fold-up {
  1015. width: 30px;
  1016. height: 30px;
  1017. position: absolute;
  1018. top: 20px;
  1019. left: 45px;
  1020. cursor: pointer;
  1021. z-index: 999;
  1022. }
  1023. .history-title {
  1024. padding-top: 12px;
  1025. display: flex;
  1026. justify-content: center;
  1027. width: 100%;
  1028. color: #00e5ff;
  1029. font-size: 32px;
  1030. font-weight: 700;
  1031. }
  1032. .history-record {
  1033. background: #080A6D;
  1034. position: fixed;
  1035. right: 0;
  1036. bottom: 0;
  1037. width: 90%;
  1038. height: 100vh;
  1039. display: flex;
  1040. flex-direction: column;
  1041. --bottom-cut-height: 220px;
  1042. overflow: hidden;
  1043. z-index: 2000;
  1044. box-shadow: -8px 0 24px rgba(0,0,0,0.6);
  1045. border-left: 1px solid rgba(255,255,255,0.03);
  1046. padding-bottom: 0;
  1047. }
  1048. .history-contents {
  1049. margin-top: 20px;
  1050. padding-right: 10px;
  1051. flex: 1 1 auto;
  1052. overflow-y: auto;
  1053. -webkit-overflow-scrolling: touch;
  1054. direction: rtl;
  1055. }
  1056. .history-contents > * { direction: ltr; }
  1057. .history-contents * { direction: ltr; }
  1058. .history-contents::-webkit-scrollbar {
  1059. width: 14px;
  1060. background-color: #20086d;
  1061. }
  1062. .history-contents::-webkit-scrollbar-thumb {
  1063. background-color: #4a4de6;
  1064. border-radius: 4px;
  1065. }
  1066. .history-contents::-webkit-scrollbar-thumb:hover {
  1067. background-color: #6b6ef5;
  1068. }
  1069. .history-decoration {
  1070. flex: 0 0 200px;
  1071. height: var(--bottom-cut-height);
  1072. position: relative;
  1073. left: 50%;
  1074. transform: translateX(-50%);
  1075. width: 100%;
  1076. pointer-events: none;
  1077. z-index: 0;
  1078. background-image: url('../../assets/img/wealthGoldenWheel/bottomDecoration.png');
  1079. background-repeat: no-repeat;
  1080. background-position: center bottom;
  1081. background-size: contain;
  1082. margin-top: 0;
  1083. }
  1084. /* 记录项样式 */
  1085. .history-record-item {
  1086. background: #0b0d99;
  1087. margin-bottom: 10px;
  1088. display: flex;
  1089. justify-content: space-between;
  1090. width: 80%;
  1091. padding: 8px;
  1092. border-radius: 8px;
  1093. }
  1094. .history-record-item .left {
  1095. display: flex;
  1096. flex-direction: column;
  1097. gap: 6px;
  1098. min-width: 0;
  1099. padding: 5px;
  1100. }
  1101. .history-record-item .title {
  1102. color: #f2f5ff;
  1103. font-size: 16px;
  1104. font-weight: 700;
  1105. white-space: nowrap;
  1106. text-overflow: ellipsis;
  1107. overflow: hidden;
  1108. display: flex;
  1109. align-items: center;
  1110. }
  1111. .history-record-item .time {
  1112. font-size: 12px;
  1113. color: #e6ecff;
  1114. font-weight: 700;
  1115. margin-left: 28px;
  1116. }
  1117. .history-record-item .right {
  1118. display: flex;
  1119. align-items: center;
  1120. justify-content: flex-end;
  1121. min-width: 72px;
  1122. padding-right: 10px;
  1123. }
  1124. .history-record-item .token {
  1125. color: #00e6ff;
  1126. font-size: 16px;
  1127. font-weight: 700;
  1128. }
  1129. .circle {
  1130. width: 10px;
  1131. height: 10px;
  1132. background-color: #fff;
  1133. margin-right: 8px;
  1134. flex-shrink: 0;
  1135. margin-left: 10px;
  1136. border-radius: 50%;
  1137. }
  1138. .month-row,
  1139. .year-header {
  1140. display: flex;
  1141. align-items: center;
  1142. padding-bottom: 12px;
  1143. cursor: pointer;
  1144. color: #ffffff;
  1145. font-weight: 700;
  1146. }
  1147. .month-text,
  1148. .year-text {
  1149. font-weight: 700;
  1150. font-size: 16px;
  1151. color: #ffffff;
  1152. }
  1153. .month-caret,
  1154. .year-caret {
  1155. margin-left: 10px;
  1156. font-size: 16px;
  1157. color: #ffffff;
  1158. }
  1159. .empty-month {
  1160. padding: 8px;
  1161. color: #cfd9ff;
  1162. background: transparent;
  1163. }
  1164. @media (max-width: 900px) {
  1165. .history-record { width: 90%; }
  1166. .history-record-item { width: calc(100% - 60px); }
  1167. }
  1168. @media (max-width: 430px) {
  1169. .history-record-item .title {
  1170. font-size: 12px;
  1171. }
  1172. .history-record-item .time {
  1173. font-size: 10px;
  1174. margin-left:10px;
  1175. }
  1176. .history-record-item .token {
  1177. font-size: 14px;
  1178. }
  1179. .history-record-item .right {
  1180. padding-right: 5px;
  1181. }
  1182. .circle {
  1183. width: 8px;
  1184. height: 8px;
  1185. margin-left: 0px;
  1186. }
  1187. .month-caret,
  1188. .year-caret {
  1189. margin-left: 5px;
  1190. }
  1191. }
  1192. </style>