瑶都万能墙
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.

447 lines
11 KiB

  1. <template>
  2. <view class="page">
  3. <navbar leftClick
  4. color="#fff"
  5. bgColor="#667eea"
  6. @leftClick="$utils.navigateBack" />
  7. <view class="turntable-container">
  8. <!-- 头部标题 -->
  9. <view class="header">
  10. <text class="title">幸运大转盘</text>
  11. <text class="subtitle">转一转好运来</text>
  12. </view>
  13. <!-- 转盘区域 -->
  14. <view class="turntable-wrapper">
  15. <view class="turntable" :class="{ 'spinning': isSpinning }" :style="{ transform: `rotate(${rotateAngle}deg)` }">
  16. <!-- 使用纯CSS创建转盘 -->
  17. <view class="wheel-bg">
  18. <!-- 8个扇形区域 -->
  19. <view
  20. v-for="(prize, index) in prizes"
  21. :key="index"
  22. class="wheel-sector"
  23. :style="{
  24. backgroundColor: sectorColors[index],
  25. transform: `rotate(${index * 45}deg)`
  26. }"
  27. >
  28. </view>
  29. </view>
  30. <!-- 奖品文字覆盖层 -->
  31. <view class="prizes-overlay">
  32. <view
  33. v-for="(prize, index) in prizes"
  34. :key="index"
  35. class="prize-item"
  36. :style="prizeStyles[index]"
  37. >
  38. <view class="prize-content">
  39. <text class="prize-icon">{{ prize.icon }}</text>
  40. <text class="prize-name">{{ prize.name }}</text>
  41. <text class="prize-value">{{ prize.value }}</text>
  42. </view>
  43. </view>
  44. </view>
  45. </view>
  46. <!-- 中心指针 -->
  47. <view class="pointer">
  48. <view class="pointer-triangle"></view>
  49. </view>
  50. <!-- 中心按钮 -->
  51. <view class="center-button" @click="startSpin" :class="{ disabled: isSpinning }">
  52. <text class="button-text">{{ isSpinning ? '抽奖中...' : '开始抽奖' }}</text>
  53. </view>
  54. </view>
  55. <!-- 抽奖结果弹窗 -->
  56. <view class="result-modal" v-if="showResult" @click="closeResult">
  57. <view class="modal-content" @click.stop>
  58. <text class="result-title">🎉 恭喜您 🎉</text>
  59. <view class="result-prize">
  60. <text class="result-icon">{{ currentPrize.icon }}</text>
  61. <text class="result-name">{{ currentPrize.name }}</text>
  62. <text class="result-value">{{ currentPrize.value }}</text>
  63. </view>
  64. <view class="result-actions">
  65. <button class="confirm-btn" @click="closeResult">确定</button>
  66. </view>
  67. </view>
  68. </view>
  69. <!-- 抽奖次数提示 -->
  70. <view class="spin-info">
  71. <text>今日剩余抽奖次数: {{ remainingSpins }}</text>
  72. </view>
  73. </view>
  74. </view>
  75. </template>
  76. <script>
  77. export default {
  78. data() {
  79. return {
  80. // 奖品配置
  81. prizes: [
  82. { name: '现金奖励', value: '¥10', icon: '💰', type: 'money', amount: 10 },
  83. { name: '精美礼品', value: '小熊', icon: '🧸', type: 'gift', item: 'teddy_bear' },
  84. { name: '现金奖励', value: '¥5', icon: '💰', type: 'money', amount: 5 },
  85. { name: '精美礼品', value: '花束', icon: '💐', type: 'gift', item: 'flowers' },
  86. { name: '现金奖励', value: '¥20', icon: '💰', type: 'money', amount: 20 },
  87. { name: '精美礼品', value: '巧克力', icon: '🍫', type: 'gift', item: 'chocolate' },
  88. { name: '现金奖励', value: '¥2', icon: '💰', type: 'money', amount: 2 },
  89. { name: '精美礼品', value: '香水', icon: '🌸', type: 'gift', item: 'perfume' }
  90. ],
  91. sectorColors: [
  92. '#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4',
  93. '#FFEAA7', '#DDA0DD', '#98D8C8', '#F7DC6F'
  94. ],
  95. isSpinning: false, // 是否正在旋转
  96. rotateAngle: 0, // 旋转角度
  97. showResult: false, // 显示结果弹窗
  98. currentPrize: null, // 当前中奖奖品
  99. remainingSpins: 3, // 剩余抽奖次数
  100. sectorAngle: 45, // 每个扇形的角度 (360/8)
  101. // 预计算奖品位置样式
  102. prizeStyles: []
  103. }
  104. },
  105. onLoad() {
  106. this.calculatePrizeStyles()
  107. },
  108. methods: {
  109. // 计算奖品位置样式
  110. calculatePrizeStyles() {
  111. this.prizeStyles = this.prizes.map((prize, index) => {
  112. // 计算奖品在圆形中的位置
  113. const angle = (index * 45 + 22.5) * Math.PI / 180; // 转换为弧度,22.5是偏移到扇形中心
  114. const radius = 150; // 奖品距离中心的距离
  115. const x = Math.cos(angle - Math.PI/2) * radius; // 减去90度,因为我们希望0度在顶部
  116. const y = Math.sin(angle - Math.PI/2) * radius;
  117. return `left: calc(50% + ${x}rpx); top: calc(50% + ${y}rpx); transform: translate(-50%, -50%);`
  118. })
  119. },
  120. // 开始抽奖
  121. startSpin() {
  122. if (this.isSpinning || this.remainingSpins <= 0) {
  123. if (this.remainingSpins <= 0) {
  124. uni.showToast({
  125. title: '今日抽奖次数已用完',
  126. icon: 'none'
  127. })
  128. }
  129. return
  130. }
  131. this.isSpinning = true
  132. this.remainingSpins--
  133. // 随机选择奖品
  134. const prizeIndex = Math.floor(Math.random() * this.prizes.length)
  135. this.currentPrize = this.prizes[prizeIndex]
  136. // 计算旋转角度
  137. // 每个扇形45度,指针指向扇形中心需要额外旋转22.5度
  138. const targetAngle = 360 - (prizeIndex * this.sectorAngle + this.sectorAngle / 2)
  139. const spinRounds = 5 // 转5圈
  140. const finalAngle = this.rotateAngle + spinRounds * 360 + targetAngle
  141. this.rotateAngle = finalAngle
  142. // 动画结束后显示结果
  143. setTimeout(() => {
  144. this.isSpinning = false
  145. this.showResult = true
  146. this.handlePrizeResult()
  147. }, 3000)
  148. },
  149. // 处理中奖结果
  150. handlePrizeResult() {
  151. if (this.currentPrize.type === 'money') {
  152. // 处理现金奖励
  153. console.log(`获得现金奖励: ${this.currentPrize.amount}`)
  154. // 这里可以调用API增加用户余额
  155. } else if (this.currentPrize.type === 'gift') {
  156. // 处理礼品奖励
  157. console.log(`获得礼品: ${this.currentPrize.item}`)
  158. // 这里可以调用API添加礼品到用户背包
  159. }
  160. },
  161. // 关闭结果弹窗
  162. closeResult() {
  163. this.showResult = false
  164. }
  165. }
  166. }
  167. </script>
  168. <style scoped lang="scss">
  169. .turntable-container {
  170. min-height: 100vh;
  171. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  172. padding: 40rpx;
  173. display: flex;
  174. flex-direction: column;
  175. align-items: center;
  176. }
  177. .header {
  178. text-align: center;
  179. margin-bottom: 60rpx;
  180. color: white;
  181. .title {
  182. font-size: 48rpx;
  183. font-weight: bold;
  184. display: block;
  185. margin-bottom: 20rpx;
  186. }
  187. .subtitle {
  188. font-size: 28rpx;
  189. opacity: 0.9;
  190. }
  191. }
  192. .turntable-wrapper {
  193. position: relative;
  194. width: 600rpx;
  195. height: 600rpx;
  196. margin-bottom: 60rpx;
  197. }
  198. .turntable {
  199. width: 100%;
  200. height: 100%;
  201. border-radius: 50%;
  202. position: relative;
  203. transition: transform 3s cubic-bezier(0.23, 1, 0.32, 1);
  204. box-shadow: 0 0 40rpx rgba(0, 0, 0, 0.3);
  205. overflow: hidden;
  206. &.spinning {
  207. transition-duration: 3s;
  208. }
  209. }
  210. .wheel-bg {
  211. position: absolute;
  212. width: 100%;
  213. height: 100%;
  214. border-radius: 50%;
  215. overflow: hidden;
  216. }
  217. .wheel-sector {
  218. position: absolute;
  219. width: 50%;
  220. height: 50%;
  221. top: 0%;
  222. left: 50%;
  223. transform-origin: 0% 100%;
  224. &::before {
  225. content: '';
  226. position: absolute;
  227. top: 0;
  228. left: 0;
  229. width: 100%;
  230. height: 100%;
  231. background: inherit;
  232. clip-path: polygon(0% 100%, 50% 0%, 100% 100%);
  233. }
  234. // 添加边框线
  235. &::after {
  236. content: '';
  237. position: absolute;
  238. top: 0;
  239. left: 0;
  240. width: 100%;
  241. height: 100%;
  242. border-right: 2rpx solid rgba(255,255,255,0.3);
  243. transform-origin: 0% 100%;
  244. }
  245. }
  246. .prizes-overlay {
  247. position: absolute;
  248. width: 100%;
  249. height: 100%;
  250. top: 0;
  251. left: 0;
  252. z-index: 990;
  253. }
  254. .prize-item {
  255. position: absolute;
  256. width: 120rpx;
  257. height: 80rpx;
  258. z-index: 10;
  259. }
  260. .prize-content {
  261. width: 100%;
  262. height: 100%;
  263. display: flex;
  264. flex-direction: column;
  265. align-items: center;
  266. justify-content: center;
  267. text-align: center;
  268. color: white;
  269. text-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.8);
  270. .prize-icon {
  271. font-size: 32rpx;
  272. display: block;
  273. margin-bottom: 4rpx;
  274. }
  275. .prize-name {
  276. font-size: 18rpx;
  277. display: block;
  278. font-weight: bold;
  279. margin-bottom: 2rpx;
  280. line-height: 1.2;
  281. }
  282. .prize-value {
  283. font-size: 20rpx;
  284. display: block;
  285. font-weight: bold;
  286. line-height: 1.2;
  287. }
  288. }
  289. .pointer {
  290. position: absolute;
  291. top: -20rpx;
  292. left: 50%;
  293. transform: translateX(-50%);
  294. z-index: 100;
  295. .pointer-triangle {
  296. width: 0;
  297. height: 0;
  298. border-left: 20rpx solid transparent;
  299. border-right: 20rpx solid transparent;
  300. border-top: 60rpx solid #FF4757;
  301. filter: drop-shadow(0 4rpx 8rpx rgba(0, 0, 0, 0.3));
  302. }
  303. }
  304. .center-button {
  305. position: absolute;
  306. top: 50%;
  307. left: 50%;
  308. transform: translate(-50%, -50%);
  309. width: 160rpx;
  310. height: 160rpx;
  311. border-radius: 50%;
  312. background: linear-gradient(145deg, #FF6B6B, #FF4757);
  313. display: flex;
  314. align-items: center;
  315. justify-content: center;
  316. box-shadow: 0 8rpx 20rpx rgba(255, 71, 87, 0.4);
  317. z-index: 50;
  318. transition: transform 0.2s;
  319. &:active:not(.disabled) {
  320. transform: translate(-50%, -50%) scale(0.95);
  321. }
  322. &.disabled {
  323. opacity: 0.6;
  324. cursor: not-allowed;
  325. }
  326. .button-text {
  327. color: white;
  328. font-size: 24rpx;
  329. font-weight: bold;
  330. text-align: center;
  331. }
  332. }
  333. .result-modal {
  334. position: fixed;
  335. top: 0;
  336. left: 0;
  337. width: 100vw;
  338. height: 100vh;
  339. background: rgba(0, 0, 0, 0.7);
  340. display: flex;
  341. align-items: center;
  342. justify-content: center;
  343. z-index: 1000;
  344. .modal-content {
  345. background: white;
  346. border-radius: 20rpx;
  347. padding: 60rpx 40rpx;
  348. text-align: center;
  349. box-shadow: 0 20rpx 40rpx rgba(0, 0, 0, 0.3);
  350. min-width: 500rpx;
  351. .result-title {
  352. font-size: 36rpx;
  353. font-weight: bold;
  354. color: #FF6B6B;
  355. margin-bottom: 40rpx;
  356. }
  357. .result-prize {
  358. margin-bottom: 40rpx;
  359. .result-icon {
  360. font-size: 60rpx;
  361. display: block;
  362. margin-bottom: 20rpx;
  363. }
  364. .result-name {
  365. font-size: 32rpx;
  366. color: #333;
  367. display: block;
  368. margin-bottom: 10rpx;
  369. }
  370. .result-value {
  371. font-size: 36rpx;
  372. color: #FF6B6B;
  373. font-weight: bold;
  374. }
  375. }
  376. .confirm-btn {
  377. background: linear-gradient(45deg, #FF6B6B, #FF4757);
  378. color: white;
  379. border: none;
  380. border-radius: 40rpx;
  381. padding: 20rpx 60rpx;
  382. font-size: 28rpx;
  383. font-weight: bold;
  384. }
  385. }
  386. }
  387. .spin-info {
  388. text-align: center;
  389. color: white;
  390. font-size: 28rpx;
  391. background: rgba(255, 255, 255, 0.2);
  392. padding: 20rpx 40rpx;
  393. border-radius: 40rpx;
  394. backdrop-filter: blur(10rpx);
  395. }
  396. </style>