mirror of
https://github.com/Megghy/vtsuru.live.git
synced 2025-12-06 18:36:55 +08:00
feat: 优化弹幕窗口样式系统并添加 Cookie 状态监控
- 将 naive-ui 版本约束从固定版本改为 ^ 范围约束 - 重构弹幕窗口背景渲染:分离窗口背景和弹幕背景,支持独立配置 - 新增颜色解析工具函数 parseColorToRgba,支持 hex/rgba 格式及透明度提取 - 优化 CSS 变量系统:添加 --dw-bg-color-rgb、--dw-bg-alpha、--dw-window-bg-color 变量 - 改进弹幕窗口布局:使用独立背景层支持 backdrop-filter 和圆角边框 - 在
This commit is contained in:
@@ -53,7 +53,7 @@
|
|||||||
"md5": "^2.3.0",
|
"md5": "^2.3.0",
|
||||||
"mitt": "^3.0.1",
|
"mitt": "^3.0.1",
|
||||||
"monaco-editor": "^0.54.0",
|
"monaco-editor": "^0.54.0",
|
||||||
"naive-ui": "2.43.2",
|
"naive-ui": "^2.43.2",
|
||||||
"nanoid": "^5.1.6",
|
"nanoid": "^5.1.6",
|
||||||
"obs-websocket-js": "^5.0.7",
|
"obs-websocket-js": "^5.0.7",
|
||||||
"peerjs": "^1.5.5",
|
"peerjs": "^1.5.5",
|
||||||
|
|||||||
@@ -15,6 +15,70 @@ type TempDanmakuType = EventModel & {
|
|||||||
timestamp?: number // 添加:记录插入时间戳
|
timestamp?: number // 添加:记录插入时间戳
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ParsedColor = {
|
||||||
|
rgb: string
|
||||||
|
alpha: number
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseColorToRgba(color?: string): ParsedColor | null {
|
||||||
|
if (!color) return null
|
||||||
|
const value = color.trim()
|
||||||
|
|
||||||
|
const rgbaMatch = value.match(/^rgba?\(([^)]+)\)$/i)
|
||||||
|
if (rgbaMatch) {
|
||||||
|
const parts = rgbaMatch[1].split(',').map(part => part.trim())
|
||||||
|
if (parts.length >= 3) {
|
||||||
|
const r = Number(parts[0])
|
||||||
|
const g = Number(parts[1])
|
||||||
|
const b = Number(parts[2])
|
||||||
|
const a = parts[3] !== undefined ? Number(parts[3]) : 1
|
||||||
|
return {
|
||||||
|
rgb: `${Number.isFinite(r) ? r : 0}, ${Number.isFinite(g) ? g : 0}, ${Number.isFinite(b) ? b : 0}`,
|
||||||
|
alpha: Number.isFinite(a) ? Math.max(0, Math.min(1, a)) : 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.startsWith('#')) {
|
||||||
|
const hex = value.slice(1)
|
||||||
|
const normalizeHex = (segment: string) => segment.length === 1 ? segment + segment : segment
|
||||||
|
let r = 0
|
||||||
|
let g = 0
|
||||||
|
let b = 0
|
||||||
|
let a = 255
|
||||||
|
|
||||||
|
if (hex.length === 3 || hex.length === 4) {
|
||||||
|
r = parseInt(normalizeHex(hex[0]), 16)
|
||||||
|
g = parseInt(normalizeHex(hex[1]), 16)
|
||||||
|
b = parseInt(normalizeHex(hex[2]), 16)
|
||||||
|
if (hex.length === 4) {
|
||||||
|
a = parseInt(normalizeHex(hex[3]), 16)
|
||||||
|
}
|
||||||
|
} else if (hex.length === 6 || hex.length === 8) {
|
||||||
|
r = parseInt(hex.slice(0, 2), 16)
|
||||||
|
g = parseInt(hex.slice(2, 4), 16)
|
||||||
|
b = parseInt(hex.slice(4, 6), 16)
|
||||||
|
if (hex.length === 8) {
|
||||||
|
a = parseInt(hex.slice(6, 8), 16)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([r, g, b, a].some(value => Number.isNaN(value))) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
rgb: `${r}, ${g}, ${b}`,
|
||||||
|
alpha: Math.max(0, Math.min(1, a / 255)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
let bc: BroadcastChannel | undefined
|
let bc: BroadcastChannel | undefined
|
||||||
const setting = ref<DanmakuWindowSettings>()
|
const setting = ref<DanmakuWindowSettings>()
|
||||||
const danmakuList = ref<TempDanmakuType[]>([])
|
const danmakuList = ref<TempDanmakuType[]>([])
|
||||||
@@ -33,7 +97,17 @@ function updateCssVariables() {
|
|||||||
root.style.setProperty('--dw-direction', setting.value.reverseOrder ? 'column-reverse' : 'column')
|
root.style.setProperty('--dw-direction', setting.value.reverseOrder ? 'column-reverse' : 'column')
|
||||||
|
|
||||||
// 背景和文字颜色
|
// 背景和文字颜色
|
||||||
root.style.setProperty('--dw-bg-color', setting.value.backgroundColor || 'rgba(0,0,0,0.6)')
|
const bgColor = setting.value.backgroundColor || 'rgba(0,0,0,0.6)'
|
||||||
|
root.style.setProperty('--dw-bg-color', bgColor)
|
||||||
|
const parsedColor = parseColorToRgba(bgColor)
|
||||||
|
if (parsedColor) {
|
||||||
|
root.style.setProperty('--dw-bg-color-rgb', parsedColor.rgb)
|
||||||
|
root.style.setProperty('--dw-bg-alpha', `${parsedColor.alpha}`)
|
||||||
|
} else {
|
||||||
|
root.style.setProperty('--dw-bg-color-rgb', '0, 0, 0')
|
||||||
|
root.style.setProperty('--dw-bg-alpha', '0.6')
|
||||||
|
}
|
||||||
|
root.style.setProperty('--dw-window-bg-color', setting.value.windowBackgroundColor || 'transparent')
|
||||||
root.style.setProperty('--dw-text-color', setting.value.textColor || '#ffffff')
|
root.style.setProperty('--dw-text-color', setting.value.textColor || '#ffffff')
|
||||||
|
|
||||||
// 尺寸相关
|
// 尺寸相关
|
||||||
@@ -224,6 +298,7 @@ watch(() => setting.value, () => {
|
|||||||
class="danmaku-window"
|
class="danmaku-window"
|
||||||
:class="{ 'has-items': hasItems, 'batch-update': isInBatchUpdate }"
|
:class="{ 'has-items': hasItems, 'batch-update': isInBatchUpdate }"
|
||||||
>
|
>
|
||||||
|
<div class="danmaku-window-bg" />
|
||||||
<div
|
<div
|
||||||
ref="scrollContainerRef"
|
ref="scrollContainerRef"
|
||||||
class="danmaku-list"
|
class="danmaku-list"
|
||||||
@@ -260,6 +335,9 @@ html,
|
|||||||
|
|
||||||
:root {
|
:root {
|
||||||
--dw-bg-color: rgba(0, 0, 0, 0.6);
|
--dw-bg-color: rgba(0, 0, 0, 0.6);
|
||||||
|
--dw-bg-color-rgb: 0, 0, 0;
|
||||||
|
--dw-bg-alpha: 0.6;
|
||||||
|
--dw-window-bg-color: transparent;
|
||||||
--dw-text-color: #ffffff;
|
--dw-text-color: #ffffff;
|
||||||
--dw-border-radius: 0px;
|
--dw-border-radius: 0px;
|
||||||
--dw-opacity: 1;
|
--dw-opacity: 1;
|
||||||
@@ -272,12 +350,12 @@ html,
|
|||||||
}
|
}
|
||||||
|
|
||||||
.danmaku-window {
|
.danmaku-window {
|
||||||
|
position: relative;
|
||||||
-webkit-app-region: drag;
|
-webkit-app-region: drag;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background-color: transparent;
|
|
||||||
/* 完全透明背景 */
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
border-radius: var(--dw-border-radius, 0);
|
||||||
color: var(--dw-text-color);
|
color: var(--dw-text-color);
|
||||||
font-size: var(--dw-font-size);
|
font-size: var(--dw-font-size);
|
||||||
box-shadow: var(--dw-shadow);
|
box-shadow: var(--dw-shadow);
|
||||||
@@ -285,6 +363,15 @@ html,
|
|||||||
transition: opacity 0.3s ease;
|
transition: opacity 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.danmaku-window-bg {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
border-radius: var(--dw-border-radius, 0);
|
||||||
|
background-color: var(--dw-window-bg-color, transparent);
|
||||||
|
backdrop-filter: blur(0);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
/* 没有弹幕时完全透明 */
|
/* 没有弹幕时完全透明 */
|
||||||
.danmaku-window:not(.has-items) {
|
.danmaku-window:not(.has-items) {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
@@ -297,7 +384,6 @@ html,
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: var(--dw-direction);
|
flex-direction: var(--dw-direction);
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
/* 确保padding不会增加元素的实际尺寸 */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.danmaku-list-container {
|
.danmaku-list-container {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import { openUrl } from '@tauri-apps/plugin-opener'
|
import { openUrl } from '@tauri-apps/plugin-opener'
|
||||||
import { useElementSize } from '@vueuse/core'
|
import { useElementSize } from '@vueuse/core'
|
||||||
import { Live24Filled, CloudArchive24Filled, FlashAuto24Filled, Mic24Filled, Settings24Filled } from '@vicons/fluent'
|
import { Live24Filled, CloudArchive24Filled, FlashAuto24Filled, Mic24Filled, Settings24Filled } from '@vicons/fluent'
|
||||||
|
import CookieInvalidAlert from './components/CookieInvalidAlert.vue'
|
||||||
import { cookie, useAccount } from '@/api/account'
|
import { cookie, useAccount } from '@/api/account'
|
||||||
import { useWebFetcher } from '@/store/useWebFetcher'
|
import { useWebFetcher } from '@/store/useWebFetcher'
|
||||||
import { roomInfo } from './data/info'
|
import { roomInfo } from './data/info'
|
||||||
@@ -31,6 +32,10 @@ function logout() {
|
|||||||
gap="large"
|
gap="large"
|
||||||
wrap
|
wrap
|
||||||
>
|
>
|
||||||
|
<CookieInvalidAlert
|
||||||
|
class="client-index-alert"
|
||||||
|
variant="home"
|
||||||
|
/>
|
||||||
<NCard
|
<NCard
|
||||||
title="首页"
|
title="首页"
|
||||||
embedded
|
embedded
|
||||||
|
|||||||
@@ -6,17 +6,18 @@ import { openUrl } from '@tauri-apps/plugin-opener'
|
|||||||
|
|
||||||
import { Chat24Filled, CloudArchive24Filled, FlashAuto24Filled, Live24Filled, Mic24Filled, Settings24Filled } from '@vicons/fluent'
|
import { Chat24Filled, CloudArchive24Filled, FlashAuto24Filled, Live24Filled, Mic24Filled, Settings24Filled } from '@vicons/fluent'
|
||||||
import { CheckmarkCircle, CloseCircle, Home } from '@vicons/ionicons5'
|
import { CheckmarkCircle, CloseCircle, Home } from '@vicons/ionicons5'
|
||||||
import { NA, NButton, NCard, NInput, NLayout, NLayoutContent, NLayoutSider, NMenu, NSpace, NSpin, NText, NTooltip } from 'naive-ui'
|
import { NA, NButton, NCard, NInput, NLayout, NLayoutContent, NLayoutSider, NMenu, NSpace, NSpin, NTag, NText, NTooltip } from 'naive-ui'
|
||||||
|
|
||||||
import { computed, h, ref } from 'vue' // 引入 ref, h, computed
|
import { computed, h, ref } from 'vue' // 引入 ref, h, computed
|
||||||
|
|
||||||
import { RouterLink, RouterView } from 'vue-router' // 引入 Vue Router 组件
|
import { RouterLink, RouterView, useRouter } from 'vue-router' // 引入 Vue Router 组件
|
||||||
// 引入自定义 API 和状态管理
|
// 引入自定义 API 和状态管理
|
||||||
import { ACCOUNT, GetSelfAccount, isLoadingAccount, isLoggedIn } from '@/api/account'
|
import { ACCOUNT, GetSelfAccount, isLoadingAccount, isLoggedIn } from '@/api/account'
|
||||||
|
|
||||||
import { useWebFetcher } from '@/store/useWebFetcher'
|
import { useWebFetcher } from '@/store/useWebFetcher'
|
||||||
import { initAll, OnClientUnmounted, clientInited, clientInitStage } from './data/initialize'
|
import { initAll, OnClientUnmounted, clientInited, clientInitStage } from './data/initialize'
|
||||||
import { useDanmakuWindow } from './store/useDanmakuWindow'
|
import { useDanmakuWindow } from './store/useDanmakuWindow'
|
||||||
|
import { useBiliCookie } from './store/useBiliCookie'
|
||||||
// 引入子组件
|
// 引入子组件
|
||||||
import WindowBar from './WindowBar.vue'
|
import WindowBar from './WindowBar.vue'
|
||||||
import { BASE_URL } from '@/data/constants'
|
import { BASE_URL } from '@/data/constants'
|
||||||
@@ -24,11 +25,31 @@ import { BASE_URL } from '@/data/constants'
|
|||||||
// --- 响应式状态 ---
|
// --- 响应式状态 ---
|
||||||
|
|
||||||
// 获取 webfetcher 状态管理的实例
|
// 获取 webfetcher 状态管理的实例
|
||||||
|
const router = useRouter()
|
||||||
const webfetcher = useWebFetcher()
|
const webfetcher = useWebFetcher()
|
||||||
const danmakuWindow = useDanmakuWindow()
|
const danmakuWindow = useDanmakuWindow()
|
||||||
|
const biliCookie = useBiliCookie()
|
||||||
// 用于存储用户输入的 Token
|
// 用于存储用户输入的 Token
|
||||||
const token = ref('')
|
const token = ref('')
|
||||||
|
|
||||||
|
const cookieStatusType = computed(() => {
|
||||||
|
if (!biliCookie.hasBiliCookie) {
|
||||||
|
return 'warning'
|
||||||
|
}
|
||||||
|
return biliCookie.isCookieValid ? 'success' : 'error'
|
||||||
|
})
|
||||||
|
|
||||||
|
const cookieStatusText = computed(() => {
|
||||||
|
if (!biliCookie.hasBiliCookie) {
|
||||||
|
return '未同步'
|
||||||
|
}
|
||||||
|
return biliCookie.isCookieValid ? '正常' : '已失效'
|
||||||
|
})
|
||||||
|
|
||||||
|
function goCookieManagement() {
|
||||||
|
router.push({ name: 'client-fetcher' })
|
||||||
|
}
|
||||||
|
|
||||||
// --- 计算属性 ---
|
// --- 计算属性 ---
|
||||||
// (这里没有显式的计算属性,但 isLoggedIn 本身可能是一个来自 account 模块的计算属性)
|
// (这里没有显式的计算属性,但 isLoggedIn 本身可能是一个来自 account 模块的计算属性)
|
||||||
|
|
||||||
@@ -279,6 +300,33 @@ onMounted(() => {
|
|||||||
default-value="go-back-home"
|
default-value="go-back-home"
|
||||||
class="sider-menu"
|
class="sider-menu"
|
||||||
/>
|
/>
|
||||||
|
<div class="cookie-status-card">
|
||||||
|
<div class="cookie-status-header">
|
||||||
|
<NText
|
||||||
|
strong
|
||||||
|
tag="div"
|
||||||
|
>
|
||||||
|
B站 Cookie
|
||||||
|
</NText>
|
||||||
|
<NTag
|
||||||
|
size="small"
|
||||||
|
:type="cookieStatusType"
|
||||||
|
:bordered="false"
|
||||||
|
>
|
||||||
|
{{ cookieStatusText }}
|
||||||
|
</NTag>
|
||||||
|
</div>
|
||||||
|
<NButton
|
||||||
|
v-if="cookieStatusType !== 'success'"
|
||||||
|
block
|
||||||
|
size="tiny"
|
||||||
|
type="primary"
|
||||||
|
class="cookie-status-button"
|
||||||
|
@click="goCookieManagement"
|
||||||
|
>
|
||||||
|
前往处理
|
||||||
|
</NButton>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</NLayoutSider>
|
</NLayoutSider>
|
||||||
|
|
||||||
@@ -452,6 +500,28 @@ onMounted(() => {
|
|||||||
/* 菜单与顶部的间距 */
|
/* 菜单与顶部的间距 */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cookie-status-card {
|
||||||
|
margin-top: 12px;
|
||||||
|
padding: 12px;
|
||||||
|
border: 1px solid var(--n-border-color);
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: var(--n-card-color);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cookie-status-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cookie-status-button {
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Suspense 后备内容样式 */
|
/* Suspense 后备内容样式 */
|
||||||
.suspense-fallback {
|
.suspense-fallback {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -210,8 +210,19 @@ const separatorOptions = [
|
|||||||
:x-gap="12"
|
:x-gap="12"
|
||||||
>
|
>
|
||||||
<NGi>
|
<NGi>
|
||||||
<NFormItem label="背景颜色">
|
<NFormItem label="弹幕背景颜色">
|
||||||
<NColorPicker />
|
<NColorPicker
|
||||||
|
v-model:value="danmakuWindow.danmakuWindowSetting.backgroundColor"
|
||||||
|
:show-alpha="true"
|
||||||
|
/>
|
||||||
|
</NFormItem>
|
||||||
|
</NGi>
|
||||||
|
<NGi>
|
||||||
|
<NFormItem label="窗口背景颜色">
|
||||||
|
<NColorPicker
|
||||||
|
v-model:value="danmakuWindow.danmakuWindowSetting.windowBackgroundColor"
|
||||||
|
:show-alpha="true"
|
||||||
|
/>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
</NGi>
|
</NGi>
|
||||||
<NGi>
|
<NGi>
|
||||||
@@ -226,7 +237,7 @@ const separatorOptions = [
|
|||||||
<NFormItem label="透明度">
|
<NFormItem label="透明度">
|
||||||
<NSlider
|
<NSlider
|
||||||
v-model:value="danmakuWindow.danmakuWindowSetting.opacity"
|
v-model:value="danmakuWindow.danmakuWindowSetting.opacity"
|
||||||
:min="0.1"
|
:min="0"
|
||||||
:max="1"
|
:max="1"
|
||||||
:step="0.05"
|
:step="0.05"
|
||||||
/>
|
/>
|
||||||
|
|||||||
82
src/client/components/CookieInvalidAlert.vue
Normal file
82
src/client/components/CookieInvalidAlert.vue
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { NAlert, NButton } from 'naive-ui'
|
||||||
|
|
||||||
|
import { useBiliCookie } from '../store/useBiliCookie'
|
||||||
|
|
||||||
|
const biliCookie = useBiliCookie()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<{
|
||||||
|
variant?: 'home' | 'fetcher'
|
||||||
|
}>(), {
|
||||||
|
variant: 'home',
|
||||||
|
})
|
||||||
|
|
||||||
|
const goToFetcher = () => {
|
||||||
|
router.push({ name: 'client-fetcher' })
|
||||||
|
}
|
||||||
|
|
||||||
|
const goToSettings = () => {
|
||||||
|
router.push({ name: 'client-settings' })
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<NAlert
|
||||||
|
v-if="biliCookie.hasBiliCookie && !biliCookie.isCookieValid"
|
||||||
|
class="cookie-invalid-alert"
|
||||||
|
type="error"
|
||||||
|
:title="props.variant === 'home' ? '需重新登录 B 站账号' : 'EventFetcher 需要有效的 B 站 Cookie'"
|
||||||
|
:show-icon="true"
|
||||||
|
:bordered="false"
|
||||||
|
>
|
||||||
|
<div class="cookie-invalid-alert__content">
|
||||||
|
<p>
|
||||||
|
{{ props.variant === 'home'
|
||||||
|
? '检测到 B 站 Cookie 已失效,客户端功能将受限。'
|
||||||
|
: '请尽快同步或重新登录 Cookie,以保证事件采集稳定运行。'
|
||||||
|
}}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
如果已经部署 CookieCloud,请尝试重新同步;否则请重新扫码登录。
|
||||||
|
</p>
|
||||||
|
<div class="cookie-invalid-alert__actions">
|
||||||
|
<NButton
|
||||||
|
size="small"
|
||||||
|
type="primary"
|
||||||
|
@click="goToFetcher"
|
||||||
|
>
|
||||||
|
前往 EventFetcher
|
||||||
|
</NButton>
|
||||||
|
<NButton
|
||||||
|
size="small"
|
||||||
|
tertiary
|
||||||
|
@click="goToSettings"
|
||||||
|
>
|
||||||
|
Cookie 设置
|
||||||
|
</NButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</NAlert>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.cookie-invalid-alert {
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cookie-invalid-alert__content {
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: rgb(51 54 57 / 90%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cookie-invalid-alert__actions {
|
||||||
|
margin-top: 8px;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -36,6 +36,50 @@ let heartbeatTimer: number | null = null
|
|||||||
let updateCheckTimer: number | null = null
|
let updateCheckTimer: number | null = null
|
||||||
let updateNotificationRef: any = null
|
let updateNotificationRef: any = null
|
||||||
|
|
||||||
|
function setInitStageSafely(stage: string) {
|
||||||
|
if (clientInitStage.value !== '启动完成') {
|
||||||
|
clientInitStage.value = stage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function startDanmakuClientInitFlow() {
|
||||||
|
const danmakuInitNoticeRef = window.$notification.info({
|
||||||
|
title: '正在初始化弹幕客户端...',
|
||||||
|
closable: false,
|
||||||
|
})
|
||||||
|
setInitStageSafely('初始化弹幕客户端...')
|
||||||
|
|
||||||
|
void initDanmakuClient()
|
||||||
|
.then((result) => {
|
||||||
|
if (result.success) {
|
||||||
|
info('[init] 弹幕客户端初始化完成')
|
||||||
|
window.$notification.success({
|
||||||
|
title: '弹幕客户端初始化完成',
|
||||||
|
duration: 3000,
|
||||||
|
})
|
||||||
|
setInitStageSafely('弹幕客户端初始化完成')
|
||||||
|
} else {
|
||||||
|
warn(`[init] 弹幕客户端初始化失败: ${result.message}`)
|
||||||
|
window.$notification.error({
|
||||||
|
title: '弹幕客户端初始化失败',
|
||||||
|
content: result.message || '请稍后重试',
|
||||||
|
})
|
||||||
|
setInitStageSafely('弹幕客户端初始化失败')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
warn(`[init] 弹幕客户端初始化异常: ${error}`)
|
||||||
|
window.$notification.error({
|
||||||
|
title: '弹幕客户端初始化异常',
|
||||||
|
content: `${error}`,
|
||||||
|
})
|
||||||
|
setInitStageSafely('弹幕客户端初始化失败')
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
danmakuInitNoticeRef?.destroy()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// interface RtmpRelayState {
|
// interface RtmpRelayState {
|
||||||
// roomId: number
|
// roomId: number
|
||||||
// targetRtmpUrl: string
|
// targetRtmpUrl: string
|
||||||
@@ -334,29 +378,6 @@ export async function initAll(isOnBoot: boolean) {
|
|||||||
initInfo()
|
initInfo()
|
||||||
info('[init] 开始更新数据')
|
info('[init] 开始更新数据')
|
||||||
|
|
||||||
if (isLoggedIn.value && accountInfo.value.isBiliVerified && !setting.settings.dev_disableDanmakuClient) {
|
|
||||||
const danmakuInitNoticeRef = window.$notification.info({
|
|
||||||
title: '正在初始化弹幕客户端...',
|
|
||||||
closable: false,
|
|
||||||
})
|
|
||||||
clientInitStage.value = '初始化弹幕客户端...'
|
|
||||||
const result = await initDanmakuClient()
|
|
||||||
danmakuInitNoticeRef.destroy()
|
|
||||||
if (result.success) {
|
|
||||||
window.$notification.success({
|
|
||||||
title: '弹幕客户端初始化完成',
|
|
||||||
duration: 3000,
|
|
||||||
})
|
|
||||||
clientInitStage.value = '弹幕客户端初始化完成'
|
|
||||||
} else {
|
|
||||||
window.$notification.error({
|
|
||||||
title: `弹幕客户端初始化失败: ${result.message}`,
|
|
||||||
})
|
|
||||||
clientInitStage.value = '弹幕客户端初始化失败'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
info('[init] 已加载弹幕客户端')
|
|
||||||
// 初始化系统托盘图标和菜单
|
|
||||||
clientInitStage.value = '创建系统托盘...'
|
clientInitStage.value = '创建系统托盘...'
|
||||||
const menu = await Menu.new({
|
const menu = await Menu.new({
|
||||||
items: [
|
items: [
|
||||||
@@ -378,7 +399,6 @@ export async function initAll(isOnBoot: boolean) {
|
|||||||
})
|
})
|
||||||
const iconData = await (await fetch('https://oss.suki.club/vtsuru/icon.ico')).arrayBuffer()
|
const iconData = await (await fetch('https://oss.suki.club/vtsuru/icon.ico')).arrayBuffer()
|
||||||
const options: TrayIconOptions = {
|
const options: TrayIconOptions = {
|
||||||
// here you can add a tray menu, title, tooltip, event handler, etc
|
|
||||||
menu,
|
menu,
|
||||||
title: 'VTsuru.Client',
|
title: 'VTsuru.Client',
|
||||||
tooltip: 'VTsuru 事件收集器',
|
tooltip: 'VTsuru 事件收集器',
|
||||||
@@ -393,6 +413,15 @@ export async function initAll(isOnBoot: boolean) {
|
|||||||
tray = await TrayIcon.new(options)
|
tray = await TrayIcon.new(options)
|
||||||
clientInitStage.value = '系统托盘就绪'
|
clientInitStage.value = '系统托盘就绪'
|
||||||
|
|
||||||
|
const shouldInitDanmakuClient = isLoggedIn.value
|
||||||
|
&& accountInfo.value.isBiliVerified
|
||||||
|
&& !setting.settings.dev_disableDanmakuClient
|
||||||
|
if (shouldInitDanmakuClient) {
|
||||||
|
startDanmakuClientInitFlow()
|
||||||
|
} else {
|
||||||
|
info('[init] 跳过弹幕客户端初始化')
|
||||||
|
}
|
||||||
|
|
||||||
appWindow.setMinSize(new PhysicalSize(720, 480))
|
appWindow.setMinSize(new PhysicalSize(720, 480))
|
||||||
|
|
||||||
getAllWebviewWindows().then(async (windows) => {
|
getAllWebviewWindows().then(async (windows) => {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export interface DanmakuWindowSettings {
|
|||||||
animationDuration: number // 动画持续时间
|
animationDuration: number // 动画持续时间
|
||||||
enableAnimation: boolean // 是否启用动画效果
|
enableAnimation: boolean // 是否启用动画效果
|
||||||
backgroundColor: string // 背景色
|
backgroundColor: string // 背景色
|
||||||
|
windowBackgroundColor: string // 窗口背景色
|
||||||
textColor: string // 文字颜色
|
textColor: string // 文字颜色
|
||||||
alwaysOnTop: boolean // 是否总在最前
|
alwaysOnTop: boolean // 是否总在最前
|
||||||
interactive: boolean // 是否可交互(穿透鼠标点击)
|
interactive: boolean // 是否可交互(穿透鼠标点击)
|
||||||
@@ -173,6 +174,7 @@ export const useDanmakuWindow = defineStore('danmakuWindow', () => {
|
|||||||
filterTypes: ['Message', 'Gift', 'SC', 'Guard'],
|
filterTypes: ['Message', 'Gift', 'SC', 'Guard'],
|
||||||
animationDuration: 300,
|
animationDuration: 300,
|
||||||
backgroundColor: 'rgba(0,0,0,0.6)',
|
backgroundColor: 'rgba(0,0,0,0.6)',
|
||||||
|
windowBackgroundColor: 'rgba(0,0,0,0)',
|
||||||
textColor: '#ffffff',
|
textColor: '#ffffff',
|
||||||
alwaysOnTop: true,
|
alwaysOnTop: true,
|
||||||
interactive: false,
|
interactive: false,
|
||||||
|
|||||||
Reference in New Issue
Block a user