mirror of
https://github.com/Megghy/vtsuru.live.git
synced 2025-12-06 18:36:55 +08:00
Compare commits
3 Commits
b97081a870
...
b24974540f
| Author | SHA1 | Date | |
|---|---|---|---|
| b24974540f | |||
| f267592e37 | |||
| dd29a141de |
@@ -452,21 +452,8 @@ function confirmTest() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const stopWatch = watch(() => danmakuClient.state, (newState) => {
|
|
||||||
if (newState === 'connected') {
|
|
||||||
console.log('[ClientAutoAction] Danmaku client connected, initializing store...');
|
|
||||||
autoActionStore.init();
|
autoActionStore.init();
|
||||||
stopWatch();
|
|
||||||
} else if (newState !== 'connecting' && newState !== 'waiting') {
|
|
||||||
console.warn(`[ClientAutoAction] Danmaku client state is ${newState}, auto actions might not work.`);
|
|
||||||
}
|
|
||||||
}, { immediate: true });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
console.log('[ClientAutoAction] Unmounted.');
|
|
||||||
});
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// 导入 Vue 和 Pinia 相关函数
|
// 导入 Vue 和 Pinia 相关函数
|
||||||
import { ref, computed, onUnmounted, watch } from 'vue';
|
import { ref, computed, watch } from 'vue';
|
||||||
import { defineStore, acceptHMRUpdate } from 'pinia';
|
import { defineStore, acceptHMRUpdate } from 'pinia';
|
||||||
// 导入 API 模型和类型
|
// 导入 API 模型和类型
|
||||||
import { EventModel, GuardLevel, EventDataTypes } from '@/api/api-models.js';
|
import { EventModel, GuardLevel, EventDataTypes } from '@/api/api-models.js';
|
||||||
@@ -365,11 +365,15 @@ export const useAutoAction = defineStore('autoAction', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// --- 初始化与事件监听 ---
|
// --- 初始化与事件监听 ---
|
||||||
|
let isInited = false
|
||||||
/**
|
/**
|
||||||
* 初始化自动操作系统
|
* 初始化自动操作系统
|
||||||
*/
|
*/
|
||||||
function init() {
|
function init() {
|
||||||
|
if (isInited) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
isInited = true;
|
||||||
// 计算属性,判断所有持久化数据是否加载完成
|
// 计算属性,判断所有持久化数据是否加载完成
|
||||||
const allLoaded = computed(() => isActionsLoaded.value && isIntervalLoaded.value && isModeLoaded.value && isIndexLoaded.value && isTriggersLoaded.value);
|
const allLoaded = computed(() => isActionsLoaded.value && isIntervalLoaded.value && isModeLoaded.value && isIndexLoaded.value && isTriggersLoaded.value);
|
||||||
|
|
||||||
@@ -448,17 +452,6 @@ export const useAutoAction = defineStore('autoAction', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 组件卸载时清理资源
|
|
||||||
*/
|
|
||||||
onUnmounted(() => {
|
|
||||||
console.log('[AutoAction] 清理定时器和监听器.');
|
|
||||||
stopAllIndividualScheduledActions(); // 停止所有独立定时器
|
|
||||||
stopGlobalTimer(); // 停止全局定时器
|
|
||||||
clearInterval(tianXuanTimer); // 清除天选状态检查定时器
|
|
||||||
});
|
|
||||||
|
|
||||||
// --- 操作项管理 (增删改查) ---
|
// --- 操作项管理 (增删改查) ---
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -811,118 +804,71 @@ export const useAutoAction = defineStore('autoAction', () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建一个模拟事件用于测试
|
// 创建基础测试事件属性
|
||||||
let testEvent: EventModel;
|
const baseTestEvent: Partial<EventModel> = {
|
||||||
|
uid: testUid || 10000,
|
||||||
// 根据不同触发类型创建不同的模拟事件
|
|
||||||
switch (triggerType) {
|
|
||||||
case TriggerType.DANMAKU:
|
|
||||||
testEvent = {
|
|
||||||
type: EventDataTypes.Message,
|
|
||||||
uid: 10000,
|
|
||||||
uname: '测试用户',
|
uname: '测试用户',
|
||||||
uface: '',
|
uface: '',
|
||||||
open_id: 'test-open-id',
|
open_id: 'test-open-id',
|
||||||
ouid: 'test-ouid',
|
ouid: 'test-ouid',
|
||||||
msg: '测试弹幕消息',
|
|
||||||
time: Math.floor(Date.now() / 1000),
|
time: Math.floor(Date.now() / 1000),
|
||||||
num: 1,
|
num: 1,
|
||||||
price: 0,
|
price: 0,
|
||||||
guard_level: 3,
|
|
||||||
fans_medal_wearing_status: true,
|
|
||||||
fans_medal_name: '测试牌子',
|
|
||||||
fans_medal_level: 10
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
case TriggerType.GIFT:
|
|
||||||
testEvent = {
|
|
||||||
type: EventDataTypes.Gift,
|
|
||||||
uid: 10003,
|
|
||||||
uname: '测试送礼者',
|
|
||||||
uface: '',
|
|
||||||
open_id: 'test-open-id-gift',
|
|
||||||
ouid: 'test-ouid-gift',
|
|
||||||
msg: '感谢 测试送礼者 赠送的 测试礼物 x 1',
|
|
||||||
time: Math.floor(Date.now() / 1000),
|
|
||||||
num: 1,
|
|
||||||
price: 100,
|
|
||||||
guard_level: 1,
|
|
||||||
fans_medal_wearing_status: true,
|
|
||||||
fans_medal_name: '测试牌子',
|
|
||||||
fans_medal_level: 15
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
case TriggerType.GUARD:
|
|
||||||
testEvent = {
|
|
||||||
type: EventDataTypes.Guard,
|
|
||||||
uid: testUid || 10002,
|
|
||||||
uname: '测试大航海成员',
|
|
||||||
uface: '',
|
|
||||||
open_id: 'test-open-id-guard',
|
|
||||||
ouid: 'test-ouid-guard',
|
|
||||||
msg: '测试大航海成员 开通了舰长',
|
|
||||||
time: Math.floor(Date.now() / 1000),
|
|
||||||
num: 1,
|
|
||||||
price: 138,
|
|
||||||
guard_level: Math.floor(Math.random() * 3) + 1, // 1-3
|
guard_level: Math.floor(Math.random() * 3) + 1, // 1-3
|
||||||
fans_medal_wearing_status: true,
|
fans_medal_wearing_status: true,
|
||||||
fans_medal_name: '测试牌子',
|
fans_medal_name: '测试牌子',
|
||||||
fans_medal_level: 20
|
fans_medal_level: Math.floor(Math.random() * 30) + 1 // 1-10
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 根据不同触发类型创建不同的模拟事件
|
||||||
|
let testEvent: EventModel;
|
||||||
|
|
||||||
|
switch (triggerType) {
|
||||||
|
case TriggerType.DANMAKU:
|
||||||
|
testEvent = {
|
||||||
|
...baseTestEvent,
|
||||||
|
type: EventDataTypes.Message,
|
||||||
|
msg: '测试弹幕消息',
|
||||||
|
} as EventModel;
|
||||||
|
break;
|
||||||
|
case TriggerType.GIFT:
|
||||||
|
testEvent = {
|
||||||
|
...baseTestEvent,
|
||||||
|
type: EventDataTypes.Gift,
|
||||||
|
msg: '测试礼物',
|
||||||
|
price: 100,
|
||||||
|
num: 5
|
||||||
|
} as EventModel;
|
||||||
|
break;
|
||||||
|
case TriggerType.GUARD:
|
||||||
|
const level = Math.floor(Math.random() * 3) + 1; // 1-3
|
||||||
|
testEvent = {
|
||||||
|
...baseTestEvent,
|
||||||
|
type: EventDataTypes.Guard,
|
||||||
|
msg: '舰长',
|
||||||
|
price: level === 1 ? 19998 : level === 2 ? 1998 : 198,
|
||||||
|
guard_level: level, // 1-3
|
||||||
|
} as EventModel;
|
||||||
break;
|
break;
|
||||||
case TriggerType.FOLLOW:
|
case TriggerType.FOLLOW:
|
||||||
testEvent = {
|
testEvent = {
|
||||||
type: EventDataTypes.Message,
|
...baseTestEvent,
|
||||||
uid: 10004,
|
type: EventDataTypes.Follow,
|
||||||
uname: '测试关注者',
|
} as EventModel;
|
||||||
uface: '',
|
|
||||||
open_id: 'test-open-id-follow',
|
|
||||||
ouid: 'test-ouid-follow',
|
|
||||||
msg: '测试关注者 关注了直播间',
|
|
||||||
time: Math.floor(Date.now() / 1000),
|
|
||||||
num: 1,
|
|
||||||
price: 0,
|
|
||||||
guard_level: 0,
|
|
||||||
fans_medal_wearing_status: false,
|
|
||||||
fans_medal_name: '',
|
|
||||||
fans_medal_level: 0
|
|
||||||
};
|
|
||||||
break;
|
break;
|
||||||
case TriggerType.ENTER:
|
case TriggerType.ENTER:
|
||||||
testEvent = {
|
testEvent = {
|
||||||
|
...baseTestEvent,
|
||||||
type: EventDataTypes.Enter,
|
type: EventDataTypes.Enter,
|
||||||
uid: 10005,
|
} as EventModel;
|
||||||
uname: '测试入场观众',
|
|
||||||
uface: '',
|
|
||||||
open_id: 'test-open-id-enter',
|
|
||||||
ouid: 'test-ouid-enter',
|
|
||||||
msg: '测试入场观众 进入了直播间',
|
|
||||||
time: Math.floor(Date.now() / 1000),
|
|
||||||
num: 1,
|
|
||||||
price: 0,
|
|
||||||
guard_level: 2,
|
|
||||||
fans_medal_wearing_status: true,
|
|
||||||
fans_medal_name: '测试牌子',
|
|
||||||
fans_medal_level: 8
|
|
||||||
};
|
|
||||||
break;
|
break;
|
||||||
case TriggerType.SUPER_CHAT:
|
case TriggerType.SUPER_CHAT:
|
||||||
testEvent = {
|
testEvent = {
|
||||||
|
...baseTestEvent,
|
||||||
type: EventDataTypes.SC,
|
type: EventDataTypes.SC,
|
||||||
uid: 10006,
|
|
||||||
uname: '测试SC用户',
|
|
||||||
uface: '',
|
|
||||||
open_id: 'test-open-id-sc',
|
|
||||||
ouid: 'test-ouid-sc',
|
|
||||||
msg: '这是一条测试SC消息',
|
msg: '这是一条测试SC消息',
|
||||||
time: Math.floor(Date.now() / 1000),
|
price: Math.floor(Math.random() * 1000) + 10,
|
||||||
num: 1,
|
} as EventModel;
|
||||||
price: 30,
|
|
||||||
guard_level: 0,
|
|
||||||
fans_medal_wearing_status: true,
|
|
||||||
fans_medal_name: '测试牌子',
|
|
||||||
fans_medal_level: 25
|
|
||||||
};
|
|
||||||
break;
|
break;
|
||||||
case TriggerType.SCHEDULED:
|
case TriggerType.SCHEDULED:
|
||||||
// 对于定时任务,使用特殊的处理方式
|
// 对于定时任务,使用特殊的处理方式
|
||||||
|
|||||||
@@ -3,20 +3,20 @@ import { updateNoteItemContentType, updateNotes } from '@/data/UpdateNote';
|
|||||||
import { NDivider, NGrid } from 'naive-ui';
|
import { NDivider, NGrid } from 'naive-ui';
|
||||||
import { VNode } from 'vue';
|
import { VNode } from 'vue';
|
||||||
|
|
||||||
const currentVer = '1'
|
function renderContent(content: updateNoteItemContentType): VNode | string | undefined {
|
||||||
const savedVer = useStorage('UpdateNoteVer', 0)
|
|
||||||
function renderContent(content: updateNoteItemContentType): VNode | string | undefined {
|
|
||||||
if (Array.isArray(content)) {
|
if (Array.isArray(content)) {
|
||||||
return h('div', { style: { whiteSpace: 'pre-wrap' } }, content.map(item => renderContent(item)))
|
return h('div', { style: { whiteSpace: 'pre-wrap' } }, content.map(item => renderContent(item)))
|
||||||
}
|
}
|
||||||
if (typeof content === 'string') {
|
const getContent = (c: unknown) => {
|
||||||
return content
|
if (typeof c === 'string') {
|
||||||
|
return c
|
||||||
}
|
}
|
||||||
if (typeof content === 'function') {
|
if (typeof c === 'function') {
|
||||||
return content()
|
return c()
|
||||||
}
|
}
|
||||||
return undefined;
|
|
||||||
}
|
}
|
||||||
|
return h('span', { style: { whiteSpace: 'pre-wrap' } }, getContent(content))
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -32,9 +32,7 @@ import { VNode } from 'vue';
|
|||||||
<NDivider title-placement="left">
|
<NDivider title-placement="left">
|
||||||
{{ item.date }}
|
{{ item.date }}
|
||||||
</NDivider>
|
</NDivider>
|
||||||
<NGrid
|
<NGrid x-gap="10">
|
||||||
x-gap="10"
|
|
||||||
>
|
|
||||||
<template
|
<template
|
||||||
v-for="note in item.items"
|
v-for="note in item.items"
|
||||||
:key="note.title"
|
:key="note.title"
|
||||||
|
|||||||
@@ -4,6 +4,30 @@ import { VNode } from "vue";
|
|||||||
import { FETCH_API } from "./constants";
|
import { FETCH_API } from "./constants";
|
||||||
|
|
||||||
export const updateNotes: updateNoteType[] = [
|
export const updateNotes: updateNoteType[] = [
|
||||||
|
{
|
||||||
|
ver: 4,
|
||||||
|
date: '2025.4.22',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: 'new',
|
||||||
|
title: '添加自动操作功能',
|
||||||
|
content: [
|
||||||
|
[
|
||||||
|
'EventFetcher客户端新增多种自动操作类型支持,包括弹幕自动回复、礼物感谢、上舰发送私信、关注感谢、入场欢迎、定时发送和SC感谢等, 可以执行js代码',
|
||||||
|
() => h(NImage, { src: 'https://pan.suki.club/d/vtsuru/imgur/QQ20250422-221703.png', width: 300 }),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'使用模板系统,支持随机回复、自定义表达式和条件判断等\r\n',
|
||||||
|
'数据持久化存储,各类操作配置和运行状态不会丢失\r\n\r\n',
|
||||||
|
'发送弹幕和私信需要客户端扫码登录, 客户端安装方式:',
|
||||||
|
() => h(NButton, {
|
||||||
|
text: true, tag: 'a', href: 'https://www.wolai.com/carN6qvUm3FErze9Xo53ii', target: '_blank', type: 'info'
|
||||||
|
}, () => '查看介绍'),
|
||||||
|
]
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
ver: 3,
|
ver: 3,
|
||||||
date: '2025.4.15',
|
date: '2025.4.15',
|
||||||
|
|||||||
@@ -8,11 +8,13 @@ import {
|
|||||||
MoreHorizontal24Filled,
|
MoreHorizontal24Filled,
|
||||||
TabletSpeaker24Filled,
|
TabletSpeaker24Filled,
|
||||||
VehicleShip24Filled,
|
VehicleShip24Filled,
|
||||||
VideoAdd20Filled
|
VideoAdd20Filled,
|
||||||
|
Chat24Filled,
|
||||||
|
PersonFeedback24Filled
|
||||||
} from '@vicons/fluent'
|
} from '@vicons/fluent'
|
||||||
import { AnalyticsSharp, Calendar, Chatbox, ListCircle, MusicalNote } from '@vicons/ionicons5'
|
import { AnalyticsSharp, Calendar, Chatbox, ListCircle, MusicalNote } from '@vicons/ionicons5'
|
||||||
import { useWindowSize } from '@vueuse/core'
|
import { useWindowSize } from '@vueuse/core'
|
||||||
import { NButton, NDivider, NEllipsis, NFlex, NGradientText, NGrid, NGridItem, NIcon, NNumberAnimation, NSpace, NText, NTooltip } from 'naive-ui'
|
import { NButton, NDivider, NEllipsis, NFlex, NGradientText, NGrid, NGridItem, NIcon, NNumberAnimation, NSpace, NText, NTooltip, NAlert, NCard, NStatistic, NTag, NBadge } from 'naive-ui'
|
||||||
import { onMounted, ref } from 'vue'
|
import { onMounted, ref } from 'vue'
|
||||||
import vtb from '@/svgs/ic_vtuber.svg'
|
import vtb from '@/svgs/ic_vtuber.svg'
|
||||||
|
|
||||||
@@ -29,6 +31,11 @@ const functions = [
|
|||||||
desc: '通过上舰, Superchat, 赠送礼物等操作可以获取积分, 并通过积分兑换虚拟或者实体礼物',
|
desc: '通过上舰, Superchat, 赠送礼物等操作可以获取积分, 并通过积分兑换虚拟或者实体礼物',
|
||||||
icon: BookCoins20Filled,
|
icon: BookCoins20Filled,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: '弹幕机 (OBS',
|
||||||
|
desc: '在OBS上显示直播间弹幕、礼物和互动内容,兼容blivechat样式 (开发中',
|
||||||
|
icon: Chat24Filled,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: '日程表',
|
name: '日程表',
|
||||||
desc: '提供多种样式的日程表',
|
desc: '提供多种样式的日程表',
|
||||||
@@ -117,8 +124,10 @@ onMounted(async () => {
|
|||||||
vertical
|
vertical
|
||||||
justify="center"
|
justify="center"
|
||||||
align="center"
|
align="center"
|
||||||
style="padding-top: 30px"
|
style="padding-top: 30px; padding-bottom: 30px;"
|
||||||
>
|
>
|
||||||
|
<!-- 顶部标题部分 -->
|
||||||
|
<NCard style="background: rgba(255, 255, 255, 0.1); backdrop-filter: blur(10px); border: none; width: 90vw; max-width: 1400px; ">
|
||||||
<NSpace
|
<NSpace
|
||||||
justify="center"
|
justify="center"
|
||||||
align="center"
|
align="center"
|
||||||
@@ -210,50 +219,190 @@ onMounted(async () => {
|
|||||||
</NSpace>
|
</NSpace>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
|
</NCard>
|
||||||
|
|
||||||
<NDivider style="width: 90vw">
|
<!-- 用户统计部分 -->
|
||||||
<NText :depth="3">
|
<NCard
|
||||||
本站用户
|
style="background: rgba(255, 255, 255, 0.05); backdrop-filter: blur(10px);
|
||||||
</NText>
|
border: none; width: 90vw; max-width: 1400px;"
|
||||||
<NDivider vertical />
|
size="small"
|
||||||
<NNumberAnimation
|
>
|
||||||
|
<NFlex
|
||||||
|
justify="center"
|
||||||
|
align="center"
|
||||||
|
>
|
||||||
|
<NText style="font-size: 0.9rem; color: rgba(255,255,255,0.7);">
|
||||||
|
本站用户: <NNumberAnimation
|
||||||
:from="0"
|
:from="0"
|
||||||
:to="indexData?.userCount"
|
:to="indexData?.userCount"
|
||||||
show-separator
|
show-separator
|
||||||
/>
|
/>
|
||||||
</NDivider>
|
</NText>
|
||||||
<NGrid
|
</NFlex>
|
||||||
cols="1 s:2 m:3 l:4 xl:5 2xl:5"
|
</NCard>
|
||||||
x-gap="50"
|
|
||||||
y-gap="50"
|
<!-- 功能列表部分 -->
|
||||||
style="max-width: 80vw"
|
<NCard style="background: rgba(255, 255, 255, 0.1); backdrop-filter: blur(10px); border: none; width: 90vw; max-width: 1400px; margin-bottom: 20px;">
|
||||||
responsive="screen"
|
<NFlex vertical>
|
||||||
|
<NFlex
|
||||||
|
justify="center"
|
||||||
|
align="center"
|
||||||
|
style="margin-bottom: 20px;"
|
||||||
>
|
>
|
||||||
<NGridItem
|
<NText style="font-size: 1.2rem; font-weight: 500; background-image: linear-gradient(to right, #e5e5e5, #c2ebeb); -webkit-background-clip: text; -webkit-text-fill-color: transparent;">
|
||||||
|
网站功能
|
||||||
|
</NText>
|
||||||
|
</NFlex>
|
||||||
|
|
||||||
|
<NFlex
|
||||||
|
:wrap="true"
|
||||||
|
justify="center"
|
||||||
|
style="gap: 15px;"
|
||||||
|
>
|
||||||
|
<NCard
|
||||||
v-for="item in functions"
|
v-for="item in functions"
|
||||||
:key="item.name"
|
:key="item.name"
|
||||||
|
style="width: 300px; max-width: 100%; background: rgba(255, 255, 255, 0.2); border: none; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);"
|
||||||
|
hoverable
|
||||||
>
|
>
|
||||||
<NSpace
|
<NFlex vertical>
|
||||||
align="end"
|
<NFlex
|
||||||
:wrap="false"
|
align="center"
|
||||||
|
style="margin-bottom: 10px;"
|
||||||
>
|
>
|
||||||
<NIcon
|
<NIcon
|
||||||
:component="item.icon"
|
:component="item.icon"
|
||||||
:color="iconColor"
|
size="24"
|
||||||
size="20"
|
color="white"
|
||||||
/>
|
/>
|
||||||
<NEllipsis>
|
<NText style="font-size: 1.1rem; font-weight: 500; margin-left: 10px; color: white;">
|
||||||
<NText class="index-feature header">
|
|
||||||
{{ item.name }}
|
{{ item.name }}
|
||||||
</NText>
|
</NText>
|
||||||
</NEllipsis>
|
</NFlex>
|
||||||
</NSpace>
|
<NText style="line-height: 1.6; color: rgba(255, 255, 255, 0.9);">
|
||||||
<NText class="index-feature content">
|
|
||||||
{{ item.desc }}
|
{{ item.desc }}
|
||||||
</NText>
|
</NText>
|
||||||
</NGridItem>
|
</NFlex>
|
||||||
</NGrid>
|
</NCard>
|
||||||
<NDivider style="width: 90vw">
|
</NFlex>
|
||||||
|
</NFlex>
|
||||||
|
</NCard>
|
||||||
|
|
||||||
|
<!-- 客户端专属功能部分 -->
|
||||||
|
<NCard style="background: rgba(255, 255, 255, 0.1); backdrop-filter: blur(10px); border: none; width: 90vw; max-width: 1400px; margin-bottom: 20px;">
|
||||||
|
<NFlex vertical>
|
||||||
|
<NFlex
|
||||||
|
justify="center"
|
||||||
|
align="center"
|
||||||
|
style="margin-bottom: 20px;"
|
||||||
|
>
|
||||||
|
<NText style="font-size: 1.2rem; font-weight: 500; background-image: linear-gradient(to right, #e5e5e5, #c2ebeb); -webkit-background-clip: text; -webkit-text-fill-color: transparent;">
|
||||||
|
客户端功能
|
||||||
|
</NText>
|
||||||
|
</NFlex>
|
||||||
|
|
||||||
|
<NFlex
|
||||||
|
:wrap="true"
|
||||||
|
justify="center"
|
||||||
|
style="gap: 20px;"
|
||||||
|
>
|
||||||
|
<NCard
|
||||||
|
style="width: 380px; max-width: 100%; background: rgba(255, 255, 255, 0.2); border: none; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);"
|
||||||
|
hoverable
|
||||||
|
>
|
||||||
|
<NFlex vertical>
|
||||||
|
<NFlex
|
||||||
|
align="center"
|
||||||
|
style="margin-bottom: 10px;"
|
||||||
|
>
|
||||||
|
<NIcon
|
||||||
|
:component="PersonFeedback24Filled"
|
||||||
|
size="24"
|
||||||
|
color="white"
|
||||||
|
/>
|
||||||
|
<NText style="font-size: 1.1rem; font-weight: 500; margin-left: 10px; color: white;">
|
||||||
|
自动操作
|
||||||
|
</NText>
|
||||||
|
</NFlex>
|
||||||
|
<NText style="line-height: 1.6; color: rgba(255, 255, 255, 0.9);">
|
||||||
|
支持弹幕自动回复、礼物感谢、上舰私信、关注感谢、入场欢迎、定时发送和SC感谢等功能,使用模板系统和JS执行环境,可定制化程度挺高
|
||||||
|
</NText>
|
||||||
|
</NFlex>
|
||||||
|
</NCard>
|
||||||
|
|
||||||
|
<NCard
|
||||||
|
style="width: 380px; max-width: 100%; background: rgba(255, 255, 255, 0.2); border: none; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);"
|
||||||
|
hoverable
|
||||||
|
>
|
||||||
|
<NFlex vertical>
|
||||||
|
<NFlex
|
||||||
|
align="center"
|
||||||
|
style="margin-bottom: 10px;"
|
||||||
|
>
|
||||||
|
<NIcon
|
||||||
|
:component="Chat24Filled"
|
||||||
|
size="24"
|
||||||
|
color="white"
|
||||||
|
/>
|
||||||
|
<NText style="font-size: 1.1rem; font-weight: 500; margin-left: 10px; color: white;">
|
||||||
|
弹幕机 (客户端)
|
||||||
|
</NText>
|
||||||
|
</NFlex>
|
||||||
|
<NText style="line-height: 1.6; color: rgba(255, 255, 255, 0.9);">
|
||||||
|
在自己电脑上显示直播间弹幕、礼物和互动内容
|
||||||
|
</NText>
|
||||||
|
</NFlex>
|
||||||
|
</NCard>
|
||||||
|
</NFlex>
|
||||||
|
|
||||||
|
<NFlex
|
||||||
|
justify="center"
|
||||||
|
style="margin-top: 20px;"
|
||||||
|
>
|
||||||
|
<NSpace>
|
||||||
|
<NButton
|
||||||
|
type="primary"
|
||||||
|
tag="a"
|
||||||
|
href="https://www.wolai.com/carN6qvUm3FErze9Xo53ii"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<NIcon :component="Info24Filled" />
|
||||||
|
</template>
|
||||||
|
客户端安装说明
|
||||||
|
</NButton>
|
||||||
|
<NButton
|
||||||
|
ghost
|
||||||
|
tag="a"
|
||||||
|
href="https://github.com/Megghy/vtsuru-fetvher-client"
|
||||||
|
target="_blank"
|
||||||
|
color="white"
|
||||||
|
>
|
||||||
|
客户端代码
|
||||||
|
</NButton>
|
||||||
|
<NButton
|
||||||
|
ghost
|
||||||
|
tag="a"
|
||||||
|
href="https://github.com/Megghy/vtsuru.live/tree/master/src/client"
|
||||||
|
target="_blank"
|
||||||
|
color="white"
|
||||||
|
>
|
||||||
|
逻辑代码
|
||||||
|
</NButton>
|
||||||
|
</NSpace>
|
||||||
|
</NFlex>
|
||||||
|
</NFlex>
|
||||||
|
</NCard>
|
||||||
|
|
||||||
|
<!-- 使用本站的主播部分 -->
|
||||||
|
<NCard style="background: rgba(255, 255, 255, 0.1); backdrop-filter: blur(10px); border: none; width: 90vw; max-width: 1400px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);">
|
||||||
|
<NFlex vertical>
|
||||||
|
<NFlex
|
||||||
|
justify="center"
|
||||||
|
align="center"
|
||||||
|
style="margin-bottom: 20px;"
|
||||||
|
>
|
||||||
|
<NText style="font-size: 1.2rem; font-weight: 500; background-image: linear-gradient(to right, #e5e5e5, #c2ebeb); -webkit-background-clip: text; -webkit-text-fill-color: transparent;">
|
||||||
正在使用本站的主播们
|
正在使用本站的主播们
|
||||||
<NTooltip>
|
<NTooltip>
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
@@ -261,16 +410,20 @@ onMounted(async () => {
|
|||||||
</template>
|
</template>
|
||||||
随机展示不分先后, 仅粉丝数大于500的主播
|
随机展示不分先后, 仅粉丝数大于500的主播
|
||||||
</NTooltip>
|
</NTooltip>
|
||||||
</NDivider>
|
</NText>
|
||||||
|
</NFlex>
|
||||||
|
|
||||||
<NFlex
|
<NFlex
|
||||||
v-if="indexData"
|
v-if="indexData"
|
||||||
vertical
|
vertical
|
||||||
style="max-width: 80vw;"
|
style="max-width: 90vw;"
|
||||||
>
|
>
|
||||||
<NFlex
|
<NFlex
|
||||||
align="center"
|
align="center"
|
||||||
justify="center"
|
justify="center"
|
||||||
:size="32"
|
:size="32"
|
||||||
|
:wrap="true"
|
||||||
|
style="gap: 10px;"
|
||||||
>
|
>
|
||||||
<NFlex
|
<NFlex
|
||||||
v-for="streamer in indexData?.streamers"
|
v-for="streamer in indexData?.streamers"
|
||||||
@@ -296,10 +449,13 @@ onMounted(async () => {
|
|||||||
</NButton>
|
</NButton>
|
||||||
</NFlex>
|
</NFlex>
|
||||||
</NFlex>
|
</NFlex>
|
||||||
<NText>
|
<NText style="text-align: center; margin-top: 10px; color: white;">
|
||||||
还有更多...
|
还有更多...
|
||||||
</NText>
|
</NText>
|
||||||
<NText depth="3">
|
<NText
|
||||||
|
depth="3"
|
||||||
|
style="text-align: center; margin-top: 5px;"
|
||||||
|
>
|
||||||
如果你不想要被展示在主页, 请前往
|
如果你不想要被展示在主页, 请前往
|
||||||
<NButton
|
<NButton
|
||||||
text
|
text
|
||||||
@@ -310,7 +466,8 @@ onMounted(async () => {
|
|||||||
进行设置
|
进行设置
|
||||||
</NText>
|
</NText>
|
||||||
</NFlex>
|
</NFlex>
|
||||||
<NDivider style="width: 90vw" />
|
</NFlex>
|
||||||
|
</NCard>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
<NSpace
|
<NSpace
|
||||||
style="position: absolute; bottom: 0; margin: 0 auto; width: 100vw"
|
style="position: absolute; bottom: 0; margin: 0 auto; width: 100vw"
|
||||||
@@ -333,22 +490,11 @@ onMounted(async () => {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
body
|
|
||||||
margin:0
|
|
||||||
.index-background
|
.index-background
|
||||||
display: abslute;
|
display: abslute;
|
||||||
height: 100vh;
|
|
||||||
background: #8360c3; /* fallback for old browsers */
|
background: #8360c3; /* fallback for old browsers */
|
||||||
background: -webkit-linear-gradient(to right, #2ebf91, #8360c3); /* Chrome 10-25, Safari 5.1-6 */
|
background: -webkit-linear-gradient(to right, #2ebf91, #8360c3); /* Chrome 10-25, Safari 5.1-6 */
|
||||||
background: linear-gradient(to right, #2ebf91, #8360c3); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
|
background: linear-gradient(to right, #2ebf91, #8360c3); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
|
||||||
overflow: auto
|
overflow: auto
|
||||||
|
padding-bottom: 50px;
|
||||||
.index-background .header
|
|
||||||
font-size: 1.3rem;
|
|
||||||
font-weight: 700;
|
|
||||||
color: white;
|
|
||||||
.index-background .content
|
|
||||||
max-width: 300px;
|
|
||||||
font-size: 17px;
|
|
||||||
color: white;
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -278,10 +278,13 @@ async function ChangeBili() {
|
|||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
onMounted(() => {
|
||||||
|
|
||||||
|
checkUpdateNote();
|
||||||
|
})
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
turnstile.value?.remove()
|
turnstile.value?.remove()
|
||||||
// 当进入管理页时检查更新日志
|
// 当进入管理页时检查更新日志
|
||||||
checkUpdateNote();
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -361,7 +361,8 @@ function GetPlayButton(song: SongsInfo) {
|
|||||||
// --- New: Helper function for Song Request Options ---
|
// --- New: Helper function for Song Request Options ---
|
||||||
function getOptionDisplay(options?: SongRequestOption) {
|
function getOptionDisplay(options?: SongRequestOption) {
|
||||||
if (!options) {
|
if (!options) {
|
||||||
return h('span', '无特殊要求');
|
// 为"无特殊要求"添加 'empty-placeholder' 类
|
||||||
|
return h('span', { class: 'empty-placeholder' }, '无特殊要求');
|
||||||
}
|
}
|
||||||
|
|
||||||
const conditions: VNode[] = [];
|
const conditions: VNode[] = [];
|
||||||
@@ -383,7 +384,8 @@ function getOptionDisplay(options?: SongRequestOption) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (conditions.length === 0) {
|
if (conditions.length === 0) {
|
||||||
return h('span', '无特殊要求');
|
// 为"无特殊要求"添加 'empty-placeholder' 类
|
||||||
|
return h('span', { class: 'empty-placeholder' }, '无特殊要求');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use NFlex for better wrapping
|
// Use NFlex for better wrapping
|
||||||
@@ -902,7 +904,11 @@ export const Config = defineTemplateConfig([
|
|||||||
>
|
>
|
||||||
{{ tag }}
|
{{ tag }}
|
||||||
</n-tag>
|
</n-tag>
|
||||||
<span v-if="!song.tags || song.tags.length === 0">无标签</span>
|
<!-- 为"无标签"添加 'empty-placeholder' 类 -->
|
||||||
|
<span
|
||||||
|
v-if="!song.tags || song.tags.length === 0"
|
||||||
|
class="empty-placeholder"
|
||||||
|
>无标签</span>
|
||||||
</n-flex>
|
</n-flex>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@@ -1432,4 +1438,15 @@ html.dark .language-link.selected-language {
|
|||||||
color: currentColor !important; fill: currentColor !important;
|
color: currentColor !important; fill: currentColor !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* --- NEW: Style for empty placeholders --- */
|
||||||
|
.empty-placeholder {
|
||||||
|
color: #999999; /* Use a standard gray color */
|
||||||
|
font-style: italic; /* Optional: make it italic */
|
||||||
|
font-size: 0.9em; /* Optional: slightly smaller */
|
||||||
|
}
|
||||||
|
|
||||||
|
html.dark .empty-placeholder {
|
||||||
|
color: var(--text-color-3); /* Use theme variable for dark mode */
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
Reference in New Issue
Block a user