mirror of
https://github.com/Megghy/vtsuru.live.git
synced 2025-12-06 18:36:55 +08:00
refactor: 将 OBS 通知从 notification 改为 message 组件并优化点歌页面布局
- 将 OBS 通知组件从 n-notification 改为 n-message,简化显示逻辑 - 优化通知内容格式,将标题和元信息作为前缀显示 - 调整通知持续时间:成功 4 秒,错误 6 秒 - 重构点歌页面布局,将功能开关和 OBS 组件按钮移至顶部卡片 - 在 MinimalRequestOBS 组件中添加点歌要求信息显示(前缀、允许类型、SC、粉丝牌等) - 优化点歌队
This commit is contained in:
@@ -44,32 +44,30 @@ export const useOBSNotification = defineStore('obs-notification', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function showNotification(payload: ObsNotificationPayload) {
|
function showNotification(payload: ObsNotificationPayload) {
|
||||||
const notification = window.$notification
|
const message = window.$message
|
||||||
if (!notification) {
|
if (!message) {
|
||||||
console.warn('[OBS] notification instance missing')
|
console.warn('[OBS] message instance missing')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
console.log('[OBS] 收到通知', payload)
|
console.log('[OBS] 收到通知', payload)
|
||||||
|
|
||||||
const method = payload.Type === 'success' ? 'success' : 'error'
|
const method = payload.Type === 'success' ? 'success' : 'error'
|
||||||
const title = resolveTitle(payload)
|
const title = resolveTitle(payload)
|
||||||
const description = payload.Message || '未知通知'
|
|
||||||
const meta = resolveMeta(payload)
|
const meta = resolveMeta(payload)
|
||||||
|
const prefix = [title, meta].filter(Boolean).join(' · ')
|
||||||
|
const description = payload.Message || '未知通知'
|
||||||
|
const finalContent = prefix ? `${prefix}\n${description}` : description
|
||||||
|
|
||||||
if (typeof notification[method] === 'function') {
|
if (typeof message[method] === 'function') {
|
||||||
notification[method]({
|
message[method](finalContent, {
|
||||||
title: payload.Type === 'success' ? '成功' : `失败`,
|
duration: method === 'error' ? 6000 : 4000,
|
||||||
description,
|
closable: true,
|
||||||
duration: method === 'error' ? 8000 : 5000,
|
|
||||||
keepAliveOnHover: true,
|
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
notification.create({
|
message.create(finalContent, {
|
||||||
title: payload.Type === 'success' ? '成功' : `失败`,
|
|
||||||
content: description,
|
|
||||||
duration: method === 'error' ? 8000 : 5000,
|
|
||||||
keepAliveOnHover: true,
|
|
||||||
type: method,
|
type: method,
|
||||||
|
duration: method === 'error' ? 6000 : 4000,
|
||||||
|
closable: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ import { copyToClipboard } from '@/Utils'
|
|||||||
import PointOrderManage from './PointOrderManage.vue'
|
import PointOrderManage from './PointOrderManage.vue'
|
||||||
import PointSettings from './PointSettings.vue'
|
import PointSettings from './PointSettings.vue'
|
||||||
import PointUserManage from './PointUserManage.vue'
|
import PointUserManage from './PointUserManage.vue'
|
||||||
|
import PointTestPanel from './PointTestPanel.vue'
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
const accountInfo = useAccount()
|
const accountInfo = useAccount()
|
||||||
@@ -734,6 +735,15 @@ onMounted(() => { })
|
|||||||
>
|
>
|
||||||
<PointSettings />
|
<PointSettings />
|
||||||
</NTabPane>
|
</NTabPane>
|
||||||
|
|
||||||
|
<!-- 测试标签页 -->
|
||||||
|
<NTabPane
|
||||||
|
name="test"
|
||||||
|
tab="测试"
|
||||||
|
display-directive="show:lazy"
|
||||||
|
>
|
||||||
|
<PointTestPanel />
|
||||||
|
</NTabPane>
|
||||||
</NTabs>
|
</NTabs>
|
||||||
|
|
||||||
<!-- 添加/修改礼物模态框 -->
|
<!-- 添加/修改礼物模态框 -->
|
||||||
|
|||||||
331
src/views/manage/point/PointTestPanel.vue
Normal file
331
src/views/manage/point/PointTestPanel.vue
Normal file
@@ -0,0 +1,331 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import {
|
||||||
|
NCard,
|
||||||
|
NButton,
|
||||||
|
NForm,
|
||||||
|
NFormItem,
|
||||||
|
NInputNumber,
|
||||||
|
NInput,
|
||||||
|
NSelect,
|
||||||
|
NFlex,
|
||||||
|
NAlert,
|
||||||
|
NStatistic,
|
||||||
|
NPopconfirm,
|
||||||
|
useMessage,
|
||||||
|
NSpin,
|
||||||
|
NTag,
|
||||||
|
NDivider,
|
||||||
|
} from 'naive-ui'
|
||||||
|
import { EventDataTypes } from '@/api/api-models'
|
||||||
|
import { useAccount } from '@/api/account'
|
||||||
|
import { QueryPostAPI as Post, QueryGetAPI as Get } from '@/api/query'
|
||||||
|
import { POINT_API_URL } from '@/data/constants'
|
||||||
|
|
||||||
|
const message = useMessage()
|
||||||
|
const accountInfo = useAccount()
|
||||||
|
|
||||||
|
// 测试表单数据
|
||||||
|
const testForm = ref({
|
||||||
|
type: EventDataTypes.Message,
|
||||||
|
giftName: '',
|
||||||
|
giftPrice: 0,
|
||||||
|
giftCount: 1,
|
||||||
|
guardLevel: '舰长',
|
||||||
|
})
|
||||||
|
|
||||||
|
// 测试账户积分
|
||||||
|
const testAccountPoint = ref<number>(0)
|
||||||
|
const isLoading = ref(false)
|
||||||
|
const isTesting = ref(false)
|
||||||
|
|
||||||
|
// 事件类型选项
|
||||||
|
const eventTypeOptions = [
|
||||||
|
{ label: '弹幕', value: EventDataTypes.Message },
|
||||||
|
{ label: '礼物', value: EventDataTypes.Gift },
|
||||||
|
{ label: '上舰', value: EventDataTypes.Guard },
|
||||||
|
{ label: 'SC', value: EventDataTypes.SC },
|
||||||
|
]
|
||||||
|
|
||||||
|
// 舰长等级选项
|
||||||
|
const guardLevelOptions = [
|
||||||
|
{ label: '舰长', value: '舰长' },
|
||||||
|
{ label: '提督', value: '提督' },
|
||||||
|
{ label: '总督', value: '总督' },
|
||||||
|
]
|
||||||
|
|
||||||
|
// 是否显示礼物相关字段
|
||||||
|
const showGiftFields = computed(() => testForm.value.type === EventDataTypes.Gift)
|
||||||
|
const showGuardFields = computed(() => testForm.value.type === EventDataTypes.Guard)
|
||||||
|
const showPriceField = computed(
|
||||||
|
() => testForm.value.type === EventDataTypes.SC || testForm.value.type === EventDataTypes.Gift
|
||||||
|
)
|
||||||
|
|
||||||
|
// 获取测试账户积分
|
||||||
|
async function fetchTestAccountPoint() {
|
||||||
|
isLoading.value = true
|
||||||
|
try {
|
||||||
|
const res = await Get<number>(POINT_API_URL + 'get-test-account-point')
|
||||||
|
if (res.code === 200) {
|
||||||
|
testAccountPoint.value = res.data ?? 0
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('获取测试账户积分失败:', err)
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行测试
|
||||||
|
async function runTest() {
|
||||||
|
isTesting.value = true
|
||||||
|
try {
|
||||||
|
const payload: any = {
|
||||||
|
type: testForm.value.type,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据类型添加参数
|
||||||
|
if (testForm.value.type === EventDataTypes.Gift) {
|
||||||
|
if (!testForm.value.giftName.trim()) {
|
||||||
|
message.error('请输入礼物名称')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (testForm.value.giftPrice < 0) {
|
||||||
|
message.error('礼物价格不能为负数')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
payload.giftName = testForm.value.giftName
|
||||||
|
payload.giftPrice = testForm.value.giftPrice
|
||||||
|
payload.giftCount = testForm.value.giftCount
|
||||||
|
} else if (testForm.value.type === EventDataTypes.Guard) {
|
||||||
|
payload.guardLevel = testForm.value.guardLevel
|
||||||
|
} else if (testForm.value.type === EventDataTypes.SC) {
|
||||||
|
if (testForm.value.giftPrice <= 0) {
|
||||||
|
message.error('SC价格必须大于0')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
payload.giftPrice = testForm.value.giftPrice
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await Post<{ success: boolean; message: string; pointsAwarded?: number }>(POINT_API_URL + 'test-point',
|
||||||
|
payload
|
||||||
|
)
|
||||||
|
|
||||||
|
if (res.code === 200 && res.data) {
|
||||||
|
if (res.data.success) {
|
||||||
|
message.success(res.data.message)
|
||||||
|
// 刷新测试账户积分
|
||||||
|
await fetchTestAccountPoint()
|
||||||
|
} else {
|
||||||
|
message.warning(res.data.message)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
message.error(res.message || '测试失败')
|
||||||
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
message.error(`测试失败: ${err.message || err}`)
|
||||||
|
console.error('测试失败:', err)
|
||||||
|
} finally {
|
||||||
|
isTesting.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置测试账户
|
||||||
|
async function resetTestAccount() {
|
||||||
|
isLoading.value = true
|
||||||
|
try {
|
||||||
|
const res = await Post(POINT_API_URL + 'reset-test-account', {})
|
||||||
|
if (res.code === 200) {
|
||||||
|
message.success('测试账户已重置')
|
||||||
|
testAccountPoint.value = 0
|
||||||
|
} else {
|
||||||
|
message.error(res.message || '重置失败')
|
||||||
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
message.error(`重置失败: ${err.message || err}`)
|
||||||
|
console.error('重置失败:', err)
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化时获取积分
|
||||||
|
fetchTestAccountPoint()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<NCard title="积分测试系统">
|
||||||
|
<template #header-extra>
|
||||||
|
<NTag type="info">
|
||||||
|
测试账户 OUId: 00000000-0000-0000-0000-000000000000
|
||||||
|
</NTag>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<NSpin :show="isLoading">
|
||||||
|
<NFlex
|
||||||
|
vertical
|
||||||
|
:gap="16"
|
||||||
|
>
|
||||||
|
<NAlert
|
||||||
|
type="info"
|
||||||
|
closable
|
||||||
|
>
|
||||||
|
此功能用于测试积分系统的配置,所有测试事件将记录到一个专用的 mock 账户(OUId=0)。你可以在这里测试不同类型事件的积分获取情况。
|
||||||
|
</NAlert>
|
||||||
|
|
||||||
|
<!-- 测试账户积分显示 -->
|
||||||
|
<NCard
|
||||||
|
size="small"
|
||||||
|
:bordered="false"
|
||||||
|
style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%)"
|
||||||
|
>
|
||||||
|
<NFlex
|
||||||
|
justify="space-between"
|
||||||
|
align="center"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<NStatistic
|
||||||
|
label="测试账户当前积分"
|
||||||
|
:value="testAccountPoint"
|
||||||
|
style="color: white"
|
||||||
|
>
|
||||||
|
<template #suffix>
|
||||||
|
<span style="color: white; font-size: 16px">分</span>
|
||||||
|
</template>
|
||||||
|
</NStatistic>
|
||||||
|
</div>
|
||||||
|
<NPopconfirm
|
||||||
|
@positive-click="resetTestAccount"
|
||||||
|
>
|
||||||
|
<template #trigger>
|
||||||
|
<NButton
|
||||||
|
type="error"
|
||||||
|
:loading="isLoading"
|
||||||
|
secondary
|
||||||
|
>
|
||||||
|
重置积分
|
||||||
|
</NButton>
|
||||||
|
</template>
|
||||||
|
确定要重置测试账户的积分吗?
|
||||||
|
</NPopconfirm>
|
||||||
|
</NFlex>
|
||||||
|
</NCard>
|
||||||
|
|
||||||
|
<NDivider>测试表单</NDivider>
|
||||||
|
|
||||||
|
<!-- 测试表单 -->
|
||||||
|
<NForm
|
||||||
|
label-placement="left"
|
||||||
|
label-width="120"
|
||||||
|
>
|
||||||
|
<NFormItem
|
||||||
|
label="事件类型"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<NSelect
|
||||||
|
v-model:value="testForm.type"
|
||||||
|
:options="eventTypeOptions"
|
||||||
|
placeholder="选择事件类型"
|
||||||
|
/>
|
||||||
|
</NFormItem>
|
||||||
|
|
||||||
|
<!-- 礼物相关字段 -->
|
||||||
|
<template v-if="showGiftFields">
|
||||||
|
<NFormItem
|
||||||
|
label="礼物名称"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<NInput
|
||||||
|
v-model:value="testForm.giftName"
|
||||||
|
placeholder="例如: 小心心、辣条"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</NFormItem>
|
||||||
|
|
||||||
|
<NFormItem
|
||||||
|
label="礼物价格"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<NInputNumber
|
||||||
|
v-model:value="testForm.giftPrice"
|
||||||
|
placeholder="礼物价格(元)"
|
||||||
|
:min="0"
|
||||||
|
:precision="2"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</NFormItem>
|
||||||
|
|
||||||
|
<NFormItem label="礼物数量">
|
||||||
|
<NInputNumber
|
||||||
|
v-model:value="testForm.giftCount"
|
||||||
|
placeholder="礼物数量"
|
||||||
|
:min="1"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</NFormItem>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 上舰相关字段 -->
|
||||||
|
<NFormItem
|
||||||
|
v-if="showGuardFields"
|
||||||
|
label="舰长等级"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<NSelect
|
||||||
|
v-model:value="testForm.guardLevel"
|
||||||
|
:options="guardLevelOptions"
|
||||||
|
placeholder="选择舰长等级"
|
||||||
|
/>
|
||||||
|
</NFormItem>
|
||||||
|
|
||||||
|
<!-- SC 价格字段 -->
|
||||||
|
<NFormItem
|
||||||
|
v-if="testForm.type === EventDataTypes.SC"
|
||||||
|
label="SC价格"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<NInputNumber
|
||||||
|
v-model:value="testForm.giftPrice"
|
||||||
|
placeholder="SC价格(元)"
|
||||||
|
:min="0.01"
|
||||||
|
:precision="2"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</NFormItem>
|
||||||
|
|
||||||
|
<NFormItem>
|
||||||
|
<NFlex
|
||||||
|
justify="end"
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<NButton
|
||||||
|
type="primary"
|
||||||
|
:loading="isTesting"
|
||||||
|
@click="runTest"
|
||||||
|
>
|
||||||
|
执行测试
|
||||||
|
</NButton>
|
||||||
|
</NFlex>
|
||||||
|
</NFormItem>
|
||||||
|
</NForm>
|
||||||
|
|
||||||
|
<NAlert
|
||||||
|
v-if="!accountInfo?.settings?.point"
|
||||||
|
type="warning"
|
||||||
|
>
|
||||||
|
请先配置积分设置
|
||||||
|
</NAlert>
|
||||||
|
</NFlex>
|
||||||
|
</NSpin>
|
||||||
|
</NCard>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
:deep(.n-statistic .n-statistic-value__content) {
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.n-statistic .n-statistic__label) {
|
||||||
|
color: rgba(255, 255, 255, 0.9) !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -172,6 +172,39 @@ onUnmounted(() => {
|
|||||||
description="暂无人点歌"
|
description="暂无人点歌"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="settings.showRequireInfo"
|
||||||
|
class="minimal-requirements"
|
||||||
|
>
|
||||||
|
<div class="minimal-requirements-row">
|
||||||
|
<div class="minimal-requirements-tag">
|
||||||
|
<span class="tag-label">前缀</span>
|
||||||
|
<span class="tag-value">{{ settings.orderPrefix || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="minimal-requirements-tag">
|
||||||
|
<span class="tag-label">允许</span>
|
||||||
|
<span class="tag-value">{{ settings.allowAllDanmaku ? '所有弹幕' : allowGuardTypes.length > 0 ? allowGuardTypes.join('/') : '无' }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="minimal-requirements-row">
|
||||||
|
<div class="minimal-requirements-tag">
|
||||||
|
<span class="tag-label">SC</span>
|
||||||
|
<span class="tag-value">{{ settings.allowSC ? `≥ ¥${settings.scMinPrice}` : '不允许' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="minimal-requirements-tag">
|
||||||
|
<span class="tag-label">粉丝牌</span>
|
||||||
|
<span class="tag-value">
|
||||||
|
{{
|
||||||
|
settings.needWearFanMedal
|
||||||
|
? settings.fanMedalMinLevel > 0
|
||||||
|
? `≥ ${settings.fanMedalMinLevel}级`
|
||||||
|
: '需佩戴'
|
||||||
|
: '无需'
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -370,4 +403,50 @@ onUnmounted(() => {
|
|||||||
.minimal-list-inner.animating:hover {
|
.minimal-list-inner.animating:hover {
|
||||||
animation-play-state: paused;
|
animation-play-state: paused;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.minimal-requirements {
|
||||||
|
margin-top: 6px;
|
||||||
|
padding: 4px 4px 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
font-size: 11px;
|
||||||
|
color: #e2e8f0;
|
||||||
|
text-shadow: 0 2px 6px rgba(0, 0, 0, 0.85);
|
||||||
|
}
|
||||||
|
|
||||||
|
.minimal-requirements-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.minimal-requirements-tag {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
padding: 3px 8px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: rgba(0, 0, 0, 0.35);
|
||||||
|
backdrop-filter: blur(2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-label {
|
||||||
|
font-size: 10px;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
color: rgba(226, 232, 240, 0.75);
|
||||||
|
text-transform: uppercase;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-value {
|
||||||
|
margin-top: 0;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #e2e8f0;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -151,18 +151,42 @@ onUnmounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NAlert
|
<!-- 顶部功能开关与全局操作 -->
|
||||||
v-if="accountInfo.id"
|
<NCard v-if="accountInfo.id" size="small">
|
||||||
:type="accountInfo.settings.enableFunctions.includes(FunctionTypes.LiveRequest) ? 'success' : 'warning'"
|
<template #header>
|
||||||
>
|
<NSpace align="center" justify="space-between">
|
||||||
启用弹幕点播功能
|
<NSpace align="center">
|
||||||
<NSwitch
|
<NText>启用弹幕点播功能</NText>
|
||||||
:value="accountInfo?.settings.enableFunctions.includes(FunctionTypes.LiveRequest)"
|
<NSwitch
|
||||||
@update:value="onUpdateFunctionEnable"
|
:value="accountInfo?.settings.enableFunctions.includes(FunctionTypes.LiveRequest)"
|
||||||
/>
|
@update:value="onUpdateFunctionEnable"
|
||||||
|
/>
|
||||||
|
</NSpace>
|
||||||
|
|
||||||
<br>
|
<!-- OBS 组件按钮 -->
|
||||||
<NText depth="3">
|
<NTooltip>
|
||||||
|
<template #trigger>
|
||||||
|
<NButton
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
:disabled="!accountInfo"
|
||||||
|
@click="showOBSModal = true"
|
||||||
|
>
|
||||||
|
OBS 组件
|
||||||
|
</NButton>
|
||||||
|
</template>
|
||||||
|
{{ liveRequest.configCanEdit ? '配置 OBS 样式与参数' : '登陆后才可以使用此功能' }}
|
||||||
|
</NTooltip>
|
||||||
|
</NSpace>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 提示信息 -->
|
||||||
|
<NAlert
|
||||||
|
v-if="accountInfo.settings.enableFunctions.includes(FunctionTypes.LiveRequest)"
|
||||||
|
type="info"
|
||||||
|
closable
|
||||||
|
style="margin-top: 10px"
|
||||||
|
>
|
||||||
如果没有部署
|
如果没有部署
|
||||||
<NButton
|
<NButton
|
||||||
text
|
text
|
||||||
@@ -173,13 +197,14 @@ onUnmounted(() => {
|
|||||||
>
|
>
|
||||||
VtsuruEventFetcher
|
VtsuruEventFetcher
|
||||||
</NButton>
|
</NButton>
|
||||||
则其需要保持此页面开启才能点播, 也不要同时开多个页面, 会导致点播重复 !(部署了则不影响)
|
则其需要保持此页面开启才能点播, 也不要同时开多个页面, 会导致点播重复 (部署了则不影响)
|
||||||
</NText>
|
</NAlert>
|
||||||
</NAlert>
|
</NCard>
|
||||||
|
|
||||||
<NAlert
|
<NAlert
|
||||||
v-else
|
v-else
|
||||||
type="warning"
|
type="warning"
|
||||||
title="你尚未注册并登录 VTsuru.live, 大部分规则设置将不可用 (因为我懒得在前段重写一遍逻辑"
|
title="你尚未注册并登录 VTsuru.live, 大部分规则设置将不可用 (因为我懒得在前端重写一遍逻辑)"
|
||||||
>
|
>
|
||||||
<NButton
|
<NButton
|
||||||
tag="a"
|
tag="a"
|
||||||
@@ -190,27 +215,12 @@ onUnmounted(() => {
|
|||||||
前往登录或注册
|
前往登录或注册
|
||||||
</NButton>
|
</NButton>
|
||||||
</NAlert>
|
</NAlert>
|
||||||
<br>
|
|
||||||
<NCard size="small">
|
<!-- 主体内容 -->
|
||||||
<NSpace align="center">
|
<NCard style="margin-top: 12px">
|
||||||
<NTooltip>
|
|
||||||
<template #trigger>
|
|
||||||
<NButton
|
|
||||||
type="primary"
|
|
||||||
:disabled="!accountInfo"
|
|
||||||
@click="showOBSModal = true"
|
|
||||||
>
|
|
||||||
OBS 组件
|
|
||||||
</NButton>
|
|
||||||
</template>
|
|
||||||
{{ liveRequest.configCanEdit ? '' : '登陆后才可以使用此功能' }}
|
|
||||||
</NTooltip>
|
|
||||||
</NSpace>
|
|
||||||
</NCard>
|
|
||||||
<br>
|
|
||||||
<NCard>
|
|
||||||
<NTabs
|
<NTabs
|
||||||
v-if="!accountInfo || accountInfo.settings.enableFunctions.includes(FunctionTypes.LiveRequest)"
|
v-if="!accountInfo || accountInfo.settings.enableFunctions.includes(FunctionTypes.LiveRequest)"
|
||||||
|
type="line"
|
||||||
animated
|
animated
|
||||||
display-directive="show:lazy"
|
display-directive="show:lazy"
|
||||||
>
|
>
|
||||||
@@ -223,12 +233,13 @@ onUnmounted(() => {
|
|||||||
<div
|
<div
|
||||||
v-if="liveRequest.selectedSong"
|
v-if="liveRequest.selectedSong"
|
||||||
class="song-list"
|
class="song-list"
|
||||||
|
style="margin-bottom: 15px"
|
||||||
>
|
>
|
||||||
<SongPlayer
|
<SongPlayer
|
||||||
v-model:is-lrc-loading="liveRequest.isLrcLoading"
|
v-model:is-lrc-loading="liveRequest.isLrcLoading"
|
||||||
:song="liveRequest.selectedSong"
|
:song="liveRequest.selectedSong"
|
||||||
/>
|
/>
|
||||||
<NDivider style="margin: 15px 0 15px 0" />
|
<NDivider style="margin: 15px 0" />
|
||||||
</div>
|
</div>
|
||||||
</Transition>
|
</Transition>
|
||||||
|
|
||||||
|
|||||||
@@ -385,288 +385,287 @@ onUnmounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NSpace>
|
<NCard size="small">
|
||||||
<NAlert type="info">
|
<template #header>
|
||||||
搜索时会优先选择非VIP歌曲, 所以点到付费曲目时可能会是猴版或者各种奇怪的歌
|
<NSpace align="center" justify="space-between">
|
||||||
</NAlert>
|
|
||||||
</NSpace>
|
|
||||||
<NDivider />
|
|
||||||
<NSpace align="center">
|
|
||||||
<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
|
|
||||||
type="info"
|
|
||||||
size="small"
|
|
||||||
@click="showOBSModal = true"
|
|
||||||
>
|
|
||||||
OBS组件
|
|
||||||
</NButton>
|
|
||||||
<NButton
|
|
||||||
size="small"
|
|
||||||
@click="showNeteaseModal = true"
|
|
||||||
>
|
|
||||||
从网易云歌单导入空闲歌单
|
|
||||||
</NButton>
|
|
||||||
|
|
||||||
<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>
|
|
||||||
</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"
|
|
||||||
>
|
|
||||||
<NSpace align="center">
|
|
||||||
<NButton
|
|
||||||
type="primary"
|
|
||||||
secondary
|
|
||||||
size="small"
|
|
||||||
@click="musicRquestStore.playMusic(item.music)"
|
|
||||||
>
|
|
||||||
播放
|
|
||||||
</NButton>
|
|
||||||
<NButton
|
|
||||||
type="error"
|
|
||||||
secondary
|
|
||||||
size="small"
|
|
||||||
@click="musicRquestStore.waitingMusics.splice(musicRquestStore.waitingMusics.indexOf(item), 1)"
|
|
||||||
>
|
|
||||||
取消
|
|
||||||
</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>
|
|
||||||
</span>
|
|
||||||
<NText>
|
|
||||||
{{ item.from.name }}
|
|
||||||
</NText>
|
|
||||||
<NText depth="3">
|
|
||||||
{{ item.music.name }} - {{ item.music.author?.join('/') }}
|
|
||||||
</NText>
|
|
||||||
</NSpace>
|
|
||||||
</NListItem>
|
|
||||||
</NList>
|
|
||||||
</NCollapseItem>
|
|
||||||
</NCollapse>
|
|
||||||
<NDivider />
|
|
||||||
<NTabs>
|
|
||||||
<NTabPane
|
|
||||||
name="settings"
|
|
||||||
tab="设置"
|
|
||||||
>
|
|
||||||
<NSpace vertical>
|
|
||||||
<NSpace align="center">
|
<NSpace align="center">
|
||||||
<NRadioGroup v-model:value="settings.platform">
|
<NButton
|
||||||
<NRadioButton value="netease">
|
:type="listening ? 'error' : 'primary'"
|
||||||
网易云
|
:style="{ animation: listening ? 'animated-border 2.5s infinite' : '' }"
|
||||||
</NRadioButton>
|
data-umami-event="Use Music Request"
|
||||||
<NRadioButton value="kugou">
|
:data-umami-event-uid="accountInfo?.biliId"
|
||||||
酷狗
|
size="small"
|
||||||
</NRadioButton>
|
@click="listening ? stopListen() : startListen()"
|
||||||
</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
|
|
||||||
}
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
是否启用点歌冷却
|
{{ listening ? '停止监听' : '开始监听' }}
|
||||||
</NCheckbox>
|
</NButton>
|
||||||
<NInputGroup
|
<NButton
|
||||||
v-if="settings.orderCooldown"
|
type="info"
|
||||||
style="width: 200px"
|
size="small"
|
||||||
|
@click="showOBSModal = true"
|
||||||
>
|
>
|
||||||
<NInputGroupLabel> 冷却时间 (秒) </NInputGroupLabel>
|
OBS组件
|
||||||
<NInputNumber
|
</NButton>
|
||||||
v-model:value="settings.orderCooldown"
|
|
||||||
@update:value="(value) => {
|
|
||||||
if (!value || value <= 0) settings.orderCooldown = undefined
|
|
||||||
}
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
</NInputGroup>
|
|
||||||
</NSpace>
|
</NSpace>
|
||||||
<NSpace>
|
|
||||||
<NCheckbox v-model:checked="settings.playMusicWhenFree">
|
<NSpace align="center">
|
||||||
空闲时播放空闲歌单
|
<NButton
|
||||||
</NCheckbox>
|
type="primary"
|
||||||
<NCheckbox v-model:checked="settings.orderMusicFirst">
|
secondary
|
||||||
优先播放点歌
|
:disabled="!accountInfo"
|
||||||
</NCheckbox>
|
size="small"
|
||||||
</NSpace>
|
@click="uploadConfig"
|
||||||
<NSpace>
|
>
|
||||||
<NTooltip>
|
保存配置到服务器
|
||||||
|
</NButton>
|
||||||
|
<NPopconfirm @positive-click="downloadConfig">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<NButton
|
<NButton
|
||||||
type="info"
|
type="primary"
|
||||||
@click="getOutputDevice"
|
secondary
|
||||||
|
:disabled="!accountInfo"
|
||||||
|
size="small"
|
||||||
>
|
>
|
||||||
获取输出设备
|
从服务器获取配置
|
||||||
</NButton>
|
</NButton>
|
||||||
</template>
|
</template>
|
||||||
获取和修改输出设备需要打开麦克风权限
|
这将覆盖当前设置, 确定?
|
||||||
</NTooltip>
|
</NPopconfirm>
|
||||||
<NSelect
|
|
||||||
v-model:value="settings.deviceId"
|
|
||||||
:options="deviceList"
|
|
||||||
:fallback-option="() => ({ label: '未选择', value: '' })"
|
|
||||||
style="min-width: 200px"
|
|
||||||
@update:value="musicRquestStore.setSinkId"
|
|
||||||
/>
|
|
||||||
</NSpace>
|
</NSpace>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
</NTabPane>
|
</template>
|
||||||
<NTabPane
|
<NAlert type="info" closable style="margin-top: 10px">
|
||||||
name="list"
|
搜索时会优先选择非VIP歌曲, 所以点到付费曲目时可能会是猴版或者各种奇怪的歌
|
||||||
tab="闲置歌单"
|
</NAlert>
|
||||||
>
|
</NCard>
|
||||||
<NSpace>
|
|
||||||
<NPopconfirm @positive-click="clearMusic">
|
<NCard style="margin-top: 12px">
|
||||||
<template #trigger>
|
<NTabs type="line" animated>
|
||||||
<NButton type="error">
|
<NTabPane
|
||||||
清空
|
name="queue"
|
||||||
</NButton>
|
tab="当前点歌"
|
||||||
</template>
|
|
||||||
确定清空吗?
|
|
||||||
</NPopconfirm>
|
|
||||||
<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
|
|
||||||
>
|
>
|
||||||
<template #default="{ item }">
|
<NEmpty v-if="musicRquestStore.waitingMusics.length == 0" description="暂无点歌">
|
||||||
<p :style="`min-height: ${30}px;width:97%;display:flex;align-items:center;`">
|
</NEmpty>
|
||||||
|
<NList
|
||||||
|
v-else
|
||||||
|
size="small"
|
||||||
|
bordered
|
||||||
|
>
|
||||||
|
<NListItem
|
||||||
|
v-for="item in musicRquestStore.waitingMusics"
|
||||||
|
:key="item.music.name"
|
||||||
|
>
|
||||||
|
<NSpace align="center">
|
||||||
|
<NButton
|
||||||
|
type="primary"
|
||||||
|
secondary
|
||||||
|
size="small"
|
||||||
|
@click="musicRquestStore.playMusic(item.music)"
|
||||||
|
>
|
||||||
|
播放
|
||||||
|
</NButton>
|
||||||
|
<NButton
|
||||||
|
type="error"
|
||||||
|
secondary
|
||||||
|
size="small"
|
||||||
|
@click="musicRquestStore.waitingMusics.splice(musicRquestStore.waitingMusics.indexOf(item), 1)"
|
||||||
|
>
|
||||||
|
取消
|
||||||
|
</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>
|
||||||
|
</span>
|
||||||
|
<NText>
|
||||||
|
{{ item.from.name }}
|
||||||
|
</NText>
|
||||||
|
<NText depth="3">
|
||||||
|
{{ item.music.name }} - {{ item.music.author?.join('/') }}
|
||||||
|
</NText>
|
||||||
|
</NSpace>
|
||||||
|
</NListItem>
|
||||||
|
</NList>
|
||||||
|
</NTabPane>
|
||||||
|
|
||||||
|
<NTabPane
|
||||||
|
name="list"
|
||||||
|
tab="闲置歌单"
|
||||||
|
>
|
||||||
|
<NSpace style="margin-bottom: 10px">
|
||||||
|
<NPopconfirm @positive-click="clearMusic">
|
||||||
|
<template #trigger>
|
||||||
|
<NButton type="error" size="small">
|
||||||
|
清空
|
||||||
|
</NButton>
|
||||||
|
</template>
|
||||||
|
确定清空吗?
|
||||||
|
</NPopconfirm>
|
||||||
|
<NButton size="small" @click="showNeteaseModal = true">
|
||||||
|
从网易云歌单导入
|
||||||
|
</NButton>
|
||||||
|
</NSpace>
|
||||||
|
|
||||||
|
<NEmpty v-if="musicRquestStore.originMusics.length == 0">
|
||||||
|
暂无
|
||||||
|
</NEmpty>
|
||||||
|
<NVirtualList
|
||||||
|
v-else
|
||||||
|
style="max-height: 600px"
|
||||||
|
:item-size="36"
|
||||||
|
:items="originMusics"
|
||||||
|
item-resizable
|
||||||
|
>
|
||||||
|
<template #default="{ item }">
|
||||||
|
<div :style="`height: ${36}px; display:flex; align-items:center; padding: 0 5px;`">
|
||||||
|
<NSpace
|
||||||
|
align="center"
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<NPopconfirm @positive-click="delMusic(item)">
|
||||||
|
<template #trigger>
|
||||||
|
<NButton
|
||||||
|
type="error"
|
||||||
|
secondary
|
||||||
|
size="tiny"
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</NButton>
|
||||||
|
</template>
|
||||||
|
确定删除?
|
||||||
|
</NPopconfirm>
|
||||||
|
|
||||||
|
<NButton
|
||||||
|
type="info"
|
||||||
|
secondary
|
||||||
|
size="tiny"
|
||||||
|
@click="musicRquestStore.playMusic(item)"
|
||||||
|
>
|
||||||
|
播放
|
||||||
|
</NButton>
|
||||||
|
<NText> {{ item.name }} - {{ item.author?.join('/') }} </NText>
|
||||||
|
</NSpace>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</NVirtualList>
|
||||||
|
</NTabPane>
|
||||||
|
|
||||||
|
<NTabPane
|
||||||
|
name="blacklist"
|
||||||
|
tab="黑名单"
|
||||||
|
>
|
||||||
|
<NList bordered>
|
||||||
|
<NListItem
|
||||||
|
v-for="item in settings.blacklist"
|
||||||
|
:key="item"
|
||||||
|
>
|
||||||
<NSpace
|
<NSpace
|
||||||
align="center"
|
align="center"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
>
|
>
|
||||||
<NPopconfirm @positive-click="delMusic(item)">
|
|
||||||
<template #trigger>
|
|
||||||
<NButton
|
|
||||||
type="error"
|
|
||||||
secondary
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
删除
|
|
||||||
</NButton>
|
|
||||||
</template>
|
|
||||||
确定删除?
|
|
||||||
</NPopconfirm>
|
|
||||||
|
|
||||||
<NButton
|
<NButton
|
||||||
type="info"
|
type="error"
|
||||||
secondary
|
secondary
|
||||||
size="small"
|
size="small"
|
||||||
@click="musicRquestStore.playMusic(item)"
|
@click="settings.blacklist.splice(settings.blacklist.indexOf(item), 1)"
|
||||||
>
|
>
|
||||||
播放
|
删除
|
||||||
</NButton>
|
</NButton>
|
||||||
<NText> {{ item.name }} - {{ item.author?.join('/') }} </NText>
|
<NText> {{ item }} </NText>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
</p>
|
</NListItem>
|
||||||
</template>
|
</NList>
|
||||||
</NVirtualList>
|
</NTabPane>
|
||||||
</NTabPane>
|
|
||||||
<NTabPane
|
<NTabPane
|
||||||
name="blacklist"
|
name="settings"
|
||||||
tab="黑名单"
|
tab="设置"
|
||||||
>
|
>
|
||||||
<NList>
|
<NSpace vertical>
|
||||||
<NListItem
|
<NSpace align="center">
|
||||||
v-for="item in settings.blacklist"
|
<NRadioGroup v-model:value="settings.platform">
|
||||||
:key="item"
|
<NRadioButton value="netease">
|
||||||
>
|
网易云
|
||||||
<NSpace
|
</NRadioButton>
|
||||||
align="center"
|
<NRadioButton value="kugou">
|
||||||
style="width: 100%"
|
酷狗
|
||||||
>
|
</NRadioButton>
|
||||||
<NButton
|
</NRadioGroup>
|
||||||
type="error"
|
<NInputGroup style="width: 250px">
|
||||||
secondary
|
<NInputGroupLabel> 点歌弹幕前缀 </NInputGroupLabel>
|
||||||
size="small"
|
<NInput v-model:value="settings.orderPrefix" />
|
||||||
@click="settings.blacklist.splice(settings.blacklist.indexOf(item), 1)"
|
</NInputGroup>
|
||||||
|
<NCheckbox
|
||||||
|
:checked="settings.orderCooldown != undefined"
|
||||||
|
@update:checked="(checked: boolean) => {
|
||||||
|
settings.orderCooldown = checked ? 300 : undefined
|
||||||
|
}
|
||||||
|
"
|
||||||
>
|
>
|
||||||
删除
|
是否启用点歌冷却
|
||||||
</NButton>
|
</NCheckbox>
|
||||||
<NText> {{ item }} </NText>
|
<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
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</NInputGroup>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
</NListItem>
|
<NSpace>
|
||||||
</NList>
|
<NCheckbox v-model:checked="settings.playMusicWhenFree">
|
||||||
</NTabPane>
|
空闲时播放空闲歌单
|
||||||
</NTabs>
|
</NCheckbox>
|
||||||
<NDivider style="height: 100px" />
|
<NCheckbox v-model:checked="settings.orderMusicFirst">
|
||||||
|
优先播放点歌
|
||||||
|
</NCheckbox>
|
||||||
|
</NSpace>
|
||||||
|
<NSpace>
|
||||||
|
<NTooltip>
|
||||||
|
<template #trigger>
|
||||||
|
<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"
|
||||||
|
/>
|
||||||
|
</NSpace>
|
||||||
|
</NSpace>
|
||||||
|
</NTabPane>
|
||||||
|
</NTabs>
|
||||||
|
</NCard>
|
||||||
<NModal
|
<NModal
|
||||||
v-model:show="showNeteaseModal"
|
v-model:show="showNeteaseModal"
|
||||||
preset="card"
|
preset="card"
|
||||||
|
|||||||
@@ -1017,24 +1017,39 @@ function getIndexStyle(status: QueueStatus): CSSProperties {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<!-- 功能启用开关 -->
|
<!-- 顶部功能开关与全局操作 -->
|
||||||
<NAlert
|
<NCard v-if="accountInfo?.id" size="small">
|
||||||
v-if="accountInfo?.id"
|
|
||||||
:type="accountInfo.settings.enableFunctions.includes(FunctionTypes.Queue) ? 'success' : 'warning'"
|
|
||||||
title="弹幕队列功能"
|
|
||||||
closable
|
|
||||||
>
|
|
||||||
<template #header>
|
<template #header>
|
||||||
<NSpace align="center">
|
<NSpace align="center" justify="space-between">
|
||||||
<NText>启用弹幕队列功能</NText>
|
<NSpace align="center">
|
||||||
<NSwitch
|
<NText>启用弹幕队列功能</NText>
|
||||||
:value="accountInfo?.settings.enableFunctions.includes(FunctionTypes.Queue)"
|
<NSwitch
|
||||||
:loading="isLoading"
|
:value="accountInfo?.settings.enableFunctions.includes(FunctionTypes.Queue)"
|
||||||
@update:value="onUpdateFunctionEnable"
|
:loading="isLoading"
|
||||||
/>
|
@update:value="onUpdateFunctionEnable"
|
||||||
|
/>
|
||||||
|
</NSpace>
|
||||||
|
<NTooltip :disabled="configCanEdit">
|
||||||
|
<template #trigger>
|
||||||
|
<NButton
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
:disabled="!configCanEdit"
|
||||||
|
@click="showOBSModal = true"
|
||||||
|
>
|
||||||
|
OBS 组件
|
||||||
|
</NButton>
|
||||||
|
</template>
|
||||||
|
登录后可使用 OBS 组件功能
|
||||||
|
</NTooltip>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
</template>
|
</template>
|
||||||
<NText depth="3">
|
<NAlert
|
||||||
|
v-if="accountInfo.settings.enableFunctions.includes(FunctionTypes.Queue)"
|
||||||
|
type="info"
|
||||||
|
closable
|
||||||
|
style="margin-top: 10px"
|
||||||
|
>
|
||||||
如果没有部署
|
如果没有部署
|
||||||
<NButton
|
<NButton
|
||||||
text
|
text
|
||||||
@@ -1046,8 +1061,9 @@ function getIndexStyle(status: QueueStatus): CSSProperties {
|
|||||||
VtsuruEventFetcher
|
VtsuruEventFetcher
|
||||||
</NButton>
|
</NButton>
|
||||||
则其需要保持此页面开启才能点播, 也不要同时开多个页面, 会导致点播重复 (部署了则不影响)
|
则其需要保持此页面开启才能点播, 也不要同时开多个页面, 会导致点播重复 (部署了则不影响)
|
||||||
</NText>
|
</NAlert>
|
||||||
</NAlert>
|
</NCard>
|
||||||
|
|
||||||
<!-- 未登录提示 -->
|
<!-- 未登录提示 -->
|
||||||
<NAlert
|
<NAlert
|
||||||
v-else
|
v-else
|
||||||
@@ -1068,29 +1084,7 @@ function getIndexStyle(status: QueueStatus): CSSProperties {
|
|||||||
</NButton>
|
</NButton>
|
||||||
</NAlert>
|
</NAlert>
|
||||||
|
|
||||||
<NCard
|
<NCard style="margin-top: 12px">
|
||||||
size="small"
|
|
||||||
style="margin-top: 10px;"
|
|
||||||
>
|
|
||||||
<NSpace align="center">
|
|
||||||
<!-- OBS 组件按钮 -->
|
|
||||||
<NTooltip :disabled="configCanEdit">
|
|
||||||
<template #trigger>
|
|
||||||
<NButton
|
|
||||||
type="primary"
|
|
||||||
:disabled="!configCanEdit"
|
|
||||||
@click="showOBSModal = true"
|
|
||||||
>
|
|
||||||
OBS 组件
|
|
||||||
</NButton>
|
|
||||||
</template>
|
|
||||||
登录后可使用 OBS 组件功能
|
|
||||||
</NTooltip>
|
|
||||||
<!-- 其他全局操作按钮可以在这里添加 -->
|
|
||||||
</NSpace>
|
|
||||||
</NCard>
|
|
||||||
|
|
||||||
<NCard style="margin-top: 10px;">
|
|
||||||
<!-- 主内容区域 -->
|
<!-- 主内容区域 -->
|
||||||
<NTabs
|
<NTabs
|
||||||
v-if="!accountInfo.id || accountInfo.settings.enableFunctions.includes(FunctionTypes.Queue)"
|
v-if="!accountInfo.id || accountInfo.settings.enableFunctions.includes(FunctionTypes.Queue)"
|
||||||
@@ -1214,17 +1208,16 @@ function getIndexStyle(status: QueueStatus): CSSProperties {
|
|||||||
|
|
||||||
<!-- 队列列表 -->
|
<!-- 队列列表 -->
|
||||||
<NSpin :show="isLoading && originQueue.length === 0">
|
<NSpin :show="isLoading && originQueue.length === 0">
|
||||||
<NList
|
<div
|
||||||
v-if="queue.length > 0"
|
v-if="queue.length > 0"
|
||||||
hoverable
|
class="queue-list-container"
|
||||||
clickable
|
|
||||||
style="max-height: 60vh; overflow-y: auto;"
|
|
||||||
>
|
>
|
||||||
<NListItem
|
<TransitionGroup name="list">
|
||||||
v-for="(queueData, index) in queue"
|
<div
|
||||||
:key="queueData.id"
|
v-for="(queueData, index) in queue"
|
||||||
style="padding: 5px 0;"
|
:key="queueData.id"
|
||||||
>
|
class="queue-item-wrapper"
|
||||||
|
>
|
||||||
<NCard
|
<NCard
|
||||||
embedded
|
embedded
|
||||||
size="small"
|
size="small"
|
||||||
@@ -1444,8 +1437,10 @@ function getIndexStyle(status: QueueStatus): CSSProperties {
|
|||||||
</NSpace>
|
</NSpace>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
</NCard>
|
</NCard>
|
||||||
</NListItem>
|
<NDivider style="margin: 0" />
|
||||||
</NList>
|
</div>
|
||||||
|
</TransitionGroup>
|
||||||
|
</div>
|
||||||
<NEmpty
|
<NEmpty
|
||||||
v-else
|
v-else
|
||||||
description="当前队列为空"
|
description="当前队列为空"
|
||||||
|
|||||||
@@ -206,13 +206,14 @@ const columns: DataTableColumns<SongRequestInfo> = [
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NCard size="small">
|
<NSpace vertical :size="12">
|
||||||
<NSpace>
|
<NSpace>
|
||||||
<NInputGroup style="width: 300px">
|
<NInputGroup style="width: 250px">
|
||||||
<NInputGroupLabel> 筛选曲名 </NInputGroupLabel>
|
<NInputGroupLabel> 筛选曲名 </NInputGroupLabel>
|
||||||
<NInput
|
<NInput
|
||||||
:value="songRequest.filterSongName"
|
:value="songRequest.filterSongName"
|
||||||
clearable
|
clearable
|
||||||
|
placeholder="搜索歌曲..."
|
||||||
@update:value="songRequest.filterSongName = $event"
|
@update:value="songRequest.filterSongName = $event"
|
||||||
>
|
>
|
||||||
<template #suffix>
|
<template #suffix>
|
||||||
@@ -225,11 +226,12 @@ const columns: DataTableColumns<SongRequestInfo> = [
|
|||||||
</template>
|
</template>
|
||||||
</NInput>
|
</NInput>
|
||||||
</NInputGroup>
|
</NInputGroup>
|
||||||
<NInputGroup style="width: 300px">
|
<NInputGroup style="width: 250px">
|
||||||
<NInputGroupLabel> 筛选用户名 </NInputGroupLabel>
|
<NInputGroupLabel> 筛选用户 </NInputGroupLabel>
|
||||||
<NInput
|
<NInput
|
||||||
:value="songRequest.filterName"
|
:value="songRequest.filterName"
|
||||||
clearable
|
clearable
|
||||||
|
placeholder="搜索用户..."
|
||||||
@update:value="songRequest.filterName = $event"
|
@update:value="songRequest.filterName = $event"
|
||||||
>
|
>
|
||||||
<template #suffix>
|
<template #suffix>
|
||||||
@@ -243,17 +245,17 @@ const columns: DataTableColumns<SongRequestInfo> = [
|
|||||||
</NInput>
|
</NInput>
|
||||||
</NInputGroup>
|
</NInputGroup>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
</NCard>
|
<NDataTable
|
||||||
<br>
|
ref="table"
|
||||||
<NDataTable
|
size="small"
|
||||||
ref="table"
|
:columns="columns"
|
||||||
size="small"
|
:data="songRequest.songs"
|
||||||
:columns="columns"
|
:bordered="false"
|
||||||
:data="songRequest.songs"
|
:loading="songRequest.isLoading"
|
||||||
:bordered="false"
|
:pagination="{ pageSize: 10 }"
|
||||||
:loading="songRequest.isLoading"
|
:row-class-name="(row, index) => (row.status == SongRequestStatus.Singing || row.status == SongRequestStatus.Waiting ? 'song-active' : '')"
|
||||||
:row-class-name="(row, index) => (row.status == SongRequestStatus.Singing || row.status == SongRequestStatus.Waiting ? 'song-active' : '')"
|
/>
|
||||||
/>
|
</NSpace>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import type { CSSProperties } from 'vue'
|
||||||
import type { SongRequestInfo } from '@/api/api-models'
|
import type { SongRequestInfo } from '@/api/api-models'
|
||||||
import {
|
import {
|
||||||
Checkmark12Regular,
|
Checkmark12Regular,
|
||||||
@@ -24,6 +25,7 @@ import { useLiveRequest } from '@/composables/useLiveRequest'
|
|||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
song: SongRequestInfo
|
song: SongRequestInfo
|
||||||
|
index: number
|
||||||
isLoading: boolean
|
isLoading: boolean
|
||||||
isLrcLoading: string
|
isLrcLoading: string
|
||||||
updateKey: number
|
updateKey: number
|
||||||
@@ -55,27 +57,34 @@ function onBlockUser() {
|
|||||||
songRequest.blockUser(props.song)
|
songRequest.blockUser(props.song)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSCColor(price: number): string {
|
function getIndexStyle(status: SongRequestStatus): CSSProperties {
|
||||||
if (price === 0) return `#2a60b2`
|
let backgroundColor
|
||||||
if (price > 0 && price < 30) return `#2a60b2`
|
switch (status) {
|
||||||
if (price >= 30 && price < 50) return `#2a60b2`
|
case SongRequestStatus.Singing:
|
||||||
if (price >= 50 && price < 100) return `#427d9e`
|
backgroundColor = '#18a058'
|
||||||
if (price >= 100 && price < 500) return `#c99801`
|
break
|
||||||
if (price >= 500 && price < 1000) return `#e09443`
|
case SongRequestStatus.Waiting:
|
||||||
if (price >= 1000 && price < 2000) return `#e54d4d`
|
backgroundColor = '#2080f0'
|
||||||
if (price >= 2000) return `#ab1a32`
|
break
|
||||||
return ''
|
default:
|
||||||
}
|
backgroundColor = '#86909c'
|
||||||
|
}
|
||||||
function getGuardColor(level: number | null | undefined): string {
|
|
||||||
if (level) {
|
return {
|
||||||
switch (level) {
|
display: 'inline-flex',
|
||||||
case 1: return 'rgb(122, 4, 35)'
|
alignItems: 'center',
|
||||||
case 2: return 'rgb(157, 155, 255)'
|
justifyContent: 'center',
|
||||||
case 3: return 'rgb(104, 136, 241)'
|
fontWeight: 'bold',
|
||||||
}
|
width: '24px',
|
||||||
|
minWidth: '24px', // 防止压缩
|
||||||
|
height: '24px',
|
||||||
|
borderRadius: '50%',
|
||||||
|
color: 'white',
|
||||||
|
fontSize: '13px',
|
||||||
|
backgroundColor,
|
||||||
|
marginRight: '8px',
|
||||||
|
flexShrink: 0, // 防止压缩
|
||||||
}
|
}
|
||||||
return ''
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取父组件中的活跃歌曲
|
// 获取父组件中的活跃歌曲
|
||||||
@@ -91,121 +100,100 @@ const hasOtherSingSong = computed(() => {
|
|||||||
<NCard
|
<NCard
|
||||||
embedded
|
embedded
|
||||||
size="small"
|
size="small"
|
||||||
content-style="padding: 5px;"
|
content-style="padding: 8px 12px;"
|
||||||
:style="`${isSingingStatus ? 'animation: animated-border 2.5s infinite;' : ''};height: 100%;`"
|
:bordered="isSingingStatus"
|
||||||
|
:style="isSingingStatus ? 'border-left: 4px solid #18a058;' : 'border-left: 4px solid transparent;'"
|
||||||
>
|
>
|
||||||
<NSpace
|
<NSpace justify="space-between" align="center" :wrap="false">
|
||||||
justify="space-between"
|
<!-- 左侧信息 -->
|
||||||
align="center"
|
<NSpace align="center" :size="8" :wrap="false">
|
||||||
style="height: 100%; margin: 0 5px 0 5px"
|
<!-- 序号 -->
|
||||||
>
|
<span :style="getIndexStyle(song.status)">
|
||||||
<NSpace align="center">
|
{{ index }}
|
||||||
<div
|
</span>
|
||||||
:style="`border-radius: 4px; background-color: ${isSingingStatus ? '#75c37f' : '#577fb8'}; width: 10px; height: 20px`"
|
|
||||||
/>
|
<!-- 歌曲名称 -->
|
||||||
<NText
|
<NText strong style="font-size: 16px">
|
||||||
strong
|
|
||||||
style="font-size: 18px"
|
|
||||||
>
|
|
||||||
{{ song.songName }}
|
{{ song.songName }}
|
||||||
</NText>
|
</NText>
|
||||||
|
|
||||||
|
<!-- 用户信息 -->
|
||||||
<template v-if="song.from == SongRequestFrom.Manual">
|
<template v-if="song.from == SongRequestFrom.Manual">
|
||||||
<!-- Manual -->
|
<NTag size="tiny" :bordered="false">
|
||||||
<NTag
|
|
||||||
size="small"
|
|
||||||
:bordered="false"
|
|
||||||
>
|
|
||||||
手动添加
|
手动添加
|
||||||
</NTag>
|
</NTag>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<NTooltip>
|
<NTooltip>
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<NTag
|
<NTag size="tiny" :bordered="false" type="info" round>
|
||||||
size="small"
|
{{ song.user?.name || '未知用户' }}
|
||||||
:bordered="false"
|
|
||||||
type="info"
|
|
||||||
>
|
|
||||||
<NText
|
|
||||||
italic
|
|
||||||
depth="3"
|
|
||||||
>
|
|
||||||
{{ song.user?.name || '未知用户' }}
|
|
||||||
</NText>
|
|
||||||
</NTag>
|
</NTag>
|
||||||
</template>
|
</template>
|
||||||
{{ song.user?.uid || '未知ID' }}
|
UID: {{ song.user?.uid || '未知' }}
|
||||||
</NTooltip>
|
</NTooltip>
|
||||||
</template>
|
</template>
|
||||||
<NSpace
|
|
||||||
v-if="
|
<!-- 粉丝牌 -->
|
||||||
(song.from == SongRequestFrom.Danmaku || song.from == SongRequestFrom.SC)
|
<NTag
|
||||||
&& song.user?.fans_medal_wearing_status
|
v-if="(song.from == SongRequestFrom.Danmaku || song.from == SongRequestFrom.SC) && song.user?.fans_medal_wearing_status"
|
||||||
"
|
size="tiny"
|
||||||
|
round
|
||||||
|
:bordered="false"
|
||||||
|
style="padding: 0 6px 0 0;"
|
||||||
>
|
>
|
||||||
<NTag
|
<NTag size="tiny" round :bordered="false" type="info" style="margin-right: 4px;">
|
||||||
size="tiny"
|
{{ song.user?.fans_medal_level }}
|
||||||
round
|
|
||||||
>
|
|
||||||
<NTag
|
|
||||||
size="tiny"
|
|
||||||
round
|
|
||||||
:bordered="false"
|
|
||||||
>
|
|
||||||
<NText depth="3">
|
|
||||||
{{ song.user?.fans_medal_level }}
|
|
||||||
</NText>
|
|
||||||
</NTag>
|
|
||||||
<span style="color: #577fb8">
|
|
||||||
{{ song.user?.fans_medal_name }}
|
|
||||||
</span>
|
|
||||||
</NTag>
|
</NTag>
|
||||||
</NSpace>
|
<span style="color: #577fb8">{{ song.user?.fans_medal_name }}</span>
|
||||||
|
</NTag>
|
||||||
|
|
||||||
|
<!-- 舰长 -->
|
||||||
<NTag
|
<NTag
|
||||||
v-if="(song.user?.guard_level ?? 0) > 0"
|
v-if="(song.user?.guard_level ?? 0) > 0"
|
||||||
size="small"
|
size="tiny"
|
||||||
:bordered="false"
|
:bordered="false"
|
||||||
:color="{ textColor: 'white', color: songRequest.getGuardColor(song.user?.guard_level) }"
|
:color="{ textColor: 'white', color: songRequest.getGuardColor(song.user?.guard_level) }"
|
||||||
>
|
>
|
||||||
{{ song.user?.guard_level == 1 ? '总督' : song.user?.guard_level == 2 ? '提督' : '舰长' }}
|
{{ song.user?.guard_level == 1 ? '总督' : song.user?.guard_level == 2 ? '提督' : '舰长' }}
|
||||||
</NTag>
|
</NTag>
|
||||||
|
|
||||||
|
<!-- SC/礼物 -->
|
||||||
<NTag
|
<NTag
|
||||||
v-if="song.from == SongRequestFrom.SC"
|
v-if="song.from == SongRequestFrom.SC"
|
||||||
size="small"
|
size="tiny"
|
||||||
:color="{ textColor: 'white', color: songRequest.getSCColor(song.price ?? 0) }"
|
:color="{ textColor: 'white', color: songRequest.getSCColor(song.price ?? 0) }"
|
||||||
>
|
>
|
||||||
SC{{ song.price ? ` | ${song.price}` : '' }}
|
SC{{ song.price ? ` | ${song.price}` : '' }}
|
||||||
</NTag>
|
</NTag>
|
||||||
<NTag
|
<NTag
|
||||||
v-if="song.from == SongRequestFrom.Gift"
|
v-if="song.from == SongRequestFrom.Gift"
|
||||||
size="small"
|
size="tiny"
|
||||||
:color="{ textColor: 'white', color: songRequest.getSCColor(song.price ?? 0) }"
|
:color="{ textColor: 'white', color: songRequest.getSCColor(song.price ?? 0) }"
|
||||||
>
|
>
|
||||||
礼物{{ song.price ? ` | ${song.price}` : '' }}
|
礼物{{ song.price ? ` | ${song.price}` : '' }}
|
||||||
</NTag>
|
</NTag>
|
||||||
|
|
||||||
|
<!-- 时间 -->
|
||||||
<NTooltip>
|
<NTooltip>
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<NText style="font-size: small">
|
<NText depth="3" style="font-size: 12px">
|
||||||
<NTime
|
<NTime :key="updateKey" :time="song.createAt" type="relative" />
|
||||||
:key="updateKey"
|
|
||||||
:time="song.createAt"
|
|
||||||
type="relative"
|
|
||||||
/>
|
|
||||||
</NText>
|
</NText>
|
||||||
</template>
|
</template>
|
||||||
<NTime :time="song.createAt" />
|
<NTime :time="song.createAt" />
|
||||||
</NTooltip>
|
</NTooltip>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
<NSpace
|
|
||||||
justify="end"
|
<!-- 右侧操作按钮 -->
|
||||||
align="center"
|
<NSpace justify="end" align="center" :size="6" :wrap="false">
|
||||||
>
|
|
||||||
<NTooltip v-if="hasSong">
|
<NTooltip v-if="hasSong">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<NButton
|
<NButton
|
||||||
circle
|
circle
|
||||||
|
size="small"
|
||||||
type="success"
|
type="success"
|
||||||
style="height: 30px; width: 30px"
|
ghost
|
||||||
:loading="isLrcLoading == song?.song?.key"
|
:loading="isLrcLoading == song?.song?.key"
|
||||||
@click="onSelectSong"
|
@click="onSelectSong"
|
||||||
>
|
>
|
||||||
@@ -216,43 +204,32 @@ const hasOtherSingSong = computed(() => {
|
|||||||
</template>
|
</template>
|
||||||
试听
|
试听
|
||||||
</NTooltip>
|
</NTooltip>
|
||||||
|
|
||||||
<NTooltip>
|
<NTooltip>
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<NButton
|
<NButton
|
||||||
circle
|
circle
|
||||||
type="primary"
|
size="small"
|
||||||
style="height: 30px; width: 30px"
|
:type="song.status == SongRequestStatus.Singing ? 'warning' : 'primary'"
|
||||||
|
:ghost="song.status == SongRequestStatus.Singing"
|
||||||
:disabled="hasOtherSingSong"
|
:disabled="hasOtherSingSong"
|
||||||
:style="`animation: ${song.status == SongRequestStatus.Waiting ? '' : 'loading 5s linear infinite'}`"
|
|
||||||
:secondary="song.status == SongRequestStatus.Singing"
|
|
||||||
:loading="isLoading"
|
:loading="isLoading"
|
||||||
@click="
|
@click="onUpdateStatus(song.status == SongRequestStatus.Singing ? SongRequestStatus.Waiting : SongRequestStatus.Singing)"
|
||||||
onUpdateStatus(
|
|
||||||
song.status == SongRequestStatus.Singing
|
|
||||||
? SongRequestStatus.Waiting
|
|
||||||
: SongRequestStatus.Singing,
|
|
||||||
)
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<NIcon :component="Mic24Filled" />
|
<NIcon :component="Mic24Filled" />
|
||||||
</template>
|
</template>
|
||||||
</NButton>
|
</NButton>
|
||||||
</template>
|
</template>
|
||||||
{{
|
{{ hasOtherSingSong ? '还有其他正在演唱' : (song.status == SongRequestStatus.Waiting ? '开始演唱' : '暂停演唱') }}
|
||||||
hasOtherSingSong
|
|
||||||
? '还有其他正在进行的点播'
|
|
||||||
: song.status == SongRequestStatus.Waiting && song.id
|
|
||||||
? '开始处理'
|
|
||||||
: '停止处理'
|
|
||||||
}}
|
|
||||||
</NTooltip>
|
</NTooltip>
|
||||||
|
|
||||||
<NTooltip>
|
<NTooltip>
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<NButton
|
<NButton
|
||||||
circle
|
circle
|
||||||
type="primary"
|
size="small"
|
||||||
style="height: 30px; width: 30px"
|
type="success"
|
||||||
:loading="isLoading"
|
:loading="isLoading"
|
||||||
@click="onUpdateStatus(SongRequestStatus.Finish)"
|
@click="onUpdateStatus(SongRequestStatus.Finish)"
|
||||||
>
|
>
|
||||||
@@ -263,55 +240,37 @@ const hasOtherSingSong = computed(() => {
|
|||||||
</template>
|
</template>
|
||||||
完成
|
完成
|
||||||
</NTooltip>
|
</NTooltip>
|
||||||
|
|
||||||
<NTooltip>
|
<NTooltip>
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<NPopconfirm
|
<NPopconfirm @positive-click="onUpdateStatus(SongRequestStatus.Cancel)">
|
||||||
@positive-click="onUpdateStatus(SongRequestStatus.Cancel)"
|
|
||||||
>
|
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<NButton
|
<NButton circle size="small" type="error" :loading="isLoading">
|
||||||
circle
|
|
||||||
type="error"
|
|
||||||
style="height: 30px; width: 30px"
|
|
||||||
:loading="isLoading"
|
|
||||||
>
|
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<NIcon :component="Dismiss16Filled" />
|
<NIcon :component="Dismiss16Filled" />
|
||||||
</template>
|
</template>
|
||||||
</NButton>
|
</NButton>
|
||||||
</template>
|
</template>
|
||||||
是否取消处理?
|
确定取消?
|
||||||
</NPopconfirm>
|
</NPopconfirm>
|
||||||
</template>
|
</template>
|
||||||
取消
|
取消
|
||||||
</NTooltip>
|
</NTooltip>
|
||||||
<NTooltip
|
|
||||||
v-if="
|
<NTooltip v-if="song.from == SongRequestFrom.Danmaku && song.user?.uid">
|
||||||
song.from == SongRequestFrom.Danmaku
|
|
||||||
&& song.user?.uid
|
|
||||||
&& song.status !== SongRequestStatus.Cancel
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<NPopconfirm
|
<NPopconfirm @positive-click="onBlockUser">
|
||||||
@positive-click="onBlockUser"
|
|
||||||
>
|
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<NButton
|
<NButton circle size="small" type="error" ghost :loading="isLoading">
|
||||||
circle
|
|
||||||
type="error"
|
|
||||||
style="height: 30px; width: 30px"
|
|
||||||
:loading="isLoading"
|
|
||||||
>
|
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<NIcon :component="PresenceBlocked16Regular" />
|
<NIcon :component="PresenceBlocked16Regular" />
|
||||||
</template>
|
</template>
|
||||||
</NButton>
|
</NButton>
|
||||||
</template>
|
</template>
|
||||||
是否拉黑此用户?
|
确定拉黑此用户?
|
||||||
</NPopconfirm>
|
</NPopconfirm>
|
||||||
</template>
|
</template>
|
||||||
拉黑用户
|
拉黑
|
||||||
</NTooltip>
|
</NTooltip>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
|
|||||||
@@ -52,105 +52,125 @@ async function updateSettings() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NCard size="small">
|
<NSpace vertical :size="12">
|
||||||
<NSpace align="center">
|
<NCard size="small" :bordered="false" content-style="padding: 0;">
|
||||||
<NTag
|
<NSpace justify="space-between" align="center">
|
||||||
type="success"
|
<!-- 左侧统计 -->
|
||||||
:bordered="false"
|
<NSpace align="center" :size="16">
|
||||||
>
|
<NTag type="success" round :bordered="false">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<NIcon :component="PeopleQueue24Filled" />
|
<NIcon :component="PeopleQueue24Filled" />
|
||||||
</template>
|
</template>
|
||||||
队列 | {{ waitingCount }}
|
队列: {{ waitingCount }}
|
||||||
</NTag>
|
</NTag>
|
||||||
<NTag
|
<NTag type="info" round :bordered="false">
|
||||||
type="success"
|
<template #icon>
|
||||||
:bordered="false"
|
<NIcon :component="Checkmark12Regular" />
|
||||||
>
|
</template>
|
||||||
<template #icon>
|
今日已点: {{ todayFinishedCount }}
|
||||||
<NIcon :component="Checkmark12Regular" />
|
</NTag>
|
||||||
</template>
|
<NText depth="3" style="font-size: 12px">
|
||||||
今日已处理 | {{ todayFinishedCount }} 个
|
共 {{ songRequest.activeSongs.length }} 首
|
||||||
</NTag>
|
</NText>
|
||||||
<NInputGroup>
|
</NSpace>
|
||||||
<NInput
|
|
||||||
:value="songRequest.newSongName"
|
<!-- 右侧操作 -->
|
||||||
placeholder="手动添加"
|
<NSpace align="center">
|
||||||
@update:value="songRequest.newSongName = $event"
|
<NInputGroup size="small">
|
||||||
/>
|
<NInput
|
||||||
<NButton
|
:value="songRequest.newSongName"
|
||||||
type="primary"
|
placeholder="手动添加歌曲"
|
||||||
@click="songRequest.addSongManual()"
|
@update:value="songRequest.newSongName = $event"
|
||||||
|
style="width: 150px"
|
||||||
|
/>
|
||||||
|
<NButton type="primary" ghost @click="songRequest.addSongManual()">
|
||||||
|
添加
|
||||||
|
</NButton>
|
||||||
|
</NInputGroup>
|
||||||
|
|
||||||
|
<NRadioGroup
|
||||||
|
v-model:value="accountInfo.settings.songRequest.sortType"
|
||||||
|
:disabled="!songRequest.configCanEdit"
|
||||||
|
size="small"
|
||||||
|
@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
|
||||||
|
:checked="currentIsReverse"
|
||||||
|
size="small"
|
||||||
|
@update:checked="value => {
|
||||||
|
if (songRequest.configCanEdit) {
|
||||||
|
accountInfo.settings.songRequest.isReverse = value
|
||||||
|
updateSettings()
|
||||||
|
} else {
|
||||||
|
songRequest.isReverse = value
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
倒序
|
||||||
|
</NCheckbox>
|
||||||
|
|
||||||
|
<NPopconfirm @positive-click="songRequest.deactiveAllSongs()">
|
||||||
|
<template #trigger>
|
||||||
|
<NButton type="error" size="small" ghost>
|
||||||
|
全部取消
|
||||||
|
</NButton>
|
||||||
|
</template>
|
||||||
|
确定全部取消吗?
|
||||||
|
</NPopconfirm>
|
||||||
|
</NSpace>
|
||||||
|
</NSpace>
|
||||||
|
</NCard>
|
||||||
|
|
||||||
|
<div v-if="songRequest.activeSongs.length > 0" class="song-list-container">
|
||||||
|
<TransitionGroup name="list">
|
||||||
|
<div
|
||||||
|
v-for="(song, index) in songRequest.activeSongs"
|
||||||
|
:key="song.id"
|
||||||
|
class="song-item-wrapper"
|
||||||
>
|
>
|
||||||
添加
|
<SongRequestItem
|
||||||
</NButton>
|
:song="song"
|
||||||
</NInputGroup>
|
:index="index + 1"
|
||||||
<NRadioGroup
|
:is-loading="songRequest.isLoading"
|
||||||
v-model:value="accountInfo.settings.songRequest.sortType"
|
:is-lrc-loading="songRequest.isLrcLoading"
|
||||||
:disabled="!songRequest.configCanEdit"
|
:update-key="songRequest.updateKey"
|
||||||
type="button"
|
/>
|
||||||
@update:value="value => {
|
<NDivider style="margin: 0" />
|
||||||
updateSettings()
|
</div>
|
||||||
}"
|
</TransitionGroup>
|
||||||
>
|
</div>
|
||||||
<NRadioButton :value="QueueSortType.TimeFirst">
|
<NEmpty
|
||||||
加入时间优先
|
v-else
|
||||||
</NRadioButton>
|
description="暂无点播内容"
|
||||||
<NRadioButton :value="QueueSortType.PaymentFist">
|
style="margin-top: 40px"
|
||||||
付费价格优先
|
/>
|
||||||
</NRadioButton>
|
</NSpace>
|
||||||
<NRadioButton :value="QueueSortType.GuardFirst">
|
|
||||||
舰长优先 (按等级)
|
|
||||||
</NRadioButton>
|
|
||||||
<NRadioButton :value="QueueSortType.FansMedalFirst">
|
|
||||||
粉丝牌等级优先
|
|
||||||
</NRadioButton>
|
|
||||||
</NRadioGroup>
|
|
||||||
<NCheckbox
|
|
||||||
:checked="currentIsReverse"
|
|
||||||
@update:checked="value => {
|
|
||||||
if (songRequest.configCanEdit) {
|
|
||||||
accountInfo.settings.songRequest.isReverse = value
|
|
||||||
updateSettings()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
songRequest.isReverse = value
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
倒序
|
|
||||||
</NCheckbox>
|
|
||||||
<NPopconfirm @positive-click="songRequest.deactiveAllSongs()">
|
|
||||||
<template #trigger>
|
|
||||||
<NButton type="error">
|
|
||||||
全部取消
|
|
||||||
</NButton>
|
|
||||||
</template>
|
|
||||||
确定全部取消吗?
|
|
||||||
</NPopconfirm>
|
|
||||||
</NSpace>
|
|
||||||
</NCard>
|
|
||||||
<NDivider> 共 {{ songRequest.activeSongs.length }} 首 </NDivider>
|
|
||||||
<NList
|
|
||||||
v-if="songRequest.activeSongs.length > 0"
|
|
||||||
:show-divider="false"
|
|
||||||
hoverable
|
|
||||||
>
|
|
||||||
<NListItem
|
|
||||||
v-for="song in songRequest.activeSongs"
|
|
||||||
:key="song.id"
|
|
||||||
style="padding: 5px"
|
|
||||||
>
|
|
||||||
<SongRequestItem
|
|
||||||
:song="song"
|
|
||||||
:is-loading="songRequest.isLoading"
|
|
||||||
:is-lrc-loading="songRequest.isLrcLoading"
|
|
||||||
:update-key="songRequest.updateKey"
|
|
||||||
/>
|
|
||||||
</NListItem>
|
|
||||||
</NList>
|
|
||||||
<NEmpty
|
|
||||||
v-else
|
|
||||||
description="暂无曲目"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.song-list-container {
|
||||||
|
margin-top: 10px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.song-item-wrapper {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-enter-from,
|
||||||
|
.list-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-leave-active {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user