重构多个组件以优化代码格式和可读性,删除不必要的文件,更新类型定义,添加数据分析路由

This commit is contained in:
2025-03-27 18:37:01 +08:00
parent 8bcf201fd4
commit 24f1c413c4
115 changed files with 10879 additions and 2691 deletions

View File

@@ -797,49 +797,88 @@ onUnmounted(() => {
</script>
<template>
<NAlert :type="accountInfo.settings.enableFunctions.includes(FunctionTypes.SongRequest) ? 'success' : 'warning'"
v-if="accountInfo.id">
<NAlert
v-if="accountInfo.id"
:type="accountInfo.settings.enableFunctions.includes(FunctionTypes.SongRequest) ? 'success' : 'warning'"
>
启用弹幕点播功能
<NSwitch :value="accountInfo?.settings.enableFunctions.includes(FunctionTypes.SongRequest)"
@update:value="onUpdateFunctionEnable" />
<NSwitch
:value="accountInfo?.settings.enableFunctions.includes(FunctionTypes.SongRequest)"
@update:value="onUpdateFunctionEnable"
/>
<br />
<br>
<NText depth="3">
如果没有部署
<NButton text type="primary" tag="a" href="https://www.wolai.com/fje5wLtcrDoZcb9rk2zrFs" target="_blank">
<NButton
text
type="primary"
tag="a"
href="https://www.wolai.com/fje5wLtcrDoZcb9rk2zrFs"
target="_blank"
>
VtsuruEventFetcher
</NButton>
则其需要保持此页面开启才能点播, 也不要同时开多个页面, 会导致点播重复 !(部署了则不影响)
</NText>
</NAlert>
<NAlert type="warning" v-else title="你尚未注册并登录 VTsuru.live, 大部分规则设置将不可用 (因为我懒得在前段重写一遍逻辑">
<NButton tag="a" href="/manage" target="_blank" type="primary"> 前往登录或注册 </NButton>
<NAlert
v-else
type="warning"
title="你尚未注册并登录 VTsuru.live, 大部分规则设置将不可用 (因为我懒得在前段重写一遍逻辑"
>
<NButton
tag="a"
href="/manage"
target="_blank"
type="primary"
>
前往登录或注册
</NButton>
</NAlert>
<br />
<br>
<NCard size="small">
<NSpace align="center">
<NTooltip>
<template #trigger>
<NButton @click="showOBSModal = true" type="primary" :disabled="!accountInfo"> OBS 组件 </NButton>
<NButton
type="primary"
:disabled="!accountInfo"
@click="showOBSModal = true"
>
OBS 组件
</NButton>
</template>
{{ configCanEdit ? '' : '登陆后才可以使用此功能' }}
</NTooltip>
</NSpace>
</NCard>
<br />
<br>
<NCard>
<NTabs v-if="!accountInfo || accountInfo.settings.enableFunctions.includes(FunctionTypes.SongRequest)" animated
display-directive="show:lazy">
<NTabPane name="list" tab="列表">
<NTabs
v-if="!accountInfo || accountInfo.settings.enableFunctions.includes(FunctionTypes.SongRequest)"
animated
display-directive="show:lazy"
>
<NTabPane
name="list"
tab="列表"
>
<NCard size="small">
<NSpace align="center">
<NTag type="success" :bordered="false">
<NTag
type="success"
:bordered="false"
>
<template #icon>
<NIcon :component="PeopleQueue24Filled" />
</template>
队列 | {{activeSongs.filter((s) => s.status == SongRequestStatus.Waiting).length}}
队列 | {{ activeSongs.filter((s) => s.status == SongRequestStatus.Waiting).length }}
</NTag>
<NTag type="success" :bordered="false">
<NTag
type="success"
:bordered="false"
>
<template #icon>
<NIcon :component="Checkmark12Regular" />
</template>
@@ -851,23 +890,54 @@ onUnmounted(() => {
</NTag>
<NInputGroup>
<NInput placeholder="手动添加" v-model:value="newSongName" />
<NButton type="primary" @click="addSongManual"> 添加 </NButton>
<NInput
v-model:value="newSongName"
placeholder="手动添加"
/>
<NButton
type="primary"
@click="addSongManual"
>
添加
</NButton>
</NInputGroup>
<NRadioGroup v-model:value="settings.sortType" :disabled="!configCanEdit" @update:value="updateSettings"
type="button">
<NRadioButton :value="QueueSortType.TimeFirst"> 加入时间优先 </NRadioButton>
<NRadioButton :value="QueueSortType.PaymentFist"> 付费价格优先 </NRadioButton>
<NRadioButton :value="QueueSortType.GuardFirst"> 舰长优先 (按等级) </NRadioButton>
<NRadioButton :value="QueueSortType.FansMedalFirst"> 粉丝牌等级优先 </NRadioButton>
<NRadioGroup
v-model:value="settings.sortType"
:disabled="!configCanEdit"
type="button"
@update:value="updateSettings"
>
<NRadioButton :value="QueueSortType.TimeFirst">
加入时间优先
</NRadioButton>
<NRadioButton :value="QueueSortType.PaymentFist">
付费价格优先
</NRadioButton>
<NRadioButton :value="QueueSortType.GuardFirst">
舰长优先 (按等级)
</NRadioButton>
<NRadioButton :value="QueueSortType.FansMedalFirst">
粉丝牌等级优先
</NRadioButton>
</NRadioGroup>
<NCheckbox v-if="configCanEdit" v-model:checked="settings.isReverse" @update:checked="updateSettings">
<NCheckbox
v-if="configCanEdit"
v-model:checked="settings.isReverse"
@update:checked="updateSettings"
>
倒序
</NCheckbox>
<NCheckbox
v-else
v-model:checked="isReverse"
>
倒序
</NCheckbox>
<NCheckbox v-else v-model:checked="isReverse"> 倒序 </NCheckbox>
<NPopconfirm @positive-click="deactiveAllSongs">
<template #trigger>
<NButton type="error"> 全部取消 </NButton>
<NButton type="error">
全部取消
</NButton>
</template>
确定全部取消吗?
</NPopconfirm>
@@ -875,31 +945,68 @@ onUnmounted(() => {
</NCard>
<NDivider> {{ activeSongs.length }} </NDivider>
<Transition>
<div v-if="selectedSong" class="song-list">
<SongPlayer :song="selectedSong" v-model:is-lrc-loading="isLrcLoading" />
<div
v-if="selectedSong"
class="song-list"
>
<SongPlayer
v-model:is-lrc-loading="isLrcLoading"
:song="selectedSong"
/>
<NDivider style="margin: 15px 0 15px 0" />
</div>
</Transition>
<NList v-if="activeSongs.length > 0" :show-divider="false" hoverable>
<NListItem v-for="song in activeSongs" :key="song.id" style="padding: 5px">
<NCard embedded size="small" content-style="padding: 5px;"
:style="`${song.status == SongRequestStatus.Singing ? 'animation: animated-border 2.5s infinite;' : ''};height: 100%;`">
<NSpace justify="space-between" align="center" style="height: 100%; margin: 0 5px 0 5px">
<NList
v-if="activeSongs.length > 0"
:show-divider="false"
hoverable
>
<NListItem
v-for="song in activeSongs"
:key="song.id"
style="padding: 5px"
>
<NCard
embedded
size="small"
content-style="padding: 5px;"
:style="`${song.status == SongRequestStatus.Singing ? 'animation: animated-border 2.5s infinite;' : ''};height: 100%;`"
>
<NSpace
justify="space-between"
align="center"
style="height: 100%; margin: 0 5px 0 5px"
>
<NSpace align="center">
<div
:style="`border-radius: 4px; background-color: ${song.status == SongRequestStatus.Singing ? '#75c37f' : '#577fb8'}; width: 10px; height: 20px`">
</div>
<NText strong style="font-size: 18px">
:style="`border-radius: 4px; background-color: ${song.status == SongRequestStatus.Singing ? '#75c37f' : '#577fb8'}; width: 10px; height: 20px`"
/>
<NText
strong
style="font-size: 18px"
>
{{ song.songName }}
</NText>
<template v-if="song.from == SongRequestFrom.Manual">
<NTag size="small" :bordered="false"> 手动添加 </NTag>
<NTag
size="small"
:bordered="false"
>
手动添加
</NTag>
</template>
<template v-else>
<NTooltip>
<template #trigger>
<NTag size="small" :bordered="false" type="info">
<NText italic depth="3">
<NTag
size="small"
:bordered="false"
type="info"
>
<NText
italic
depth="3"
>
{{ song.user?.name }}
</NText>
</NTag>
@@ -907,12 +1014,21 @@ onUnmounted(() => {
{{ song.user?.uid }}
</NTooltip>
</template>
<NSpace v-if="
(song.from == SongRequestFrom.Danmaku || song.from == SongRequestFrom.SC) &&
song.user?.fans_medal_wearing_status
">
<NTag size="tiny" round>
<NTag size="tiny" round :bordered="false">
<NSpace
v-if="
(song.from == SongRequestFrom.Danmaku || song.from == SongRequestFrom.SC) &&
song.user?.fans_medal_wearing_status
"
>
<NTag
size="tiny"
round
>
<NTag
size="tiny"
round
:bordered="false"
>
<NText depth="3">
{{ song.user?.fans_medal_level }}
</NText>
@@ -922,32 +1038,54 @@ onUnmounted(() => {
</span>
</NTag>
</NSpace>
<NTag v-if="(song.user?.guard_level ?? 0) > 0" size="small" :bordered="false"
:color="{ textColor: 'white', color: GetGuardColor(song.user?.guard_level) }">
<NTag
v-if="(song.user?.guard_level ?? 0) > 0"
size="small"
:bordered="false"
:color="{ textColor: 'white', color: GetGuardColor(song.user?.guard_level) }"
>
{{ song.user?.guard_level == 1 ? '总督' : song.user?.guard_level == 2 ? '提督' : '舰长' }}
</NTag>
<NTag v-if="song.from == SongRequestFrom.SC" size="small"
:color="{ textColor: 'white', color: GetSCColor(song.price ?? 0) }">
<NTag
v-if="song.from == SongRequestFrom.SC"
size="small"
:color="{ textColor: 'white', color: GetSCColor(song.price ?? 0) }"
>
SC | {{ song.price }}
</NTag>
<NTag v-if="song.from == SongRequestFrom.Gift" size="small"
:color="{ textColor: 'white', color: GetSCColor(song.price ?? 0) }">
<NTag
v-if="song.from == SongRequestFrom.Gift"
size="small"
:color="{ textColor: 'white', color: GetSCColor(song.price ?? 0) }"
>
Gift | {{ song.price }}
</NTag>
<NTooltip>
<template #trigger>
<NText style="font-size: small">
<NTime :time="song.createAt" type="relative" :key="updateKey" />
<NTime
:key="updateKey"
:time="song.createAt"
type="relative"
/>
</NText>
</template>
<NTime :time="song.createAt" />
</NTooltip>
</NSpace>
<NSpace justify="end" align="center">
<NSpace
justify="end"
align="center"
>
<NTooltip v-if="song.song?.url">
<template #trigger>
<NButton circle type="success" style="height: 30px; width: 30px"
:loading="isLrcLoading == song?.song?.key" @click="selectedSong = song.song">
<NButton
circle
type="success"
style="height: 30px; width: 30px"
:loading="isLrcLoading == song?.song?.key"
@click="selectedSong = song.song"
>
<template #icon>
<NIcon :component="Play24Filled" />
</template>
@@ -957,17 +1095,24 @@ onUnmounted(() => {
</NTooltip>
<NTooltip>
<template #trigger>
<NButton circle type="primary" style="height: 30px; width: 30px" :disabled="songs.findIndex((s) => s.id != song.id && s.status == SongRequestStatus.Singing) > -1
" @click="
<NButton
circle
type="primary"
style="height: 30px; width: 30px"
:disabled="songs.findIndex((s) => s.id != song.id && s.status == SongRequestStatus.Singing) > -1
"
:style="`animation: ${song.status == SongRequestStatus.Waiting ? '' : 'loading 5s linear infinite'}`"
:secondary="song.status == SongRequestStatus.Singing"
:loading="isLoading"
@click="
updateSongStatus(
song,
song.status == SongRequestStatus.Singing
? SongRequestStatus.Waiting
: SongRequestStatus.Singing,
)
"
:style="`animation: ${song.status == SongRequestStatus.Waiting ? '' : 'loading 5s linear infinite'}`"
:secondary="song.status == SongRequestStatus.Singing" :loading="isLoading">
"
>
<template #icon>
<NIcon :component="Mic24Filled" />
</template>
@@ -983,8 +1128,13 @@ onUnmounted(() => {
</NTooltip>
<NTooltip>
<template #trigger>
<NButton circle type="success" style="height: 30px; width: 30px" :loading="isLoading"
@click="updateSongStatus(song, SongRequestStatus.Finish)">
<NButton
circle
type="success"
style="height: 30px; width: 30px"
:loading="isLoading"
@click="updateSongStatus(song, SongRequestStatus.Finish)"
>
<template #icon>
<NIcon :component="Checkmark12Regular" />
</template>
@@ -996,7 +1146,12 @@ onUnmounted(() => {
<template #trigger>
<NPopconfirm @positive-click="blockUser(song)">
<template #trigger>
<NButton circle type="warning" style="height: 30px; width: 30px" :loading="isLoading">
<NButton
circle
type="warning"
style="height: 30px; width: 30px"
:loading="isLoading"
>
<template #icon>
<NIcon :component="PresenceBlocked16Regular" />
</template>
@@ -1009,8 +1164,13 @@ onUnmounted(() => {
</NTooltip>
<NTooltip>
<template #trigger>
<NButton circle type="error" style="height: 30px; width: 30px" :loading="isLoading"
@click="updateSongStatus(song, SongRequestStatus.Cancel)">
<NButton
circle
type="error"
style="height: 30px; width: 30px"
:loading="isLoading"
@click="updateSongStatus(song, SongRequestStatus.Cancel)"
>
<template #icon>
<NIcon :component="Dismiss16Filled" />
</template>
@@ -1023,39 +1183,64 @@ onUnmounted(() => {
</NCard>
</NListItem>
</NList>
<NEmpty v-else description="暂无曲目" />
<NEmpty
v-else
description="暂无曲目"
/>
</NTabPane>
<NTabPane name="history" tab="历史">
<NTabPane
name="history"
tab="历史"
>
<NCard size="small">
<NSpace>
<NInputGroup style="width: 300px">
<NInputGroupLabel> 筛选曲名 </NInputGroupLabel>
<NInput v-model:value="filterSongName" clearable>
<NInput
v-model:value="filterSongName"
clearable
>
<template #suffix>
<NCheckbox v-model:checked="filterSongNameContains"> 包含 </NCheckbox>
<NCheckbox v-model:checked="filterSongNameContains">
包含
</NCheckbox>
</template>
</NInput>
</NInputGroup>
<NInputGroup style="width: 300px">
<NInputGroupLabel> 筛选用户 </NInputGroupLabel>
<NInput v-model:value="filterName" clearable>
<NInput
v-model:value="filterName"
clearable
>
<template #suffix>
<NCheckbox v-model:checked="filterNameContains"> 包含 </NCheckbox>
<NCheckbox v-model:checked="filterNameContains">
包含
</NCheckbox>
</template>
</NInput>
</NInputGroup>
</NSpace>
</NCard>
<NDataTable size="small" ref="table" :columns="columns" :data="songs" :pagination="{
itemCount: songs.length,
pageSizes: [20, 50, 100],
showSizePicker: true,
prefix({ itemCount }) {
return `共 ${itemCount} 条记录`
},
}" />
<NDataTable
ref="table"
size="small"
:columns="columns"
:data="songs"
:pagination="{
itemCount: songs.length,
pageSizes: [20, 50, 100],
showSizePicker: true,
prefix({ itemCount }) {
return `共 ${itemCount} 条记录`
},
}"
/>
</NTabPane>
<NTabPane name="setting" tab="设置">
<NTabPane
name="setting"
tab="设置"
>
<NSpin :show="isLoading">
<NDivider> 规则 </NDivider>
<NSpace vertical>
@@ -1064,57 +1249,119 @@ onUnmounted(() => {
<NInputGroupLabel> 点播弹幕前缀 </NInputGroupLabel>
<template v-if="configCanEdit">
<NInput v-model:value="settings.orderPrefix" />
<NButton @click="updateSettings" type="primary">确定</NButton>
<NButton
type="primary"
@click="updateSettings"
>
确定
</NButton>
</template>
<NInput v-else v-model:value="defaultPrefix" />
<NInput
v-else
v-model:value="defaultPrefix"
/>
</NInputGroup>
<NAlert v-if="settings.orderPrefix.includes(' ')" type="info"> 前缀包含空格 </NAlert>
<NAlert
v-if="settings.orderPrefix.includes(' ')"
type="info"
>
前缀包含空格
</NAlert>
</NSpace>
<NInputGroup style="width: 250px">
<NInputGroupLabel> 最大队列长度 </NInputGroupLabel>
<NInputNumber v-model:value="settings.queueMaxSize" :disabled="!configCanEdit" />
<NButton @click="updateSettings" type="info" :disabled="!configCanEdit">确定</NButton>
<NInputNumber
v-model:value="settings.queueMaxSize"
:disabled="!configCanEdit"
/>
<NButton
type="info"
:disabled="!configCanEdit"
@click="updateSettings"
>
确定
</NButton>
</NInputGroup>
<NSpace align="center">
<NCheckbox v-model:checked="settings.enableOnStreaming" @update:checked="updateSettings"
:disabled="!configCanEdit">
<NCheckbox
v-model:checked="settings.enableOnStreaming"
:disabled="!configCanEdit"
@update:checked="updateSettings"
>
仅在直播时才允许加入
</NCheckbox>
<NCheckbox v-model:checked="settings.allowAllDanmaku" @update:checked="updateSettings"
:disabled="!configCanEdit">
<NCheckbox
v-model:checked="settings.allowAllDanmaku"
:disabled="!configCanEdit"
@update:checked="updateSettings"
>
允许所有弹幕点播
</NCheckbox>
<template v-if="!settings.allowAllDanmaku">
<NCheckbox v-model:checked="settings.needWearFanMedal" @update:checked="updateSettings"
:disabled="!configCanEdit">
<NCheckbox
v-model:checked="settings.needWearFanMedal"
:disabled="!configCanEdit"
@update:checked="updateSettings"
>
需要拥有粉丝牌
</NCheckbox>
<NInputGroup v-if="settings.needWearFanMedal" style="width: 250px">
<NInputGroup
v-if="settings.needWearFanMedal"
style="width: 250px"
>
<NInputGroupLabel> 最低粉丝牌等级 </NInputGroupLabel>
<NInputNumber v-model:value="settings.fanMedalMinLevel" :disabled="!configCanEdit" />
<NButton @click="updateSettings" type="info" :disabled="!configCanEdit">确定</NButton>
<NInputNumber
v-model:value="settings.fanMedalMinLevel"
:disabled="!configCanEdit"
/>
<NButton
type="info"
:disabled="!configCanEdit"
@click="updateSettings"
>
确定
</NButton>
</NInputGroup>
<NCheckbox v-if="!settings.allowAllDanmaku" v-model:checked="settings.needJianzhang"
@update:checked="updateSettings" :disabled="!configCanEdit">
<NCheckbox
v-if="!settings.allowAllDanmaku"
v-model:checked="settings.needJianzhang"
:disabled="!configCanEdit"
@update:checked="updateSettings"
>
只允许舰长
</NCheckbox>
<NCheckbox v-if="!settings.allowAllDanmaku" v-model:checked="settings.needTidu"
@update:checked="updateSettings" :disabled="!configCanEdit">
<NCheckbox
v-if="!settings.allowAllDanmaku"
v-model:checked="settings.needTidu"
:disabled="!configCanEdit"
@update:checked="updateSettings"
>
只允许提督
</NCheckbox>
<NCheckbox v-if="!settings.allowAllDanmaku" v-model:checked="settings.needZongdu"
@update:checked="updateSettings" :disabled="!configCanEdit">
<NCheckbox
v-if="!settings.allowAllDanmaku"
v-model:checked="settings.needZongdu"
:disabled="!configCanEdit"
@update:checked="updateSettings"
>
只允许总督
</NCheckbox>
</template>
</NSpace>
<NSpace align="center">
<NCheckbox v-model:checked="settings.allowSC" @update:checked="updateSettings" :disabled="!configCanEdit">
<NCheckbox
v-model:checked="settings.allowSC"
:disabled="!configCanEdit"
@update:checked="updateSettings"
>
允许通过 SuperChat 点播
</NCheckbox>
<span v-if="settings.allowSC">
<NCheckbox v-model:checked="settings.allowSC" @update:checked="updateSettings"
:disabled="!configCanEdit">
<NCheckbox
v-model:checked="settings.allowSC"
:disabled="!configCanEdit"
@update:checked="updateSettings"
>
SC 点播无视限制
</NCheckbox>
<NTooltip>
@@ -1124,10 +1371,22 @@ onUnmounted(() => {
包含冷却时间, 队列长度, 重复点播等
</NTooltip>
</span>
<NInputGroup v-if="settings.allowSC" style="width: 250px">
<NInputGroup
v-if="settings.allowSC"
style="width: 250px"
>
<NInputGroupLabel> 最低SC价格 </NInputGroupLabel>
<NInputNumber v-model:value="settings.scMinPrice" :disabled="!configCanEdit" />
<NButton @click="updateSettings" type="info" :disabled="!configCanEdit">确定</NButton>
<NInputNumber
v-model:value="settings.scMinPrice"
:disabled="!configCanEdit"
/>
<NButton
type="info"
:disabled="!configCanEdit"
@click="updateSettings"
>
确定
</NButton>
</NInputGroup>
</NSpace>
@@ -1193,46 +1452,103 @@ onUnmounted(() => {
</NSpace> -->
<NDivider> 点歌 </NDivider>
<NSpace>
<NCheckbox v-model:checked="settings.onlyAllowSongList" @update:checked="updateSettings"
:disabled="!configCanEdit">
<NCheckbox
v-model:checked="settings.onlyAllowSongList"
:disabled="!configCanEdit"
@update:checked="updateSettings"
>
仅允许点
<NButton text tag="a" href="/manage/song-list" target="_blank" type="info"> 歌单 </NButton>
<NButton
text
tag="a"
href="/manage/song-list"
target="_blank"
type="info"
>
歌单
</NButton>
内的歌曲
</NCheckbox>
<NCheckbox v-model:checked="settings.allowFromWeb" @update:checked="updateSettings"
:disabled="!configCanEdit">
<NCheckbox
v-model:checked="settings.allowFromWeb"
:disabled="!configCanEdit"
@update:checked="updateSettings"
>
允许通过网页点歌
</NCheckbox>
<NCheckbox v-if="settings.allowFromWeb" v-model:checked="settings.allowAnonymousFromWeb" @update:checked="updateSettings"
:disabled="!configCanEdit">
<NCheckbox
v-if="settings.allowFromWeb"
v-model:checked="settings.allowAnonymousFromWeb"
:disabled="!configCanEdit"
@update:checked="updateSettings"
>
允许匿名通过网页点歌
</NCheckbox>
</NSpace>
<NDivider> 冷却 (单位: 秒) </NDivider>
<NCheckbox v-model:checked="settings.enableCooldown" @update:checked="updateSettings"
:disabled="!configCanEdit">
<NCheckbox
v-model:checked="settings.enableCooldown"
:disabled="!configCanEdit"
@update:checked="updateSettings"
>
启用点播冷却
</NCheckbox>
<NSpace v-if="settings.enableCooldown">
<NInputGroup style="width: 250px">
<NInputGroupLabel> 普通弹幕 </NInputGroupLabel>
<NInputNumber v-model:value="settings.cooldownSecond" :disabled="!configCanEdit" />
<NButton @click="updateSettings" type="info" :disabled="!configCanEdit">确定</NButton>
<NInputNumber
v-model:value="settings.cooldownSecond"
:disabled="!configCanEdit"
/>
<NButton
type="info"
:disabled="!configCanEdit"
@click="updateSettings"
>
确定
</NButton>
</NInputGroup>
<NInputGroup style="width: 220px">
<NInputGroupLabel> 舰长 </NInputGroupLabel>
<NInputNumber v-model:value="settings.jianzhangCooldownSecond" :disabled="!configCanEdit" />
<NButton @click="updateSettings" type="info" :disabled="!configCanEdit">确定</NButton>
<NInputNumber
v-model:value="settings.jianzhangCooldownSecond"
:disabled="!configCanEdit"
/>
<NButton
type="info"
:disabled="!configCanEdit"
@click="updateSettings"
>
确定
</NButton>
</NInputGroup>
<NInputGroup style="width: 220px">
<NInputGroupLabel> 提督 </NInputGroupLabel>
<NInputNumber v-model:value="settings.tiduCooldownSecond" :disabled="!configCanEdit" />
<NButton @click="updateSettings" type="info" :disabled="!configCanEdit">确定</NButton>
<NInputNumber
v-model:value="settings.tiduCooldownSecond"
:disabled="!configCanEdit"
/>
<NButton
type="info"
:disabled="!configCanEdit"
@click="updateSettings"
>
确定
</NButton>
</NInputGroup>
<NInputGroup style="width: 220px">
<NInputGroupLabel> 总督 </NInputGroupLabel>
<NInputNumber v-model:value="settings.zongduCooldownSecond" :disabled="!configCanEdit" />
<NButton @click="updateSettings" type="info" :disabled="!configCanEdit">确定</NButton>
<NInputNumber
v-model:value="settings.zongduCooldownSecond"
:disabled="!configCanEdit"
/>
<NButton
type="info"
:disabled="!configCanEdit"
@click="updateSettings"
>
确定
</NButton>
</NInputGroup>
</NSpace>
<NDivider> OBS </NDivider>
@@ -1240,40 +1556,74 @@ onUnmounted(() => {
<NInputGroup style="width: 220px">
<NInputGroupLabel> 标题 </NInputGroupLabel>
<template v-if="configCanEdit">
<NInput v-model:value="settings.obsTitle" placeholder="默认为 点播" />
<NButton @click="updateSettings" type="primary">确定</NButton>
<NInput
v-model:value="settings.obsTitle"
placeholder="默认为 点播"
/>
<NButton
type="primary"
@click="updateSettings"
>
确定
</NButton>
</template>
</NInputGroup>
<NCheckbox v-model:checked="settings.showRequireInfo" :disabled="!configCanEdit"
@update:checked="updateSettings">
<NCheckbox
v-model:checked="settings.showRequireInfo"
:disabled="!configCanEdit"
@update:checked="updateSettings"
>
显示底部的需求信息
</NCheckbox>
<NCheckbox v-model:checked="settings.showUserName" :disabled="!configCanEdit"
@update:checked="updateSettings">
<NCheckbox
v-model:checked="settings.showUserName"
:disabled="!configCanEdit"
@update:checked="updateSettings"
>
显示点播用户名
</NCheckbox>
<NCheckbox v-model:checked="settings.showFanMadelInfo" :disabled="!configCanEdit"
@update:checked="updateSettings">
<NCheckbox
v-model:checked="settings.showFanMadelInfo"
:disabled="!configCanEdit"
@update:checked="updateSettings"
>
显示点播用户粉丝牌
</NCheckbox>
</NSpace>
<NDivider> 其他 </NDivider>
<NCheckbox v-model:checked="isWarnMessageAutoClose"> 自动关闭点播失败时的提示消息 </NCheckbox>
<NCheckbox v-model:checked="isWarnMessageAutoClose">
自动关闭点播失败时的提示消息
</NCheckbox>
</NSpace>
</NSpin>
</NTabPane>
</NTabs>
<template v-else>
<NAlert title="未启用" type="error"> 请先启用弹幕点播功能 </NAlert>
<NAlert
title="未启用"
type="error"
>
请先启用弹幕点播功能
</NAlert>
</template>
</NCard>
<NModal v-model:show="showOBSModal" title="OBS组件" preset="card" style="width: 800px">
<NAlert title="这是什么? " type="info"> 将等待队列以及结果显示在OBS中 </NAlert>
<NModal
v-model:show="showOBSModal"
title="OBS组件"
preset="card"
style="width: 800px"
>
<NAlert
title="这是什么? "
type="info"
>
将等待队列以及结果显示在OBS中
</NAlert>
<NDivider> 浏览 </NDivider>
<div style="height: 500px; width: 280px; position: relative; margin: 0 auto">
<LiveRequestOBS :id="accountInfo?.id" />
</div>
<br />
<br>
<NInput :value="`${CURRENT_HOST}obs/live-request?id=` + accountInfo?.id" />
<NDivider />
<NCollapse>

View File

@@ -382,51 +382,120 @@ onUnmounted(() => {
<template>
<NSpace>
<NAlert type="info"> 搜索时会优先选择非VIP歌曲, 所以点到付费曲目时可能会是猴版或者各种奇怪的歌 </NAlert>
<NAlert type="info">
搜索时会优先选择非VIP歌曲, 所以点到付费曲目时可能会是猴版或者各种奇怪的歌
</NAlert>
</NSpace>
<NDivider />
<NSpace align="center">
<NButton @click="listening ? stopListen() : startListen()" :type="listening ? 'error' : 'primary'"
:style="{ animation: listening ? 'animated-border 2.5s infinite' : '' }" data-umami-event="Use Music Request"
:data-umami-event-uid="accountInfo?.biliId" size="large">
<NButton
:type="listening ? 'error' : 'primary'"
:style="{ animation: listening ? 'animated-border 2.5s infinite' : '' }"
data-umami-event="Use Music Request"
:data-umami-event-uid="accountInfo?.biliId"
size="large"
@click="listening ? stopListen() : startListen()"
>
{{ listening ? '停止监听' : '开始监听' }}
</NButton>
<NButton @click="showOBSModal = true" type="info" size="small"> OBS组件 </NButton>
<NButton @click="showNeteaseModal = true" size="small"> 从网易云歌单导入空闲歌单 </NButton>
<NButton
type="info"
size="small"
@click="showOBSModal = true"
>
OBS组件
</NButton>
<NButton
size="small"
@click="showNeteaseModal = true"
>
从网易云歌单导入空闲歌单
</NButton>
<NButton @click="uploadConfig" type="primary" secondary :disabled="!accountInfo" size="small">
<NButton
type="primary"
secondary
:disabled="!accountInfo"
size="small"
@click="uploadConfig"
>
保存配置到服务器
</NButton>
<NPopconfirm @positive-click="downloadConfig">
<template #trigger>
<NButton type="primary" secondary :disabled="!accountInfo" size="small"> 从服务器获取配置 </NButton>
<NButton
type="primary"
secondary
:disabled="!accountInfo"
size="small"
>
从服务器获取配置
</NButton>
</template>
这将覆盖当前设置, 确定?
</NPopconfirm>
</NSpace>
<NDivider />
<NCollapse :default-expanded-names="['1']">
<NCollapseItem title="队列" name="1">
<NEmpty v-if="musicRquestStore.waitingMusics.length == 0"> 暂无 </NEmpty>
<NList v-else size="small" bordered>
<NListItem v-for="item in musicRquestStore.waitingMusics" :key="item.music.name">
<NCollapseItem
title="队列"
name="1"
>
<NEmpty v-if="musicRquestStore.waitingMusics.length == 0">
暂无
</NEmpty>
<NList
v-else
size="small"
bordered
>
<NListItem
v-for="item in musicRquestStore.waitingMusics"
:key="item.music.name"
>
<NSpace align="center">
<NButton @click="musicRquestStore.playMusic(item.music)" type="primary" secondary size="small">
<NButton
type="primary"
secondary
size="small"
@click="musicRquestStore.playMusic(item.music)"
>
播放
</NButton>
<NButton @click="musicRquestStore.waitingMusics.splice(musicRquestStore.waitingMusics.indexOf(item), 1)"
type="error" secondary size="small">
<NButton
type="error"
secondary
size="small"
@click="musicRquestStore.waitingMusics.splice(musicRquestStore.waitingMusics.indexOf(item), 1)"
>
取消
</NButton>
<NButton @click="blockMusic(item.music)" type="warning" secondary size="small"> 拉黑 </NButton>
<NButton
type="warning"
secondary
size="small"
@click="blockMusic(item.music)"
>
拉黑
</NButton>
<span>
<NTag v-if="item.music.from == SongFrom.Netease" type="success" size="small"> 网易</NTag>
<NTag v-else-if="item.music.from == SongFrom.Kugou" type="success" size="small"> 酷狗</NTag>
<NTag
v-if="item.music.from == SongFrom.Netease"
type="success"
size="small"
> 网易</NTag>
<NTag
v-else-if="item.music.from == SongFrom.Kugou"
type="success"
size="small"
> 酷狗</NTag>
</span>
<NText>
{{ item.from.name }}
</NText>
<NText depth="3"> {{ item.music.name }} - {{ item.music.author?.join('/') }} </NText>
<NText depth="3">
{{ item.music.name }} - {{ item.music.author?.join('/') }}
</NText>
</NSpace>
</NListItem>
</NList>
@@ -434,84 +503,157 @@ onUnmounted(() => {
</NCollapse>
<NDivider />
<NTabs>
<NTabPane name="settings" tab="设置">
<NTabPane
name="settings"
tab="设置"
>
<NSpace vertical>
<NSpace align="center">
<NRadioGroup v-model:value="settings.platform">
<NRadioButton value="netease"> 网易云 </NRadioButton>
<NRadioButton value="kugou"> 酷狗 </NRadioButton>
<NRadioButton value="netease">
网易云
</NRadioButton>
<NRadioButton value="kugou">
酷狗
</NRadioButton>
</NRadioGroup>
<NInputGroup style="width: 250px">
<NInputGroupLabel> 点歌弹幕前缀 </NInputGroupLabel>
<NInput v-model:value="settings.orderPrefix" />
</NInputGroup>
<NCheckbox :checked="settings.orderCooldown != undefined" @update:checked="(checked: boolean) => {
settings.orderCooldown = checked ? 300 : undefined
}
">
<NCheckbox
:checked="settings.orderCooldown != undefined"
@update:checked="(checked: boolean) => {
settings.orderCooldown = checked ? 300 : undefined
}
"
>
是否启用点歌冷却
</NCheckbox>
<NInputGroup v-if="settings.orderCooldown" style="width: 200px">
<NInputGroup
v-if="settings.orderCooldown"
style="width: 200px"
>
<NInputGroupLabel> 冷却时间 () </NInputGroupLabel>
<NInputNumber v-model:value="settings.orderCooldown" @update:value="(value) => {
if (!value || value <= 0) settings.orderCooldown = undefined
}
" />
<NInputNumber
v-model:value="settings.orderCooldown"
@update:value="(value) => {
if (!value || value <= 0) settings.orderCooldown = undefined
}
"
/>
</NInputGroup>
</NSpace>
<NSpace>
<NCheckbox v-model:checked="settings.playMusicWhenFree"> 空闲时播放空闲歌单 </NCheckbox>
<NCheckbox v-model:checked="settings.orderMusicFirst"> 优先播放点歌 </NCheckbox>
<NCheckbox v-model:checked="settings.playMusicWhenFree">
空闲时播放空闲歌单
</NCheckbox>
<NCheckbox v-model:checked="settings.orderMusicFirst">
优先播放点歌
</NCheckbox>
</NSpace>
<NSpace>
<NTooltip>
<template #trigger>
<NButton @click="getOutputDevice" type="info"> 获取输出设备 </NButton>
<NButton
type="info"
@click="getOutputDevice"
>
获取输出设备
</NButton>
</template>
获取和修改输出设备需要打开麦克风权限
</NTooltip>
<NSelect v-model:value="settings.deviceId" :options="deviceList"
:fallback-option="() => ({ label: '未选择', value: '' })" style="min-width: 200px"
@update:value="musicRquestStore.setSinkId" />
<NSelect
v-model:value="settings.deviceId"
:options="deviceList"
:fallback-option="() => ({ label: '未选择', value: '' })"
style="min-width: 200px"
@update:value="musicRquestStore.setSinkId"
/>
</NSpace>
</NSpace>
</NTabPane>
<NTabPane name="list" tab="闲置歌单">
<NTabPane
name="list"
tab="闲置歌单"
>
<NSpace>
<NPopconfirm @positive-click="clearMusic">
<template #trigger>
<NButton type="error"> 清空 </NButton>
<NButton type="error">
清空
</NButton>
</template>
确定清空吗?
</NPopconfirm>
<NButton @click="showNeteaseModal = true"> 从网易云歌单导入 </NButton>
<NButton @click="showNeteaseModal = true">
从网易云歌单导入
</NButton>
</NSpace>
<NDivider style="margin: 15px 0 10px 0" />
<NEmpty v-if="musicRquestStore.originMusics.length == 0"> 暂无 </NEmpty>
<NVirtualList v-else :style="`max-height: 1000px`" :item-size="30" :items="originMusics" item-resizable>
<NEmpty v-if="musicRquestStore.originMusics.length == 0">
暂无
</NEmpty>
<NVirtualList
v-else
:style="`max-height: 1000px`"
:item-size="30"
:items="originMusics"
item-resizable
>
<template #default="{ item }">
<p :style="`min-height: ${30}px;width:97%;display:flex;align-items:center;`">
<NSpace align="center" style="width: 100%">
<NSpace
align="center"
style="width: 100%"
>
<NPopconfirm @positive-click="delMusic(item)">
<template #trigger>
<NButton type="error" secondary size="small"> 删除 </NButton>
<NButton
type="error"
secondary
size="small"
>
删除
</NButton>
</template>
确定删除?
</NPopconfirm>
<NButton type="info" secondary size="small" @click="musicRquestStore.playMusic(item)"> 播放 </NButton>
<NButton
type="info"
secondary
size="small"
@click="musicRquestStore.playMusic(item)"
>
播放
</NButton>
<NText> {{ item.name }} - {{ item.author?.join('/') }} </NText>
</NSpace>
</p>
</template>
</NVirtualList>
</NTabPane>
<NTabPane name="blacklist" tab="黑名单">
<NTabPane
name="blacklist"
tab="黑名单"
>
<NList>
<NListItem v-for="item in settings.blacklist" :key="item">
<NSpace align="center" style="width: 100%">
<NButton @click="settings.blacklist.splice(settings.blacklist.indexOf(item), 1)" type="error" secondary
size="small">
<NListItem
v-for="item in settings.blacklist"
:key="item"
>
<NSpace
align="center"
style="width: 100%"
>
<NButton
type="error"
secondary
size="small"
@click="settings.blacklist.splice(settings.blacklist.indexOf(item), 1)"
>
删除
</NButton>
<NText> {{ item }} </NText>
@@ -521,34 +663,75 @@ onUnmounted(() => {
</NTabPane>
</NTabs>
<NDivider style="height: 100px" />
<NModal v-model:show="showNeteaseModal" preset="card" :title="`获取歌单`" style="max-width: 600px">
<NInput clearable style="width: 100%" autosize :status="neteaseSongListId ? 'success' : 'error'"
v-model:value="neteaseIdInput" placeholder="直接输入歌单Id或者网页链接">
<NModal
v-model:show="showNeteaseModal"
preset="card"
:title="`获取歌单`"
style="max-width: 600px"
>
<NInput
v-model:value="neteaseIdInput"
clearable
style="width: 100%"
autosize
:status="neteaseSongListId ? 'success' : 'error'"
placeholder="直接输入歌单Id或者网页链接"
>
<template #suffix>
<NTag v-if="neteaseSongListId" type="success" size="small"> 歌单Id: {{ neteaseSongListId }} </NTag>
<NTag
v-if="neteaseSongListId"
type="success"
size="small"
>
歌单Id: {{ neteaseSongListId }}
</NTag>
</template>
</NInput>
<NDivider style="margin: 10px" />
<NButton type="primary" @click="getNeteaseSongList" :disabled="!neteaseSongListId" :loading="isLoading">
<NButton
type="primary"
:disabled="!neteaseSongListId"
:loading="isLoading"
@click="getNeteaseSongList"
>
获取
</NButton>
<template v-if="neteaseSongsOptions.length > 0">
<NDivider style="margin: 10px" />
<NTransfer style="height: 500px" ref="transfer" v-model:value="selectedNeteaseSongs"
:options="neteaseSongsOptions" source-filterable />
<NTransfer
ref="transfer"
v-model:value="selectedNeteaseSongs"
style="height: 500px"
:options="neteaseSongsOptions"
source-filterable
/>
<NDivider style="margin: 10px" />
<NButton type="primary" @click="addNeteaseSongs" :loading="isLoading">
<NButton
type="primary"
:loading="isLoading"
@click="addNeteaseSongs"
>
添加到歌单 | {{ selectedNeteaseSongs.length }} 首
</NButton>
</template>
</NModal>
<NModal v-model:show="showOBSModal" title="OBS组件" preset="card" style="width: 800px">
<NAlert title="这是什么? " type="info"> 将等待队列以及结果显示在OBS中 </NAlert>
<NModal
v-model:show="showOBSModal"
title="OBS组件"
preset="card"
style="width: 800px"
>
<NAlert
title="这是什么? "
type="info"
>
将等待队列以及结果显示在OBS中
</NAlert>
<NDivider> 浏览 </NDivider>
<div style="height: 500px; width: 280px; position: relative; margin: 0 auto">
<MusicRequestOBS :id="accountInfo?.id" />
</div>
<br />
<br>
<NInput :value="`${CURRENT_HOST}obs/music-request?id=` + accountInfo?.id" />
<NDivider />
<NCollapse>

View File

@@ -14,61 +14,135 @@ const accountInfo = useAccount()
<template>
<NDivider> 功能 </NDivider>
<NSpace justify="center">
<NCard hoverable embedded size="small" title="弹幕抽奖" style="width: 300px">
<NCard
hoverable
embedded
size="small"
title="弹幕抽奖"
style="width: 300px"
>
通过弹幕或者礼物收集用户, 并进行抽取, 允许设置多种条件
<template #footer>
<NButton @click="$router.push({ name: 'open-live-lottery', query: $route.query })" type="primary">
<NButton
type="primary"
@click="$router.push({ name: 'open-live-lottery', query: $route.query })"
>
前往使用
</NButton>
</template>
</NCard>
<NCard hoverable embedded size="small" title="弹幕点播" style="width: 300px">
<NCard
hoverable
embedded
size="small"
title="弹幕点播"
style="width: 300px"
>
通过弹幕或者SC进行点歌或者其他的点播(比如跳舞或者点播视频之类的), 注册后可以保存和导出
<template #footer>
<NButton @click="$router.push({ name: 'open-live-live-request', query: $route.query })" type="primary">
<NButton
type="primary"
@click="$router.push({ name: 'open-live-live-request', query: $route.query })"
>
前往使用
</NButton>
</template>
</NCard>
<NCard hoverable embedded size="small" title="弹幕排队" style="width: 300px">
<NCard
hoverable
embedded
size="small"
title="弹幕排队"
style="width: 300px"
>
通过发送弹幕或者礼物进行排队, 允许设置多种条件
<template #footer>
<NButton @click="$router.push({ name: 'open-live-queue', query: $route.query })" type="primary">
<NButton
type="primary"
@click="$router.push({ name: 'open-live-queue', query: $route.query })"
>
前往使用
</NButton>
</template>
</NCard>
<NCard hoverable embedded size="small" title="读弹幕" style="width: 300px">
<NCard
hoverable
embedded
size="small"
title="读弹幕"
style="width: 300px"
>
通过浏览器自带的tts服务读弹幕 (此功能需要 Chrome, Edge 等现代浏览器!)
<template #footer>
<NButton @click="$router.push({ name: 'open-live-speech', query: $route.query })" type="primary">
<NButton
type="primary"
@click="$router.push({ name: 'open-live-speech', query: $route.query })"
>
前往使用
</NButton>
</template>
</NCard>
</NSpace>
<br />
<NAlert v-if="accountInfo?.eventFetcherState?.online != true" type="warning" title="可用性警告"
style="max-width: 600px; margin: 0 auto">
<br>
<NAlert
v-if="accountInfo?.eventFetcherState?.online != true"
type="warning"
title="可用性警告"
style="max-width: 600px; margin: 0 auto"
>
当浏览器在后台运行时, 定时器和 Websocket 连接将受到严格限制, 这会导致弹幕接收功能无法正常工作 (详见
<NButton text tag="a" href="https://developer.chrome.com/blog/background_tabs/" target="_blank" type="info">此文章
<NButton
text
tag="a"
href="https://developer.chrome.com/blog/background_tabs/"
target="_blank"
type="info"
>
此文章
</NButton>), 虽然本站已经针对此问题做出了处理, 一般情况下即使掉线了也会重连, 不过还是有可能会遗漏事件
<br />
<br>
为避免这种情况, 建议注册本站账后使用
<NButton type="primary" text size="tiny" tag="a" href="https://www.wolai.com/fje5wLtcrDoZcb9rk2zrFs"
target="_blank">
VtsuruEventFetcher </NButton>, 否则请在使用功能时尽量保持网页在前台运行
<NButton
type="primary"
text
size="tiny"
tag="a"
href="https://www.wolai.com/fje5wLtcrDoZcb9rk2zrFs"
target="_blank"
>
VtsuruEventFetcher
</NButton>, 否则请在使用功能时尽量保持网页在前台运行
</NAlert>
<NDivider> 还有更多 </NDivider>
<NSpace justify="center" align="center" vertical>
<NSpace
justify="center"
align="center"
vertical
>
舰长积分动态抽奖视频征集歌单棉花糖日程表...
<p>
详见
<NButton text tag="a" href="/" target="_blank" type="primary"> VTsuru.live </NButton>
<NButton
text
tag="a"
href="/"
target="_blank"
type="primary"
>
VTsuru.live
</NButton>
<NDivider vertical />
<NButton text tag="a" href="/about" target="_blank" type="info"> 关于本站 </NButton>
<NButton
text
tag="a"
href="/about"
target="_blank"
type="info"
>
关于本站
</NButton>
</p>
</NSpace>
</template>

View File

@@ -342,52 +342,134 @@ onUnmounted(() => {
</script>
<template>
<NResult v-if="!code && !accountInfo" status="403" title="403" description="该页面只能从幻星平台访问或者注册用户使用" />
<NResult
v-if="!code && !accountInfo"
status="403"
title="403"
description="该页面只能从幻星平台访问或者注册用户使用"
/>
<template v-else>
<NCard>
<template #header>
直播抽奖
<NDivider vertical />
<NButton text type="primary" tag="a" href="https://vtsuru.live" target="_blank">
<NButton
text
type="primary"
tag="a"
href="https://vtsuru.live"
target="_blank"
>
前往 VTsuru.live 主站
</NButton>
</template>
<NAlert v-if="!code && accountInfo && !accountInfo.isBiliVerified" type="error"> 请先绑定B站账号 </NAlert>
<NAlert v-else-if="!code && accountInfo && accountInfo.biliAuthCodeStatus != 1" type="error">
<NAlert
v-if="!code && accountInfo && !accountInfo.isBiliVerified"
type="error"
>
请先绑定B站账号
</NAlert>
<NAlert
v-else-if="!code && accountInfo && accountInfo.biliAuthCodeStatus != 1"
type="error"
>
身份码状态异常, 请重新绑定
</NAlert>
<NCard>
<NSpace align="center">
<NButton type="info" @click="showModal = true" size="small"> 抽奖历史</NButton>
<NButton type="success" @click="showOBSModal = true" size="small"> OBS组件</NButton>
<NButton
type="info"
size="small"
@click="showModal = true"
>
抽奖历史
</NButton>
<NButton
type="success"
size="small"
@click="showOBSModal = true"
>
OBS组件
</NButton>
</NSpace>
</NCard>
<NCard size="small" embedded title="抽奖选项">
<NCard
size="small"
embedded
title="抽奖选项"
>
<template #header-extra>
<NButton size="small" secondary @click="lotteryOption = defaultOption" :disabled="isStartLottery">
<NButton
size="small"
secondary
:disabled="isStartLottery"
@click="lotteryOption = defaultOption"
>
恢复默认
</NButton>
</template>
<NSpace justify="center" align="center">
<NTag :bordered="false"> 抽奖类型 </NTag>
<NRadioGroup v-model:value="lotteryOption.type" :disabled="isLottering" size="small">
<NRadioButton value="danmaku" :disabled="isStartLottery"> 弹幕 </NRadioButton>
<NRadioButton value="gift" :disabled="isStartLottery"> 礼物 </NRadioButton>
<NSpace
justify="center"
align="center"
>
<NTag :bordered="false">
抽奖类型
</NTag>
<NRadioGroup
v-model:value="lotteryOption.type"
:disabled="isLottering"
size="small"
>
<NRadioButton
value="danmaku"
:disabled="isStartLottery"
>
弹幕
</NRadioButton>
<NRadioButton
value="gift"
:disabled="isStartLottery"
>
礼物
</NRadioButton>
</NRadioGroup>
</NSpace>
<NDivider style="margin: 10px 0 10px 0"></NDivider>
<NDivider style="margin: 10px 0 10px 0" />
<NSpace align="center">
<NInputGroup style="max-width: 200px">
<NInputGroupLabel> 抽选人数 </NInputGroupLabel>
<NInputNumber :disabled="isStartLottery" v-model:value="lotteryOption.resultCount" placeholder="" min="1" />
<NInputNumber
v-model:value="lotteryOption.resultCount"
:disabled="isStartLottery"
placeholder=""
min="1"
/>
</NInputGroup>
<NCheckbox :disabled="isStartLottery" v-model:checked="lotteryOption.needGuard"> 需要上舰 </NCheckbox>
<NCheckbox :disabled="isStartLottery" v-model:checked="lotteryOption.needFanMedal"> 需要粉丝牌 </NCheckbox>
<NCheckbox
v-model:checked="lotteryOption.needGuard"
:disabled="isStartLottery"
>
需要上舰
</NCheckbox>
<NCheckbox
v-model:checked="lotteryOption.needFanMedal"
:disabled="isStartLottery"
>
需要粉丝牌
</NCheckbox>
<NCollapseTransition>
<NInputGroup v-if="lotteryOption.needFanMedal" style="max-width: 200px">
<NInputGroup
v-if="lotteryOption.needFanMedal"
style="max-width: 200px"
>
<NInputGroupLabel> 最低粉丝牌等级 </NInputGroupLabel>
<NInputNumber v-model:value="lotteryOption.fanCardLevel" min="1" max="50" :default-value="1"
:disabled="isLottering || isStartLottery" />
<NInputNumber
v-model:value="lotteryOption.fanCardLevel"
min="1"
max="50"
:default-value="1"
:disabled="isLottering || isStartLottery"
/>
</NInputGroup>
</NCollapseTransition>
<template v-if="lotteryOption.type == 'danmaku'">
@@ -395,35 +477,74 @@ onUnmounted(() => {
<template #trigger>
<NInputGroup style="max-width: 250px">
<NInputGroupLabel> 弹幕内容 </NInputGroupLabel>
<NInput :disabled="isStartLottery" v-model:value="lotteryOption.danmakuKeyword"
placeholder="留空则任何弹幕都可以" />
<NInput
v-model:value="lotteryOption.danmakuKeyword"
:disabled="isStartLottery"
placeholder="留空则任何弹幕都可以"
/>
</NInputGroup>
</template>
符合规则的弹幕才会被添加到抽奖队列中
</NTooltip>
<NRadioGroup v-model:value="lotteryOption.danmakuFilterType" name="判定类型" :disabled="isLottering"
size="small">
<NRadioButton :disabled="isStartLottery" value="all"> 完全一致 </NRadioButton>
<NRadioButton :disabled="isStartLottery" value="contains"> 包含 </NRadioButton>
<NRadioButton :disabled="isStartLottery" value="regex"> 正则 </NRadioButton>
<NRadioGroup
v-model:value="lotteryOption.danmakuFilterType"
name="判定类型"
:disabled="isLottering"
size="small"
>
<NRadioButton
:disabled="isStartLottery"
value="all"
>
完全一致
</NRadioButton>
<NRadioButton
:disabled="isStartLottery"
value="contains"
>
包含
</NRadioButton>
<NRadioButton
:disabled="isStartLottery"
value="regex"
>
正则
</NRadioButton>
</NRadioGroup>
</template>
<template v-else-if="lotteryOption.type == 'gift'">
<NInputGroup style="max-width: 250px">
<NInputGroupLabel> 最低价格 </NInputGroupLabel>
<NInputNumber :disabled="isStartLottery" v-model:value="lotteryOption.giftMinPrice"
placeholder="留空则不限制" />
<NInputNumber
v-model:value="lotteryOption.giftMinPrice"
:disabled="isStartLottery"
placeholder="留空则不限制"
/>
</NInputGroup>
<NInputGroup style="max-width: 200px">
<NInputGroupLabel> 礼物名称 </NInputGroupLabel>
<NInput :disabled="isStartLottery" v-model:value="lotteryOption.giftName" placeholder="留空则不限制" />
<NInput
v-model:value="lotteryOption.giftName"
:disabled="isStartLottery"
placeholder="留空则不限制"
/>
</NInputGroup>
</template>
</NSpace>
<NDivider style="margin: 10px 0 10px 0"></NDivider>
<NSpace justify="center" align="center">
<NTag :bordered="false"> 抽取方式 </NTag>
<NRadioGroup v-model:value="lotteryOption.lotteryType" name="抽取类型" size="small" :disabled="isLottering">
<NDivider style="margin: 10px 0 10px 0" />
<NSpace
justify="center"
align="center"
>
<NTag :bordered="false">
抽取方式
</NTag>
<NRadioGroup
v-model:value="lotteryOption.lotteryType"
name="抽取类型"
size="small"
:disabled="isLottering"
>
<NRadioButton value="single">
单个
<NTooltip>
@@ -445,39 +566,107 @@ onUnmounted(() => {
</NRadioGroup>
</NSpace>
</NCard>
<NCard v-if="originUsers" size="small">
<NSpace justify="center" align="center">
<NButton type="primary" @click="continueLottery" :loading="isStartLottery"
:disabled="isStartLottery || isLotteried || !client">
<NCard
v-if="originUsers"
size="small"
>
<NSpace
justify="center"
align="center"
>
<NButton
type="primary"
:loading="isStartLottery"
:disabled="isStartLottery || isLotteried || !client"
@click="continueLottery"
>
开始
</NButton>
<NButton type="warning" :disabled="!isStartLottery" @click="pause"> 停止 </NButton>
<NButton type="error" :disabled="isLottering || originUsers.length == 0" @click="clear"> 清空 </NButton>
<NButton
type="warning"
:disabled="!isStartLottery"
@click="pause"
>
停止
</NButton>
<NButton
type="error"
:disabled="isLottering || originUsers.length == 0"
@click="clear"
>
清空
</NButton>
</NSpace>
<NDivider style="margin: 20px 0 20px 0">
<template v-if="isStartLottery"> 进行抽取前需要先停止 </template>
<template v-if="isStartLottery">
进行抽取前需要先停止
</template>
</NDivider>
<NSpace justify="center">
<NButton type="primary" secondary @click="startLottery" :loading="isLottering"
:disabled="isStartLottery || isLotteried" data-umami-event="Open-Live Use Lottery"
:data-umami-event-uid="client?.authInfo?.anchor_info?.uid">
<NButton
type="primary"
secondary
:loading="isLottering"
:disabled="isStartLottery || isLotteried"
data-umami-event="Open-Live Use Lottery"
:data-umami-event-uid="client?.authInfo?.anchor_info?.uid"
@click="startLottery"
>
进行抽取
</NButton>
<NButton type="info" secondary :disabled="isStartLottery || isLottering || !isLotteried" @click="reset">
<NButton
type="info"
secondary
:disabled="isStartLottery || isLottering || !isLotteried"
@click="reset"
>
重置
</NButton>
</NSpace>
<NDivider style="margin: 10px 0 10px 0"> {{ currentUsers?.length }} </NDivider>
<NGrid v-if="currentUsers.length > 0" cols="1 500:2 800:3 1000:4" :x-gap="12" :y-gap="8">
<NGridItem v-for="item in currentUsers" v-bind:key="item.openId">
<NCard size="small" :title="item.name" style="height: 155px" embedded>
<NDivider style="margin: 10px 0 10px 0">
{{ currentUsers?.length }}
</NDivider>
<NGrid
v-if="currentUsers.length > 0"
cols="1 500:2 800:3 1000:4"
:x-gap="12"
:y-gap="8"
>
<NGridItem
v-for="item in currentUsers"
:key="item.openId"
>
<NCard
size="small"
:title="item.name"
style="height: 155px"
embedded
>
<template #header>
<NSpace align="center" vertical :size="5">
<NAvatar round lazy borderd :size="64" :src="item.avatar + '@64w_64h'"
:img-props="{ referrerpolicy: 'no-referrer' }" style="box-shadow: 0 3px 5px rgba(0, 0, 0, 0.2)" />
<NSpace
align="center"
vertical
:size="5"
>
<NAvatar
round
lazy
borderd
:size="64"
:src="item.avatar + '@64w_64h'"
:img-props="{ referrerpolicy: 'no-referrer' }"
style="box-shadow: 0 3px 5px rgba(0, 0, 0, 0.2)"
/>
<NSpace v-if="item.fans_medal_wearing_status">
<NTag size="tiny" round>
<NTag size="tiny" round :bordered="false">
<NTag
size="tiny"
round
>
<NTag
size="tiny"
round
:bordered="false"
>
{{ item.fans_medal_level }}
</NTag>
<span style="color: #577fb8">
@@ -485,12 +674,23 @@ onUnmounted(() => {
</span>
</NTag>
</NSpace>
<NTag v-else size="tiny" round :bordered="false"> 无粉丝牌 </NTag>
<NTag
v-else
size="tiny"
round
:bordered="false"
>
无粉丝牌
</NTag>
{{ item.name }}
</NSpace>
<NButton style="position: absolute; right: 5px; top: 5px; color: #753e3e" @click="removeUser(item)"
size="small" circle>
<NButton
style="position: absolute; right: 5px; top: 5px; color: #753e3e"
size="small"
circle
@click="removeUser(item)"
>
<template #icon>
<NIcon :component="Delete24Filled" />
</template>
@@ -499,29 +699,62 @@ onUnmounted(() => {
</NCard>
</NGridItem>
</NGrid>
<NEmpty v-else description="暂无用户" />
<NEmpty
v-else
description="暂无用户"
/>
</NCard>
</NCard>
</template>
<NModal v-model:show="showModal" preset="card" title="抽奖结果" style="max-width: 90%; width: 800px" closable>
<NModal
v-model:show="showModal"
preset="card"
title="抽奖结果"
style="max-width: 90%; width: 800px"
closable
>
<template #header-extra>
<NButton type="error" size="small" @click="lotteryHistory = []"> 清空 </NButton>
<NButton
type="error"
size="small"
@click="lotteryHistory = []"
>
清空
</NButton>
</template>
<NScrollbar v-if="lotteryHistory.length > 0" style="max-height: 80vh">
<NScrollbar
v-if="lotteryHistory.length > 0"
style="max-height: 80vh"
>
<NList>
<NListItem v-for="item in lotteryHistory" :key="item.time">
<NListItem
v-for="item in lotteryHistory"
:key="item.time"
>
<NCard size="small">
<template #header>
<NTime :time="item.time" />
</template>
<template #header-extra>
<NButton type="error" size="small" @click="lotteryHistory.splice(lotteryHistory.indexOf(item), 1)">
<NButton
type="error"
size="small"
@click="lotteryHistory.splice(lotteryHistory.indexOf(item), 1)"
>
删除
</NButton>
</template>
<NSpace vertical>
<NSpace v-for="user in item.users" :key="user.openId">
<NAvatar round lazy :src="user.avatar + '@64w_64h'" :img-props="{ referrerpolicy: 'no-referrer' }" />
<NSpace
v-for="user in item.users"
:key="user.openId"
>
<NAvatar
round
lazy
:src="user.avatar + '@64w_64h'"
:img-props="{ referrerpolicy: 'no-referrer' }"
/>
{{ user.name }}
</NSpace>
</NSpace>
@@ -529,16 +762,30 @@ onUnmounted(() => {
</NListItem>
</NList>
</NScrollbar>
<NEmpty v-else description="暂无记录" />
<NEmpty
v-else
description="暂无记录"
/>
</NModal>
<NModal v-model:show="showOBSModal" preset="card" title="OBS 组件"
style="max-width: 90%; width: 800px; max-height: 90vh" closable content-style="overflow: auto">
<NAlert title="这是什么? " type="info"> 将等待队列以及结果显示在OBS中 </NAlert>
<NModal
v-model:show="showOBSModal"
preset="card"
title="OBS 组件"
style="max-width: 90%; width: 800px; max-height: 90vh"
closable
content-style="overflow: auto"
>
<NAlert
title="这是什么? "
type="info"
>
将等待队列以及结果显示在OBS中
</NAlert>
<NDivider> 浏览 </NDivider>
<div style="height: 400px; width: 250px; position: relative; margin: 0 auto">
<LiveLotteryOBS :code="code" />
</div>
<br />
<br>
<NInput :value="`${CURRENT_HOST}obs/live-lottery?code=` + code" />
<NDivider />
<NCollapse>

View File

@@ -762,91 +762,181 @@ onUnmounted(() => {
</script>
<template>
<NAlert :type="accountInfo.settings.enableFunctions.includes(FunctionTypes.Queue) ? 'success' : 'warning'"
v-if="accountInfo.id">
<NAlert
v-if="accountInfo.id"
:type="accountInfo.settings.enableFunctions.includes(FunctionTypes.Queue) ? 'success' : 'warning'"
>
启用弹幕队列功能
<NSwitch :value="accountInfo?.settings.enableFunctions.includes(FunctionTypes.Queue)"
@update:value="onUpdateFunctionEnable" />
<NSwitch
:value="accountInfo?.settings.enableFunctions.includes(FunctionTypes.Queue)"
@update:value="onUpdateFunctionEnable"
/>
<br />
<br>
<NText depth="3">
如果没有部署
<NButton text type="primary" tag="a" href="https://www.wolai.com/fje5wLtcrDoZcb9rk2zrFs" target="_blank">
<NButton
text
type="primary"
tag="a"
href="https://www.wolai.com/fje5wLtcrDoZcb9rk2zrFs"
target="_blank"
>
VtsuruEventFetcher
</NButton>
则其需要保持此页面开启才能使用, 也不要同时开多个页面, 会导致重复 !(部署了则不影响)
</NText>
</NAlert>
<NAlert type="warning" v-else title="你尚未注册并登录 VTsuru.live, 大部分规则设置将不可用 (因为我懒得在前段重写一遍逻辑">
<NButton tag="a" href="/manage" target="_blank" type="primary"> 前往登录或注册 </NButton>
<NAlert
v-else
type="warning"
title="你尚未注册并登录 VTsuru.live, 大部分规则设置将不可用 (因为我懒得在前段重写一遍逻辑"
>
<NButton
tag="a"
href="/manage"
target="_blank"
type="primary"
>
前往登录或注册
</NButton>
</NAlert>
<br />
<br>
<NCard size="small">
<NSpace align="center">
<NTooltip>
<template #trigger>
<NButton @click="showOBSModal = true" type="primary" :disabled="!accountInfo"> OBS 组件 </NButton>
<NButton
type="primary"
:disabled="!accountInfo"
@click="showOBSModal = true"
>
OBS 组件
</NButton>
</template>
{{ configCanEdit ? '' : '登陆后才可以使用此功能' }}
</NTooltip>
</NSpace>
</NCard>
<br />
<br>
<NCard>
<NTabs v-if="!accountInfo || accountInfo.settings.enableFunctions.includes(FunctionTypes.Queue)" animated
display-directive="show:lazy">
<NTabPane name="list" tab="列表">
<NTabs
v-if="!accountInfo || accountInfo.settings.enableFunctions.includes(FunctionTypes.Queue)"
animated
display-directive="show:lazy"
>
<NTabPane
name="list"
tab="列表"
>
<NCard size="small">
<NSpace align="center">
<NTag type="success" :bordered="false">
<NTag
type="success"
:bordered="false"
>
<template #icon>
<NIcon :component="PeopleQueue24Filled" />
</template>
队列 | {{queue.filter((s) => s.status == QueueStatus.Waiting).length}}
队列 | {{ queue.filter((s) => s.status == QueueStatus.Waiting).length }}
</NTag>
<NTag type="success" :bordered="false">
<NTag
type="success"
:bordered="false"
>
<template #icon>
<NIcon :component="Checkmark12Regular" />
</template>
今日已处理 |
{{queue.filter((s) => s.status == QueueStatus.Finish && isSameDay(s.finishAt ?? 0, Date.now())).length}}
{{ queue.filter((s) => s.status == QueueStatus.Finish && isSameDay(s.finishAt ?? 0, Date.now())).length }}
</NTag>
<NInputGroup>
<NInput placeholder="手动添加" v-model:value="newQueueName" />
<NButton type="primary" @click="addManual"> 添加 </NButton>
<NInput
v-model:value="newQueueName"
placeholder="手动添加"
/>
<NButton
type="primary"
@click="addManual"
>
添加
</NButton>
</NInputGroup>
<NPopconfirm @positive-click="deactiveAllSongs">
<template #trigger>
<NButton type="error"> 全部取消 </NButton>
<NButton type="error">
全部取消
</NButton>
</template>
确定全部取消吗?
</NPopconfirm>
<NRadioGroup v-model:value="settings.sortType" :disabled="!configCanEdit" @update:value="updateSettings"
type="button">
<NRadioButton :value="QueueSortType.TimeFirst"> 加入时间优先 </NRadioButton>
<NRadioButton :value="QueueSortType.PaymentFist"> 付费价格优先 </NRadioButton>
<NRadioButton :value="QueueSortType.GuardFirst"> 舰长优先 (按等级) </NRadioButton>
<NRadioButton :value="QueueSortType.FansMedalFirst"> 粉丝牌等级优先 </NRadioButton>
<NRadioGroup
v-model:value="settings.sortType"
:disabled="!configCanEdit"
type="button"
@update:value="updateSettings"
>
<NRadioButton :value="QueueSortType.TimeFirst">
加入时间优先
</NRadioButton>
<NRadioButton :value="QueueSortType.PaymentFist">
付费价格优先
</NRadioButton>
<NRadioButton :value="QueueSortType.GuardFirst">
舰长优先 (按等级)
</NRadioButton>
<NRadioButton :value="QueueSortType.FansMedalFirst">
粉丝牌等级优先
</NRadioButton>
</NRadioGroup>
<NCheckbox v-if="configCanEdit" v-model:checked="settings.isReverse" @update:checked="updateSettings">
<NCheckbox
v-if="configCanEdit"
v-model:checked="settings.isReverse"
@update:checked="updateSettings"
>
倒序
</NCheckbox>
<NCheckbox
v-else
v-model:checked="isReverse"
>
倒序
</NCheckbox>
<NCheckbox v-else v-model:checked="isReverse"> 倒序 </NCheckbox>
</NSpace>
</NCard>
<NDivider> {{ queue.length }} </NDivider>
<NList v-if="queue.length > 0" :show-divider="false" hoverable>
<NListItem v-for="(queueData, index) in queue" :key="queueData.id" style="padding: 5px">
<NCard embedded size="small" content-style="padding: 5px;"
:style="`${queueData.status == QueueStatus.Progressing ? 'animation: animated-border 2.5s infinite;' : ''};height: 100%;`">
<NSpace justify="space-between" align="center" style="height: 100%; margin: 0 5px 0 5px">
<NList
v-if="queue.length > 0"
:show-divider="false"
hoverable
>
<NListItem
v-for="(queueData, index) in queue"
:key="queueData.id"
style="padding: 5px"
>
<NCard
embedded
size="small"
content-style="padding: 5px;"
:style="`${queueData.status == QueueStatus.Progressing ? 'animation: animated-border 2.5s infinite;' : ''};height: 100%;`"
>
<NSpace
justify="space-between"
align="center"
style="height: 100%; margin: 0 5px 0 5px"
>
<NSpace align="center">
<div
:style="`border-radius: 4px; background-color: ${queueData.status == QueueStatus.Progressing ? '#75c37f' : '#577fb8'}; width: 20px; height: 20px;text-align: center;color: white;`">
:style="`border-radius: 4px; background-color: ${queueData.status == QueueStatus.Progressing ? '#75c37f' : '#577fb8'}; width: 20px; height: 20px;text-align: center;color: white;`"
>
{{ index + 1 }}
</div>
<NText strong style="font-size: 18px">
<NText
strong
style="font-size: 18px"
>
<NTooltip>
<template #trigger>
{{ queueData.user?.name }}
@@ -855,14 +945,28 @@ onUnmounted(() => {
</NTooltip>
</NText>
<template v-if="queueData.from == QueueFrom.Manual">
<NTag size="small" :bordered="false"> 手动添加 </NTag>
<NTag
size="small"
:bordered="false"
>
手动添加
</NTag>
</template>
<NSpace v-if="
(queueData.from == QueueFrom.Danmaku || queueData.from == QueueFrom.Gift) &&
queueData.user?.fans_medal_wearing_status
">
<NTag size="tiny" round>
<NTag size="tiny" round :bordered="false">
<NSpace
v-if="
(queueData.from == QueueFrom.Danmaku || queueData.from == QueueFrom.Gift) &&
queueData.user?.fans_medal_wearing_status
"
>
<NTag
size="tiny"
round
>
<NTag
size="tiny"
round
:bordered="false"
>
<NText depth="3">
{{ queueData.user?.fans_medal_level }}
</NText>
@@ -872,29 +976,51 @@ onUnmounted(() => {
</span>
</NTag>
</NSpace>
<NTag v-if="(queueData.user?.guard_level ?? 0) > 0" size="small" :bordered="false"
:color="{ textColor: 'white', color: GetGuardColor(queueData.user?.guard_level) }">
<NTag
v-if="(queueData.user?.guard_level ?? 0) > 0"
size="small"
:bordered="false"
:color="{ textColor: 'white', color: GetGuardColor(queueData.user?.guard_level) }"
>
{{ queueData.user?.guard_level == 1 ? '总督' : queueData.user?.guard_level == 2 ? '提督' : '舰长' }}
</NTag>
<NTag v-if="(queueData.giftPrice ?? 0) > 0" size="small" :bordered="false" type="error">
<NTag
v-if="(queueData.giftPrice ?? 0) > 0"
size="small"
:bordered="false"
type="error"
>
付费 | {{ queueData.giftPrice }}
</NTag>
<NTooltip>
<template #trigger>
<NText style="font-size: small">
<NTime :time="queueData.createAt" type="relative" :key="updateKey" />
<NTime
:key="updateKey"
:time="queueData.createAt"
type="relative"
/>
</NText>
</template>
<NTime :time="queueData.createAt" />
</NTooltip>
<NTooltip v-if="queueData.content" content-style="margin: 0">
<NTooltip
v-if="queueData.content"
content-style="margin: 0"
>
<template #trigger>
<NText strong style="font-size: 18px">
<NText
strong
style="font-size: 18px"
>
<NIcon :component="Info24Filled" />
</NText>
</template>
<NCard size="small" :bordered="false">
<NCard
size="small"
:bordered="false"
>
<template #header>
<span style="font-size: small; color: gray;">
{{ '来自' + (queueData?.from == QueueFrom.Gift ? '礼物' : '弹幕') + ': ' }}
@@ -904,18 +1030,28 @@ onUnmounted(() => {
</NCard>
</NTooltip>
</NSpace>
<NSpace justify="end" align="center">
<NSpace
justify="end"
align="center"
>
<NTooltip>
<template #trigger>
<NButton circle type="primary" style="height: 30px; width: 30px" :disabled="queue.findIndex((s) => s.id != queueData.id && s.status == QueueStatus.Progressing) > -1
" @click="
<NButton
circle
type="primary"
style="height: 30px; width: 30px"
:disabled="queue.findIndex((s) => s.id != queueData.id && s.status == QueueStatus.Progressing) > -1
"
:style="`animation: ${queueData.status == QueueStatus.Waiting ? '' : 'loading 5s linear infinite'}`"
:secondary="queueData.status == QueueStatus.Progressing"
:loading="isLoading"
@click="
updateStatus(
queueData,
queueData.status == QueueStatus.Progressing ? QueueStatus.Waiting : QueueStatus.Progressing,
)
"
:style="`animation: ${queueData.status == QueueStatus.Waiting ? '' : 'loading 5s linear infinite'}`"
:secondary="queueData.status == QueueStatus.Progressing" :loading="isLoading">
"
>
<template #icon>
<NIcon :component="ClipboardTextLtr24Filled" />
</template>
@@ -931,8 +1067,13 @@ onUnmounted(() => {
</NTooltip>
<NTooltip>
<template #trigger>
<NButton circle type="success" style="height: 30px; width: 30px" :loading="isLoading"
@click="updateStatus(queueData, QueueStatus.Finish)">
<NButton
circle
type="success"
style="height: 30px; width: 30px"
:loading="isLoading"
@click="updateStatus(queueData, QueueStatus.Finish)"
>
<template #icon>
<NIcon :component="Checkmark12Regular" />
</template>
@@ -944,7 +1085,12 @@ onUnmounted(() => {
<template #trigger>
<NPopconfirm @positive-click="blockUser(queueData)">
<template #trigger>
<NButton circle type="warning" style="height: 30px; width: 30px" :loading="isLoading">
<NButton
circle
type="warning"
style="height: 30px; width: 30px"
:loading="isLoading"
>
<template #icon>
<NIcon :component="PresenceBlocked16Regular" />
</template>
@@ -957,8 +1103,13 @@ onUnmounted(() => {
</NTooltip>
<NTooltip>
<template #trigger>
<NButton circle type="error" style="height: 30px; width: 30px" :loading="isLoading"
@click="updateStatus(queueData, QueueStatus.Cancel)">
<NButton
circle
type="error"
style="height: 30px; width: 30px"
:loading="isLoading"
@click="updateStatus(queueData, QueueStatus.Cancel)"
>
<template #icon>
<NIcon :component="Dismiss16Filled" />
</template>
@@ -971,31 +1122,51 @@ onUnmounted(() => {
</NCard>
</NListItem>
</NList>
<NEmpty v-else description="暂无用户" />
<NEmpty
v-else
description="暂无用户"
/>
</NTabPane>
<NTabPane name="history" tab="历史">
<NTabPane
name="history"
tab="历史"
>
<NCard size="small">
<NSpace>
<NInputGroup style="width: 300px">
<NInputGroupLabel> 筛选用户 </NInputGroupLabel>
<NInput v-model:value="filterName" clearable>
<NInput
v-model:value="filterName"
clearable
>
<template #suffix>
<NCheckbox v-model:checked="filterNameContains"> 包含 </NCheckbox>
<NCheckbox v-model:checked="filterNameContains">
包含
</NCheckbox>
</template>
</NInput>
</NInputGroup>
</NSpace>
</NCard>
<NDataTable size="small" ref="table" :columns="columns" :data="originQueue" :pagination="{
itemCount: originQueue.length,
pageSizes: [20, 50, 100],
showSizePicker: true,
prefix({ itemCount }) {
return `共 ${itemCount} 条记录`
},
}" />
<NDataTable
ref="table"
size="small"
:columns="columns"
:data="originQueue"
:pagination="{
itemCount: originQueue.length,
pageSizes: [20, 50, 100],
showSizePicker: true,
prefix({ itemCount }) {
return `共 ${itemCount} 条记录`
},
}"
/>
</NTabPane>
<NTabPane name="setting" tab="设置">
<NTabPane
name="setting"
tab="设置"
>
<NSpin :show="isLoading">
<NDivider> 规则 </NDivider>
<NSpace vertical>
@@ -1004,77 +1175,164 @@ onUnmounted(() => {
<NInputGroupLabel> 加入队列关键词 </NInputGroupLabel>
<template v-if="configCanEdit">
<NInput v-model:value="settings.keyword" />
<NButton @click="updateSettings" type="primary">确定</NButton>
<NButton
type="primary"
@click="updateSettings"
>
确定
</NButton>
</template>
<NInput v-else v-model:value="defaultKeyword" />
<NInput
v-else
v-model:value="defaultKeyword"
/>
</NInputGroup>
<NRadioGroup v-model:value="settings.matchType" :disabled="!configCanEdit" @update:value="updateSettings"
type="button">
<NRadioButton :value="KeywordMatchType.Full"> 完全一致 </NRadioButton>
<NRadioButton :value="KeywordMatchType.Contains"> 包含 </NRadioButton>
<NRadioButton :value="KeywordMatchType.Regex"> 正则 </NRadioButton>
<NRadioGroup
v-model:value="settings.matchType"
:disabled="!configCanEdit"
type="button"
@update:value="updateSettings"
>
<NRadioButton :value="KeywordMatchType.Full">
完全一致
</NRadioButton>
<NRadioButton :value="KeywordMatchType.Contains">
包含
</NRadioButton>
<NRadioButton :value="KeywordMatchType.Regex">
正则
</NRadioButton>
</NRadioGroup>
</NSpace>
<NInputGroup style="width: 250px">
<NInputGroupLabel> 最大队列长度 </NInputGroupLabel>
<NInputNumber v-model:value="settings.queueMaxSize" :disabled="!configCanEdit" min="0" max="1000" />
<NButton @click="updateSettings" type="info" :disabled="!configCanEdit">确定</NButton>
<NInputNumber
v-model:value="settings.queueMaxSize"
:disabled="!configCanEdit"
min="0"
max="1000"
/>
<NButton
type="info"
:disabled="!configCanEdit"
@click="updateSettings"
>
确定
</NButton>
</NInputGroup>
<NSpace align="center">
<NCheckbox v-model:checked="settings.enableOnStreaming" @update:checked="updateSettings"
:disabled="!configCanEdit">
<NCheckbox
v-model:checked="settings.enableOnStreaming"
:disabled="!configCanEdit"
@update:checked="updateSettings"
>
仅在直播时才允许加入
</NCheckbox>
<NCheckbox v-model:checked="settings.allowAllDanmaku" @update:checked="updateSettings"
:disabled="!configCanEdit">
<NCheckbox
v-model:checked="settings.allowAllDanmaku"
:disabled="!configCanEdit"
@update:checked="updateSettings"
>
允许所有用户加入
</NCheckbox>
<template v-if="!settings.allowAllDanmaku">
<NInputGroup style="width: 270px">
<NInputGroupLabel> 最低粉丝牌等级 </NInputGroupLabel>
<NInputNumber v-model:value="settings.fanMedalMinLevel" :disabled="!configCanEdit" min="0" />
<NButton @click="updateSettings" type="info" :disabled="!configCanEdit">确定</NButton>
<NInputNumber
v-model:value="settings.fanMedalMinLevel"
:disabled="!configCanEdit"
min="0"
/>
<NButton
type="info"
:disabled="!configCanEdit"
@click="updateSettings"
>
确定
</NButton>
</NInputGroup>
<NCheckbox v-if="!settings.allowAllDanmaku" v-model:checked="settings.needJianzhang"
@update:checked="updateSettings" :disabled="!configCanEdit">
<NCheckbox
v-if="!settings.allowAllDanmaku"
v-model:checked="settings.needJianzhang"
:disabled="!configCanEdit"
@update:checked="updateSettings"
>
允许舰长
</NCheckbox>
<NCheckbox v-if="!settings.allowAllDanmaku" v-model:checked="settings.needTidu"
@update:checked="updateSettings" :disabled="!configCanEdit">
<NCheckbox
v-if="!settings.allowAllDanmaku"
v-model:checked="settings.needTidu"
:disabled="!configCanEdit"
@update:checked="updateSettings"
>
允许提督
</NCheckbox>
<NCheckbox v-if="!settings.allowAllDanmaku" v-model:checked="settings.needZongdu"
@update:checked="updateSettings" :disabled="!configCanEdit">
<NCheckbox
v-if="!settings.allowAllDanmaku"
v-model:checked="settings.needZongdu"
:disabled="!configCanEdit"
@update:checked="updateSettings"
>
允许总督
</NCheckbox>
</template>
</NSpace>
<NSpace align="center">
<NCheckbox v-model:checked="settings.allowGift" @update:checked="updateSettings"
:disabled="!configCanEdit">
<NCheckbox
v-model:checked="settings.allowGift"
:disabled="!configCanEdit"
@update:checked="updateSettings"
>
允许通过发送礼物加入队列
</NCheckbox>
<template v-if="settings.allowGift">
<NInputGroup v-if="settings.allowGift" style="width: 250px">
<NInputGroup
v-if="settings.allowGift"
style="width: 250px"
>
<NInputGroupLabel> 最低价格 </NInputGroupLabel>
<NInputNumber v-model:value="settings.minGiftPrice" :disabled="!configCanEdit" />
<NButton @click="updateSettings" type="info" :disabled="!configCanEdit">确定</NButton>
<NInputNumber
v-model:value="settings.minGiftPrice"
:disabled="!configCanEdit"
/>
<NButton
type="info"
:disabled="!configCanEdit"
@click="updateSettings"
>
确定
</NButton>
</NInputGroup>
<NSpace align="center">
礼物名
<NSelect style="width: 250px" v-model:value="settings.giftNames" :disabled="!configCanEdit" filterable
multiple tag placeholder="礼物名称,按回车确认" :show-arrow="false" :show="false"
@update:value="updateSettings" />
<NSelect
v-model:value="settings.giftNames"
style="width: 250px"
:disabled="!configCanEdit"
filterable
multiple
tag
placeholder="礼物名称,按回车确认"
:show-arrow="false"
:show="false"
@update:value="updateSettings"
/>
</NSpace>
<span>
<NRadioGroup v-model:value="settings.giftFilterType" :disabled="!configCanEdit"
@update:value="updateSettings">
<NRadioGroup
v-model:value="settings.giftFilterType"
:disabled="!configCanEdit"
@update:value="updateSettings"
>
<NRadioButton :value="QueueGiftFilterType.And"> 需同时满足礼物名和价格 </NRadioButton>
<NRadioButton :value="QueueGiftFilterType.Or"> 礼物名/价格 二选一 </NRadioButton>
</NRadioGroup>
</span>
<NCheckbox v-model:checked="settings.sendGiftDirectJoin" @update:checked="updateSettings"
:disabled="!configCanEdit">
<NCheckbox
v-model:checked="settings.sendGiftDirectJoin"
:disabled="!configCanEdit"
@update:checked="updateSettings"
>
赠送礼物后自动加入队列
<NTooltip>
<template #trigger>
@@ -1084,78 +1342,152 @@ onUnmounted(() => {
</NTooltip>
</NCheckbox>
<NCheckbox v-model:checked="settings.sendGiftIgnoreLimit" @update:checked="updateSettings"
:disabled="!configCanEdit">
<NCheckbox
v-model:checked="settings.sendGiftIgnoreLimit"
:disabled="!configCanEdit"
@update:checked="updateSettings"
>
赠送礼物后无视用户等级限制
</NCheckbox>
</template>
<NCheckbox v-model:checked="settings.allowIncreasePaymentBySendGift" @update:checked="updateSettings"
:disabled="!configCanEdit">
<NCheckbox
v-model:checked="settings.allowIncreasePaymentBySendGift"
:disabled="!configCanEdit"
@update:checked="updateSettings"
>
在队列中时允许继续发送礼物累计付费量 (仅限上方设定的礼物)
</NCheckbox>
<NCheckbox v-if="settings.allowIncreasePaymentBySendGift"
v-model:checked="settings.allowIncreaseByAnyPayment" @update:checked="updateSettings"
:disabled="!configCanEdit">
<NCheckbox
v-if="settings.allowIncreasePaymentBySendGift"
v-model:checked="settings.allowIncreaseByAnyPayment"
:disabled="!configCanEdit"
@update:checked="updateSettings"
>
允许发送任意礼物来叠加付费量
</NCheckbox>
</NSpace>
<NDivider> 冷却 (单位: 秒) </NDivider>
<NCheckbox v-model:checked="settings.enableCooldown" @update:checked="updateSettings"
:disabled="!configCanEdit">
<NCheckbox
v-model:checked="settings.enableCooldown"
:disabled="!configCanEdit"
@update:checked="updateSettings"
>
启用排队冷却
</NCheckbox>
<NSpace v-if="settings.enableCooldown">
<NInputGroup style="width: 250px">
<NInputGroupLabel> 普通弹幕 </NInputGroupLabel>
<NInputNumber v-model:value="settings.cooldownSecond" :disabled="!configCanEdit" />
<NButton @click="updateSettings" type="info" :disabled="!configCanEdit">确定</NButton>
<NInputNumber
v-model:value="settings.cooldownSecond"
:disabled="!configCanEdit"
/>
<NButton
type="info"
:disabled="!configCanEdit"
@click="updateSettings"
>
确定
</NButton>
</NInputGroup>
<NInputGroup style="width: 220px">
<NInputGroupLabel> 舰长 </NInputGroupLabel>
<NInputNumber v-model:value="settings.jianzhangCooldownSecond" :disabled="!configCanEdit" />
<NButton @click="updateSettings" type="info" :disabled="!configCanEdit">确定</NButton>
<NInputNumber
v-model:value="settings.jianzhangCooldownSecond"
:disabled="!configCanEdit"
/>
<NButton
type="info"
:disabled="!configCanEdit"
@click="updateSettings"
>
确定
</NButton>
</NInputGroup>
<NInputGroup style="width: 220px">
<NInputGroupLabel> 提督 </NInputGroupLabel>
<NInputNumber v-model:value="settings.tiduCooldownSecond" :disabled="!configCanEdit" />
<NButton @click="updateSettings" type="info" :disabled="!configCanEdit">确定</NButton>
<NInputNumber
v-model:value="settings.tiduCooldownSecond"
:disabled="!configCanEdit"
/>
<NButton
type="info"
:disabled="!configCanEdit"
@click="updateSettings"
>
确定
</NButton>
</NInputGroup>
<NInputGroup style="width: 220px">
<NInputGroupLabel> 总督 </NInputGroupLabel>
<NInputNumber v-model:value="settings.zongduCooldownSecond" :disabled="!configCanEdit" />
<NButton @click="updateSettings" type="info" :disabled="!configCanEdit">确定</NButton>
<NInputNumber
v-model:value="settings.zongduCooldownSecond"
:disabled="!configCanEdit"
/>
<NButton
type="info"
:disabled="!configCanEdit"
@click="updateSettings"
>
确定
</NButton>
</NInputGroup>
</NSpace>
<NDivider> OBS </NDivider>
<NCheckbox v-model:checked="settings.showRequireInfo" :disabled="!configCanEdit"
@update:checked="updateSettings">
<NCheckbox
v-model:checked="settings.showRequireInfo"
:disabled="!configCanEdit"
@update:checked="updateSettings"
>
显示底部的需求信息
</NCheckbox>
<NCheckbox v-model:checked="settings.showPayment" :disabled="!configCanEdit"
@update:checked="updateSettings">
<NCheckbox
v-model:checked="settings.showPayment"
:disabled="!configCanEdit"
@update:checked="updateSettings"
>
显示付费信息
</NCheckbox>
<NCheckbox v-model:checked="settings.showFanMadelInfo" :disabled="!configCanEdit"
@update:checked="updateSettings">
<NCheckbox
v-model:checked="settings.showFanMadelInfo"
:disabled="!configCanEdit"
@update:checked="updateSettings"
>
显示用户粉丝牌
</NCheckbox>
<NDivider> 其他 </NDivider>
<NCheckbox v-model:checked="isWarnMessageAutoClose"> 自动关闭加入队列失败时的提示消息 </NCheckbox>
<NCheckbox v-model:checked="isWarnMessageAutoClose">
自动关闭加入队列失败时的提示消息
</NCheckbox>
</NSpace>
</NSpin>
</NTabPane>
</NTabs>
<template v-else>
<NAlert title="未启用" type="error"> 请先启用弹幕排队功能 </NAlert>
<NAlert
title="未启用"
type="error"
>
请先启用弹幕排队功能
</NAlert>
</template>
</NCard>
<NModal v-model:show="showOBSModal" title="OBS组件" preset="card" style="width: 800px">
<NAlert title="这是什么? " type="info"> 将等待队列以及结果显示在OBS中 </NAlert>
<NModal
v-model:show="showOBSModal"
title="OBS组件"
preset="card"
style="width: 800px"
>
<NAlert
title="这是什么? "
type="info"
>
将等待队列以及结果显示在OBS中
</NAlert>
<NDivider> 浏览 </NDivider>
<div style="height: 500px; width: 280px; position: relative; margin: 0 auto">
<QueueOBS :id="accountInfo?.id" />
</div>
<br />
<br>
<NInput :value="`${CURRENT_HOST}obs/queue?id=` + accountInfo?.id" />
<NDivider />
<NCollapse>

View File

@@ -585,60 +585,110 @@ onUnmounted(() => {
</script>
<template>
<NAlert v-if="!speechSynthesisInfo || !speechSynthesisInfo.speechSynthesis" type="error">
<NAlert
v-if="!speechSynthesisInfo || !speechSynthesisInfo.speechSynthesis"
type="error"
>
你的浏览器不支持语音功能
</NAlert>
<template v-else>
<NSpace vertical>
<NAlert v-if="settings.voiceType == 'local'" type="info" closeable>
<NAlert
v-if="settings.voiceType == 'local'"
type="info"
closeable
>
建议在 Edge 浏览器使用
<NTooltip>
<template #trigger>
<NText strong italic type="primary">Microsoft 某某 Online (Nature)</NText>
<NText
strong
italic
type="primary"
>
Microsoft 某某 Online (Nature)
</NText>
</template>
例如 Microsoft Xiaoxiao Online (Natural) - Chinese (Mainland), 各种营销号就用的这些配音
</NTooltip>
系列语音, 效果要好
<NText strong>很多很多</NText>
<NText strong>
很多很多
</NText>
</NAlert>
<NAlert type="info" closeable>
<NAlert
type="info"
closeable
>
当在后台运行时请关闭浏览器的 页面休眠/内存节省功能. Chrome:
<NButton tag="a" type="info"
<NButton
tag="a"
type="info"
href="https://support.google.com/chrome/answer/12929150?hl=zh-Hans#zippy=%2C%E5%BC%80%E5%90%AF%E6%88%96%E5%85%B3%E9%97%AD%E7%9C%81%E5%86%85%E5%AD%98%E6%A8%A1%E5%BC%8F%2C%E8%AE%A9%E7%89%B9%E5%AE%9A%E7%BD%91%E7%AB%99%E4%BF%9D%E6%8C%81%E6%B4%BB%E5%8A%A8%E7%8A%B6%E6%80%81"
target="_blank" text>
target="_blank"
text
>
让特定网站保持活动状态
</NButton>
Edge:
<NButton tag="a" type="info"
<NButton
tag="a"
type="info"
href="https://support.microsoft.com/zh-cn/topic/%E4%BA%86%E8%A7%A3-microsoft-edge-%E4%B8%AD%E7%9A%84%E6%80%A7%E8%83%BD%E5%8A%9F%E8%83%BD-7b36f363-2119-448a-8de6-375cfd88ab25"
target="_blank" text>
target="_blank"
text
>
永远不想进入睡眠状态的网站
</NButton>
</NAlert>
</NSpace>
<br />
<br>
<NSpace align="center">
<NButton @click="canSpeech ? stopSpeech() : startSpeech()" :type="canSpeech ? 'error' : 'primary'"
data-umami-event="Use TTS" :data-umami-event-uid="accountInfo?.id" size="large">
<NButton
:type="canSpeech ? 'error' : 'primary'"
data-umami-event="Use TTS"
:data-umami-event-uid="accountInfo?.id"
size="large"
@click="canSpeech ? stopSpeech() : startSpeech()"
>
{{ canSpeech ? '停止监听' : '开始监听' }}
</NButton>
<NButton @click="uploadConfig" type="primary" secondary :disabled="!accountInfo" size="small">
<NButton
type="primary"
secondary
:disabled="!accountInfo"
size="small"
@click="uploadConfig"
>
保存配置到服务器
</NButton>
<NPopconfirm @positive-click="downloadConfig">
<template #trigger>
<NButton type="primary" secondary :disabled="!accountInfo" size="small"> 从服务器获取配置 </NButton>
<NButton
type="primary"
secondary
:disabled="!accountInfo"
size="small"
>
从服务器获取配置
</NButton>
</template>
这将覆盖当前设置, 确定?
</NPopconfirm>
</NSpace>
<template v-if="canSpeech">
<NDivider> 状态 </NDivider>
<NSpace vertical align="center">
<NSpace
vertical
align="center"
>
<NTooltip v-if="settings.voiceType == 'api' && isApiAudioLoading">
<template #trigger>
<NButton circle @click="cancelSpeech">
<NButton
circle
@click="cancelSpeech"
>
<template #icon>
<NSpin show />
</template>
@@ -648,40 +698,96 @@ onUnmounted(() => {
</NTooltip>
<NTooltip v-else>
<template #trigger>
<NButton circle :disabled="!isSpeaking" @click="cancelSpeech"
:style="`animation: ${isSpeaking ? 'animated-border 2.5s infinite;' : ''}`">
<NButton
circle
:disabled="!isSpeaking"
:style="`animation: ${isSpeaking ? 'animated-border 2.5s infinite;' : ''}`"
@click="cancelSpeech"
>
<template #icon>
<NIcon :component="Mic24Filled" :color="isSpeaking ? 'green' : 'gray'" />
<NIcon
:component="Mic24Filled"
:color="isSpeaking ? 'green' : 'gray'"
/>
</template>
</NButton>
</template>
{{ isSpeaking ? '取消朗读' : '未朗读' }}
</NTooltip>
<NText depth="3"> 队列: {{ speakQueue.length }}
<NText depth="3">
队列: {{ speakQueue.length }}
<NDivider vertical /> 已读: {{ readedDanmaku }}
</NText>
<NCollapse :default-expanded-names="['1']">
<NCollapseItem title="队列" name="1">
<NEmpty v-if="speakQueue.length == 0"> 暂无 </NEmpty>
<NList v-else size="small" bordered>
<NListItem v-for="item in speakQueue" :key="item.data.time">
<NCollapseItem
title="队列"
name="1"
>
<NEmpty v-if="speakQueue.length == 0">
暂无
</NEmpty>
<NList
v-else
size="small"
bordered
>
<NListItem
v-for="item in speakQueue"
:key="item.data.time"
>
<NSpace align="center">
<NButton @click="forceSpeak(item.data)" type="primary" secondary size="small"> </NButton>
<NButton @click="speakQueue.splice(speakQueue.indexOf(item), 1)" type="error" secondary size="small">
<NButton
type="primary"
secondary
size="small"
@click="forceSpeak(item.data)"
>
</NButton>
<NButton
type="error"
secondary
size="small"
@click="speakQueue.splice(speakQueue.indexOf(item), 1)"
>
取消
</NButton>
<NTag v-if="item.data.type == EventDataTypes.Gift && item.combineCount" type="info" size="small"
style="animation: animated-border 2.5s infinite">
连续赠送中</NTag>
<NTag v-else-if="item.data.type == EventDataTypes.Gift && settings.combineGiftDelay" type="success"
size="small">
<NTag
v-if="item.data.type == EventDataTypes.Gift && item.combineCount"
type="info"
size="small"
style="animation: animated-border 2.5s infinite"
>
连续赠送中
</NTag>
<NTag
v-else-if="item.data.type == EventDataTypes.Gift && settings.combineGiftDelay"
type="success"
size="small"
>
等待连续赠送检查
</NTag>
<span>
<NTag v-if="item.data.type == EventDataTypes.Message" type="success" size="small"> 弹幕</NTag>
<NTag v-else-if="item.data.type == EventDataTypes.Gift" type="success" size="small"> 礼物</NTag>
<NTag v-else-if="item.data.type == EventDataTypes.Guard" type="success" size="small"> 舰长</NTag>
<NTag v-else-if="item.data.type == EventDataTypes.SC" type="success" size="small"> SC</NTag>
<NTag
v-if="item.data.type == EventDataTypes.Message"
type="success"
size="small"
> 弹幕</NTag>
<NTag
v-else-if="item.data.type == EventDataTypes.Gift"
type="success"
size="small"
> 礼物</NTag>
<NTag
v-else-if="item.data.type == EventDataTypes.Guard"
type="success"
size="small"
> 舰长</NTag>
<NTag
v-else-if="item.data.type == EventDataTypes.SC"
type="success"
size="small"
> SC</NTag>
</span>
<NText>
{{ item.data.name }}
@@ -697,8 +803,13 @@ onUnmounted(() => {
</NSpace>
</template>
<NDivider>
<NRadioGroup v-model:value="settings.voiceType" size="small">
<NRadioButton value="local">本地</NRadioButton>
<NRadioGroup
v-model:value="settings.voiceType"
size="small"
>
<NRadioButton value="local">
本地
</NRadioButton>
<NRadioButton value="api">
API
@@ -711,27 +822,57 @@ onUnmounted(() => {
</NRadioButton>
</NRadioGroup>
</NDivider>
<Transition name="fade" mode="out-in">
<NSpace v-if="settings.voiceType == 'local'" vertical>
<NSelect v-model:value="settings.speechInfo.voice" :options="voiceOptions"
:fallback-option="() => ({ label: '未选择, 将使用默认语音', value: '' })" />
<Transition
name="fade"
mode="out-in"
>
<NSpace
v-if="settings.voiceType == 'local'"
vertical
>
<NSelect
v-model:value="settings.speechInfo.voice"
:options="voiceOptions"
:fallback-option="() => ({ label: '未选择, 将使用默认语音', value: '' })"
/>
<span style="width: 100%">
<NText> 音量 </NText>
<NSlider style="min-width: 200px" v-model:value="settings.speechInfo.volume" :min="0" :max="1" :step="0.01" />
<NSlider
v-model:value="settings.speechInfo.volume"
style="min-width: 200px"
:min="0"
:max="1"
:step="0.01"
/>
</span>
<span style="width: 100%">
<NText> 音调 </NText>
<NSlider style="min-width: 200px" v-model:value="settings.speechInfo.pitch" :min="0" :max="2" :step="0.01" />
<NSlider
v-model:value="settings.speechInfo.pitch"
style="min-width: 200px"
:min="0"
:max="2"
:step="0.01"
/>
</span>
<span style="width: 100%">
<NText> 语速 </NText>
<NSlider style="min-width: 200px" v-model:value="settings.speechInfo.rate" :min="0" :max="2" :step="0.01" />
<NSlider
v-model:value="settings.speechInfo.rate"
style="min-width: 200px"
:min="0"
:max="2"
:step="0.01"
/>
</span>
</NSpace>
<template v-else>
<div>
<NCollapse>
<NCollapseItem title="要求 👀 " name="1">
<NCollapseItem
title="要求 👀 "
name="1"
>
<NUl>
<NLi> 直接返回音频数据 (wav, mp3, m4a etc.) </NLi>
<NLi>
@@ -746,49 +887,88 @@ onUnmounted(() => {
<NLi> 指定API可以被外部访问 (除非你本地部署并且启用了https) </NLi>
</NUl>
推荐项目, 可以用于本地部署:
<NButton text type="info" tag="a" href="https://github.com/Artrajz/vits-simple-api" target="_blank">
<NButton
text
type="info"
tag="a"
href="https://github.com/Artrajz/vits-simple-api"
target="_blank"
>
vits-simple-api
</NButton>
</NCollapseItem>
</NCollapse>
<br />
<br>
<NSpace vertical>
<NAlert type="info">
地址中的
<NButton @click="copyToClipboard('{{text}}')" size="tiny" :bordered="false" type="primary" secondary>
<NButton
size="tiny"
:bordered="false"
type="primary"
secondary
@click="copyToClipboard('{{text}}')"
>
{{ '\{\{ text \}\}' }}
</NButton>
将被替换为要念的文本
</NAlert>
<NAlert v-if="isVtsuruVoiceAPI" type="success" closable>
<NAlert
v-if="isVtsuruVoiceAPI"
type="success"
closable
>
看起来你正在使用本站提供的测试API (voice.vtsuru.live), 这个接口将会返回
<NButton text type="info" tag="a" href="https://space.bilibili.com/5859321" target="_blank">
<NButton
text
type="info"
tag="a"
href="https://space.bilibili.com/5859321"
target="_blank"
>
Xz乔希
</NButton>
训练的
<NTooltip>
<template #trigger> Taffy </template>
<template #trigger>
Taffy
</template>
链接里的 id 改成 0 会变成莲莲捏🥰
</NTooltip>
模型结果, 不支持部分英文, 仅用于测试, 用的人多的时候会比较慢, 不保证可用性. 侵删
</NAlert>
</NSpace>
<br />
<br>
<NInputGroup>
<NSelect v-model:value="settings.voiceAPISchemeType" :options="[
{ label: 'https://', value: 'https' },
{ label: 'http://', value: 'http' },
]" style="width: 110px" />
<NInput v-model:value="settings.voiceAPI"
<NSelect
v-model:value="settings.voiceAPISchemeType"
:options="[
{ label: 'https://', value: 'https' },
{ label: 'http://', value: 'http' },
]"
style="width: 110px"
/>
<NInput
v-model:value="settings.voiceAPI"
placeholder="API 地址, 例如 xxx.com/voice/bert-vits2?text={{text}}&id=0 (前面不要带https://)"
:status="/^(?:https?:\/\/)/.test(settings.voiceAPI?.toLowerCase() ?? '') ? 'error' : 'success'" />
<NButton @click="testAPI" type="info" :loading="isApiAudioLoading"> 测试 </NButton>
:status="/^(?:https?:\/\/)/.test(settings.voiceAPI?.toLowerCase() ?? '') ? 'error' : 'success'"
/>
<NButton
type="info"
:loading="isApiAudioLoading"
@click="testAPI"
>
测试
</NButton>
</NInputGroup>
<br /><br />
<br><br>
<NSpace vertical>
<NAlert v-if="settings.voiceAPISchemeType == 'http'" type="info">
<NAlert
v-if="settings.voiceAPISchemeType == 'http'"
type="info"
>
不使用https的话默认将会使用 cloudflare workers 进行代理, 会慢很多
<br />
<br>
<NCheckbox v-model:checked="settings.useAPIDirectly">
不使用代理
<NTooltip>
@@ -801,12 +981,23 @@ onUnmounted(() => {
</NAlert>
<span style="width: 100%">
<NText> 音量 </NText>
<NSlider style="min-width: 200px" v-model:value="settings.speechInfo.volume" :min="0" :max="1"
:step="0.01" />
<NSlider
v-model:value="settings.speechInfo.volume"
style="min-width: 200px"
:min="0"
:max="1"
:step="0.01"
/>
</span>
</NSpace>
<audio ref="apiAudio" :src="apiAudioSrc" :volume="settings.speechInfo.volume" @ended="cancelSpeech"
@canplay="isApiAudioLoading = false" @error="onAPIError"></audio>
<audio
ref="apiAudio"
:src="apiAudioSrc"
:volume="settings.speechInfo.volume"
@ended="cancelSpeech"
@canplay="isApiAudioLoading = false"
@error="onAPIError"
/>
</div>
</template>
</Transition>
@@ -822,54 +1013,104 @@ onUnmounted(() => {
<NSpace vertical>
<NSpace>
支持的变量:
<NButton size="tiny" secondary v-for="item in Object.values(templateConstants)" :key="item.name"
@click="copyToClipboard(item.words)">
<NButton
v-for="item in Object.values(templateConstants)"
:key="item.name"
size="tiny"
secondary
@click="copyToClipboard(item.words)"
>
{{ item.words }} | {{ item.name }}
</NButton>
</NSpace>
<NInputGroup>
<NInputGroupLabel> 弹幕模板 </NInputGroupLabel>
<NInput v-model:value="settings.danmakuTemplate" placeholder="弹幕消息" />
<NButton @click="test(EventDataTypes.Message)" type="info" :loading="isApiAudioLoading"> 测试 </NButton>
<NInput
v-model:value="settings.danmakuTemplate"
placeholder="弹幕消息"
/>
<NButton
type="info"
:loading="isApiAudioLoading"
@click="test(EventDataTypes.Message)"
>
测试
</NButton>
</NInputGroup>
<NInputGroup>
<NInputGroupLabel> 礼物模板 </NInputGroupLabel>
<NInput v-model:value="settings.giftTemplate" placeholder="礼物消息" />
<NButton @click="test(EventDataTypes.Gift)" type="info" :loading="isApiAudioLoading"> 测试 </NButton>
<NInput
v-model:value="settings.giftTemplate"
placeholder="礼物消息"
/>
<NButton
type="info"
:loading="isApiAudioLoading"
@click="test(EventDataTypes.Gift)"
>
测试
</NButton>
</NInputGroup>
<NInputGroup>
<NInputGroupLabel> SC模板 </NInputGroupLabel>
<NInput v-model:value="settings.scTemplate" placeholder="SC消息" />
<NButton @click="test(EventDataTypes.SC)" type="info" :loading="isApiAudioLoading"> 测试 </NButton>
<NInput
v-model:value="settings.scTemplate"
placeholder="SC消息"
/>
<NButton
type="info"
:loading="isApiAudioLoading"
@click="test(EventDataTypes.SC)"
>
测试
</NButton>
</NInputGroup>
<NInputGroup>
<NInputGroupLabel> 上舰模板 </NInputGroupLabel>
<NInput v-model:value="settings.guardTemplate" placeholder="上舰消息" />
<NButton @click="test(EventDataTypes.Guard)" type="info" :loading="isApiAudioLoading"> 测试 </NButton>
<NInput
v-model:value="settings.guardTemplate"
placeholder="上舰消息"
/>
<NButton
type="info"
:loading="isApiAudioLoading"
@click="test(EventDataTypes.Guard)"
>
测试
</NButton>
</NInputGroup>
</NSpace>
<NDivider> 设置 </NDivider>
<NSpace align="center">
<NCheckbox :checked="settings.combineGiftDelay != undefined" @update:checked="(checked: boolean) => {
settings.combineGiftDelay = checked ? 2 : undefined
}
">
<NCheckbox
:checked="settings.combineGiftDelay != undefined"
@update:checked="(checked: boolean) => {
settings.combineGiftDelay = checked ? 2 : undefined
}
"
>
是否启用礼物合并
<NTooltip>
<template #trigger>
<NIcon :component="Info24Filled" />
</template>
在指定时间内连续送相同礼物会等停止送礼物之后才会念
<br />
<br>
这也会导致送的礼物会等待指定时间之后才会念, 即使没有连续赠送
</NTooltip>
</NCheckbox>
<NInputGroup v-if="settings.combineGiftDelay" style="width: 200px">
<NInputGroup
v-if="settings.combineGiftDelay"
style="width: 200px"
>
<NInputGroupLabel> 送礼间隔 () </NInputGroupLabel>
<NInputNumber v-model:value="settings.combineGiftDelay" @update:value="(value) => {
if (!value || value <= 0) settings.combineGiftDelay = undefined
}
" />
<NInputNumber
v-model:value="settings.combineGiftDelay"
@update:value="(value) => {
if (!value || value <= 0) settings.combineGiftDelay = undefined
}
"
/>
</NInputGroup>
<NCheckbox v-model:checked="settings.splitText">
启用句子拆分
@@ -878,9 +1119,9 @@ onUnmounted(() => {
<NIcon :component="Info24Filled" />
</template>
仅API方式可用, 为英文用户名用引号包裹起来, 并将所有大写单词拆分成单个单词, 以防止部分单词念不出来
<br />
<br>
: 原文: Megghy : UPPERCASE单词,word.
<br />
<br>
结果: 'Megghy' : U P P E R C A S E 单词,word.
</NTooltip>
</NCheckbox>