xiongzhu 2 år sedan
förälder
incheckning
40054e4be4
8 ändrade filer med 152 tillägg och 29 borttagningar
  1. 2 1
      .env.development
  2. 1 0
      .env.production
  3. 1 0
      package.json
  4. 1 1
      src/router/index.js
  5. 7 2
      src/views/GameView.vue
  6. 1 1
      src/views/MainView.vue
  7. 89 23
      src/views/PlayView.vue
  8. 50 1
      yarn.lock

+ 2 - 1
.env.development

@@ -1,2 +1,3 @@
 VITE_BASE_URL=/
-VITE_API_BASE_URL=http://localhost:3000/api
+VITE_API_BASE_URL=http://localhost:3000/api
+VITE_WS_URL=ws://localhost:3000

+ 1 - 0
.env.production

@@ -1,2 +1,3 @@
 VITE_BASE_URL=/admin/
 VITE_API_BASE_URL=/api
+VITE_WS_URL=/

+ 1 - 0
package.json

@@ -18,6 +18,7 @@
     "pinia": "^2.0.32",
     "qs": "^6.11.1",
     "resolve-url": "^0.2.1",
+    "socket.io-client": "^4.7.2",
     "vue": "^3.2.47",
     "vue-router": "^4.1.6"
   },

+ 1 - 1
src/router/index.js

