websocket消息支持 (#188)

* Release (#162)

* feat:  websocket server demo

* feat:  ws server demo dev

* feat:  ws server and mobile page

* feat:  手机端发送消息

* feat:  手机网页发送消息

* 添加了抽奖中和抽奖完成时的音效

* feat:  自定义设置弹幕服务器地址

* feat:  ws not done

* fix: 🐛 fix pr-185 #185

为播放音效添加控制

* feat:  server worker demo not done

* feat:  websocket server

* feat:  全局接收websocket消息并存储到indexdb中

---------

Co-authored-by: Silence@2024 <707261624@qq.com>
This commit is contained in:
LOG1997
2026-01-09 17:11:43 +08:00
committed by GitHub
parent 52d2fcd0cb
commit 3eac4e1aac
40 changed files with 3489 additions and 279 deletions

View File

@@ -0,0 +1,32 @@
<script setup lang='ts'>
import { useI18n } from 'vue-i18n'
import PageHeader from '@/components/PageHeader/index.vue'
import MsgListContainer from './parts/MsgListContainer.vue'
import ServerSetting from './parts/ServerSetting.vue'
import { useViewModel } from './useViewModel'
const { t } = useI18n()
const { serverList, currentServerValue, wsStatus, handleConnectWs, closeWs, msgList } = useViewModel()
</script>
<template>
<div>
<PageHeader :title="t('sidebar.server')" />
<div>
<ServerSetting
v-model:current-server="currentServerValue"
:server-list="serverList"
:ws-status="wsStatus"
:open-ws="handleConnectWs"
:close-ws="closeWs"
/>
<MsgListContainer
:msg-list="msgList"
/>
</div>
</div>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,29 @@
<script setup lang='ts'>
import type { WsMsgData } from '@/types/storeType'
interface Props {
msgList: WsMsgData[]
}
defineProps<Props>()
</script>
<template>
<div class="w-1/2 h-1/2 border rounded-md shadow-lg">
<ul>
<li v-for="item in msgList" :key="item.id" class="mb-3">
<div class="chat chat-end">
<div class="chat-header">
<time class="text-xs opacity-50">{{ item.dateTime }}</time>
</div>
<div class="chat-bubble break-all whitespace-normal">
{{ item.data }}
</div>
</div>
</li>
</ul>
</div>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,107 @@
<script setup lang='ts'>
import type { ServerType } from '@/types/storeType'
import { ref, watch } from 'vue'
// import { useI18n } from 'vue-i18n'
interface Props {
serverList: ServerType[]
wsStatus: { status: WebSocket['readyState'], connected: boolean } | undefined
openWs: () => void
closeWs: () => void
}
defineProps<Props>()
const currentServer = defineModel<ServerType>('currentServer', { required: true })
const hostValue = ref('')
// const { t } = useI18n()
// 监听 currentServer 的 id 变化,重置 hostValue
watch(() => currentServer.value?.id, (newId, oldId) => {
if (newId !== oldId) {
hostValue.value = currentServer.value?.host || ''
}
}, { immediate: true })
// 监听 hostValue 变化,同步更新 currentServer.host
watch(hostValue, (newHost) => {
if (currentServer.value) {
currentServer.value.host = newHost
}
})
// 初始化 hostValue
hostValue.value = currentServer.value?.host || ''
</script>
<template>
<fieldset class="p-4 border text-setting fieldset bg-base-200 border-base-300 rounded-box w-xs pb-10">
<legend class="fieldset-legend">
弹幕服务
</legend>
<label class="flex flex-row items-center form-control">
<div class="">
<div class="label flex flex-col justify-start items-start">
<label class="label">
<span class="label-text text-left">弹幕服务地址</span>
<div class="tooltip" data-tip="改变弹幕服务地址后会断开连接">
<button class="btn btn-circle h-4 hover:bg-base-300">
?
</button>
</div>
</label>
<div class="radio-group flex gap-9">
<ul class="flex gap-3">
<li v-for="item in serverList" :key="item.id" class="flex flex-col">
<label for="default-server">{{ item.name }}</label>
<input id="default-server" type="radio" name="radio-1" class="radio" :checked="currentServer?.value === item.value" @change="currentServer = item">
</li>
</ul>
</div>
<input
type="text"
placeholder="请输入服务地址"
:disabled="currentServer.value === 'default'"
class="w-full max-w-xs input input-bordered"
:value="hostValue"
@input="hostValue = ($event.target as HTMLInputElement).value"
>
</div>
</div>
</label>
<label class="flex flex-row items-center form-control">
<div class="flex flex-col gap-3">
<div class="label">
<span class="label-text">弹幕服务器连接状态</span>
</div>
<div class="flex gap-2 items-center">
<div class="ws-status">
<div v-if="wsStatus && wsStatus.connected">
<div aria-label="success" class="status status-success" />
<span>已连接</span>
</div>
<div v-else-if="wsStatus && wsStatus.connected === false">
<div aria-label="error" class="status status-error" />
<span>已断开</span>
</div>
<div v-else-if="wsStatus && wsStatus.status">
<div aria-label="error" class="status status-error" />
<span>操作中</span>
</div>
<div v-else>
<div aria-label="warning" class="status status-warning" />
<span>未连接</span>
</div>
</div>
<div class="flex gap-3">
<button v-if="wsStatus?.connected === true" class="btn btn-error btn-sm" @click="closeWs">断开</button>
<button v-else class="btn btn-primary btn-sm" @click="openWs">连接</button>
</div>
</div>
</div>
</label>
</fieldset>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,55 @@
import type { ServerType, WsMsgData } from '@/types/storeType'
import { cloneDeep } from 'lodash-es'
import { storeToRefs } from 'pinia'
import { onMounted, ref, watch } from 'vue'
import { useWebsocket } from '@/hooks/useWebsocket'
import useStore from '@/store'
import { getUniqueSignature } from '@/utils/auth'
import { IndexDb } from '@/utils/dexie'
export function useViewModel() {
const serverConfig = useStore().serverConfig
const { getServerList: serverList, getCurrentServer: currentServer } = storeToRefs(serverConfig)
const currentServerValue = ref<ServerType>(cloneDeep(currentServer.value))
const wsUrl = ref<string>('ws://localhost:8080/echo')
const msgList = ref<WsMsgData[]>([])
const { open: openWs, close: closeWs, status: wsStatus } = useWebsocket()
const msgListDb = new IndexDb('msgList', ['msgList'], 1, ['createTime'])
const handleConnectWs = async () => {
const userSignature = await getUniqueSignature()
wsUrl.value = `ws://localhost:8080/echo?userSignature=${userSignature}`
openWs(wsUrl.value)
}
const getAllMsg = async () => {
msgListDb.getDataSortedByDateTime('msgList', 'dateTime').then((data) => {
msgList.value = data
})
}
watch(
() => currentServerValue.value.id,
(newValue) => {
serverList.value.forEach((item) => {
if (item.id === newValue) {
currentServerValue.value = item
serverConfig.setCurrentServer(currentServerValue.value)
}
})
},
)
watch(() => currentServer.value.host, (newValue) => {
currentServerValue.value.host = newValue
serverConfig.updateServerList(currentServerValue.value)
})
onMounted(() => {
getAllMsg()
})
return {
serverList,
currentServerValue,
wsStatus,
handleConnectWs,
closeWs,
msgList,
}
}