index.vue 13 KB

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