index.vue 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693
  1. <template>
  2. <view class="app-layout">
  3. <app-layout :haveBackground="false">
  4. <view class="scratch">
  5. <view class="bg" :style="{'background-image': `url(${setting.bg_pic})`}"></view>
  6. <view class="scratch-head">
  7. <view class="s-h" @click="rules">规则</view>
  8. <view class="s-h" @click="share">分享</view>
  9. <app-share-qr-code-poster @share="hShareAppMessage"
  10. v-model="shareShow"
  11. :url="poster" title="生成海报"
  12. ></app-share-qr-code-poster>
  13. </view>
  14. <view class="scratch-oppty">
  15. <view>您还有
  16. <text>{{oppty}}</text>
  17. 次抽奖机会
  18. </view>
  19. </view>
  20. <view class="container">
  21. <image class="image-bg-a" src="/static/image/scratch_border_bg.png" load-lazy></image>
  22. <image id='frame' class="image-bg-b" src="/static/image/scratch_border.png"
  23. load-lazy></image>
  24. <view v-if="is_integral">
  25. <image class="image-bg-b" src="/static/image/scratch_hide.png" load-lazy></image>
  26. <view @click="register" class="image-bg-c">{{setting.deplete_integral_num}}积分刮一次</view>
  27. </view>
  28. <view v-else class="integral-show">
  29. <view class="scratch-award main-center" :hidden="shareShow || userLoginShow">
  30. <!--头条小程序不支持action -->
  31. <canvas v-if="is_start"
  32. class="scratch-canvas"
  33. id="scratch"
  34. canvas-id="scratch"
  35. @touchstart="touchStart"
  36. @touchmove="touchMove"
  37. @touchend="touchEnd"
  38. disable-scroll></canvas>
  39. <view v-if="is_name" class="award-name">
  40. <text>{{list ? list.name : ''}}</text>
  41. <app-button v-if="oppty > 0" @click="onStart" background="#f05525" height="80"
  42. width="400"
  43. border-color="#f05525"
  44. color="#FFFFFF" font-size="32" round>再刮一次
  45. </app-button>
  46. <app-button v-if="oppty == 0" @click="onStart" background="#cdcdcd" height="80"
  47. width="400"
  48. border-color="#cdcdcd"
  49. color="#FFFFFF" font-size="32" round>再刮一次
  50. </app-button>
  51. </view>
  52. </view>
  53. </view>
  54. </view>
  55. <view class="dir-left-nowrap main-center cross-center end">
  56. <view @click="home" class="dir-left-nowrap cross-center box-grow-0 left">
  57. <icon class="end-icon home" type></icon>
  58. <view>回到首页</view>
  59. </view>
  60. <view @click="prize" class="dir-left-nowrap cross-center box-grow-0">
  61. <view>我的中奖记录</view>
  62. <icon class="end-icon price" type></icon>
  63. </view>
  64. </view>
  65. <view class="prize">
  66. <view class="dir-left-nowrap cross-center main-center">
  67. <view class="line"></view>
  68. <view class="text">中奖名单</view>
  69. <view class="line"></view>
  70. </view>
  71. <swiper duration="500" vertical="true" autoplay="false" class="scratch-swiper">
  72. <swiper-item>
  73. <block v-for="v in logs" :key="v.id">
  74. <view class="scratch-item">{{v.create_time}} {{v.user}} {{v.name}}</view>
  75. </block>
  76. </swiper-item>
  77. </swiper>
  78. </view>
  79. </view>
  80. <!-- 弹窗 -->
  81. <view v-if="is_award">
  82. <view class="act-modal">
  83. <view class="act-modal-bg"></view>
  84. <view class="act-modal-pic cross-center main-center">
  85. <view class="success main-center">
  86. <icon class="image-bg" :style="{'background-image': `url(${appImg.scratch_win})`}" type></icon>
  87. <view class="dir-top-nowrap name main-center">
  88. <view>恭喜获得</view>
  89. <text class="main-center">{{list.name}}</text>
  90. </view>
  91. </view>
  92. <view class="act-modal-end dir-top-nowrap cross-center main-center">
  93. <app-button @click="onStart" height="72" width="320" color="#FFFFFF" background="#f05525"
  94. border-color="#f05525"
  95. round>再刮一次
  96. </app-button>
  97. <view class="act-zh">奖品已放入您的账号</view>
  98. </view>
  99. </view>
  100. </view>
  101. </view>
  102. </app-layout>
  103. </view>
  104. </template>
  105. <script>
  106. import appShareQrCodePoster from '../../../components/page-component/app-share-qr-code-poster/app-share-qr-code-poster';
  107. import {mapState} from 'vuex';
  108. export default {
  109. name: "index",
  110. components: {appShareQrCodePoster},
  111. computed: {
  112. ...mapState({
  113. appImg: state => state.mallConfig.plugin.scratch.app_image,
  114. userInfo: state => state.user.info,
  115. }),
  116. },
  117. data() {
  118. let ctx, interval, minX, minY, maxX, maxY, lastX, lastY;
  119. return {
  120. ctx,
  121. interval,
  122. minX,
  123. minY,
  124. maxX,
  125. maxY,
  126. lastX,
  127. lastY,
  128. logs: [],
  129. setting: {},
  130. list: null,
  131. oppty: 0,
  132. msg: '', //错误信息
  133. is_start: true,
  134. is_award: false,
  135. is_name: false,
  136. extra_list: null,
  137. touch_end: true,
  138. poster: this.$api.scratch.poster,
  139. poster_status: false, //hide model
  140. shareShow: false,
  141. userLoginShow: false,
  142. r: 16,
  143. is_integral: false,
  144. }
  145. },
  146. watch: {
  147. shareShow(val, oldVal) {
  148. if (val === false && oldVal === true) {
  149. this.init();
  150. }
  151. }
  152. },
  153. onUnload () {
  154. clearInterval(this.interval);
  155. },
  156. onHide () {
  157. clearInterval(this.interval);
  158. },
  159. // #ifdef MP-WEIXIN
  160. onLoad(options) { this.$commonLoad.onload(options);
  161. wx.showShareMenu({
  162. withShareTicket: true,
  163. menus: ['shareAppMessage', 'shareTimeline']
  164. })
  165. },
  166. onShareTimeline() {
  167. return this.$shareTimeline({
  168. title: this.$children[0].navigationBarTitle,
  169. query: {}
  170. });
  171. },
  172. // #endif
  173. onShow() {
  174. const self = this;
  175. self.$showLoading({title: '加载中'});
  176. self.$request({
  177. url: self.$api.scratch.index,
  178. }).then(info => {
  179. self.$hideLoading();
  180. self.is_award = false;
  181. if (info.data) {
  182. [self.msg, self.setting, self.oppty, self.list] = [info.code === 1 ? info.msg : '', info.data.setting, info.data.oppty, info.data.list];
  183. self.is_integral = self.setting.deplete_integral_num > 0;
  184. } else {
  185. uni.showToast({'title': info.msg, icon: 'none'});
  186. }
  187. self.init();
  188. });
  189. self.prizeList();
  190. self.interval = setInterval(() => {
  191. self.prizeList();
  192. }, 10000)
  193. },
  194. // #ifdef MP
  195. onShareAppMessage() {
  196. return this.$shareAppMessage({
  197. title: this.$children[0].navigationBarTitle,
  198. path: '/plugins/scratch/index/index',
  199. params: {}
  200. });
  201. },
  202. // #endif
  203. methods: {
  204. hShareAppMessage(s = false){
  205. return this.$shareAppMessage({
  206. title: this.$children[0].navigationBarTitle,
  207. path: '/plugins/scratch/index/index',
  208. params: {}
  209. }, s);
  210. },
  211. register() {
  212. if (this.msg) {
  213. uni.showToast({icon: `none`, title: this.msg});
  214. return;
  215. }
  216. this.is_integral = false;
  217. this.init();
  218. },
  219. share() {
  220. this.shareShow = true;
  221. },
  222. home() {
  223. uni.redirectTo({url: `/pages/index/index`});
  224. },
  225. prize() {
  226. uni.navigateTo({url: `/plugins/scratch/prize/prize`});
  227. },
  228. rules() {
  229. // uni.navigateTo({url: `/plugins/scratch/rule/rule`});
  230. uni.navigateTo({
  231. url: `/pages/rules/index?url=${encodeURIComponent(this.$api.scratch.setting)}&keys=${JSON.stringify(['setting', 'rule'])}`,
  232. });
  233. },
  234. prizeList() {
  235. const self = this;
  236. self.$request({
  237. url: self.$api.scratch.record,
  238. }).then(info => {
  239. if (info.code === 0) {
  240. self.logs = info.data;
  241. }
  242. })
  243. },
  244. onStart() {
  245. this.init();
  246. [this.is_integral, this.list, this.is_award] = [this.setting.deplete_integral_num > 0, this.extra_list, false];
  247. },
  248. init() {
  249. const self = this;
  250. [self.is_start, self.is_name] = [true, false];
  251. //Promise
  252. setTimeout(() => {
  253. let query = uni.createSelectorQuery();
  254. query.select('#frame').boundingClientRect();
  255. query.exec((res) => {
  256. const canvasWidth = res[0].width;
  257. const canvasHeight = res[0].height;
  258. let imageResource = './../image/scratch_hide.png';
  259. [self.lastX, self.lastY, self.minX, self.minY, self.maxX, self.maxY] = ['', '', '', '', '', ''];
  260. [self.r, self.canvasWidth, self.canvasHeight] = [16, canvasWidth, canvasHeight]
  261. let ctx = uni.createCanvasContext('scratch');
  262. ctx.drawImage(imageResource, 0, 0, canvasWidth, canvasHeight);
  263. self.ctx = ctx;
  264. ctx.draw(true, (e) => {
  265. self.is_name = true;
  266. })
  267. })
  268. })
  269. },
  270. //
  271. drawRect(x, y) {
  272. const self = this;
  273. const r = self.r / 2;
  274. const x1 = x - r > 0 ? x - r : 0
  275. const y1 = y - r > 0 ? y - r : 0
  276. if ('' !== self.minX) {
  277. self.minX = self.minX > x1 ? x1 : self.minX;
  278. self.minY = self.minY > y1 ? y1 : self.minY;
  279. self.maxX = self.maxX > x1 ? self.maxX : x1;
  280. self.maxY = self.maxY > y1 ? self.maxY : y1;
  281. } else {
  282. [self.minX, self.minY, self.maxX, self.maxY] = [x1, y1, x1, y1];
  283. }
  284. [self.lastX, self.lastY] = [x1, y1];
  285. return [x1, y1, 2 * r]
  286. },
  287. // 绘图
  288. clearArc(x, y, stepClear) {
  289. let r = this.r;
  290. let ctx = this.ctx;
  291. let calcWidth = r - stepClear;
  292. let calcHeight = Math.sqrt(r * r - calcWidth * calcWidth);
  293. let posX = x - calcWidth;
  294. let posY = y - calcHeight;
  295. let widthX = 2 * calcWidth;
  296. let heightY = 2 * calcHeight;
  297. if (stepClear <= r) {
  298. ctx.clearRect(posX, posY, widthX, heightY);
  299. stepClear += 2;
  300. this.clearArc(x, y, stepClear);
  301. }
  302. },
  303. //action
  304. touchStart(e) {
  305. const self = this;
  306. if (self.msg === 'User ID不能为空。') {
  307. this.userLoginShow = true;
  308. self.$store.dispatch('user/info');
  309. } else if (self.msg) {
  310. uni.showToast({icon: 'none', title: self.msg})
  311. }
  312. },
  313. touchMove(e) {
  314. if (!this.is_start || this.msg) return;
  315. let stepClear = 2;
  316. this.drawRect(e.touches[0].x, e.touches[0].y)
  317. this.clearArc(e.touches[0].x, e.touches[0].y, stepClear)
  318. this.ctx.draw(true)
  319. },
  320. touchEnd(e) {
  321. const self = this;
  322. if (!self.is_start || self.msg) return;
  323. //自动清楚采用点范围值方式判断
  324. let canvasWidth = self.canvasWidth;
  325. let canvasHeight = self.canvasHeight;
  326. let minX = self.minX;
  327. let minY = self.minY;
  328. let maxX = self.maxX;
  329. let maxY = self.maxY;
  330. if (maxX - minX > .4 * canvasWidth && maxY - minY > .4 * canvasHeight && self.touch_end) {
  331. self.touch_end = false;
  332. self.$request({
  333. url: self.$api.scratch.receive,
  334. data: {
  335. id: self.list.id
  336. }
  337. }).then(info => {
  338. if (info.code === 0) {
  339. self.ctx.draw();
  340. [self.msg, self.setting, self.oppty, self.extra_list] = [info.msg, info.data.setting, info.data.oppty, info.data.list];
  341. [self.is_start, self.is_award] = [false, self.list.type != 5];
  342. } else {
  343. uni.showToast({icon: 'none', content: info.msg});
  344. self.onStart();
  345. }
  346. self.touch_end = true;
  347. });
  348. }
  349. },
  350. }
  351. }
  352. </script>
  353. <style scoped lang="scss">
  354. .app-layout /deep/ .app-layout {
  355. background: #5e00b8;
  356. min-height: 100vh;
  357. }
  358. .scratch-canvas {
  359. width: #{600rpx};
  360. height: #{320rpx};
  361. position: absolute;
  362. left: 0;
  363. z-index: 1;
  364. }
  365. .scratch {
  366. text-align: center;
  367. padding-bottom: #{1rpx};
  368. .bg {
  369. position: absolute;
  370. left: 0;
  371. top: 0;
  372. height: 100vh;
  373. width: #{100%};
  374. display: block;
  375. background-repeat: no-repeat;
  376. background-size: 100% 100%;
  377. }
  378. .scratch-head {
  379. .s-h {
  380. color: #FFFFFF;
  381. font-size: $uni-font-size-weak-one;
  382. background: #000000;
  383. text-align: center;
  384. background-color: rgba(0, 0, 0, 0.3);
  385. line-height: #{48rpx};
  386. width: #{92rpx};
  387. border-radius: #{24rpx} 0 0 #{24rpx};
  388. position: absolute;
  389. right: 0;
  390. z-index: 1;
  391. top: #{40rpx};
  392. }
  393. .s-h:nth-child(2) {
  394. top: #{112rpx}
  395. }
  396. }
  397. .scratch-oppty {
  398. line-height: #{56rpx};
  399. font-size: #{28rpx};
  400. padding-top: #{380rpx};
  401. position: relative;
  402. view {
  403. color: #ffffff;
  404. padding: 0 #{30rpx};
  405. display: inline-block;
  406. background: rgba(0, 0, 0, 0.3);;
  407. border-radius: #{28rpx};
  408. }
  409. text {
  410. color: #ffb92a;
  411. }
  412. }
  413. .container {
  414. position: relative;
  415. margin-top: #{40rpx};
  416. margin-bottom: #{48rpx};
  417. height: #{360rpx};
  418. width: 100%;
  419. .image-bg-a {
  420. height: #{360rpx};
  421. width: #{654rpx};
  422. }
  423. .image-bg-b {
  424. height: #{320rpx};
  425. width: #{600rpx};
  426. position: absolute;
  427. top: #{20rpx};
  428. left: 50%;
  429. -webkit-transform: translate(-50%, -50%);
  430. transform: translate(-50%, 0);
  431. }
  432. .image-bg-c {
  433. position: absolute;
  434. top: #{150rpx};
  435. left: 50%;
  436. -webkit-transform: translate(-50%, -50%);
  437. transform: translate(-50%, 0);
  438. line-height: #{80rpx};
  439. background: #f05525;
  440. border-radius: #{40rpx};
  441. padding: 0 #{48rpx};
  442. color: #ffffff;
  443. }
  444. }
  445. .end {
  446. position: relative;
  447. color: #FFFFFF;
  448. font-size: #{28rpx};
  449. .left {
  450. margin-right: #{160rpx};
  451. }
  452. .end-icon {
  453. background-repeat: no-repeat;
  454. background-size: 100% 100%;
  455. margin: 0 #{16rpx};
  456. }
  457. .end-icon.home {
  458. height: #{30rpx};
  459. width: #{30rpx};
  460. background-image: url('../../../static/image/icon/icon-home-white.png');
  461. }
  462. .end-icon.price {
  463. height: #{22rpx};
  464. width: #{12rpx};
  465. background-image: url('../../../static/image/icon/arrow-right-white.png');
  466. }
  467. }
  468. .prize {
  469. position: relative;
  470. height: #{186rpx};
  471. width: #{654rpx};
  472. background: #420080;
  473. display: inline-block;
  474. border-radius: #{16rpx};
  475. text-align: left;
  476. padding-top: #{26rpx};
  477. margin-top: #{48rpx};
  478. .line {
  479. height: #{1px};
  480. width: #{120rpx};
  481. background: #FFFFFF;
  482. }
  483. .text {
  484. padding: 0 #{40rpx};
  485. color: #FFFFFF;
  486. font-size: #{28rpx};
  487. padding-bottom:#{12rpx};
  488. }
  489. .scratch-swiper {
  490. height: #{110rpx};
  491. font-size: #{24rpx};
  492. color: #ffffff;
  493. }
  494. .scratch-item {
  495. line-height: #{40rpx};
  496. padding: 0 #{50rpx};
  497. display: inline-block;
  498. white-space: nowrap;
  499. width: 100%;
  500. overflow: hidden;
  501. text-overflow: ellipsis;
  502. }
  503. }
  504. .integral-show {
  505. position: absolute;
  506. top: #{20rpx};
  507. left: 50%;
  508. -webkit-transform: translate(-50%, -50%);
  509. transform: translate(-50%, 0);
  510. width: #{600rpx};
  511. height: #{320rpx};
  512. .scratch-award {
  513. width: 100%;
  514. height: 100%;
  515. canvas {
  516. }
  517. .award-name {
  518. text {
  519. width: #{410rpx};
  520. color: #f05525;
  521. font-size: #{36rpx};
  522. margin-bottom: #{40rpx};
  523. margin-top: #{88rpx};
  524. overflow: hidden;
  525. text-overflow: ellipsis;
  526. display: -webkit-box;
  527. -webkit-line-clamp: 1;
  528. -webkit-box-orient: vertical;
  529. }
  530. view {
  531. }
  532. }
  533. }
  534. }
  535. .scraxtch-award {
  536. view {
  537. width: #{400rpx};
  538. line-height: #{80rpx};
  539. color: #ffffff;
  540. margin-top: #{40rpx};
  541. border-radius: #{40rpx};
  542. }
  543. .next {
  544. background: #f05525;
  545. }
  546. .none {
  547. background: #cdcdcd;
  548. }
  549. }
  550. }
  551. .act-modal {
  552. position: fixed;
  553. left: 0;
  554. top: 0;
  555. width: 100%;
  556. height: 100%;
  557. z-index: 2001;
  558. transition: 200ms;
  559. .show {
  560. visibility: visible;
  561. opacity: 1;
  562. }
  563. .act-modal-bg {
  564. background: rgba(0, 0, 0, 0.5);
  565. position: fixed;
  566. left: 0;
  567. top: 0;
  568. width: 100%;
  569. height: 100%;
  570. z-index: 1;
  571. }
  572. .act-modal-pic {
  573. background: rgba(0, 0, 0, 0.25);
  574. position: fixed;
  575. left: 0;
  576. top: 0;
  577. width: 100%;
  578. height: 100%;
  579. z-index: 1;
  580. }
  581. .success {
  582. position: absolute;
  583. top: 0;
  584. width: 100%;
  585. left: 0;
  586. .image-bg {
  587. height: #{733rpx};
  588. width: 100%;
  589. background-repeat: no-repeat;
  590. background-size: 100% 100%;
  591. }
  592. .name {
  593. position: absolute;
  594. top: #{540rpx};
  595. font-size: #{32rpx};
  596. color: #f05525;
  597. left: 50%;
  598. right: 0;
  599. text-align: center;
  600. -webkit-transform: translate(-50%, 0);
  601. transform: translate(-50%, 0);
  602. view {
  603. padding-bottom: #{10rpx};
  604. }
  605. text {
  606. //text-align: left;
  607. }
  608. }
  609. }
  610. .act-modal-end {
  611. position: absolute;
  612. top: #{730rpx};
  613. border-bottom: #{10rpx} solid #ffb947;
  614. border-radius: 0 0 #{24rpx} #{24rpx};
  615. border-left: #{10rpx} solid #ffb947;
  616. border-right: #{10rpx} solid #ffb947;
  617. width: #{490rpx};
  618. background: #ffffff;
  619. height: #{160rpx};
  620. left: 0;
  621. right: 0;
  622. margin: 0 auto;
  623. .act-zh {
  624. margin-top: #{24rpx};
  625. font-size: #{24rpx};
  626. color: #999999;
  627. }
  628. }
  629. }
  630. </style>