turntableOne.vue 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  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. this.targetIdx = 4
  173. const that = this
  174. this.spinning = false;
  175. clearInterval(this.idleTimer);
  176. if (this.targetIdx == -1) {
  177. uni.showToast({
  178. title: '抽奖失败',
  179. icon: 'none'
  180. })
  181. this.running = false;
  182. return
  183. }
  184. this.animation(this.targetIdx)
  185. setTimeout(() => {
  186. this.running = false;
  187. uni.showModal({
  188. title: '恭喜,中奖',
  189. content: this.list[this.targetIdx].name,
  190. showCancel: false,
  191. success: function(res) {
  192. if (res.confirm) {
  193. that.$refs.turntablePopup.close()
  194. that.$emit("openAppPop")
  195. } else if (res.cancel) {
  196. that.$refs.turntablePopup.close()
  197. that.$emit("openAppPop")
  198. }
  199. }
  200. });
  201. }, this.duration + 1000)
  202. }
  203. }
  204. }
  205. </script>
  206. <style scoped lang="scss">
  207. @keyframes spin {
  208. from {
  209. transform: rotate(0deg);
  210. }
  211. to {
  212. transform: rotate(360deg);
  213. }
  214. }
  215. .infinite-spin {
  216. animation: spin 1s linear infinite;
  217. }
  218. .close {
  219. width: 64rpx;
  220. height: 64rpx;
  221. margin: 24rpx auto 0 auto;
  222. display: block;
  223. }
  224. .turntable {
  225. position: relative;
  226. width: 660rpx;
  227. height: 880rpx;
  228. .bg {
  229. width: 660rpx;
  230. height: 880rpx;
  231. position: absolute;
  232. top: 0;
  233. left: 0;
  234. }
  235. &-con {
  236. width: 660rpx;
  237. height: 880rpx;
  238. display: flex;
  239. flex-direction: column;
  240. align-items: center;
  241. position: relative;
  242. z-index: 2;
  243. }
  244. .text {
  245. width: 520rpx;
  246. height: 104rpx;
  247. margin-top: 47rpx;
  248. }
  249. .base {
  250. width: 451rpx;
  251. height: 177rpx;
  252. position: absolute;
  253. bottom: 55rpx;
  254. left: 50%;
  255. transform: translateX(-50%);
  256. z-index: 3;
  257. }
  258. .decoration_img {
  259. width: 637rpx;
  260. height: 592rpx;
  261. position: absolute;
  262. bottom: 85rpx;
  263. left: 50%;
  264. transform: translateX(-50%);
  265. z-index: 4;
  266. }
  267. .ring {
  268. width: 502rpx;
  269. height: 502rpx;
  270. background: #FFFDFD;
  271. border-radius: 50%;
  272. position: relative;
  273. margin-top: 79rpx;
  274. z-index: 9;
  275. }
  276. .button {
  277. width: 158rpx;
  278. height: 200rpx;
  279. position: absolute;
  280. top: calc(50% - 21rpx);
  281. left: 50%;
  282. transform: translate(-50%, -50%);
  283. z-index: 99;
  284. }
  285. .ring_bg {
  286. width: 620rpx;
  287. height: 620rpx;
  288. position: absolute;
  289. bottom: 74rpx;
  290. left: 50%;
  291. transform: translateX(-50%);
  292. z-index: 6;
  293. }
  294. .canvas-content {
  295. position: absolute;
  296. left: 0;
  297. top: 0;
  298. z-index: 1;
  299. display: block;
  300. width: 502rpx;
  301. height: 502rpx;
  302. border-radius: inherit;
  303. /* background-clip: padding-box; */
  304. /* background-color: #ffcb3f; */
  305. }
  306. .icon-awrad {
  307. width: 72rpx;
  308. height: 72rpx;
  309. margin-top: 10rpx;
  310. object-fit: contain;
  311. }
  312. .canvas-list {
  313. position: absolute;
  314. left: 0;
  315. top: 0;
  316. width: inherit;
  317. height: inherit;
  318. z-index: 99;
  319. }
  320. .canvas-item {
  321. position: absolute;
  322. left: 0;
  323. top: 0;
  324. width: 100%;
  325. height: 100%;
  326. color: #e4370e;
  327. /* text-shadow: 0 1rpx 1rpx rgba(255, 255, 255, 0.6); */
  328. }
  329. .canvas-item-text {
  330. position: relative;
  331. display: block;
  332. padding-top: 14rpx;
  333. margin: 0 auto;
  334. text-align: center;
  335. -webkit-transform-origin: 50% 251rpx;
  336. transform-origin: 50% 251rpx;
  337. display: flex;
  338. flex-direction: column;
  339. align-items: center;
  340. color: #F72F26;
  341. font-weight: 500;
  342. font-size: 24rpx;
  343. }
  344. .canvas-item-text text {
  345. font-size: 30rpx;
  346. }
  347. /* 分隔线 */
  348. .canvas-line {
  349. position: absolute;
  350. left: 0;
  351. top: 0;
  352. width: inherit;
  353. height: inherit;
  354. z-index: 99;
  355. }
  356. .canvas-litem {
  357. position: absolute;
  358. left: 251rpx;
  359. top: 0;
  360. width: 1rpx;
  361. height: 251rpx;
  362. background-color: #FF4D2C;
  363. overflow: hidden;
  364. -webkit-transform-origin: 50% 251rpx;
  365. transform-origin: 50% 251rpx;
  366. }
  367. }
  368. </style>