update alert

This commit is contained in:
2023-11-25 11:42:09 +08:00
parent f3b3e02120
commit 10d7f591fd
10 changed files with 218 additions and 75 deletions

View File

@@ -22,7 +22,9 @@ const accountInfo = useAccount()
<NTag :type="accountInfo?.eventFetcherOnline ? (accountInfo?.eventFetcherStatus == 'ok' ? 'success' : 'warning') : 'error'"> <NTag :type="accountInfo?.eventFetcherOnline ? (accountInfo?.eventFetcherStatus == 'ok' ? 'success' : 'warning') : 'error'">
{{ accountInfo?.eventFetcherOnline ? (accountInfo?.eventFetcherStatus == 'ok' ? '正常' : `异常: ${accountInfo.eventFetcherStatus}`) : '未连接' }} {{ accountInfo?.eventFetcherOnline ? (accountInfo?.eventFetcherStatus == 'ok' ? '正常' : `异常: ${accountInfo.eventFetcherStatus}`) : '未连接' }}
</NTag> </NTag>
<template v-if="accountInfo?.eventFetcherOnline != true">
<NDivider vertical /> <NDivider vertical />
<NButton v-if="accountInfo?.eventFetcherOnline != true" type="info" size="small" tag="a" href="https://www.yuque.com/megghy/dez70g/vfvcyv3024xvaa1p" target="_blank"> 关于 EVENT-FETCHER </NButton> <NButton type="info" size="small" tag="a" href="https://www.yuque.com/megghy/dez70g/vfvcyv3024xvaa1p" target="_blank"> 关于 EVENT-FETCHER </NButton>
</template>
</NAlert> </NAlert>
</template> </template>

View File

