turntableOne.vue 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. <template>
  2. <uni-popup ref="turntablePopup" type="center" :is-mask-click="false">
  3. <view class="turntable">
  4. <image class="bg" src="https://cos.his.cdwjyyh.com/fs/20250910/9a6263291ec0429ea5828a15b2b490dc.png" mode="aspectFit"></image>
  5. <view class="turntable-con">
  6. <image class="text" src="https://cos.his.cdwjyyh.com/fs/20250910/fc08b64307a344b4948568fa0f81401e.png" mode="heightFix"></image>
  7. <image class="base" src="https://cos.his.cdwjyyh.com/fs/20250910/9385e4a6d5e245dfb86f425f49235589.png" mode="aspectFit"></image>
  8. <image class="decoration_img" src="https://cos.his.cdwjyyh.com/fs/20250910/86577ed4fc50420d99b1153e2dd6e1df.png" mode="aspectFit"></image>
  9. <view class="ring">
  10. <view class="canvas-content" :class="{'infinite-spin': spinning}">
  11. <view :animation="animationData" class="canvas-content" id="zhuanpano" style="">
  12. <view class="canvas-line">
  13. <!-- <canvas canvas-id="sector" style="width:502rpx;height:502rpx" /> -->
  14. <view class="canvas-litem" v-for="(item,index) in list" :key="index"
  15. :style="{transform:'rotate('+(index * width + width / 2)+'deg)'}"></view>
  16. </view>
  17. <view class="canvas-list">
  18. <view class="canvas-item"
  19. :style="{transform: 'rotate('+((index) * width)+'deg)', zIndex:index}"
  20. v-for="(iteml,index) in list" :key="index">
  21. <view class="canvas-item-text" :style="'transform:rotate('+(index)+')'">
  22. <view class="b">{{iteml.name}}</view>
  23. <image class="icon-awrad iconfont" :src="iteml.iconUrl" mode="aspectFit">
  24. </image>
  25. </view>
  26. </view>
  27. </view>
  28. </view>
  29. </view>
  30. <image class="button" src="https://cos.his.cdwjyyh.com/fs/20250910/d05462c59e7f4ab98e5205b7f8172325.png" mode="aspectFit" @click="playReward">
  31. </image>
  32. </view>
  33. <image class="ring_bg" src="https://cos.his.cdwjyyh.com/fs/20250910/41bea8eda62e484d94a19f18316e509b.png" mode="aspectFit"></image>
  34. </view>
  35. </view>
  36. <image class="close" src="https://cos.his.cdwjyyh.com/fs/20250910/93608bad8ad1479a854ec26e8eaaacea.png" @click="close"></image>
  37. </uni-popup>
  38. </template>
  39. <script>
  40. import {
  41. getVideoRewardRules
  42. } from "@/api/course.js"
  43. export default {
  44. data() {
  45. return {
  46. running: false,
  47. spinning: false, // 是否处于无限旋转
  48. list: [],
  49. width: 0,
  50. animationData: {},
  51. btnDisabled: '',
  52. runDeg: 0,
  53. targetIdx: -1,
  54. duration: 1000,
  55. currentDeg: 0, // 实时角度
  56. idleTimer: null,
  57. animationRun: null
  58. }
  59. },
  60. methods: {
  61. getVideoRewardRules(urlOption) {
  62. const param = {
  63. type: 3,
  64. ...urlOption
  65. }
  66. getVideoRewardRules(param).then(res => {
  67. if (res.code == 200) {
  68. this.list = res.data || []
  69. this.width = 360 / this.list.length;
  70. } else {
  71. uni.showToast({
  72. title: res.msg,
  73. icon: 'none'
  74. })
  75. }
  76. })
  77. },
  78. open(urlOption) {
  79. this.animationRun = uni.createAnimation({
  80. duration: 0
  81. });
  82. this.animationRun.rotate(0).step();
  83. this.animationData = this.animationRun.export();
  84. this.running = false
  85. this.spinning = false;
  86. clearInterval(this.idleTimer);
  87. this.runDeg = 0
  88. this.targetIdx = -1
  89. this.$refs.turntablePopup.open()
  90. this.getVideoRewardRules(urlOption)
  91. },
  92. close(type) {
  93. if (type == 'close') {
  94. this.running = false
  95. this.spinning = false;
  96. clearInterval(this.idleTimer);
  97. }
  98. if (this.running) {
  99. uni.showToast({
  100. title: '抽取中,请勿关闭',
  101. icon: 'none'
  102. })
  103. return
  104. }
  105. this.$refs.turntablePopup.close()
  106. },
  107. drawFanWithAlternateColor(id, x, y, r, count) {
  108. const ctx = uni.createCanvasContext(id, this);
  109. const sweep = 360 / count; // 每份角度
  110. let start = -90 - sweep / 2; // 第一个扇形起始角度(deg)
  111. for (let i = 0; i < count; i++) {
  112. const end = start + sweep; // 结束角度
  113. let color;
  114. color = i % 2 === 0 ? '#FFDFD3' : '#FFF';
  115. ctx.beginPath();
  116. ctx.moveTo(x, y);
  117. ctx.arc(
  118. x, y, r,
  119. (start * Math.PI) / 180,
  120. (end * Math.PI) / 180
  121. );
  122. ctx.closePath();
  123. ctx.setFillStyle(color);
  124. ctx.fill();
  125. start = end; // 下一个扇形接着画
  126. }
  127. ctx.draw();
  128. },
  129. animation(index = null) {
  130. //中奖index
  131. let list = this.list;
  132. let runNum = 1; //旋转8周
  133. // 旋转角度
  134. this.runDeg = this.runDeg || 0;
  135. this.runDeg = this.runDeg + (360 - this.runDeg % 360) + (360 * runNum - index * (360 / list.length)) + 1
  136. // const a = 360 / list.length;
  137. // this.runDeg = this.currentDeg + 360 * 4 + (360 - this.currentDeg % 360) - index * a + 1;
  138. //创建动画
  139. this.animationRun = uni.createAnimation({
  140. duration: this.duration,
  141. timingFunction: 'ease'
  142. })
  143. console.log("=====animationRun=", this.animationRun)
  144. this.animationRun.rotate(this.runDeg).step();
  145. this.animationData = this.animationRun.export();
  146. },
  147. //发起抽奖
  148. playReward() {
  149. if (this.running) {
  150. uni.showToast({
  151. title: '抽取中',
  152. icon: 'none'
  153. })
  154. return
  155. }
  156. this.running = true
  157. this.startIdle();
  158. this.$emit('sendRewardFun',3)
  159. // setTimeout(() => {
  160. // this.endSuccess()
  161. // }, 2000)
  162. },
  163. startIdle() {
  164. this.spinning = true; // 打开 CSS 动画
  165. // 同时用定时器记录角度,方便后面衔接
  166. this.idleTimer = setInterval(() => {
  167. this.runDeg = (this.runDeg + 36) % 360; // 每 100ms 走 6°
  168. }, 100);
  169. },
  170. endSuccess(code) {
  171. this.targetIdx = this.list.findIndex(it=>it.code == code)
  172. const that = this
  173. this.spinning = false;
  174. clearInterval(this.idleTimer);
  175. if (this.targetIdx == -1) {
  176. uni.showToast({
  177. title: '抽奖失败',
  178. icon: 'none'
  179. })
  180. this.running = false;
  181. return
  182. }
  183. this.animation(this.targetIdx)
  184. setTimeout(() => {
  185. this.running = false;
  186. uni.showModal({
  187. title: '恭喜,中奖',
  188. content: this.list[this.targetIdx].name,
  189. showCancel: false,
  190. success: function(res) {
  191. if (res.confirm) {
  192. that.$refs.turntablePopup.close()
  193. that.$emit("openAppPop")
  194. } else if (res.cancel) {
  195. that.$refs.turntablePopup.close()
  196. that.$emit("openAppPop")
  197. }
  198. }
  199. });
  200. }, this.duration + 1000)
  201. }
  202. }
  203. }
  204. </script>
  205. <style scoped lang="scss">
  206. @keyframes spin {
  207. from {
  208. transform: rotate(0deg);
  209. }
  210. to {
  211. transform: rotate(360deg);
  212. }
  213. }
  214. .infinite-spin {
  215. animation: spin 1s linear infinite;
  216. }
  217. .close {
  218. width: 64rpx;
  219. height: 64rpx;
  220. margin: 24rpx auto 0 auto;
  221. display: block;
  222. }
  223. .turntable {
  224. position: relative;
  225. width: 660rpx;
  226. height: 880rpx;
  227. .bg {
  228. width: 660rpx;
  229. height: 880rpx;
  230. position: absolute;
  231. top: 0;
  232. left: 0;
  233. }
  234. &-con {
  235. width: 660rpx;
  236. height: 880rpx;
  237. display: flex;
  238. flex-direction: column;
  239. align-items: center;
  240. position: relative;
  241. z-index: 2;
  242. }
  243. .text {
  244. width: 520rpx;
  245. height: 104rpx;
  246. margin-top: 47rpx;
  247. }
  248. .base {
  249. width: 451rpx;
  250. height: 177rpx;
  251. position: absolute;
  252. bottom: 55rpx;
  253. left: 50%;
  254. transform: translateX(-50%);
  255. z-index: 3;
  256. }
  257. .decoration_img {
  258. width: 637rpx;
  259. height: 592rpx;
  260. position: absolute;
  261. bottom: 85rpx;
  262. left: 50%;
  263. transform: translateX(-50%);
  264. z-index: 4;
  265. }
  266. .ring {
  267. width: 502rpx;
  268. height: 502rpx;
  269. background: #FFFDFD;
  270. border-radius: 50%;
  271. position: relative;
  272. margin-top: 79rpx;
  273. z-index: 9;
  274. }
  275. .button {
  276. width: 158rpx;
  277. height: 200rpx;
  278. position: absolute;
  279. top: calc(50% - 21rpx);
  280. left: 50%;
  281. transform: translate(-50%, -50%);
  282. z-index: 99;
  283. }
  284. .ring_bg {
  285. width: 620rpx;
  286. height: 620rpx;
  287. position: absolute;
  288. bottom: 74rpx;
  289. left: 50%;
  290. transform: translateX(-50%);
  291. z-index: 6;
  292. }
  293. .canvas-content {
  294. position: absolute;
  295. left: 0;
  296. top: 0;
  297. z-index: 1;
  298. display: block;
  299. width: 502rpx;
  300. height: 502rpx;
  301. border-radius: inherit;
  302. /* background-clip: padding-box; */
  303. /* background-color: #ffcb3f; */
  304. }
  305. .icon-awrad {
  306. width: 72rpx;
  307. height: 72rpx;
  308. margin-top: 10rpx;
  309. object-fit: contain;
  310. }
  311. .canvas-list {
  312. position: absolute;
  313. left: 0;
  314. top: 0;
  315. width: inherit;
  316. height: inherit;
  317. z-index: 99;
  318. }
  319. .canvas-item {
  320. position: absolute;
  321. left: 0;
  322. top: 0;
  323. width: 100%;
  324. height: 100%;
  325. color: #e4370e;
  326. /* text-shadow: 0 1rpx 1rpx rgba(255, 255, 255, 0.6); */
  327. }
  328. .canvas-item-text {
  329. position: relative;
  330. display: block;
  331. padding-top: 14rpx;
  332. margin: 0 auto;
  333. text-align: center;
  334. -webkit-transform-origin: 50% 251rpx;
  335. transform-origin: 50% 251rpx;
  336. display: flex;
  337. flex-direction: column;
  338. align-items: center;
  339. color: #F72F26;
  340. font-weight: 500;
  341. font-size: 24rpx;
  342. }
  343. .canvas-item-text text {
  344. font-size: 30rpx;
  345. }
  346. /* 分隔线 */
  347. .canvas-line {
  348. position: absolute;
  349. left: 0;
  350. top: 0;
  351. width: inherit;
  352. height: inherit;
  353. z-index: 99;
  354. }
  355. .canvas-litem {
  356. position: absolute;
  357. left: 251rpx;
  358. top: 0;
  359. width: 1rpx;
  360. height: 251rpx;
  361. background-color: #FF4D2C;
  362. overflow: hidden;
  363. -webkit-transform-origin: 50% 251rpx;
  364. transform-origin: 50% 251rpx;
  365. }
  366. }
  367. </style>