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.

1448 lines
36 KiB

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