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

@@ -1,9 +1,12 @@
<script setup lang='ts'>
import { useFullscreen } from '@vueuse/core'
import { Maximize, Minimize } from 'lucide-vue-next'
import { onMounted, ref } from 'vue'
import { useQRCode } from '@vueuse/integrations/useQRCode'
import { Maximize, Minimize, TabletSmartphone } from 'lucide-vue-next'
import { onMounted, ref, shallowRef, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRoute, useRouter } from 'vue-router'
import CustomDialog from '@/components/Dialog/index.vue'
import { getOriginUrl, getUniqueSignature } from '@/utils/auth'
import { usePlayMusic } from './usePlayMusic'
const { playMusic, currentMusic, nextPlay } = usePlayMusic()
@@ -12,8 +15,12 @@ const { t } = useI18n()
const router = useRouter()
const route = useRoute()
const customDialogRef = ref()
const settingRef = ref()
const fullScreenRef = ref()
const mobileUrl = shallowRef<string>('')
const qrCodeImg = useQRCode(mobileUrl)
const visible = ref(true)
function enterConfig() {
router.push('/log-lottery/config')
@@ -21,7 +28,26 @@ function enterConfig() {
function enterHome() {
router.push('/log-lottery')
}
async function openMobileQrCode() {
const originUrl = getOriginUrl()
const userSignature = await getUniqueSignature()
mobileUrl.value = `${originUrl}/log-lottery/mobile?userSignature=${userSignature}`
customDialogRef.value.showDialog()
}
function handleSubmit() {
}
watch(() => route, (val) => {
const { meta } = val
if (meta && meta.isMobile) {
visible.value = false
}
}, { immediate: true })
onMounted(() => {
if (!settingRef.value) {
return
}
settingRef.value.addEventListener('mouseenter', () => {
fullScreenRef.value.style.display = 'block'
})
@@ -32,7 +58,20 @@ onMounted(() => {
</script>
<template>
<div ref="settingRef" class="flex flex-col gap-3">
<div v-if="visible" ref="settingRef" class="flex flex-col gap-3">
<CustomDialog
ref="customDialogRef"
title=""
:submit-func="handleSubmit"
footer="center"
dialog-class="h-120 p-6"
>
<template #content>
<div class="flex w-full justify-center h-90">
<img :src="qrCodeImg" alt="qr code">
</div>
</template>
</CustomDialog>
<div ref="fullScreenRef" class="tooltip tooltip-left hidden" @click="toggleScreen">
<div
v-if="isFullscreen"
@@ -71,6 +110,11 @@ onMounted(() => {
<svg-icon :name="currentMusic.paused ? 'play' : 'pause'" />
</div>
</div>
<div class="tooltip tooltip-left" data-tip="访问手机端">
<div class="flex items-center justify-center w-10 h-10 p-0 m-0 cursor-pointer setting-container bg-slate-500/50 rounded-l-xl hover:bg-slate-500/80 hover:text-blue-400/90" @click="openMobileQrCode">
<TabletSmartphone />
</div>
</div>
</div>
</template>

View File

@@ -1,10 +1,13 @@
import type { Ref } from 'vue'
import { storeToRefs } from 'pinia'
import { onMounted, provide, ref } from 'vue'
import { onMounted, provide, ref, toRaw, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRoute } from 'vue-router'
import { loadingKey, loadingState } from '@/components/Loading'
import { useWebsocket } from '@/hooks/useWebsocket'
import useStore from '@/store'
import { themeChange } from '@/utils'
import { IndexDb } from '@/utils/dexie'
export function useMounted(tipDialog: Ref<any>) {
provide(loadingKey, loadingState)
@@ -15,6 +18,9 @@ export function useMounted(tipDialog: Ref<any>) {
const { getPrizeConfig: prizeList, getTemporaryPrize: temporaryPrize } = storeToRefs(prizeConfig)
const tipDesc = ref('')
const { t } = useI18n()
const route = useRoute()
const { data } = useWebsocket()
const msgListDb = new IndexDb('msgList', ['msgList'], 1, ['createTime'])
// 设置当前奖列表
function setCurrentPrize() {
if (prizeList.value.length <= 0) {
@@ -52,10 +58,26 @@ export function useMounted(tipDialog: Ref<any>) {
return isChrome || isEdge
}
const isShowMobileWarn = () => {
const isMobilePage = judgeMobile()
const { meta } = route
let allowMobile = false
if (meta && meta.isMobile) {
allowMobile = true
}
return !allowMobile && isMobilePage
}
watch(() => data.value, (newValue) => {
if (!newValue) {
return
}
msgListDb.setData('msgList', toRaw(newValue))
}, { immediate: true, deep: true })
onMounted(() => {
themeChange(localTheme.value.name)
setCurrentPrize()
if (judgeMobile()) {
if (isShowMobileWarn()) {
tipDialog.value.showDialog()
tipDesc.value = t('dialog.dialogPCWeb')
}