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.

1458 lines
36 KiB

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