@@ -161,11 +161,11 @@ export default class DanmakuClient {
sc: [], sc: [],
} }
public async Start(): Promise<boolean> { public async Start(): Promise<{ success: boolean; message: string }> {
if (!this.client) { if (!this.client) {
console.log('[OPEN-LIVE] 正在启动弹幕客户端') console.log('[OPEN-LIVE] 正在启动弹幕客户端')
const result = await this.initClient() const result = await this.initClient()
if (result) { if (result.success) {
this.timer = setInterval(() => { this.timer = setInterval(() => {
this.sendHeartbeat() this.sendHeartbeat()
}, 20 * 1000) }, 20 * 1000)
@@ -173,7 +173,10 @@ export default class DanmakuClient {
return result return result
} else { } else {
console.warn('[OPEN-LIVE] 弹幕客户端已被启动过') console.warn('[OPEN-LIVE] 弹幕客户端已被启动过')
return false return {
success: false,
message: '弹幕客户端已被启动过',
}
} }
} }
public Stop() { public Stop() {
@@ -194,7 +197,8 @@ export default class DanmakuClient {
} }
private sendHeartbeat() { private sendHeartbeat() {
if (this.client) { if (this.client) {
(this.authInfo ? QueryPostAPI<OpenLiveInfo>(OPEN_LIVE_API_URL + 'heartbeat', this.authInfo) : QueryGetAPI<OpenLiveInfo>(OPEN_LIVE_API_URL + 'heartbeat-internal')).then((data) => { const query = this.authInfo ? QueryPostAPI<OpenLiveInfo>(OPEN_LIVE_API_URL + 'heartbeat', this.authInfo) : QueryGetAPI<OpenLiveInfo>(OPEN_LIVE_API_URL + 'heartbeat-internal')
query.then((data) => {
if (data.code != 200) { if (data.code != 200) {
console.error('[OPEN-LIVE] 心跳失败: ' + data.message) console.error('[OPEN-LIVE] 心跳失败: ' + data.message)
this.client.stop() this.client.stop()
@@ -239,40 +243,56 @@ export default class DanmakuClient {
} }
return this return this
} }
private async initClient() { private async initClient(): Promise<{ success: boolean; message: string }> {
const auth = await this.getAuthInfo() const auth = await this.getAuthInfo()
if (auth) { if (auth.data) {
const chatClient = new ChatClientDirectOpenLive(auth) const chatClient = new ChatClientDirectOpenLive(auth.data)
//chatClient.msgHandler = this; //chatClient.msgHandler = this;
chatClient.CMD_CALLBACK_MAP = this.CMD_CALLBACK_MAP chatClient.CMD_CALLBACK_MAP = this.CMD_CALLBACK_MAP
chatClient.start() chatClient.start()
console.log('[OPEN-LIVE] 已连接房间: ' + auth.anchor_info.room_id) this.roomAuthInfo.value = auth.data
this.roomAuthInfo.value = auth
this.client = chatClient this.client = chatClient
return true console.log('[OPEN-LIVE] 已连接房间: ' + auth.data.anchor_info.room_id)
return {
success: true,
message: '',
}
} else { } else {
console.log('[OPEN-LIVE] 无法开启场次') console.log('[OPEN-LIVE] 无法开启场次')
return false return {
success: false,
message: auth.message,
} }
} }
private async getAuthInfo() { }
private async getAuthInfo(): Promise<{ data: OpenLiveInfo | null; message: string }> {
try { try {
const data = await QueryPostAPI<OpenLiveInfo>(OPEN_LIVE_API_URL + 'start', this.authInfo?.Code ? this.authInfo : undefined) const data = await QueryPostAPI<OpenLiveInfo>(OPEN_LIVE_API_URL + 'start', this.authInfo?.Code ? this.authInfo : undefined)
if (data.code == 200) { if (data.code == 200) {
console.log('[OPEN-LIVE] 已获取场次信息') console.log('[OPEN-LIVE] 已获取场次信息')
return data.data return {
data: data.data,
message: '',
}
} else { } else {
console.error('无法获取场次数据: ' + data.message) console.error('无法获取场次数据: ' + data.message)
return null return {
data: null,
message: data.message,
}
} }
} catch (err) { } catch (err) {
console.error(err) console.error(err)
return {
data: null,
message: err?.toString() || '未知错误',
}
} }
return null
} }
private CMD_CALLBACK_MAP = { private CMD_CALLBACK_MAP = {
LIVE_OPEN_PLATFORM_DM: this.onDanmaku, LIVE_OPEN_PLATFORM_DM: this.onDanmaku.bind(this),
LIVE_OPEN_PLATFORM_SEND_GIFT: this.onGift, LIVE_OPEN_PLATFORM_SEND_GIFT: this.onGift.bind(this),
} }
} }

View File

@@ -1,26 +1,33 @@
import { QueryGetAPI } from '@/api/query' import { QueryGetAPI } from '@/api/query'
import { BASE_API } from '@/data/constants' import { BASE_API } from '@/data/constants'
import { createApp } from 'vue' import { createApp, h } from 'vue'
import App from './App.vue' import App from './App.vue'
import router from './router' import router from './router'
import { GetSelfAccount } from './api/account' import { GetSelfAccount } from './api/account'
import { GetNotifactions } from './data/notifactions' import { GetNotifactions } from './data/notifactions'
import { NText, createDiscreteApi } from 'naive-ui'
let currentVersion
QueryGetAPI(BASE_API + 'vtsuru/version').then((version) => {
if (version.code == 200) {
currentVersion = version.data
const savedVersion = localStorage.getItem('Version')
if (currentVersion && savedVersion && savedVersion !== currentVersion) {
alert('发现新的版本更新, 请按 Ctrl+F5 强制刷新页面')
}
localStorage.setItem('Version', currentVersion)
}
})
createApp(App).use(router).mount('#app') createApp(App).use(router).mount('#app')
GetSelfAccount() GetSelfAccount()
GetNotifactions() GetNotifactions()
let currentVersion: string
const { notification } = createDiscreteApi(['notification'])
QueryGetAPI<string>(BASE_API + 'vtsuru/version').then((version) => {
if (version.code == 200) {
currentVersion = version.data
const savedVersion = localStorage.getItem('Version')
if (currentVersion && savedVersion && savedVersion !== currentVersion) {
//alert('发现新的版本更新, 请按 Ctrl+F5 强制刷新页面')
notification.info({
title: '发现新的版本更新',
content: '请按 Ctrl+F5 强制刷新页面',
duration: 5000,
meta: () => h(NText, { depth: 3 }, () => currentVersion),
})
}
localStorage.setItem('Version', currentVersion)
}
})

View File

@@ -183,19 +183,21 @@ const routes: Array<RouteRecordRaw> = [
{ {
path: 'live-lottery', path: 'live-lottery',
name: 'manage-liveLottery', name: 'manage-liveLottery',
component: () => import('@/views/manage/LiveLotteryManage.vue'), component: () => import('@/views/open_live/OpenLottery.vue'),
meta: { meta: {
title: '直播抽奖', title: '直播抽奖',
keepAlive: true, keepAlive: true,
danmaku: true,
}, },
}, },
{ {
path: 'song-request', path: 'song-request',
name: 'manage-songRequest', name: 'manage-songRequest',
component: () => import('@/views/manage/SongRequestManage.vue'), component: () => import('@/views/open_live/MusicRequest.vue'),
meta: { meta: {
title: '弹幕点歌', title: '弹幕点歌',
keepAlive: true, keepAlive: true,
danmaku: true,
}, },
}, },
], ],

View File

@@ -18,26 +18,36 @@ import {
NAlert, NAlert,
NBackTop, NBackTop,
NCountdown, NCountdown,
NTooltip,
} from 'naive-ui' } from 'naive-ui'
import { h, onMounted, ref } from 'vue' import { h, onMounted, ref } from 'vue'
import { BrowsersOutline, Chatbox, Moon, MusicalNote, Sunny, AnalyticsSharp } from '@vicons/ionicons5' import { BrowsersOutline, Chatbox, Moon, MusicalNote, Sunny, AnalyticsSharp } from '@vicons/ionicons5'
import { CalendarClock24Filled, Lottery24Filled, VehicleShip24Filled, VideoAdd20Filled } from '@vicons/fluent' import { CalendarClock24Filled, Chat24Filled, Info24Filled, Lottery24Filled, VehicleShip24Filled, VideoAdd20Filled } from '@vicons/fluent'
import { isLoadingAccount, useAccount } from '@/api/account' import { isLoadingAccount, useAccount } from '@/api/account'
import RegisterAndLogin from '@/components/RegisterAndLogin.vue' import RegisterAndLogin from '@/components/RegisterAndLogin.vue'
import { RouterLink } from 'vue-router' import { RouterLink, useRoute } from 'vue-router'
import { useElementSize, useStorage } from '@vueuse/core' import { useElementSize, useStorage } from '@vueuse/core'
import { ACCOUNT_API_URL } from '@/data/constants' import { ACCOUNT_API_URL } from '@/data/constants'
import { QueryGetAPI } from '@/api/query' import { QueryGetAPI } from '@/api/query'
import { FunctionTypes, ThemeType } from '@/api/api-models' import { FunctionTypes, ThemeType } from '@/api/api-models'
import { isDarkMode } from '@/Utils' import { isDarkMode } from '@/Utils'
import DanmakuLayout from './manage/DanmakuLayout.vue'
import { computed } from '@vue/reactivity'
const accountInfo = useAccount() const accountInfo = useAccount()
const message = useMessage() const message = useMessage()
const route = useRoute()
const windowWidth = window.innerWidth const windowWidth = window.innerWidth
const sider = ref() const sider = ref()
const { width } = useElementSize(sider) const { width } = useElementSize(sider)
const themeType = useStorage('Settings.Theme', ThemeType.Auto) const themeType = useStorage('Settings.Theme', ThemeType.Auto)
const type = computed(() => {
if (route.meta.danmaku) {
return 'danmaku'
}
return ''
})
const canResendEmail = ref(false) const canResendEmail = ref(false)
@@ -151,6 +161,65 @@ const menuOptions = [
icon: renderIcon(Lottery24Filled), icon: renderIcon(Lottery24Filled),
//disabled: accountInfo.value?.isEmailVerified == false, //disabled: accountInfo.value?.isEmailVerified == false,
}, },
{
label: () =>
h(NText, () => [
'弹幕相关',
h(
NTooltip,
{
style: 'padding: 0;',
},
{
trigger: () => h(NIcon, { component: Info24Filled }),
default: () =>
h(
NAlert,
{
type: 'warning',
size: 'small',
title: '可用性警告',
style: 'max-width: 600px;',
},
() =>
h('div', {}, [
'当浏览器在后台运行时, 定时器和 Websocket 连接将受到严格限制, 这会导致弹幕接收功能无法正常工作 (详见',
h(
NButton,
{
text: true,
tag: 'a',
href: 'https://developer.chrome.com/blog/background_tabs/',
target: '_blank',
type: 'info',
},
'此文章'
),
'), 虽然本站已经针对此问题做出了处理, 一般情况下即使掉线了也会重连, 不过还是有可能会遗漏事件',
h('br'),
'为避免这种情况, 建议注册本站账后使用',
h(
NButton,
{
type: 'primary',
text: true,
size: 'small',
tag: 'a',
href: 'https://www.yuque.com/megghy/dez70g/vfvcyv3024xvaa1p',
target: '_blank',
},
'VtsuruEventFetcher'
),
', 否则请在使用功能时尽量保持网页在前台运行',
])
),
}
),
]),
key: 'manage-danmaku',
icon: renderIcon(Chat24Filled),
//disabled: accountInfo.value?.isEmailVerified == false,
children: [
{ {
label: () => label: () =>
h( h(
@@ -175,12 +244,14 @@ const menuOptions = [
name: 'manage-songRequest', name: 'manage-songRequest',
}, },
}, },
{ default: () => '弹幕点歌' } { default: () => '点歌' }
), ),
key: 'manage-songRequest', key: 'manage-songRequest',
icon: renderIcon(MusicalNote), icon: renderIcon(MusicalNote),
//disabled: accountInfo.value?.isEmailVerified == false, //disabled: accountInfo.value?.isEmailVerified == false,
}, },
],
},
] ]
async function resendEmail() { async function resendEmail() {
@@ -226,7 +297,7 @@ onMounted(() => {
<NIcon :component="Moon" /> <NIcon :component="Moon" />
</template> </template>
</NSwitch> </NSwitch>
<NButton size="small" style="right: 0px; position: relative" type="primary" @click="$router.push({ name: 'user-index', params: { id: accountInfo.name } })"> 回到主页 </NButton> <NButton size="small" style="right: 0px; position: relative" type="primary" @click="$router.push({ name: 'user-index', params: { id: accountInfo?.name } })"> 回到主页 </NButton>
</NSpace> </NSpace>
</template> </template>
</NPageHeader> </NPageHeader>
@@ -260,9 +331,10 @@ onMounted(() => {
<NScrollbar style="height: calc(100vh - 50px)"> <NScrollbar style="height: calc(100vh - 50px)">
<NLayout> <NLayout>
<div style="box-sizing: border-box; padding: 20px; min-width: 300px"> <div style="box-sizing: border-box; padding: 20px; min-width: 300px">
<RouterView v-slot="{ Component }" v-if="accountInfo?.isEmailVerified"> <RouterView v-if="accountInfo?.isEmailVerified" v-slot="{ Component, route }">
<KeepAlive> <KeepAlive>
<Suspense> <DanmakuLayout v-if="route.meta.danmaku" :component="Component" />
<Suspense v-else>
<component :is="Component" /> <component :is="Component" />
<template #fallback> <template #fallback>
<NSpin show /> <NSpin show />

View File

@@ -80,12 +80,16 @@ const menuOptions = [
function renderIcon(icon: unknown) { function renderIcon(icon: unknown) {
return () => h(NIcon, null, { default: () => h(icon as any) }) return () => h(NIcon, null, { default: () => h(icon as any) })
} }
const danmakuClientError = ref<string>()
onMounted(async () => { onMounted(async () => {
authInfo.value = route.query as unknown as AuthInfo authInfo.value = route.query as unknown as AuthInfo
if (authInfo.value?.Code) { if (authInfo.value?.Code) {
client.value = new DanmakuClient(authInfo.value) client.value = new DanmakuClient(authInfo.value)
await client.value.Start() const result = await client.value.Start()
if (!result.success) {
message.error('无法启动弹幕客户端: ' + result.message)
danmakuClientError.value = result.message
}
} else { } else {
message.error('你不是从幻星平台访问此页面, 或未提供对应参数, 无法使用此功能') message.error('你不是从幻星平台访问此页面, 或未提供对应参数, 无法使用此功能')
} }
@@ -159,6 +163,9 @@ onUnmounted(() => {
</NSpace> </NSpace>
</NLayoutSider> </NLayoutSider>
<NLayoutContent style="height: 100%; padding: 10px" :native-scrollbar="false"> <NLayoutContent style="height: 100%; padding: 10px" :native-scrollbar="false">
<NAlert v-if="danmakuClientError" type="error" title="无法启动弹幕客户端">
{{ danmakuClientError }}
</NAlert>
<RouterView v-if="client?.roomAuthInfo.value" v-slot="{ Component }"> <RouterView v-if="client?.roomAuthInfo.value" v-slot="{ Component }">
<KeepAlive> <KeepAlive>
<component :is="Component" :room-info="client?.roomAuthInfo" :client="client" :code="authInfo.Code" /> <component :is="Component" :room-info="client?.roomAuthInfo" :client="client" :code="authInfo.Code" />

View File

@@ -0,0 +1,35 @@
<script setup lang="ts">
import { useAccount } from '@/api/account'
import DanmakuClient from '@/data/DanmakuClient'
import { NAlert, NSpin, useMessage } from 'naive-ui'
import { VNode, onMounted, onUnmounted, ref } from 'vue'
const props = defineProps<{
component: VNode
}>()
const accountInfo = useAccount()
const message = useMessage()
let client = new DanmakuClient(null)
const isClientLoading = ref(true)
onMounted(async () => {
const result = await client.Start()
if (!result.success) {
message.error('无法启动弹幕客户端: ' + result.message)
}
isClientLoading.value = false
})
onUnmounted(() => {
client.Stop()
})
</script>
<template>
<NAlert v-if="accountInfo?.isBiliVerified != true" type="info"> 尚未进行Bilibili认证 </NAlert>
<NSpin v-else-if="isClientLoading" show />
<KeepAlive v-else>
<component :is="component" :client="client" :room-info="client.roomAuthInfo?.value" :code="accountInfo?.biliAuthCode"/>
</KeepAlive>
</template>

View File

@@ -66,7 +66,7 @@ const lotteryHistory = useStorage<LotteryHistory[]>('LotteryHistory', [])
const message = useMessage() const message = useMessage()
const notification = useNotification() const notification = useNotification()
const token = ref() const token = ref('')
const turnstile = ref() const turnstile = ref()
const defaultOption = { const defaultOption = {
resultCount: 1, resultCount: 1,

View File

@@ -22,7 +22,7 @@ const accountInfo = useAccount()
</template> </template>
</NCard> </NCard>
<NCard hoverable embedded size="small" title="弹幕点歌" style="width: 300px"> <NCard hoverable embedded size="small" title="弹幕点歌" style="width: 300px">
通过弹幕或者SC进行点歌, 注册后可以保存和导出 (开发中 通过弹幕或者SC进行点歌, 注册后可以保存和导出 (这个是歌势用的点歌, 不是拿来放歌的那种!)
<template #footer> <template #footer>
<NButton @click="$router.push({ name: 'open-live-song-request', query: $route.query })" type="primary"> 前往使用 </NButton> <NButton @click="$router.push({ name: 'open-live-song-request', query: $route.query })" type="primary"> 前往使用 </NButton>
</template> </template>
@@ -30,7 +30,7 @@ const accountInfo = useAccount()
</NSpace> </NSpace>
<br /> <br />
<NAlert v-if="accountInfo?.eventFetcherOnline != true" type="warning" title="可用性警告" style="max-width: 600px; margin: 0 auto"> <NAlert v-if="accountInfo?.eventFetcherOnline != true" type="warning" title="可用性警告" style="max-width: 600px; margin: 0 auto">
当浏览器在后台运行时定时器和 Websocket 连接将受到严格限制, 这会导致弹幕接收功能无法正常工作 (详见 当浏览器在后台运行时, 定时器和 Websocket 连接将受到严格限制, 这会导致弹幕接收功能无法正常工作 (详见
<NButton text tag="a" href="https://developer.chrome.com/blog/background_tabs/" target="_blank" type="info">此文章</NButton>), 虽然本站已经针对此问题做出了处理, 一般情况下即使掉线了也会重连, <NButton text tag="a" href="https://developer.chrome.com/blog/background_tabs/" target="_blank" type="info">此文章</NButton>), 虽然本站已经针对此问题做出了处理, 一般情况下即使掉线了也会重连,
不过还是有可能会遗漏事件 不过还是有可能会遗漏事件
<br /> <br />

View File

@@ -317,10 +317,8 @@ onMounted(async () => {
message.info('从历史记录中加载 ' + users.length + ' 位用户') message.info('从历史记录中加载 ' + users.length + ' 位用户')
} }
} }
if (props.client) { props.client?.on('danmaku', onDanmaku)
props.client.on('danmaku', onDanmaku) props.client?.on('gift', onGift)
props.client.on('gift', onGift)
}
timer = setInterval(updateUsers, 1000 * 10) timer = setInterval(updateUsers, 1000 * 10)
}) })
onUnmounted(() => { onUnmounted(() => {