index.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558
  1. <template>
  2. <div class="game">
  3. <Header @confirmExit="getExit" :showTool="false" closeClass="close2"></Header>
  4. <div class="menu" v-if="projectList.length" :class="projectList.length <= 12 ? 'menu1' : 'menu2'"
  5. :key="projectList.length">
  6. <swiper :slides-per-view="6" :modules="[Grid]" :grid="{
  7. fill: projectList.length <= 12 ? 'row' : 'column',
  8. rows: 2,
  9. }" :space-between="20" :slides-per-group="12">
  10. <swiper-slide v-for="(item, index) in projectList" :key="index" @click="getJump(item)">
  11. <div class="li">
  12. <div>
  13. <div class="pic"><img :src="'static/images/game/' + item.exam_name + '.png'" /></div>
  14. <div class="name">
  15. {{ item.exam_name_cn }}
  16. </div>
  17. </div>
  18. </div>
  19. </swiper-slide>
  20. </swiper>
  21. </div>
  22. <Transition :enter-active-class="proxy?.animate.rankingWindow.enter"
  23. :leave-active-class="proxy?.animate.rankingWindow.leave">
  24. <div class="gameWindow" v-if="start">
  25. <!---人体姿态识别-->
  26. <div class="columns" v-if="currentGame == 'bodyposecontroller'">
  27. <template v-if="currentGameArea.length == 2">
  28. <div class="item left">
  29. <HumanBody ref="humanBodyLeftRef" type="left" :currentGameArea="currentGameArea[0]"
  30. :areaStateList="areaStateList"></HumanBody>
  31. </div>
  32. <div class="item right">
  33. <HumanBody ref="humanBodyRightRef" type="right" :currentGameArea="currentGameArea[1]"
  34. :areaStateList="areaStateList"></HumanBody>
  35. </div>
  36. </template>
  37. <template v-else>
  38. <HumanBody ref="humanBodyLeftRef" type="left" :currentGameArea="currentGameArea[0]"
  39. :areaStateList="areaStateList"></HumanBody>
  40. </template>
  41. </div>
  42. <!---篮球投篮-->
  43. <div class="columns" v-if="currentGame == 'game_basketball'">
  44. <div class="item left">
  45. <Basketball ref="basketballLeftRef" type="left" @confirmExit="getExitGame"
  46. :currentGameArea="currentGameArea[0]" :areaStateList="areaStateList"></Basketball>
  47. </div>
  48. <div class="item right">
  49. <Basketball ref="basketballRightRef" type="right" @confirmExit="getExitGame"
  50. :currentGameArea="currentGameArea[1]" :areaStateList="areaStateList"></Basketball>
  51. </div>
  52. </div>
  53. <!---足球带球-->
  54. <div class="columns" v-if="currentGame == 'game_football'">
  55. <div class="item left">
  56. <Football ref="footballLeftRef" type="left" @confirmExit="getExitGame" :currentGameArea="currentGameArea[0]"
  57. :areaStateList="areaStateList">
  58. </Football>
  59. </div>
  60. <div class="item right">
  61. <Football ref="footballRightRef" type="right" @confirmExit="getExitGame"
  62. :currentGameArea="currentGameArea[1]" :areaStateList="areaStateList">
  63. </Football>
  64. </div>
  65. </div>
  66. <!---切水果-->
  67. <div class="columns" v-if="currentGame == 'game_fruit'">
  68. <Fruit ref="fruitRef" @confirmExit="getExitGame" :currentGameArea="currentGameArea[0]"
  69. :areaStateList="areaStateList">
  70. </Fruit>
  71. </div>
  72. </div>
  73. </Transition>
  74. <div class="resumeGame" v-if="resumeGame" @click="getResumeGame">继续游戏</div>
  75. <div class="close" v-if="start" @click="getExitGame"></div>
  76. <ActionConfirmWindow ref="actionConfirmRef" @confirmExit="getExitGame({ type: 1 })" @confirmStart="getStartGame">
  77. </ActionConfirmWindow>
  78. </div>
  79. </template>
  80. <script setup name="GameIndex" lang="ts">
  81. import { initSpeech, speckText, playMusic, controlMusic, speckCancel, chineseNumber } from '@/utils/speech';
  82. import { Swiper, SwiperSlide } from 'swiper/vue';
  83. import { Grid } from 'swiper/modules';
  84. import 'swiper/css';
  85. import 'swiper/css/grid';
  86. import { useWebSocket } from '@/utils/bodyposeWs';
  87. import HumanBody from "./components/humanBody.vue";
  88. import Basketball from "./components/basketball.vue";
  89. import Football from "./components/football.vue";
  90. import Fruit from "./components/fruit.vue";
  91. const router = useRouter();
  92. const { proxy } = getCurrentInstance() as any;
  93. const humanBodyLeftRef = ref();
  94. const humanBodyRightRef = ref();
  95. const basketballLeftRef = ref();
  96. const basketballRightRef = ref();
  97. const footballLeftRef = ref();
  98. const footballRightRef = ref();
  99. const fruitRef = ref();
  100. const actionConfirmRef = ref();
  101. const { bodyposeWs, startDevice, checkBodypose, openBodypose, terminateBodypose, suspendBodypose, resumeBodypose, getBodyposeState, closeWS } = useWebSocket();
  102. const data = reactive<any>({
  103. projectList: [],//项目列表
  104. wsState: false,//WS状态
  105. bodyposeState: false,//姿态识别窗口状态
  106. deviceInfo: {},//设备信息
  107. currentGame: "",//当前游戏
  108. currentGameArea: [],//当前游戏区
  109. start: false,//是否开始游戏
  110. areaStateList: [],//各游戏就绪状态
  111. resumeGame: false,//游戏结束显示继续游戏
  112. });
  113. const { projectList, wsState, bodyposeState, deviceInfo, currentGame, currentGameArea, start, areaStateList, resumeGame } = toRefs(data);
  114. /**
  115. * 监听数据变化
  116. */
  117. watch(
  118. () => areaStateList.value,
  119. (newData, oldData) => {
  120. //如果当前都是结束了就再重新弹窗
  121. let state = newData.every((item: any) => {
  122. return item.gameover;
  123. })
  124. if (state) {
  125. // start.value = false;
  126. // actionConfirmRef.value.getOpen(currentGame.value, currentGameArea.value);
  127. resumeGame.value = true;
  128. }
  129. },
  130. { deep: true }
  131. );
  132. /**
  133. * 初始化
  134. */
  135. const getInit = async () => {
  136. console.log("触发姿态识别")
  137. let deviceid = localStorage.getItem('deviceid') || '';
  138. if (!deviceid) {
  139. proxy?.$modal.msgError(`请重新登录绑定设备号后使用`);
  140. getExit();
  141. return false;
  142. }
  143. bodyposeState.value = true;
  144. if (wsState.value) {
  145. proxy?.$modal.msgWarning(`操作过快,请稍后重试`);
  146. setTimeout(() => {
  147. bodyposeState.value = false;
  148. }, 1000)
  149. return false;
  150. }
  151. bodyposeWs((e: any) => {
  152. if (e?.wksid) {
  153. wsState.value = true;
  154. //获取设备信息
  155. startDevice({ deviceid: deviceid });
  156. console.log("获取设备信息")
  157. }
  158. if (e?.type == 'fe_device_init_result') {
  159. //接收设备信息并发送请求
  160. if (e?.device_info) {
  161. deviceInfo.value = e.device_info;
  162. let list = deviceInfo.value.project_list.filter((item: any) => {
  163. return ['bodyposecontroller', 'game_basketball', 'game_football', 'game_fruit'].includes(item.exam_name)
  164. })
  165. projectList.value = list;
  166. if (import.meta.env.APP_ENV != 'pro') {
  167. // projectList.value.push({ exam_name_cn: "单个17点位(区号在主表)", exam_name: "test" })
  168. }
  169. } else {
  170. proxy?.$modal.msgError(`设备信息缺失,请重新登录绑定设备号后使用`);
  171. }
  172. }
  173. if (e?.cmd == 'check_bodyposecontroller_available') {
  174. let area = e.ctrl_name.replace('bodyposecontroller_', '');
  175. if (e?.code == 0) {
  176. getBodyposeState(area);
  177. }
  178. }
  179. if (e?.cmd == 'get_bodyposecontroller_state') {
  180. let area = e.ctrl_name.replace('bodyposecontroller_', '');
  181. //state说明: 0:关闭,3:空闲,36:工作中
  182. if ([3, 36].includes(e.state)) {
  183. terminateBodypose(area);
  184. } else {
  185. openBodypose(area);
  186. }
  187. }
  188. if (e?.type == 'bodyposecontroller_result') {
  189. if (!currentGame.value) {
  190. return false;
  191. }
  192. if (!start.value) {
  193. //传给预备窗口
  194. actionConfirmRef.value.getInit(e);
  195. return false;
  196. }
  197. let area = e.ctrl_name.replace('bodyposecontroller_', '');
  198. //传给游戏窗口
  199. if (currentGame.value == 'bodyposecontroller') {
  200. if (currentGameArea.value.length == 2) {
  201. if (area == currentGameArea.value[0]) {
  202. humanBodyLeftRef.value.getInit(e);
  203. }
  204. if (area == currentGameArea.value[1]) {
  205. humanBodyRightRef.value.getInit(e);
  206. }
  207. } else {
  208. if (area == currentGameArea.value[0]) {
  209. humanBodyLeftRef.value.getInit(e);
  210. }
  211. }
  212. }
  213. if (currentGame.value == 'game_basketball') {
  214. if (area == currentGameArea.value[0]) {
  215. basketballLeftRef.value.getInit(e);
  216. }
  217. if (area == currentGameArea.value[1]) {
  218. basketballRightRef.value.getInit(e);
  219. }
  220. }
  221. if (currentGame.value == 'game_football') {
  222. if (area == currentGameArea.value[0]) {
  223. footballLeftRef.value.getInit(e);
  224. }
  225. if (area == currentGameArea.value[1]) {
  226. footballRightRef.value.getInit(e);
  227. }
  228. }
  229. if (currentGame.value == 'game_fruit') {
  230. fruitRef.value.getInit(e);
  231. }
  232. }
  233. if (e?.cmd == 'terminate_bodyposecontroller') {
  234. }
  235. if (e?.type == 'disconnect') {
  236. getExit();
  237. }
  238. });
  239. };
  240. //打开窗口
  241. const getJump = (data: any) => {
  242. console.log("222", data)
  243. if (data.exam_name == 'test') {
  244. router.push({ path: '/game/test' });
  245. } else {
  246. currentGame.value = data.exam_name;
  247. currentGameArea.value = data.area_test_id?.split(",") || [];
  248. actionConfirmRef.value.getOpen(currentGame.value, currentGameArea.value);
  249. currentGameArea.value.forEach((item: any) => {
  250. checkBodypose(item);
  251. })
  252. }
  253. };
  254. /**
  255. * 退出
  256. */
  257. const getExit = () => {
  258. // router.go(-1);
  259. router.push({ path: '/home' });
  260. };
  261. /**
  262. * 退出游戏
  263. */
  264. const getExitGame = (data: any) => {
  265. if (data.type == 1) {
  266. //浮窗退出
  267. console.log("currentGameArea.value", currentGameArea.value)
  268. currentGameArea.value.forEach((item: any) => {
  269. terminateBodypose(item);
  270. })
  271. currentGame.value = "";
  272. start.value = false;
  273. } else if (data.type == 2) {
  274. //游戏结束显示下一场
  275. let myIndex = areaStateList.value.findIndex((item: any) => {
  276. return item.area == data.area;
  277. })
  278. areaStateList.value[myIndex].gameover = true;
  279. } else {
  280. //游戏中退出
  281. proxy?.$modal.confirm("确定退出吗?").then(() => {
  282. currentGameArea.value.forEach((item: any) => {
  283. terminateBodypose(item);
  284. })
  285. currentGame.value = "";
  286. start.value = false;
  287. }).finally(() => {
  288. });
  289. }
  290. };
  291. /**
  292. * 开始游戏
  293. */
  294. const getStartGame = (data: any) => {
  295. areaStateList.value = data;
  296. start.value = true;
  297. resumeGame.value = false;
  298. };
  299. /**
  300. * 继续游戏
  301. */
  302. const getResumeGame = () => {
  303. resumeGame.value = false;
  304. start.value = false;
  305. actionConfirmRef.value.getOpen(currentGame.value, currentGameArea.value);
  306. };
  307. onBeforeMount(async () => {
  308. getInit();
  309. });
  310. onMounted(() => {
  311. });
  312. onBeforeUnmount(() => {
  313. closeWS();
  314. });
  315. </script>
  316. <style lang="scss" scoped>
  317. $topPadding: 5.19rem;
  318. $waiPadding: 6.51rem;
  319. .menu {
  320. width: calc(100% - ($waiPadding * 2));
  321. height: 72vh;
  322. padding-top: 10rem;
  323. margin: 0 auto;
  324. display: flex;
  325. .li {
  326. // width: calc((100% / 6) - 1rem + (1rem/6));
  327. // margin-right: 1rem;
  328. // margin-bottom: 1rem;
  329. width: 100%;
  330. height: calc((72vh / 2) - 20px);
  331. padding: 2.2vh 0;
  332. border-radius: 1.6rem;
  333. box-sizing: border-box;
  334. box-shadow: inset 0px 1px 0px 2px rgba(255, 255, 255, 0.9046), inset 0px 3px 6px 0px rgba(0, 0, 0, 0.0851);
  335. display: flex;
  336. justify-content: center;
  337. align-items: center;
  338. cursor: pointer;
  339. .pic {
  340. width: 11.36vw;
  341. height: 11.36vw;
  342. border-radius: 50%;
  343. background: radial-gradient(78% 78% at 53% 50%, #07121a 0%, #2a4256 49%, #5180a9 100%);
  344. box-shadow: 0px 0px 2px 2px #ffffff;
  345. margin-bottom: 2.5vh;
  346. overflow: hidden;
  347. display: flex;
  348. align-items: center;
  349. justify-content: center;
  350. flex-shrink: 0;
  351. img {
  352. max-width: 88%;
  353. max-height: 88%;
  354. transition: all 1s;
  355. }
  356. }
  357. .name {
  358. width: 100%;
  359. font-size: 2.1rem;
  360. color: #1a293a;
  361. text-align: center;
  362. }
  363. &:hover {
  364. img {
  365. transform: translateY(-0.5vw);
  366. }
  367. }
  368. }
  369. .swiper-slide {
  370. border-radius: 1.6rem;
  371. overflow: hidden;
  372. }
  373. }
  374. .menu1 {
  375. align-items: center;
  376. .swiper-slide {
  377. margin-bottom: 20px;
  378. .li {
  379. background: radial-gradient(96% 96% at 2% 32%, #ffffff 0%, #fcfdfd 54%, #e1e4e7 100%);
  380. }
  381. &:nth-child(2),
  382. &:nth-child(4),
  383. &:nth-child(6),
  384. &:nth-child(7),
  385. &:nth-child(9),
  386. &:nth-child(11) {
  387. .li {
  388. background: radial-gradient(167% 126% at 97% 6%, #35ffc6 0%, #00ffe8 100%);
  389. }
  390. }
  391. }
  392. }
  393. .menu2 {
  394. display: flex;
  395. .swiper-slide {
  396. &:nth-child(1),
  397. &:nth-child(4),
  398. &:nth-child(5),
  399. &:nth-child(8),
  400. &:nth-child(9),
  401. &:nth-child(12),
  402. &:nth-child(13),
  403. &:nth-child(16),
  404. &:nth-child(17),
  405. &:nth-child(20),
  406. &:nth-child(21),
  407. &:nth-child(24),
  408. &:nth-child(25),
  409. &:nth-child(28),
  410. &:nth-child(29),
  411. &:nth-child(32),
  412. &:nth-child(33),
  413. &:nth-child(36),
  414. &:nth-child(37),
  415. &:nth-child(40),
  416. &:nth-child(41),
  417. &:nth-child(44) {
  418. .li {
  419. background: radial-gradient(96% 96% at 2% 32%, #ffffff 0%, #fcfdfd 54%, #e1e4e7 100%);
  420. }
  421. }
  422. &:nth-child(2),
  423. &:nth-child(3),
  424. &:nth-child(6),
  425. &:nth-child(7),
  426. &:nth-child(10),
  427. &:nth-child(11),
  428. &:nth-child(14),
  429. &:nth-child(15),
  430. &:nth-child(18),
  431. &:nth-child(19),
  432. &:nth-child(22),
  433. &:nth-child(23),
  434. &:nth-child(26),
  435. &:nth-child(27),
  436. &:nth-child(30),
  437. &:nth-child(31),
  438. &:nth-child(34),
  439. &:nth-child(35),
  440. &:nth-child(38),
  441. &:nth-child(39),
  442. &:nth-child(42),
  443. &:nth-child(43) {
  444. .li {
  445. background: radial-gradient(167% 126% at 97% 6%, #35ffc6 0%, #00ffe8 100%);
  446. }
  447. }
  448. }
  449. }
  450. .gameWindow {
  451. width: 100vw;
  452. height: 100vh;
  453. position: absolute;
  454. z-index: 990;
  455. left: 0;
  456. top: 0;
  457. display: flex;
  458. .columns {
  459. display: flex;
  460. flex-wrap: wrap;
  461. width: 100%;
  462. .item {
  463. flex: 1;
  464. display: flex;
  465. box-sizing: border-box;
  466. width: 50%;
  467. }
  468. .left {
  469. border-right: 10px solid #07121a;
  470. }
  471. .right {
  472. border-left: 10px solid #07121a;
  473. }
  474. }
  475. }
  476. .close {
  477. z-index: 991;
  478. position: fixed;
  479. bottom: 35px;
  480. left: 50%;
  481. margin-left: -1.6rem;
  482. }
  483. .resumeGame{
  484. width: 10rem;
  485. height: 3.2rem;
  486. line-height: 3.2rem;
  487. font-size: 1.8rem;
  488. color: #000000;
  489. text-align: center;
  490. background-color: rgba(216, 216, 216, 0.8);
  491. z-index: 991;
  492. position: fixed;
  493. bottom: calc(35px + 3.2rem + 25px) ;
  494. left: 50%;
  495. margin-left: -5rem;
  496. cursor: pointer;
  497. }
  498. ::v-deep(.menu) {
  499. .swiper-horizontal {
  500. width: 100%;
  501. }
  502. }
  503. @media screen and (max-width: 1450px) {
  504. .menu {
  505. .li {
  506. .name {
  507. font-size: 1.6rem;
  508. }
  509. .pic {
  510. width: 10vw;
  511. height: 10vw;
  512. }
  513. }
  514. }
  515. }
  516. </style>