mirror of
https://github.com/Megghy/vtsuru.live.git
synced 2025-12-06 18:36:55 +08:00
feat: 添加弹幕窗口管理功能;优化弹幕客户端连接逻辑;实现自动滚动和设置更新; 修复浏览页页面切换的问题
This commit is contained in:
@@ -3,7 +3,8 @@ import { useDanmakuClient } from '@/store/useDanmakuClient';
|
|||||||
import { DANMAKU_WINDOW_BROADCAST_CHANNEL, DanmakuWindowBCData, DanmakuWindowSettings } from './store/useDanmakuWindow';
|
import { DANMAKU_WINDOW_BROADCAST_CHANNEL, DanmakuWindowBCData, DanmakuWindowSettings } from './store/useDanmakuWindow';
|
||||||
import { NSpin, NEmpty, NIcon } from 'naive-ui';
|
import { NSpin, NEmpty, NIcon } from 'naive-ui';
|
||||||
import { EventDataTypes, EventModel } from '@/api/api-models';
|
import { EventDataTypes, EventModel } from '@/api/api-models';
|
||||||
import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
|
// Import nextTick
|
||||||
|
import { computed, onMounted, onUnmounted, ref, watch, nextTick } from 'vue';
|
||||||
import { TransitionGroup } from 'vue';
|
import { TransitionGroup } from 'vue';
|
||||||
import { Money24Regular, VehicleShip24Filled } from '@vicons/fluent';
|
import { Money24Regular, VehicleShip24Filled } from '@vicons/fluent';
|
||||||
|
|
||||||
@@ -11,6 +12,8 @@ let bc: BroadcastChannel | undefined = undefined;
|
|||||||
const setting = ref<DanmakuWindowSettings>();
|
const setting = ref<DanmakuWindowSettings>();
|
||||||
const danmakuList = ref<EventModel[]>([]);
|
const danmakuList = ref<EventModel[]>([]);
|
||||||
const maxItems = computed(() => setting.value?.maxDanmakuCount || 50);
|
const maxItems = computed(() => setting.value?.maxDanmakuCount || 50);
|
||||||
|
// Ref for the scroll container
|
||||||
|
const scrollContainerRef = ref<HTMLElement | null>(null);
|
||||||
|
|
||||||
const isConnected = computed(() => {
|
const isConnected = computed(() => {
|
||||||
return setting.value !== undefined;
|
return setting.value !== undefined;
|
||||||
@@ -49,8 +52,8 @@ function formatUsername(item: EventModel): string {
|
|||||||
function addDanmaku(data: EventModel) {
|
function addDanmaku(data: EventModel) {
|
||||||
if (!setting.value) return;
|
if (!setting.value) return;
|
||||||
|
|
||||||
// 检查是否是需要过滤的消息类型
|
// Map EventDataTypes enum values to the string values used in filterTypes
|
||||||
const typeMap: Record<number, string> = {
|
const typeToStringMap: { [key in EventDataTypes]?: string } = {
|
||||||
[EventDataTypes.Message]: "Message",
|
[EventDataTypes.Message]: "Message",
|
||||||
[EventDataTypes.Gift]: "Gift",
|
[EventDataTypes.Gift]: "Gift",
|
||||||
[EventDataTypes.SC]: "SC",
|
[EventDataTypes.SC]: "SC",
|
||||||
@@ -58,12 +61,30 @@ function addDanmaku(data: EventModel) {
|
|||||||
[EventDataTypes.Enter]: "Enter"
|
[EventDataTypes.Enter]: "Enter"
|
||||||
};
|
};
|
||||||
|
|
||||||
const typeStr = typeMap[data.type];
|
const typeStr = typeToStringMap[data.type];
|
||||||
|
|
||||||
|
// Check if the type should be filtered out
|
||||||
if (!typeStr || !setting.value.filterTypes.includes(typeStr)) {
|
if (!typeStr || !setting.value.filterTypes.includes(typeStr)) {
|
||||||
return;
|
return; // Don't add if filtered
|
||||||
}
|
}
|
||||||
|
|
||||||
// 维护最大消息数量
|
// --- Auto Scroll Logic ---
|
||||||
|
const el = scrollContainerRef.value;
|
||||||
|
let shouldScroll = false;
|
||||||
|
if (el) {
|
||||||
|
const threshold = 5; // Pixels threshold to consider "at the end"
|
||||||
|
if (setting.value?.reverseOrder) {
|
||||||
|
// Check if scrolled to the top before adding
|
||||||
|
shouldScroll = el.scrollTop <= threshold;
|
||||||
|
} else {
|
||||||
|
// Check if scrolled to the bottom before adding
|
||||||
|
shouldScroll = el.scrollHeight - el.scrollTop - el.clientHeight <= threshold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// --- End Auto Scroll Logic ---
|
||||||
|
|
||||||
|
|
||||||
|
// Maintain max message count
|
||||||
if (setting.value.reverseOrder) {
|
if (setting.value.reverseOrder) {
|
||||||
danmakuList.value.unshift(data);
|
danmakuList.value.unshift(data);
|
||||||
if (danmakuList.value.length > maxItems.value) {
|
if (danmakuList.value.length > maxItems.value) {
|
||||||
@@ -75,18 +96,47 @@ function addDanmaku(data: EventModel) {
|
|||||||
danmakuList.value.shift();
|
danmakuList.value.shift();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Auto Scroll Execution ---
|
||||||
|
if (shouldScroll && el) {
|
||||||
|
nextTick(() => {
|
||||||
|
if (setting.value?.reverseOrder) {
|
||||||
|
el.scrollTop = 0; // Scroll to top
|
||||||
|
} else {
|
||||||
|
el.scrollTop = el.scrollHeight; // Scroll to bottom
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// --- End Auto Scroll Execution ---
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
bc = new BroadcastChannel(DANMAKU_WINDOW_BROADCAST_CHANNEL);
|
bc = new BroadcastChannel(DANMAKU_WINDOW_BROADCAST_CHANNEL);
|
||||||
|
console.log(`[DanmakuWindow] BroadcastChannel 已创建: ${DANMAKU_WINDOW_BROADCAST_CHANNEL}`);
|
||||||
|
bc.postMessage({
|
||||||
|
type: 'window-ready',
|
||||||
|
})
|
||||||
bc.onmessage = (event) => {
|
bc.onmessage = (event) => {
|
||||||
const data = event.data as DanmakuWindowBCData;
|
const data = event.data as DanmakuWindowBCData;
|
||||||
switch (data.type) {
|
switch (data.type) {
|
||||||
case 'danmaku':
|
case 'danmaku':
|
||||||
addDanmaku(data.data);
|
addDanmaku(data.data); // addDanmaku now handles scrolling
|
||||||
|
// console.log('[DanmakuWindow] 收到弹幕:', data.data); // Keep console logs minimal if not debugging
|
||||||
break;
|
break;
|
||||||
case 'update-setting':
|
case 'update-setting':
|
||||||
setting.value = data.data;
|
setting.value = data.data;
|
||||||
|
console.log('[DanmakuWindow] 设置已更新:', data.data);
|
||||||
|
// Adjust scroll on setting change if needed (e.g., reverseOrder changes)
|
||||||
|
nextTick(() => {
|
||||||
|
const el = scrollContainerRef.value;
|
||||||
|
if (el) {
|
||||||
|
if (setting.value?.reverseOrder) {
|
||||||
|
el.scrollTop = 0;
|
||||||
|
} else {
|
||||||
|
el.scrollTop = el.scrollHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -143,6 +193,7 @@ function formatMessage(item: EventModel): string {
|
|||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<TransitionGroup
|
<TransitionGroup
|
||||||
|
ref="scrollContainerRef"
|
||||||
:class="['danmaku-list', {'reverse': setting?.reverseOrder}]"
|
:class="['danmaku-list', {'reverse': setting?.reverseOrder}]"
|
||||||
name="danmaku-list"
|
name="danmaku-list"
|
||||||
tag="div"
|
tag="div"
|
||||||
@@ -158,6 +209,7 @@ function formatMessage(item: EventModel): string {
|
|||||||
<div
|
<div
|
||||||
v-for="(item, index) in danmakuList"
|
v-for="(item, index) in danmakuList"
|
||||||
:key="`${item.time}-${index}`"
|
:key="`${item.time}-${index}`"
|
||||||
|
:data-type="item.type"
|
||||||
class="danmaku-item"
|
class="danmaku-item"
|
||||||
:style="{
|
:style="{
|
||||||
marginBottom: `${setting?.itemSpacing || 5}px`,
|
marginBottom: `${setting?.itemSpacing || 5}px`,
|
||||||
@@ -235,7 +287,11 @@ function formatMessage(item: EventModel): string {
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style>
|
||||||
|
html, body{
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
.danmaku-list-enter-active,
|
.danmaku-list-enter-active,
|
||||||
.danmaku-list-leave-active {
|
.danmaku-list-leave-active {
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
@@ -283,6 +339,10 @@ function formatMessage(item: EventModel): string {
|
|||||||
color: #9d78c1;
|
color: #9d78c1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.danmaku-item[data-type="3"] { /* Guard */
|
||||||
|
color: #9d78c1;
|
||||||
|
}
|
||||||
|
|
||||||
.danmaku-item[data-type="4"] { /* Enter */
|
.danmaku-item[data-type="4"] { /* Enter */
|
||||||
color: #4caf50;
|
color: #4caf50;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,12 @@ async function testNotification() {
|
|||||||
>
|
>
|
||||||
测试通知
|
测试通知
|
||||||
</NButton>
|
</NButton>
|
||||||
|
<NButton
|
||||||
|
type="primary"
|
||||||
|
@click="$router.push({ name: 'client-danmaku-window-manage'})"
|
||||||
|
>
|
||||||
|
弹幕机
|
||||||
|
</NButton>
|
||||||
<LabelItem label="关闭弹幕客户端">
|
<LabelItem label="关闭弹幕客户端">
|
||||||
<NSwitch
|
<NSwitch
|
||||||
v-model:value="setting.settings.dev_disableDanmakuClient"
|
v-model:value="setting.settings.dev_disableDanmakuClient"
|
||||||
|
|||||||
@@ -58,33 +58,17 @@ const presets = {
|
|||||||
// 应用预设
|
// 应用预设
|
||||||
function applyPreset(preset: 'dark' | 'light' | 'transparent') {
|
function applyPreset(preset: 'dark' | 'light' | 'transparent') {
|
||||||
const presetData = presets[preset];
|
const presetData = presets[preset];
|
||||||
danmakuWindow.updateSetting('backgroundColor', presetData.backgroundColor);
|
danmakuWindow.danmakuWindowSetting.backgroundColor = presetData.backgroundColor;
|
||||||
danmakuWindow.updateSetting('textColor', presetData.textColor);
|
danmakuWindow.danmakuWindowSetting.textColor = presetData.textColor;
|
||||||
danmakuWindow.updateSetting('shadowColor', presetData.shadowColor);
|
danmakuWindow.danmakuWindowSetting.shadowColor = presetData.shadowColor;
|
||||||
message.success(`已应用${preset === 'dark' ? '暗黑' : preset === 'light' ? '明亮' : '透明'}主题预设`);
|
message.success(`已应用${preset === 'dark' ? '暗黑' : preset === 'light' ? '明亮' : '透明'}主题预设`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重置位置到屏幕中央
|
// 重置位置到屏幕中央
|
||||||
async function resetPosition() {
|
async function resetPosition() {
|
||||||
// 假设屏幕尺寸为 1920x1080,将窗口居中
|
danmakuWindow.setDanmakuWindowPosition(0, 0);
|
||||||
const width = danmakuWindow.danmakuWindowSetting.width;
|
|
||||||
const height = danmakuWindow.danmakuWindowSetting.height;
|
|
||||||
|
|
||||||
// 计算居中位置
|
|
||||||
const x = Math.floor((1920 - width) / 2);
|
|
||||||
const y = Math.floor((1080 - height) / 2);
|
|
||||||
|
|
||||||
danmakuWindow.setDanmakuWindowPosition(x, y);
|
|
||||||
message.success('窗口位置已重置');
|
message.success('窗口位置已重置');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新设置,包装了updateSetting方法
|
|
||||||
function updateSetting<K extends keyof typeof danmakuWindow.danmakuWindowSetting>(
|
|
||||||
key: K,
|
|
||||||
value: typeof danmakuWindow.danmakuWindowSetting[K]
|
|
||||||
) {
|
|
||||||
danmakuWindow.updateSetting(key, value);
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -95,7 +79,7 @@ function updateSetting<K extends keyof typeof danmakuWindow.danmakuWindowSetting
|
|||||||
<template #header-extra>
|
<template #header-extra>
|
||||||
<NButton
|
<NButton
|
||||||
:type="danmakuWindow.isDanmakuWindowOpen ? 'warning' : 'primary'"
|
:type="danmakuWindow.isDanmakuWindowOpen ? 'warning' : 'primary'"
|
||||||
@click="danmakuWindow.isDanmakuWindowOpen ? danmakuWindow.closeWindow() : danmakuWindow.createWindow()"
|
@click="danmakuWindow.isDanmakuWindowOpen ? danmakuWindow.closeWindow() : danmakuWindow.openWindow()"
|
||||||
>
|
>
|
||||||
{{ danmakuWindow.isDanmakuWindowOpen ? '关闭弹幕窗口' : '打开弹幕窗口' }}
|
{{ danmakuWindow.isDanmakuWindowOpen ? '关闭弹幕窗口' : '打开弹幕窗口' }}
|
||||||
</NButton>
|
</NButton>
|
||||||
@@ -136,7 +120,7 @@ function updateSetting<K extends keyof typeof danmakuWindow.danmakuWindowSetting
|
|||||||
v-model:value="danmakuWindow.danmakuWindowSetting.width"
|
v-model:value="danmakuWindow.danmakuWindowSetting.width"
|
||||||
:min="200"
|
:min="200"
|
||||||
:max="2000"
|
:max="2000"
|
||||||
@update:value="val => updateSetting('width', val || 400)"
|
@update:value="(value) => danmakuWindow.setDanmakuWindowSize(value as number, danmakuWindow.danmakuWindowSetting.height)"
|
||||||
/>
|
/>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
</NGi>
|
</NGi>
|
||||||
@@ -146,7 +130,7 @@ function updateSetting<K extends keyof typeof danmakuWindow.danmakuWindowSetting
|
|||||||
v-model:value="danmakuWindow.danmakuWindowSetting.height"
|
v-model:value="danmakuWindow.danmakuWindowSetting.height"
|
||||||
:min="200"
|
:min="200"
|
||||||
:max="2000"
|
:max="2000"
|
||||||
@update:value="val => updateSetting('height', val || 600)"
|
@update:value="(value) => danmakuWindow.setDanmakuWindowSize(danmakuWindow.danmakuWindowSetting.width, value as number)"
|
||||||
/>
|
/>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
</NGi>
|
</NGi>
|
||||||
@@ -155,7 +139,7 @@ function updateSetting<K extends keyof typeof danmakuWindow.danmakuWindowSetting
|
|||||||
<NInputNumber
|
<NInputNumber
|
||||||
v-model:value="danmakuWindow.danmakuWindowSetting.x"
|
v-model:value="danmakuWindow.danmakuWindowSetting.x"
|
||||||
:min="0"
|
:min="0"
|
||||||
@update:value="val => updateSetting('x', val || 0)"
|
@update:value="() => danmakuWindow.updateWindowPosition()"
|
||||||
/>
|
/>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
</NGi>
|
</NGi>
|
||||||
@@ -164,7 +148,7 @@ function updateSetting<K extends keyof typeof danmakuWindow.danmakuWindowSetting
|
|||||||
<NInputNumber
|
<NInputNumber
|
||||||
v-model:value="danmakuWindow.danmakuWindowSetting.y"
|
v-model:value="danmakuWindow.danmakuWindowSetting.y"
|
||||||
:min="0"
|
:min="0"
|
||||||
@update:value="val => updateSetting('y', val || 0)"
|
@update:value="() => danmakuWindow.updateWindowPosition()"
|
||||||
/>
|
/>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
</NGi>
|
</NGi>
|
||||||
@@ -188,13 +172,11 @@ function updateSetting<K extends keyof typeof danmakuWindow.danmakuWindowSetting
|
|||||||
<NFormItem label="总是置顶">
|
<NFormItem label="总是置顶">
|
||||||
<NSwitch
|
<NSwitch
|
||||||
v-model:value="danmakuWindow.danmakuWindowSetting.alwaysOnTop"
|
v-model:value="danmakuWindow.danmakuWindowSetting.alwaysOnTop"
|
||||||
@update:value="val => updateSetting('alwaysOnTop', val)"
|
|
||||||
/>
|
/>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
<NFormItem label="鼠标穿透">
|
<NFormItem label="鼠标穿透">
|
||||||
<NSwitch
|
<NSwitch
|
||||||
v-model:value="danmakuWindow.danmakuWindowSetting.interactive"
|
v-model:value="danmakuWindow.danmakuWindowSetting.interactive"
|
||||||
@update:value="val => updateSetting('interactive', val)"
|
|
||||||
/>
|
/>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
@@ -217,7 +199,6 @@ function updateSetting<K extends keyof typeof danmakuWindow.danmakuWindowSetting
|
|||||||
<NColorPicker
|
<NColorPicker
|
||||||
v-model:value="danmakuWindow.danmakuWindowSetting.backgroundColor"
|
v-model:value="danmakuWindow.danmakuWindowSetting.backgroundColor"
|
||||||
:show-alpha="true"
|
:show-alpha="true"
|
||||||
@update:value="val => updateSetting('backgroundColor', val)"
|
|
||||||
/>
|
/>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
</NGi>
|
</NGi>
|
||||||
@@ -226,7 +207,6 @@ function updateSetting<K extends keyof typeof danmakuWindow.danmakuWindowSetting
|
|||||||
<NColorPicker
|
<NColorPicker
|
||||||
v-model:value="danmakuWindow.danmakuWindowSetting.textColor"
|
v-model:value="danmakuWindow.danmakuWindowSetting.textColor"
|
||||||
:show-alpha="true"
|
:show-alpha="true"
|
||||||
@update:value="val => updateSetting('textColor', val)"
|
|
||||||
/>
|
/>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
</NGi>
|
</NGi>
|
||||||
@@ -237,7 +217,6 @@ function updateSetting<K extends keyof typeof danmakuWindow.danmakuWindowSetting
|
|||||||
:min="0.1"
|
:min="0.1"
|
||||||
:max="1"
|
:max="1"
|
||||||
:step="0.05"
|
:step="0.05"
|
||||||
@update:value="val => updateSetting('opacity', val)"
|
|
||||||
/>
|
/>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
</NGi>
|
</NGi>
|
||||||
@@ -248,7 +227,6 @@ function updateSetting<K extends keyof typeof danmakuWindow.danmakuWindowSetting
|
|||||||
:min="10"
|
:min="10"
|
||||||
:max="24"
|
:max="24"
|
||||||
:step="1"
|
:step="1"
|
||||||
@update:value="val => updateSetting('fontSize', val)"
|
|
||||||
/>
|
/>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
</NGi>
|
</NGi>
|
||||||
@@ -259,7 +237,6 @@ function updateSetting<K extends keyof typeof danmakuWindow.danmakuWindowSetting
|
|||||||
:min="0"
|
:min="0"
|
||||||
:max="20"
|
:max="20"
|
||||||
:step="1"
|
:step="1"
|
||||||
@update:value="val => updateSetting('borderRadius', val)"
|
|
||||||
/>
|
/>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
</NGi>
|
</NGi>
|
||||||
@@ -270,7 +247,6 @@ function updateSetting<K extends keyof typeof danmakuWindow.danmakuWindowSetting
|
|||||||
:min="0"
|
:min="0"
|
||||||
:max="20"
|
:max="20"
|
||||||
:step="1"
|
:step="1"
|
||||||
@update:value="val => updateSetting('itemSpacing', val)"
|
|
||||||
/>
|
/>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
</NGi>
|
</NGi>
|
||||||
@@ -284,7 +260,6 @@ function updateSetting<K extends keyof typeof danmakuWindow.danmakuWindowSetting
|
|||||||
<NFormItem label="启用阴影">
|
<NFormItem label="启用阴影">
|
||||||
<NSwitch
|
<NSwitch
|
||||||
v-model:value="danmakuWindow.danmakuWindowSetting.enableShadow"
|
v-model:value="danmakuWindow.danmakuWindowSetting.enableShadow"
|
||||||
@update:value="val => updateSetting('enableShadow', val)"
|
|
||||||
/>
|
/>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
<NFormItem
|
<NFormItem
|
||||||
@@ -294,7 +269,6 @@ function updateSetting<K extends keyof typeof danmakuWindow.danmakuWindowSetting
|
|||||||
<NColorPicker
|
<NColorPicker
|
||||||
v-model:value="danmakuWindow.danmakuWindowSetting.shadowColor"
|
v-model:value="danmakuWindow.danmakuWindowSetting.shadowColor"
|
||||||
:show-alpha="true"
|
:show-alpha="true"
|
||||||
@update:value="val => updateSetting('shadowColor', val)"
|
|
||||||
/>
|
/>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
@@ -331,15 +305,6 @@ function updateSetting<K extends keyof typeof danmakuWindow.danmakuWindowSetting
|
|||||||
:key="option.value"
|
:key="option.value"
|
||||||
:value="option.value"
|
:value="option.value"
|
||||||
:label="option.label"
|
:label="option.label"
|
||||||
@update:checked="val => {
|
|
||||||
let types = [...danmakuWindow.danmakuWindowSetting.filterTypes];
|
|
||||||
if (val) {
|
|
||||||
if (!types.includes(option.value)) types.push(option.value);
|
|
||||||
} else {
|
|
||||||
types = types.filter(t => t !== option.value);
|
|
||||||
}
|
|
||||||
updateSetting('filterTypes', types);
|
|
||||||
}"
|
|
||||||
/>
|
/>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
</NCheckboxGroup>
|
</NCheckboxGroup>
|
||||||
@@ -351,25 +316,21 @@ function updateSetting<K extends keyof typeof danmakuWindow.danmakuWindowSetting
|
|||||||
<NSpace>
|
<NSpace>
|
||||||
<NCheckbox
|
<NCheckbox
|
||||||
:checked="danmakuWindow.danmakuWindowSetting.showAvatar"
|
:checked="danmakuWindow.danmakuWindowSetting.showAvatar"
|
||||||
@update:checked="val => updateSetting('showAvatar', val)"
|
|
||||||
>
|
>
|
||||||
显示头像
|
显示头像
|
||||||
</NCheckbox>
|
</NCheckbox>
|
||||||
<NCheckbox
|
<NCheckbox
|
||||||
:checked="danmakuWindow.danmakuWindowSetting.showUsername"
|
:checked="danmakuWindow.danmakuWindowSetting.showUsername"
|
||||||
@update:checked="val => updateSetting('showUsername', val)"
|
|
||||||
>
|
>
|
||||||
显示用户名
|
显示用户名
|
||||||
</NCheckbox>
|
</NCheckbox>
|
||||||
<NCheckbox
|
<NCheckbox
|
||||||
:checked="danmakuWindow.danmakuWindowSetting.showFansMedal"
|
:checked="danmakuWindow.danmakuWindowSetting.showFansMedal"
|
||||||
@update:checked="val => updateSetting('showFansMedal', val)"
|
|
||||||
>
|
>
|
||||||
显示粉丝牌
|
显示粉丝牌
|
||||||
</NCheckbox>
|
</NCheckbox>
|
||||||
<NCheckbox
|
<NCheckbox
|
||||||
:checked="danmakuWindow.danmakuWindowSetting.showGuardIcon"
|
:checked="danmakuWindow.danmakuWindowSetting.showGuardIcon"
|
||||||
@update:checked="val => updateSetting('showGuardIcon', val)"
|
|
||||||
>
|
>
|
||||||
显示舰长图标
|
显示舰长图标
|
||||||
</NCheckbox>
|
</NCheckbox>
|
||||||
@@ -383,7 +344,6 @@ function updateSetting<K extends keyof typeof danmakuWindow.danmakuWindowSetting
|
|||||||
<NText>从上往下</NText>
|
<NText>从上往下</NText>
|
||||||
<NSwitch
|
<NSwitch
|
||||||
v-model:value="danmakuWindow.danmakuWindowSetting.reverseOrder"
|
v-model:value="danmakuWindow.danmakuWindowSetting.reverseOrder"
|
||||||
@update:value="val => updateSetting('reverseOrder', val)"
|
|
||||||
/>
|
/>
|
||||||
<NText>从下往上</NText>
|
<NText>从下往上</NText>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
@@ -396,7 +356,6 @@ function updateSetting<K extends keyof typeof danmakuWindow.danmakuWindowSetting
|
|||||||
v-model:value="danmakuWindow.danmakuWindowSetting.maxDanmakuCount"
|
v-model:value="danmakuWindow.danmakuWindowSetting.maxDanmakuCount"
|
||||||
:min="10"
|
:min="10"
|
||||||
:max="200"
|
:max="200"
|
||||||
@update:value="val => updateSetting('maxDanmakuCount', val || 50)"
|
|
||||||
/>
|
/>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
</NGi>
|
</NGi>
|
||||||
@@ -408,7 +367,6 @@ function updateSetting<K extends keyof typeof danmakuWindow.danmakuWindowSetting
|
|||||||
:min="0"
|
:min="0"
|
||||||
:max="1000"
|
:max="1000"
|
||||||
:step="50"
|
:step="50"
|
||||||
@update:value="val => updateSetting('animationDuration', val || 300)"
|
|
||||||
>
|
>
|
||||||
<template #suffix>
|
<template #suffix>
|
||||||
ms
|
ms
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
import { EventDataTypes, EventModel } from "@/api/api-models";
|
import { EventDataTypes, EventModel } from "@/api/api-models";
|
||||||
import { CURRENT_HOST } from "@/data/constants";
|
|
||||||
import { useDanmakuClient } from "@/store/useDanmakuClient";
|
import { useDanmakuClient } from "@/store/useDanmakuClient";
|
||||||
import { useWebFetcher } from "@/store/useWebFetcher";
|
|
||||||
import { PhysicalPosition, PhysicalSize } from "@tauri-apps/api/dpi";
|
import { PhysicalPosition, PhysicalSize } from "@tauri-apps/api/dpi";
|
||||||
import { Webview } from "@tauri-apps/api/webview";
|
import { getAllWebviewWindows, WebviewWindow } from "@tauri-apps/api/webviewWindow";
|
||||||
import { WebviewWindow } from "@tauri-apps/api/webviewWindow";
|
|
||||||
import { Window } from "@tauri-apps/api/window";
|
|
||||||
|
|
||||||
export type DanmakuWindowSettings = {
|
export type DanmakuWindowSettings = {
|
||||||
width: number;
|
width: number;
|
||||||
@@ -39,10 +35,12 @@ export type DanmakuWindowBCData = {
|
|||||||
} | {
|
} | {
|
||||||
type: 'update-setting',
|
type: 'update-setting',
|
||||||
data: DanmakuWindowSettings;
|
data: DanmakuWindowSettings;
|
||||||
|
} | {
|
||||||
|
type: 'window-ready';
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useDanmakuWindow = defineStore('danmakuWindow', () => {
|
export const useDanmakuWindow = defineStore('danmakuWindow', () => {
|
||||||
const danmakuWindow = ref<Webview>();
|
const danmakuWindow = ref<WebviewWindow>();
|
||||||
const danmakuWindowSetting = useStorage<DanmakuWindowSettings>('Setting.DanmakuWindow', {
|
const danmakuWindowSetting = useStorage<DanmakuWindowSettings>('Setting.DanmakuWindow', {
|
||||||
width: 400,
|
width: 400,
|
||||||
height: 600,
|
height: 600,
|
||||||
@@ -68,15 +66,19 @@ export const useDanmakuWindow = defineStore('danmakuWindow', () => {
|
|||||||
shadowColor: 'rgba(0,0,0,0.5)'
|
shadowColor: 'rgba(0,0,0,0.5)'
|
||||||
});
|
});
|
||||||
const danmakuClient = useDanmakuClient();
|
const danmakuClient = useDanmakuClient();
|
||||||
|
const isWindowOpened = ref(false);
|
||||||
let bc: BroadcastChannel | undefined = undefined;
|
let bc: BroadcastChannel | undefined = undefined;
|
||||||
|
|
||||||
function hideWindow() {
|
|
||||||
danmakuWindow.value?.window.hide();
|
|
||||||
danmakuWindow.value = undefined;
|
|
||||||
}
|
|
||||||
function closeWindow() {
|
function closeWindow() {
|
||||||
danmakuWindow.value?.close();
|
danmakuWindow.value?.hide();
|
||||||
danmakuWindow.value = undefined;
|
isWindowOpened.value = false;
|
||||||
|
}
|
||||||
|
function openWindow() {
|
||||||
|
if (!isInited) {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
danmakuWindow.value?.show();
|
||||||
|
isWindowOpened.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setDanmakuWindowSize(width: number, height: number) {
|
function setDanmakuWindowSize(width: number, height: number) {
|
||||||
@@ -90,47 +92,58 @@ export const useDanmakuWindow = defineStore('danmakuWindow', () => {
|
|||||||
danmakuWindowSetting.value.y = y;
|
danmakuWindowSetting.value.y = y;
|
||||||
danmakuWindow.value?.setPosition(new PhysicalPosition(x, y));
|
danmakuWindow.value?.setPosition(new PhysicalPosition(x, y));
|
||||||
}
|
}
|
||||||
|
function updateWindowPosition() {
|
||||||
function updateSetting<K extends keyof DanmakuWindowSettings>(key: K, value: DanmakuWindowSettings[K]) {
|
danmakuWindow.value?.setPosition(new PhysicalPosition(danmakuWindowSetting.value.x, danmakuWindowSetting.value.y));
|
||||||
danmakuWindowSetting.value[key] = value;
|
|
||||||
// 特定设置需要直接应用到窗口
|
|
||||||
if (key === 'alwaysOnTop' && danmakuWindow.value) {
|
|
||||||
danmakuWindow.value.window.setAlwaysOnTop(value as boolean);
|
|
||||||
}
|
}
|
||||||
if (key === 'interactive' && danmakuWindow.value) {
|
let isInited = false;
|
||||||
danmakuWindow.value.window.setIgnoreCursorEvents(value as boolean);
|
|
||||||
|
async function init() {
|
||||||
|
if (isInited) return;
|
||||||
|
danmakuWindow.value = (await getAllWebviewWindows()).find((win) => win.label === 'danmaku-window');
|
||||||
|
if (!danmakuWindow.value) {
|
||||||
|
window.$message.error('弹幕窗口不存在,请先打开弹幕窗口。');
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
console.log('打开弹幕窗口', danmakuWindow.value.label, danmakuWindowSetting.value);
|
||||||
|
danmakuWindow.value.onCloseRequested(() => {
|
||||||
async function createWindow() {
|
|
||||||
const appWindow = new Window('uniqueLabel', {
|
|
||||||
decorations: true,
|
|
||||||
resizable: true,
|
|
||||||
transparent: true,
|
|
||||||
fullscreen: false,
|
|
||||||
alwaysOnTop: danmakuWindowSetting.value.alwaysOnTop,
|
|
||||||
title: "VTsuru 弹幕窗口",
|
|
||||||
});
|
|
||||||
|
|
||||||
// loading embedded asset:
|
|
||||||
danmakuWindow.value = new Webview(appWindow, 'theUniqueLabel', {
|
|
||||||
url: `${CURRENT_HOST}/client/danaku-window-manage`,
|
|
||||||
width: danmakuWindowSetting.value.width,
|
|
||||||
height: danmakuWindowSetting.value.height,
|
|
||||||
x: danmakuWindowSetting.value.x,
|
|
||||||
y: danmakuWindowSetting.value.y,
|
|
||||||
});
|
|
||||||
|
|
||||||
appWindow.onCloseRequested(() => {
|
|
||||||
danmakuWindow.value = undefined;
|
danmakuWindow.value = undefined;
|
||||||
bc?.close();
|
bc?.close();
|
||||||
bc = undefined;
|
bc = undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
danmakuWindow.value.once('tauri://window-created', async () => {
|
await danmakuWindow.value.setIgnoreCursorEvents(false);
|
||||||
console.log('弹幕窗口已创建');
|
await danmakuWindow.value.show();
|
||||||
await danmakuWindow.value?.window.setIgnoreCursorEvents(true);
|
danmakuWindow.value.onCloseRequested(() => {
|
||||||
|
closeWindow();
|
||||||
|
console.log('弹幕窗口关闭');
|
||||||
});
|
});
|
||||||
|
danmakuWindow.value.onMoved(({
|
||||||
|
payload: position
|
||||||
|
}) => {
|
||||||
|
danmakuWindowSetting.value.x = position.x;
|
||||||
|
danmakuWindowSetting.value.y = position.y;
|
||||||
|
});
|
||||||
|
|
||||||
|
isWindowOpened.value = true;
|
||||||
|
|
||||||
|
bc = new BroadcastChannel(DANMAKU_WINDOW_BROADCAST_CHANNEL);
|
||||||
|
bc.onmessage = (event: MessageEvent<DanmakuWindowBCData>) => {
|
||||||
|
if (event.data.type === 'window-ready') {
|
||||||
|
console.log(`[danmaku-window] 窗口已就绪`);
|
||||||
|
bc?.postMessage({
|
||||||
|
type: 'update-setting',
|
||||||
|
data: toRaw(danmakuWindowSetting.value),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
bc.postMessage({
|
||||||
|
type: 'window-ready',
|
||||||
|
});
|
||||||
|
bc.postMessage({
|
||||||
|
type: 'update-setting',
|
||||||
|
data: toRaw(danmakuWindowSetting.value),
|
||||||
|
});
|
||||||
|
|
||||||
bc?.postMessage({
|
bc?.postMessage({
|
||||||
type: 'danmaku',
|
type: 'danmaku',
|
||||||
data: {
|
data: {
|
||||||
@@ -139,16 +152,34 @@ export const useDanmakuWindow = defineStore('danmakuWindow', () => {
|
|||||||
} as Partial<EventModel>,
|
} as Partial<EventModel>,
|
||||||
});
|
});
|
||||||
|
|
||||||
bc = new BroadcastChannel(DANMAKU_WINDOW_BROADCAST_CHANNEL);
|
|
||||||
|
|
||||||
if (danmakuClient.connected) {
|
|
||||||
danmakuClient.onEvent('danmaku', (event) => onGetDanmakus(event));
|
danmakuClient.onEvent('danmaku', (event) => onGetDanmakus(event));
|
||||||
danmakuClient.onEvent('gift', (event) => onGetDanmakus(event));
|
danmakuClient.onEvent('gift', (event) => onGetDanmakus(event));
|
||||||
danmakuClient.onEvent('sc', (event) => onGetDanmakus(event));
|
danmakuClient.onEvent('sc', (event) => onGetDanmakus(event));
|
||||||
danmakuClient.onEvent('guard', (event) => onGetDanmakus(event));
|
danmakuClient.onEvent('guard', (event) => onGetDanmakus(event));
|
||||||
danmakuClient.onEvent('enter', (event) => onGetDanmakus(event));
|
danmakuClient.onEvent('enter', (event) => onGetDanmakus(event));
|
||||||
danmakuClient.onEvent('scDel', (event) => onGetDanmakus(event));
|
danmakuClient.onEvent('scDel', (event) => onGetDanmakus(event));
|
||||||
|
|
||||||
|
watch(() => danmakuWindowSetting, async (newValue) => {
|
||||||
|
if (danmakuWindow.value) {
|
||||||
|
bc?.postMessage({
|
||||||
|
type: 'update-setting',
|
||||||
|
data: toRaw(newValue.value),
|
||||||
|
});
|
||||||
|
if (newValue.value.alwaysOnTop) {
|
||||||
|
await danmakuWindow.value.setAlwaysOnTop(true);
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
await danmakuWindow.value.setAlwaysOnTop(false);
|
||||||
|
}
|
||||||
|
if (newValue.value.interactive) {
|
||||||
|
await danmakuWindow.value.setIgnoreCursorEvents(true);
|
||||||
|
} else {
|
||||||
|
await danmakuWindow.value.setIgnoreCursorEvents(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, { deep: true });
|
||||||
|
|
||||||
|
isInited = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function onGetDanmakus(data: EventModel) {
|
function onGetDanmakus(data: EventModel) {
|
||||||
@@ -158,29 +189,17 @@ export const useDanmakuWindow = defineStore('danmakuWindow', () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(danmakuWindowSetting, async (newValue) => {
|
|
||||||
if (danmakuWindow.value) {
|
|
||||||
if (await danmakuWindow.value.window.isVisible()) {
|
|
||||||
danmakuWindow.value.setSize(new PhysicalSize(newValue.width, newValue.height));
|
|
||||||
danmakuWindow.value.setPosition(new PhysicalPosition(newValue.x, newValue.y));
|
|
||||||
}
|
|
||||||
bc?.postMessage({
|
|
||||||
type: 'update-setting',
|
|
||||||
data: newValue,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, { deep: true });
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
danmakuWindow,
|
danmakuWindow,
|
||||||
danmakuWindowSetting,
|
danmakuWindowSetting,
|
||||||
setDanmakuWindowSize,
|
setDanmakuWindowSize,
|
||||||
setDanmakuWindowPosition,
|
setDanmakuWindowPosition,
|
||||||
updateSetting,
|
updateWindowPosition,
|
||||||
isDanmakuWindowOpen: computed(() => !!danmakuWindow.value),
|
isDanmakuWindowOpen: isWindowOpened,
|
||||||
createWindow,
|
openWindow,
|
||||||
closeWindow,
|
closeWindow,
|
||||||
hideWindow
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ export default abstract class BaseDanmakuClient {
|
|||||||
const result = await this.initClient();
|
const result = await this.initClient();
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
this.state = 'connected';
|
this.state = 'connected';
|
||||||
console.log(`[${this.type}] 弹幕客户端启动成功`);
|
console.log(`[${this.type}] 弹幕客户端已完成启动`);
|
||||||
} else {
|
} else {
|
||||||
this.state = 'disconnected';
|
this.state = 'disconnected';
|
||||||
console.error(`[${this.type}] 弹幕客户端启动失败: ${result.message}`);
|
console.error(`[${this.type}] 弹幕客户端启动失败: ${result.message}`);
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export default class DirectClient extends BaseDanmakuClient {
|
|||||||
});
|
});
|
||||||
|
|
||||||
chatClient.on('live', () => {
|
chatClient.on('live', () => {
|
||||||
console.log('[DIRECT] 已连接房间: ' + this.authInfo.roomId);
|
console.log('[direct] 已连接房间: ' + this.authInfo.roomId);
|
||||||
});
|
});
|
||||||
chatClient.on('DANMU_MSG', (data) => this.onDanmaku(data));
|
chatClient.on('DANMU_MSG', (data) => this.onDanmaku(data));
|
||||||
chatClient.on('SEND_GIFT', (data) => this.onGift(data));
|
chatClient.on('SEND_GIFT', (data) => this.onGift(data));
|
||||||
@@ -45,7 +45,7 @@ export default class DirectClient extends BaseDanmakuClient {
|
|||||||
|
|
||||||
return await super.initClientInner(chatClient);
|
return await super.initClientInner(chatClient);
|
||||||
} else {
|
} else {
|
||||||
console.log('[DIRECT] 无法开启场次, 未提供弹幕客户端认证信息');
|
console.log('[direct] 无法开启场次, 未提供弹幕客户端认证信息');
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: '未提供弹幕客户端认证信息'
|
message: '未提供弹幕客户端认证信息'
|
||||||
|
|||||||
@@ -34,6 +34,13 @@ export default {
|
|||||||
title: '弹幕窗口管理',
|
title: '弹幕窗口管理',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'danmaku-window',
|
||||||
|
name: 'client-danmaku-window-redirect',
|
||||||
|
redirect: {
|
||||||
|
name: 'client-danmaku-window'
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'test',
|
path: 'test',
|
||||||
name: 'client-test',
|
name: 'client-test',
|
||||||
|
|||||||
@@ -17,10 +17,11 @@ export default [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/danmaku-window',
|
path: '/danmaku-window',
|
||||||
name: 'client-danmaku-client',
|
name: 'client-danmaku-window',
|
||||||
component: () => import('@/client/ClientDanmakuWindow.vue'),
|
component: () => import('@/client/ClientDanmakuWindow.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
title: '弹幕窗口'
|
title: '弹幕窗口',
|
||||||
|
ignoreLogin: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ type GenericListener = Listener | AllEventListener;
|
|||||||
|
|
||||||
export const useDanmakuClient = defineStore('DanmakuClient', () => {
|
export const useDanmakuClient = defineStore('DanmakuClient', () => {
|
||||||
// 使用 shallowRef 存储 danmakuClient 实例, 性能稍好
|
// 使用 shallowRef 存储 danmakuClient 实例, 性能稍好
|
||||||
const danmakuClient = shallowRef<BaseDanmakuClient>();
|
const danmakuClient = shallowRef<BaseDanmakuClient | undefined>(new OpenLiveClient()); // 默认实例化一个 OpenLiveClient
|
||||||
|
|
||||||
// 连接状态: 'waiting'-等待初始化, 'connecting'-连接中, 'connected'-已连接
|
// 连接状态: 'waiting'-等待初始化, 'connecting'-连接中, 'connected'-已连接
|
||||||
const state = ref<'waiting' | 'connecting' | 'connected'>('waiting');
|
const state = ref<'waiting' | 'connecting' | 'connected'>('waiting');
|
||||||
@@ -189,7 +189,9 @@ export const useDanmakuClient = defineStore('DanmakuClient', () => {
|
|||||||
// 先停止并清理旧客户端 (如果存在)
|
// 先停止并清理旧客户端 (如果存在)
|
||||||
if (danmakuClient.value) {
|
if (danmakuClient.value) {
|
||||||
console.log('[DanmakuClient] 正在处理旧的客户端实例...');
|
console.log('[DanmakuClient] 正在处理旧的客户端实例...');
|
||||||
|
if (danmakuClient.value.state === 'connected') {
|
||||||
await disposeClientInstance(danmakuClient.value);
|
await disposeClientInstance(danmakuClient.value);
|
||||||
|
}
|
||||||
danmakuClient.value = undefined; // 显式清除旧实例引用
|
danmakuClient.value = undefined; // 显式清除旧实例引用
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -186,6 +186,7 @@ export const useWebFetcher = defineStore('WebFetcher', () => {
|
|||||||
return { success: false, message: '未提供弹幕客户端认证信息' };
|
return { success: false, message: '未提供弹幕客户端认证信息' };
|
||||||
}
|
}
|
||||||
await client.initDirect(directConnectInfo);
|
await client.initDirect(directConnectInfo);
|
||||||
|
return { success: true, message: '弹幕客户端已启动' };
|
||||||
}
|
}
|
||||||
|
|
||||||
// 监听所有事件,用于处理和转发
|
// 监听所有事件,用于处理和转发
|
||||||
@@ -197,11 +198,14 @@ export const useWebFetcher = defineStore('WebFetcher', () => {
|
|||||||
danmakuServerUrl.value = client.danmakuClient!.serverUrl; // 获取服务器地址
|
danmakuServerUrl.value = client.danmakuClient!.serverUrl; // 获取服务器地址
|
||||||
// 启动事件发送定时器 (如果之前没有启动)
|
// 启动事件发送定时器 (如果之前没有启动)
|
||||||
timer ??= setInterval(sendEvents, 1500); // 每 1.5 秒尝试发送一次事件
|
timer ??= setInterval(sendEvents, 1500); // 每 1.5 秒尝试发送一次事件
|
||||||
|
return { success: true, message: '弹幕客户端已启动' };
|
||||||
} else {
|
} else {
|
||||||
console.error(prefix.value + '弹幕客户端启动失败');
|
console.error(prefix.value + '弹幕客户端启动失败');
|
||||||
danmakuClientState.value = 'stopped';
|
danmakuClientState.value = 'stopped';
|
||||||
danmakuServerUrl.value = undefined;
|
danmakuServerUrl.value = undefined;
|
||||||
client.dispose(); // 启动失败,清理实例,下次会重建
|
client.dispose(); // 启动失败,清理实例,下次会重建
|
||||||
|
return { success: false, message: '弹幕客户端启动失败' };
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,11 +269,11 @@ export const useWebFetcher = defineStore('WebFetcher', () => {
|
|||||||
// --- 尝试启动连接 ---
|
// --- 尝试启动连接 ---
|
||||||
try {
|
try {
|
||||||
await connection.start();
|
await connection.start();
|
||||||
console.log(prefix.value + '已连接到 vtsuru 服务器, ConnectionId: ' + connection.connectionId); // 调试输出连接状态
|
|
||||||
signalRConnectionId.value = connection.connectionId ?? undefined; // 保存连接ID
|
signalRConnectionId.value = connection.connectionId ?? undefined; // 保存连接ID
|
||||||
signalRId.value = await sendSelfInfo(connection); // 发送客户端信息
|
signalRId.value = await sendSelfInfo(connection); // 发送客户端信息
|
||||||
await connection.send('Finished'); // 通知服务器已准备好
|
await connection.send('Finished'); // 通知服务器已准备好
|
||||||
signalRClient.value = connection; // 保存实例
|
signalRClient.value = connection; // 保存实例
|
||||||
|
console.log(prefix.value + '已连接到 vtsuru 服务器, ConnectionId: ' + signalRId.value); // 调试输出连接状态
|
||||||
// state.value = 'connected'; // 状态将在 Start 函数末尾统一设置
|
// state.value = 'connected'; // 状态将在 Start 函数末尾统一设置
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -459,7 +459,6 @@
|
|||||||
<RouterView v-slot="{ Component }">
|
<RouterView v-slot="{ Component }">
|
||||||
<Transition
|
<Transition
|
||||||
name="fade-slide"
|
name="fade-slide"
|
||||||
mode="out-in"
|
|
||||||
:appear="true"
|
:appear="true"
|
||||||
>
|
>
|
||||||
<KeepAlive>
|
<KeepAlive>
|
||||||
|
|||||||
Reference in New Issue
Block a user