PairUp.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499
  1. <template>
  2. <div class="game-view">
  3. <img
  4. class="title"
  5. src="@/assets/images/pair-up-title.png"
  6. alt=""
  7. draggable="false"
  8. >
  9. <div
  10. v-show="!isOver"
  11. class="tip"
  12. >
  13. 请翻开相同的企业logo
  14. </div>
  15. <!-- 左上角按钮 -->
  16. <div class="btn-group">
  17. <button
  18. class="return-home"
  19. @click="onClickReturnHome"
  20. />
  21. <button
  22. v-if="store.state.gameRuleList[2].rtf"
  23. class="game-rule"
  24. @click="isShowRule = true"
  25. />
  26. </div>
  27. <!-- 卡牌列表 -->
  28. <div
  29. v-show="!isOver"
  30. class="card-list"
  31. >
  32. <div
  33. v-for="(card, cardIdx) in cardList"
  34. :key="cardIdx"
  35. class="card"
  36. :class="{
  37. active: activeCardIdxList.includes(cardIdx),
  38. hide: card.isDone,
  39. }"
  40. @click="onClickCard(card, cardIdx)"
  41. >
  42. <div class="front">
  43. <img
  44. class="card-frame"
  45. src="@/assets/images/pair-up-card-front-side.png"
  46. alt=""
  47. draggable="false"
  48. >
  49. <img
  50. class="card-content"
  51. :src="require(`@/assets/images/pair-up-logos/${logoFileNameList[card.logoIdx]}`)"
  52. alt=""
  53. draggable="false"
  54. >
  55. </div>
  56. <img
  57. class="back"
  58. src="@/assets/images/pair-up-card-back-side.png"
  59. alt=""
  60. draggable="false"
  61. >
  62. </div>
  63. </div>
  64. <!-- 底部信息栏 -->
  65. <div
  66. v-show="!isOver"
  67. class="common-info-group"
  68. >
  69. <div class="info-item bonus-point">
  70. <img
  71. class="icon"
  72. src="@/assets/images/icon_bonus_point.png"
  73. alt=""
  74. draggable="false"
  75. >
  76. <span class="number">{{ bonusPoint }}</span>
  77. </div>
  78. <div class="info-item time-count">
  79. <img
  80. class="icon"
  81. src="@/assets/images/icon_time_count.png"
  82. alt=""
  83. draggable="false"
  84. >
  85. <span class="number">{{ timeCountForShow }}</span>
  86. </div>
  87. </div>
  88. <PairUpOver
  89. v-show="isOver"
  90. :corp-count="recognizedCorpList.length"
  91. :bonus-count="bonusPoint"
  92. @replay="replay"
  93. />
  94. <transition name="fade-out">
  95. <NotifyBonusPointReachedLimit v-if="isShowNotifyBonusPointReachedLimit" />
  96. </transition>
  97. <GameRule
  98. v-show="isShowRule"
  99. game-title="企业翻翻看"
  100. :rich-text="store.state.gameRuleList[2].rtf"
  101. @close="isShowRule=false"
  102. />
  103. </div>
  104. </template>
  105. <script setup>
  106. import useSizeAdapt from "@/useFunctions/useSizeAdapt"
  107. import { ref, computed, watch, onMounted, onBeforeUnmount, nextTick } from "vue"
  108. import { useRoute, useRouter } from "vue-router"
  109. import { useStore } from "vuex"
  110. import dayjs from 'dayjs'
  111. import { shuffle } from 'lodash'
  112. import PairUpOver from '@/components/PairUpOver.vue'
  113. import { addScore, getScore, notifyQuit } from '@/api.js'
  114. import GameRule from '@/components/GameRule.vue'
  115. import NotifyBonusPointReachedLimit from '@/components/NotifyBonusPointReachedLimit.vue'
  116. const route = useRoute()
  117. const router = useRouter()
  118. const store = useStore()
  119. const {
  120. windowSizeInCssForRef,
  121. windowSizeWhenDesignForRef,
  122. } = useSizeAdapt(390, 752)
  123. const isShowRule = ref(false)
  124. function onClickReturnHome() {
  125. if (store.state.isDirectPlayGame) {
  126. notifyQuit()
  127. } else {
  128. router.push({
  129. name: 'HomeView',
  130. })
  131. }
  132. }
  133. const isOver = ref(false)
  134. /**
  135. * 倒计时
  136. */
  137. const timeCount = ref(store.state.gameRuleList[2].second)
  138. let timeCountIntervalId = null
  139. timeCountIntervalId = setInterval(() => {
  140. if (isShowRule.value) {
  141. return
  142. }
  143. timeCount.value--
  144. if (timeCount.value === 0) {
  145. clearInterval(timeCountIntervalId)
  146. isOver.value = true
  147. }
  148. }, 1000)
  149. onBeforeUnmount(() => {
  150. clearInterval(timeCountIntervalId)
  151. })
  152. const timeCountForShow = computed(() => {
  153. return dayjs(timeCount.value * 1000).format('m:ss')
  154. })
  155. /**
  156. * 本次游戏积分
  157. */
  158. const bonusPoint = ref(0)
  159. /**
  160. * 本次游戏识别企业数
  161. */
  162. const recognizedCorpList = ref([])
  163. /**
  164. * 重玩
  165. */
  166. function replay() {
  167. isOver.value = false
  168. bonusPoint.value = 0
  169. timeCount.value = store.state.gameRuleList[2].second
  170. timeCountIntervalId = setInterval(() => {
  171. if (isShowRule.value) {
  172. return
  173. }
  174. timeCount.value--
  175. if (timeCount.value === 0) {
  176. clearInterval(timeCountIntervalId)
  177. isOver.value = true
  178. }
  179. }, 1000)
  180. recognizedCorpList.value = 0
  181. }
  182. watch(isOver, (vNew) => {
  183. if (vNew) {
  184. if (store.state.loginStatus && !store.state.ifScoreLimitReached && bonusPoint.value !== 0) {
  185. addScore(bonusPoint.value).then(() => {
  186. getScore().then((res) => {
  187. store.commit('setScore', res.total)
  188. store.commit('setIfScoreLimitReached', res.hasOver)
  189. })
  190. })
  191. }
  192. }
  193. })
  194. /**
  195. * 具体游戏规则
  196. */
  197. const logoFileNameList = [
  198. '北京中金公益基金会.png',
  199. '创金合信基金管理有限公司.png',
  200. '德邦基金管理有限公司.png',
  201. '东方财富证券股份有限公司.png',
  202. '东海证券股份有限公司.png',
  203. '富国基金管理有限公司.png',
  204. '广发证券股份有限公司.png',
  205. '国盛证券有限责任公司.png',
  206. '国元证券股份有限公司.png',
  207. '华安基金管理有限公司.png',
  208. '华泰证券股份有限公司.png',
  209. '汇添富基金管理股份有限公司.png',
  210. '金信期货有限公司.png',
  211. '南华期货股份有限公司.png',
  212. '鹏华基金管理有限公司.png',
  213. '鹏扬基金管理有限公司.png',
  214. '平安证券股份有限公司.png',
  215. '睿远公益基金会.png',
  216. '上海东方证券资产管理有限公司.jpg',
  217. '上海证券交易所公益基金会.png',
  218. '深圳市银华公益基金会.png',
  219. '泰达宏利基金.png',
  220. '万家基金管理有限公司.png',
  221. '兴业证券股份有限公司.png',
  222. '兴证全球基金管理有限公司.png',
  223. '圆信永丰基金管理有限公司.png',
  224. '长江证券股份有限公司.png',
  225. '郑州商品交易所.png',
  226. '中国金融期货交易所.png',
  227. '中国期货市场监控中心有限责任公司.png',
  228. '中国证券登记结算有限责任公司.png',
  229. '中海基金管理有限公司.png',
  230. '中航证券有限公司.png',
  231. '中欧基金管理有限公司.png',
  232. '中信期货有限公司.png',
  233. '中信证券股份有限公司.png',
  234. '中证数据有限责任公司.png',
  235. '朱雀基金管理有限公司.png',
  236. ]
  237. const cardList = ref([])
  238. function setCardList() {
  239. cardList.value = []
  240. nextTick(() => {
  241. for (let index = 0; index < 8; index++) {
  242. const logoIdx = Math.floor(Math.random() * logoFileNameList.length)
  243. cardList.value.push({
  244. logoIdx,
  245. isDone: false,
  246. })
  247. cardList.value.push({
  248. logoIdx,
  249. isDone: false,
  250. })
  251. }
  252. if (process.env.VUE_APP_CLI_MODE !== 'dev') {
  253. cardList.value = shuffle(cardList.value)
  254. }
  255. })
  256. }
  257. setCardList()
  258. const activeCardIdxList = ref([])
  259. const activeCardIdxListRealTime = ref([])
  260. function onClickCard(card, cardIdx) {
  261. if (activeCardIdxList.value.includes(cardIdx)) {
  262. return
  263. }
  264. activeCardIdxList.value.push(cardIdx)
  265. activeCardIdxListRealTime.value.push(cardIdx)
  266. if (activeCardIdxListRealTime.value.length === 2 && cardList.value[activeCardIdxListRealTime.value[0]].logoIdx === cardList.value[activeCardIdxListRealTime.value[1]].logoIdx) {
  267. const logoIdx = cardList.value[activeCardIdxListRealTime.value[0]].logoIdx
  268. const toDeleteValue1 = activeCardIdxListRealTime.value.shift()
  269. const toDeleteValue2 = activeCardIdxListRealTime.value.shift()
  270. setTimeout(() => {
  271. cardList.value[toDeleteValue1].isDone = true
  272. cardList.value[toDeleteValue2].isDone = true
  273. if (!recognizedCorpList.value.includes(logoIdx)) {
  274. recognizedCorpList.value.push(logoIdx)
  275. }
  276. setTimeout(() => {
  277. const toDeleteIdx1 = activeCardIdxList.value.findIndex((item) => {
  278. return item === toDeleteValue1
  279. })
  280. activeCardIdxList.value.splice(toDeleteIdx1, 1)
  281. const toDeleteIdx2 = activeCardIdxList.value.findIndex((item) => {
  282. return item === toDeleteValue2
  283. })
  284. activeCardIdxList.value.splice(toDeleteIdx2, 1)
  285. if (cardList.value.every((item) => {
  286. return item.isDone
  287. })) {
  288. if (store.state.loginStatus && !store.state.ifScoreLimitReached) {
  289. bonusPoint.value += store.state.gameRuleList[2].score
  290. }
  291. setCardList()
  292. }
  293. }, 400)
  294. }, 400)
  295. } else if (activeCardIdxListRealTime.value.length === 3) {
  296. const toDeleteValue1 = activeCardIdxListRealTime.value.shift()
  297. const toDeleteValue2 = activeCardIdxListRealTime.value.shift()
  298. setTimeout(() => {
  299. const toDeleteIdx1 = activeCardIdxList.value.findIndex((item) => {
  300. return item === toDeleteValue1
  301. })
  302. activeCardIdxList.value.splice(toDeleteIdx1, 1)
  303. const toDeleteIdx2 = activeCardIdxList.value.findIndex((item) => {
  304. return item === toDeleteValue2
  305. })
  306. activeCardIdxList.value.splice(toDeleteIdx2, 1)
  307. }, 400)
  308. }
  309. }
  310. const isShowNotifyBonusPointReachedLimit = ref(false)
  311. if (store.state.ifScoreLimitReached && !isOver.value) {
  312. isShowNotifyBonusPointReachedLimit.value = true
  313. setTimeout(() => {
  314. isShowNotifyBonusPointReachedLimit.value = false
  315. }, 2000)
  316. }
  317. </script>
  318. <style lang="less" scoped>
  319. .game-view{
  320. position: absolute;
  321. left: 0;
  322. top: 0;
  323. width: 100%;
  324. height: 100%;
  325. background-image: url(@/assets/images/pair-up-bg.jpg);
  326. background-size: cover;
  327. background-repeat: no-repeat;
  328. background-position: center center;
  329. >img.title{
  330. position: absolute;
  331. left: 50%;
  332. top: calc(35 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  333. transform: translate(-50%, 0);
  334. width: calc(244 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  335. height: calc(80 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  336. }
  337. >.tip{
  338. position: absolute;
  339. left: 50%;
  340. top: calc(122 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  341. transform: translate(-50%, 0);
  342. font-size: calc(14 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  343. font-family: Source Han Sans SC, Source Han Sans SC;
  344. font-weight: 400;
  345. color: #FFFFFF;
  346. line-height: calc(16 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  347. }
  348. >.btn-group{
  349. position: absolute;
  350. top: calc(36 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  351. right: calc(12 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  352. display: flex;
  353. flex-direction: column;
  354. gap: calc(16 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  355. z-index: 1;
  356. >button.return-home{
  357. background-image: url(@/assets/images/icon_home.png);
  358. background-size: contain;
  359. background-repeat: no-repeat;
  360. background-position: center center;
  361. width: calc(40 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  362. height: calc(40 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  363. }
  364. >button.game-rule{
  365. background-image: url(@/assets/images/icon_rules.png);
  366. background-size: contain;
  367. background-repeat: no-repeat;
  368. background-position: center center;
  369. width: calc(40 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  370. height: calc(40 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  371. }
  372. }
  373. >.card-list{
  374. position: absolute;
  375. top: calc(163 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  376. left: calc(31 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  377. right: calc((31 - 9) / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  378. margin-right: calc(-12 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  379. >.card{
  380. position: relative;
  381. display: inline-block;
  382. margin-right: calc(9 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  383. margin-bottom: calc(9 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  384. width: calc(75 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  385. height: calc(115 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  386. border-radius: calc(3 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  387. cursor: pointer;
  388. transition: transform 0.4s;
  389. >.front{
  390. position: absolute;
  391. left: 0;
  392. top: 0;
  393. width: 100%;
  394. height: 100%;
  395. transition: transform 0.4s;
  396. transform: rotateY(180deg);
  397. >img.card-frame{
  398. position: absolute;
  399. left: 0;
  400. top: 0;
  401. width: 100%;
  402. height: 100%;
  403. }
  404. >img.card-content{
  405. position: absolute;
  406. left: 50%;
  407. top: 50%;
  408. transform: translate(-50%, -50%);
  409. width: 70%;
  410. }
  411. }
  412. >img.back{
  413. position: absolute;
  414. left: 0;
  415. top: 0;
  416. width: 100%;
  417. height: 100%;
  418. backface-visibility: hidden;
  419. transition: transform 0.4s;
  420. }
  421. }
  422. >.card.active{
  423. >.front{
  424. transform: rotateY(0);
  425. }
  426. >img.back{
  427. transform: rotateY(180deg);
  428. }
  429. }
  430. >.card.hide{
  431. pointer-events: none;
  432. transform: scale(0) rotateZ(360deg);
  433. }
  434. }
  435. >.common-info-group{
  436. position: absolute;
  437. left: 50%;
  438. bottom: calc(35 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  439. transform: translate(-50%, 0);
  440. display: flex;
  441. gap: calc(11 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  442. >.info-item{
  443. position: relative;
  444. display: flex;
  445. justify-content: space-between;
  446. align-items: flex-end;
  447. padding-bottom: calc(3 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  448. width: calc(143 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  449. height: calc(28 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  450. padding-left: calc(5 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  451. padding-right: calc(18 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  452. background: #BB9565;
  453. border-radius: calc(14 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  454. box-shadow: #78511F calc(2 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef')) calc(3 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef')) 0 0;
  455. >.icon{
  456. }
  457. >.number{
  458. font-size: calc(20 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  459. font-family: heiti;
  460. font-weight: 400;
  461. color: #FFFFFF;
  462. line-height: calc(23 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  463. }
  464. }
  465. >.bonus-point{
  466. >.icon{
  467. width: calc(46 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  468. height: calc(41 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  469. }
  470. }
  471. >.time-count{
  472. >.icon{
  473. width: calc(37 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  474. height: calc(46 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
  475. }
  476. }
  477. }
  478. }
  479. </style>