@@ -55,7 +55,7 @@ const router = createRouter({
                     }
                 },
                 {
-                    path: '/play/:id',
+                    path: '/play/:roomId/:gameId',
                     name: 'play',
                     component: () => import('../views/PlayView.vue'),
                     meta: {

+ 7 - 2
src/views/GameView.vue

@@ -11,7 +11,11 @@
         <ElTableColumn prop="resetNum" label="重置次数" />
         <ElTableColumn prop="running" label="运行" width="100" align="center">
             <template #default="{ row }">
-                <ElSwitch :model-value="row.status === 'running'" @update:model-value="onActiveChange($event, row)" />
+                <ElSwitch
+                    v-if="row.status === 'initialized' || row.status === 'running'"
+                    :model-value="row.status === 'running'"
+                    @update:model-value="onActiveChange($event, row)"
+                />
             </template>
         </ElTableColumn>
         <ElTableColumn prop="createdAt" label="创建时间" :formatter="timeFormatter" width="150" />
@@ -194,7 +198,8 @@ function onPlay(row) {
     router.push({
         name: 'play',
         params: {
-            id: row.id
+            gameId: row.id,
+            roomId: row.roomId
         }
     })
 }

+ 1 - 1
src/views/MainView.vue

@@ -30,7 +30,7 @@
                     </template>
                 </el-dropdown>
             </ElHeader>
-            <ElMain class="bg-neutral-50 dark:bg-neutral-900">
+            <ElMain class="bg-neutral-50 dark:bg-neutral-900" id="main-container">
                 <RouterView></RouterView>
             </ElMain>
         </ElContainer>

+ 89 - 23
src/views/PlayView.vue

@@ -1,4 +1,4 @@
-<template>
+<template id="asdfasdf">
     <!-- <div class="h-full overflow-auto p-4 rounded-lg bg-white dark:bg-neutral-800"> -->
     <el-timeline>
         <el-timeline-item
@@ -65,6 +65,7 @@
             <ElButton :loading="loading" @click="onAddCharactor(true)">加入角色生成选择</ElButton>
             <ElButton :loading="loading" @click="revert">回退</ElButton>
         </ElButtonGroup>
+        <ElButton v-if="game.status === 'finished'" :loading="loading" @click="revert">回退</ElButton>
     </div>
     <ElDialog title="详情" v-model="showDetailDialog" width="1000px">
         <ElCollapse :model-value="['1', '2']">
@@ -143,18 +144,23 @@ import { ElMessage, ElMessageBox } from 'element-plus'
 import { InfoCircle } from '@vicons/tabler'
 import { useTimeFormatter } from '@/utils/formatter'
 import SingleUpload from '@/components/SingleUpload.vue'
+import { io } from 'socket.io-client'
+
 const route = useRoute()
+const roomId = route.params.roomId
+const gameId = route.params.gameId
 const game = ref({})
 const history = ref([])
 const choice = ref(-1)
+
 const needChoice = computed(() => {
     return (history.value[history.value.length - 1].options || []).length > 0
 })
 async function getData(params) {
-    http.get(`/game/${route.params.id}`, { params }).then((res) => {
+    http.get(`/game/${gameId}`, { params }).then((res) => {
         game.value = res
     })
-    http.get(`/game/${route.params.id}/history`, { params }).then((res) => {
+    http.get(`/game/${gameId}/history`, { params }).then((res) => {
         res.forEach((i) => {
             if (i.options && i.choice) {
                 i.choiceIndex = i.options.findIndex((o) => o.content === i.choice.content)
@@ -173,32 +179,92 @@ async function getData(params) {
         }
 
         history.value = res
+        setTimeout(() => {
+            document
+                .querySelector('#main-container')
+                .scrollTo({ top: document.querySelector('#main-container').scrollHeight, behavior: 'smooth' })
+        }, 500)
     })
 }
 getData()
-const timer = setInterval(() => {
-    getData()
-}, 2000)
+const socket = ref(null)
+onMounted(() => {
+    socket.value = io(import.meta.env.VITE_WS_URL)
+    socket.value.on('connect', () => {
+        console.log('[websocket] connected')
+    })
+    socket.value.on('disconnect', () => {
+        console.log('[websocket] disconnected')
+    })
+    socket.value.on(`${gameId}`, (...args) => {
+        console.log('[websocket] received message', args[0])
+        const data = args[0]
+        switch (data.type) {
+            case 'timeChange':
+                if (history.value[history.value.length - 1].id !== -1) {
+                    history.value.push({
+                        id: -1
+                    })
+                }
+                history.value[history.value.length - 1] = {
+                    ...history.value[history.value.length - 1],
+                    ...data.data
+                }
+                break
+            case 'plot':
+                if (history.value[history.value.length - 1].id !== -1) {
+                    history.value.push({
+                        id: -1
+                    })
+                }
+                history.value[history.value.length - 1] = {
+                    ...history.value[history.value.length - 1],
+                    plot: data.data
+                }
+                break
+            case 'options':
+                if (history.value[history.value.length - 1].id !== -1) {
+                    history.value.push({
+                        id: -1
+                    })
+                }
+                history.value[history.value.length - 1] = {
+                    ...history.value[history.value.length - 1],
+                    options: data.data
+                }
+                break
+            case 'state':
+                getData()
+                break
+        }
+        if (data.type !== 'votes') {
+            setTimeout(() => {
+                document
+                    .querySelector('#main-container')
+                    .scrollTo({ top: document.querySelector('#main-container').scrollHeight, behavior: 'smooth' })
+            }, 500)
+        }
+    })
+    socket.value.connect()
+})
 onBeforeUnmount(() => {
-    clearInterval(timer)
+    socket.value?.disconnect()
 })
 function formatDatetime(date, time) {
-    return (
-        format(new Date(date), 'MMMdo', { locale: zhCN }) +
-        {
-            morning: '上午',
-            afternoon: '下午',
-            evening: '晚上'
-        }[time]
-    )
+    try {
+        return (
+            format(new Date(date), 'MMMdo', { locale: zhCN }) +
+            {
+                morning: '上午',
+                afternoon: '下午',
+                evening: '晚上'
+            }[time]
+        )
+    } catch (error) {
+        return ''
+    }
 }
 const loading = ref(false)
-async function start() {
-    loading.value = true
-    await http.post(`/game/${route.params.id}/start`, { date: new Date() })
-    loading.value = false
-    getData()
-}
 
 async function continueGame(genChoice = false, newCharactor = null) {
     if (needChoice.value && choice.value < 0) {
@@ -207,7 +273,7 @@ async function continueGame(genChoice = false, newCharactor = null) {
     }
     loading.value = true
     try {
-        await http.post(`/game/${route.params.id}/continue`, {
+        await http.post(`/game/${gameId}/continue`, {
             genChoice: genChoice,
             choice: history.value[history.value.length - 1].options[choice.value],
             addCharactor: newCharactor
@@ -223,7 +289,7 @@ async function continueGame(genChoice = false, newCharactor = null) {
 async function revert() {
     loading.value = true
     try {
-        await http.post(`/game/${route.params.id}/revert`)
+        await http.post(`/game/${gameId}/revert`)
     } catch (error) {
         ElMessage.error(error.message)
     }

+ 50 - 1
yarn.lock

@@ -272,6 +272,11 @@
   resolved "https://registry.npmmirror.com/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz#8be36a1f66f3265389e90b5f9c9962146758f728"
   integrity sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==
 
+"@socket.io/component-emitter@~3.1.0":
+  version "3.1.0"
+  resolved "https://registry.npmmirror.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz#96116f2a912e0c02817345b3c10751069920d553"
+  integrity sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==
+
 "@types/estree@^1.0.0":
   version "1.0.1"
   resolved "https://registry.npmmirror.com/@types/estree/-/estree-1.0.1.tgz#aa22750962f3bf0e79d753d3cc067f010c95f194"
@@ -707,7 +712,7 @@ debug@^3.2.6:
   dependencies:
     ms "^2.1.1"
 
-debug@^4.1.1, debug@^4.3.2, debug@^4.3.4:
+debug@^4.1.1, debug@^4.3.2, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2:
   version "4.3.4"
   resolved "https://registry.npmmirror.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
   integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
@@ -767,6 +772,22 @@ element-plus@^2.3.3:
     memoize-one "^6.0.0"
     normalize-wheel-es "^1.2.0"
 
+engine.io-client@~6.5.2:
+  version "6.5.2"
+  resolved "https://registry.npmmirror.com/engine.io-client/-/engine.io-client-6.5.2.tgz#8709e22c291d4297ae80318d3c8baeae71f0e002"
+  integrity sha512-CQZqbrpEYnrpGqC07a9dJDz4gePZUgTPMU3NKJPSeQOyw27Tst4Pl3FemKoFGAlHzgZmKjoRmiJvbWfhCXUlIg==
+  dependencies:
+    "@socket.io/component-emitter" "~3.1.0"
+    debug "~4.3.1"
+    engine.io-parser "~5.2.1"
+    ws "~8.11.0"
+    xmlhttprequest-ssl "~2.0.0"
+
+engine.io-parser@~5.2.1:
+  version "5.2.1"
+  resolved "https://registry.npmmirror.com/engine.io-parser/-/engine.io-parser-5.2.1.tgz#9f213c77512ff1a6cc0c7a86108a7ffceb16fcfb"
+  integrity sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ==
+
 errno@^0.1.1:
   version "0.1.8"
   resolved "https://registry.npmmirror.com/errno/-/errno-0.1.8.tgz#8bb3e9c7d463be4976ff888f76b4809ebc2e811f"
@@ -1816,6 +1837,24 @@ side-channel@^1.0.4:
     get-intrinsic "^1.0.2"
     object-inspect "^1.9.0"
 
+socket.io-client@^4.7.2:
+  version "4.7.2"
+  resolved "https://registry.npmmirror.com/socket.io-client/-/socket.io-client-4.7.2.tgz#f2f13f68058bd4e40f94f2a1541f275157ff2c08"
+  integrity sha512-vtA0uD4ibrYD793SOIAwlo8cj6haOeMHrGvwPxJsxH7CeIksqJ+3Zc06RvWTIFgiSqx4A3sOnTXpfAEE2Zyz6w==
+  dependencies:
+    "@socket.io/component-emitter" "~3.1.0"
+    debug "~4.3.2"
+    engine.io-client "~6.5.2"
+    socket.io-parser "~4.2.4"
+
+socket.io-parser@~4.2.4:
+  version "4.2.4"
+  resolved "https://registry.npmmirror.com/socket.io-parser/-/socket.io-parser-4.2.4.tgz#c806966cf7270601e47469ddeec30fbdfda44c83"
+  integrity sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==
+  dependencies:
+    "@socket.io/component-emitter" "~3.1.0"
+    debug "~4.3.1"
+
 source-map-js@^1.0.2:
   version "1.0.2"
   resolved "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
@@ -2108,11 +2147,21 @@ wrappy@1:
   resolved "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
   integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
 
+ws@~8.11.0:
+  version "8.11.0"
+  resolved "https://registry.npmmirror.com/ws/-/ws-8.11.0.tgz#6a0d36b8edfd9f96d8b25683db2f8d7de6e8e143"
+  integrity sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==
+
 xml-name-validator@^4.0.0:
   version "4.0.0"
   resolved "https://registry.npmmirror.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz#79a006e2e63149a8600f15430f0a4725d1524835"
   integrity sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==
 
+xmlhttprequest-ssl@~2.0.0:
+  version "2.0.0"
+  resolved "https://registry.npmmirror.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz#91360c86b914e67f44dce769180027c0da618c67"
+  integrity sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==
+
 yallist@^4.0.0:
   version "4.0.0"
   resolved "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"