mirror of
https://github.com/Megghy/vtsuru.live.git
synced 2025-12-06 18:36:55 +08:00
feat: 在QueueOBS组件中添加滚动速度控制功能
- 新增speedMultiplier属性以控制滚动速度 - 在OpenQueue组件中添加滚动速度输入框,允许用户设置速度并复制带速度的URL - 优化了队列项的显示和动画效果
This commit is contained in:
@@ -8,20 +8,18 @@ import {
|
|||||||
} from '@/api/api-models'
|
} from '@/api/api-models'
|
||||||
import { QueryGetAPI } from '@/api/query'
|
import { QueryGetAPI } from '@/api/query'
|
||||||
import { QUEUE_API_URL } from '@/data/constants'
|
import { QUEUE_API_URL } from '@/data/constants'
|
||||||
import { MittType } from '@/mitt'
|
|
||||||
import { useWebRTC } from '@/store/useRTC'
|
import { useWebRTC } from '@/store/useRTC'
|
||||||
import { useElementSize } from '@vueuse/core'
|
import { useElementSize } from '@vueuse/core'
|
||||||
import { List } from 'linqts'
|
import { List } from 'linqts'
|
||||||
import mitt from 'mitt'
|
|
||||||
import { NDivider, NEmpty, useMessage } from 'naive-ui'
|
import { NDivider, NEmpty, useMessage } from 'naive-ui'
|
||||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import { Vue3Marquee } from 'vue3-marquee'
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
id?: number,
|
id?: number,
|
||||||
active?: boolean,
|
active?: boolean,
|
||||||
visible?: boolean,
|
visible?: boolean,
|
||||||
|
speedMultiplier?: number,
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
@@ -31,6 +29,15 @@ const currentId = computed(() => {
|
|||||||
})
|
})
|
||||||
const rtc = await useWebRTC().Init('slave')
|
const rtc = await useWebRTC().Init('slave')
|
||||||
|
|
||||||
|
const speedMultiplier = computed(() => {
|
||||||
|
if (props.speedMultiplier !== undefined && props.speedMultiplier > 0) {
|
||||||
|
return props.speedMultiplier
|
||||||
|
}
|
||||||
|
const speedParam = route.query.speed
|
||||||
|
const speed = parseFloat(speedParam?.toString() ?? '1')
|
||||||
|
return isNaN(speed) || speed <= 0 ? 1 : speed
|
||||||
|
})
|
||||||
|
|
||||||
const listContainerRef = ref()
|
const listContainerRef = ref()
|
||||||
const footerRef = ref()
|
const footerRef = ref()
|
||||||
const footerListRef = ref()
|
const footerListRef = ref()
|
||||||
@@ -39,7 +46,17 @@ const footerSize = useElementSize(footerRef)
|
|||||||
const footerListSize = useElementSize(footerListRef)
|
const footerListSize = useElementSize(footerListRef)
|
||||||
const itemHeight = 40
|
const itemHeight = 40
|
||||||
|
|
||||||
const key = ref(Date.now())
|
const queueListInnerRef = ref<HTMLElement | null>(null)
|
||||||
|
const { height: innerListHeight } = useElementSize(queueListInnerRef)
|
||||||
|
|
||||||
|
const itemMarginBottom = 0
|
||||||
|
const totalContentHeightWithLastMargin = computed(() => {
|
||||||
|
const count = activeItems.value.length
|
||||||
|
if (count === 0 || innerListHeight.value <= 0) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return innerListHeight.value + itemMarginBottom
|
||||||
|
})
|
||||||
|
|
||||||
const queue = ref<ResponseQueueModel[]>([])
|
const queue = ref<ResponseQueueModel[]>([])
|
||||||
const settings = ref<Setting_Queue>({} as Setting_Queue)
|
const settings = ref<Setting_Queue>({} as Setting_Queue)
|
||||||
@@ -79,6 +96,9 @@ const activeItems = computed(() => {
|
|||||||
list = list.OrderByDescending((q) => (q.status == QueueStatus.Progressing ? 1 : 0))
|
list = list.OrderByDescending((q) => (q.status == QueueStatus.Progressing ? 1 : 0))
|
||||||
return list.ToArray()
|
return list.ToArray()
|
||||||
})
|
})
|
||||||
|
const itemNum = computed(() => {
|
||||||
|
return queue.value.length
|
||||||
|
})
|
||||||
|
|
||||||
async function get() {
|
async function get() {
|
||||||
try {
|
try {
|
||||||
@@ -94,9 +114,26 @@ async function get() {
|
|||||||
} catch (err) { }
|
} catch (err) { }
|
||||||
return {} as { queue: ResponseQueueModel[]; setting: Setting_Queue }
|
return {} as { queue: ResponseQueueModel[]; setting: Setting_Queue }
|
||||||
}
|
}
|
||||||
|
|
||||||
const isMoreThanContainer = computed(() => {
|
const isMoreThanContainer = computed(() => {
|
||||||
return queue.value.length * itemHeight > height.value
|
return totalContentHeightWithLastMargin.value > height.value
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const animationTranslateY = computed(() => {
|
||||||
|
if (!isMoreThanContainer.value || height.value <= 0) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return height.value - totalContentHeightWithLastMargin.value
|
||||||
|
})
|
||||||
|
const animationTranslateYCss = computed(() => `${animationTranslateY.value}px`)
|
||||||
|
|
||||||
|
const animationDuration = computed(() => {
|
||||||
|
const baseDuration = activeItems.value.length * 1
|
||||||
|
const adjustedDuration = baseDuration / speedMultiplier.value
|
||||||
|
return Math.max(adjustedDuration, 1)
|
||||||
|
})
|
||||||
|
const animationDurationCss = computed(() => `${animationDuration.value}s`)
|
||||||
|
|
||||||
const allowGuardTypes = computed(() => {
|
const allowGuardTypes = computed(() => {
|
||||||
const types = []
|
const types = []
|
||||||
if (settings.value.needTidu) {
|
if (settings.value.needTidu) {
|
||||||
@@ -174,13 +211,11 @@ onUnmounted(() => {
|
|||||||
class="queue-content"
|
class="queue-content"
|
||||||
>
|
>
|
||||||
<template v-if="activeItems.length > 0">
|
<template v-if="activeItems.length > 0">
|
||||||
<Vue3Marquee
|
<div
|
||||||
:key="key"
|
ref="queueListInnerRef"
|
||||||
class="queue-list"
|
class="queue-list"
|
||||||
vertical
|
:class="{ animating: isMoreThanContainer }"
|
||||||
:pause="!isMoreThanContainer"
|
:style="`width: ${width}px;`"
|
||||||
:duration="20"
|
|
||||||
:style="`height: ${height}px;width: ${width}px;`"
|
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
v-for="(item, index) in activeItems"
|
v-for="(item, index) in activeItems"
|
||||||
@@ -198,31 +233,23 @@ onUnmounted(() => {
|
|||||||
{{ index + 1 }}
|
{{ index + 1 }}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="settings.showFanMadelInfo"
|
v-if="settings.showFanMadelInfo && (item.user?.fans_medal_level ?? 0) > 0"
|
||||||
class="queue-list-item-level"
|
class="queue-list-item-level"
|
||||||
:has-level="(item.user?.fans_medal_level ?? 0) > 0"
|
:has-level="(item.user?.fans_medal_level ?? 0) > 0"
|
||||||
>
|
>
|
||||||
{{ `${item.user?.fans_medal_name} ${item.user?.fans_medal_level}` }}
|
{{ `${item.user?.fans_medal_name || ''} ${item.user?.fans_medal_level || ''}` }}
|
||||||
</div>
|
</div>
|
||||||
<div class="queue-list-item-user-name">
|
<div class="queue-list-item-user-name">
|
||||||
{{ item.user?.name }}
|
{{ item.user?.name || '未知用户' }}
|
||||||
</div>
|
</div>
|
||||||
<p
|
<div
|
||||||
v-if="settings.showPayment"
|
v-if="item.from == QueueFrom.Manual || ((item.giftPrice ?? 0) > 0 || settings.showPayment)"
|
||||||
class="queue-list-item-payment"
|
class="queue-list-item-payment"
|
||||||
>
|
>
|
||||||
{{
|
{{ item.from == QueueFrom.Manual ? '主播添加' : item.giftPrice == undefined ? '无' : '¥ ' + item.giftPrice }}
|
||||||
item.from == QueueFrom.Manual ? '主播添加' : item.giftPrice == undefined ? '无' : '¥ ' + item.giftPrice
|
</div>
|
||||||
}}
|
|
||||||
</p>
|
|
||||||
</span>
|
</span>
|
||||||
|
</div>
|
||||||
<NDivider
|
|
||||||
v-if="isMoreThanContainer"
|
|
||||||
class="queue-footer-divider"
|
|
||||||
style="margin: 10px 0 10px 0"
|
|
||||||
/>
|
|
||||||
</Vue3Marquee>
|
|
||||||
</template>
|
</template>
|
||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
@@ -239,74 +266,112 @@ onUnmounted(() => {
|
|||||||
ref="footerRef"
|
ref="footerRef"
|
||||||
class="queue-footer"
|
class="queue-footer"
|
||||||
>
|
>
|
||||||
<Vue3Marquee
|
<div class="queue-footer-info">
|
||||||
:key="key"
|
<div class="queue-footer-tags">
|
||||||
ref="footerListRef"
|
<div
|
||||||
class="queue-footer-marquee"
|
class="queue-footer-tag"
|
||||||
:pause="footerSize.width < footerListSize.width"
|
type="keyword"
|
||||||
:duration="20"
|
>
|
||||||
>
|
<span class="tag-label">关键词</span>
|
||||||
<span
|
<span class="tag-value">{{ settings.keyword }}</span>
|
||||||
class="queue-tag"
|
|
||||||
type="prefix"
|
|
||||||
>
|
|
||||||
<div class="queue-tag-key">关键词</div>
|
|
||||||
<div class="queue-tag-value">
|
|
||||||
{{ settings.keyword }}
|
|
||||||
</div>
|
</div>
|
||||||
</span>
|
<div
|
||||||
<span
|
class="queue-footer-tag"
|
||||||
class="queue-tag"
|
type="allow"
|
||||||
type="prefix"
|
>
|
||||||
>
|
<span class="tag-label">允许</span>
|
||||||
<div class="queue-tag-key">允许</div>
|
<span class="tag-value">{{ settings.allowAllDanmaku ? '所有弹幕' : allowGuardTypes.length > 0 ? allowGuardTypes.join('/') : '无' }}</span>
|
||||||
<div class="queue-tag-value">
|
|
||||||
{{ settings.allowAllDanmaku ? '所有弹幕' : allowGuardTypes.length > 0 ? allowGuardTypes.join(',') : '无' }}
|
|
||||||
</div>
|
</div>
|
||||||
</span>
|
<div
|
||||||
<span
|
class="queue-footer-tag"
|
||||||
class="queue-tag"
|
type="gift"
|
||||||
type="gift"
|
>
|
||||||
>
|
<span class="tag-label">礼物</span>
|
||||||
<div class="queue-tag-key">通过礼物</div>
|
<span class="tag-value">{{ settings.allowGift ? '允许' : '不允许' }}</span>
|
||||||
<div class="queue-tag-value">
|
|
||||||
{{ settings.allowGift ? '允许' : '不允许' }}
|
|
||||||
</div>
|
</div>
|
||||||
</span>
|
<div
|
||||||
<span
|
class="queue-footer-tag"
|
||||||
class="queue-tag"
|
type="price"
|
||||||
type="gift-price"
|
>
|
||||||
>
|
<span class="tag-label">最低价格</span>
|
||||||
<div class="queue-tag-key">最低价格</div>
|
<span class="tag-value">{{ settings.minGiftPrice ? '> ¥' + settings.minGiftPrice : '任意' }}</span>
|
||||||
<div class="queue-tag-value">
|
|
||||||
{{ settings.minGiftPrice ? '> ¥' + settings.minGiftPrice : '任意' }}
|
|
||||||
</div>
|
</div>
|
||||||
</span>
|
<div
|
||||||
<span
|
class="queue-footer-tag"
|
||||||
class="queue-tag"
|
type="gift-names"
|
||||||
type="gift-type"
|
>
|
||||||
>
|
<span class="tag-label">礼物名</span>
|
||||||
<div class="queue-tag-key">礼物名</div>
|
<span class="tag-value">{{ settings.giftNames ? settings.giftNames.join(', ') : '无' }}</span>
|
||||||
<div class="queue-tag-value">
|
|
||||||
{{ settings.giftNames ? settings.giftNames.join(', ') : '无' }}
|
|
||||||
</div>
|
</div>
|
||||||
</span>
|
<div
|
||||||
<span
|
class="queue-footer-tag"
|
||||||
class="queue-tag"
|
type="medal"
|
||||||
type="fan-madel"
|
>
|
||||||
>
|
<span class="tag-label">粉丝牌</span>
|
||||||
<div class="queue-tag-key">粉丝牌</div>
|
<span class="tag-value">
|
||||||
<div class="queue-tag-value">
|
{{
|
||||||
{{
|
settings.fanMedalMinLevel != undefined && !settings.allowAllDanmaku
|
||||||
settings.fanMedalMinLevel != undefined && !settings.allowAllDanmaku
|
? settings.fanMedalMinLevel > 0
|
||||||
? settings.fanMedalMinLevel > 0
|
? '> ' + settings.fanMedalMinLevel
|
||||||
? '> ' + settings.fanMedalMinLevel
|
: '佩戴'
|
||||||
: '佩戴'
|
: '无需'
|
||||||
: '无需'
|
}}
|
||||||
}}
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</span>
|
<!-- 重复标签组,实现无缝滚动 -->
|
||||||
</Vue3Marquee>
|
<div
|
||||||
|
class="queue-footer-tag"
|
||||||
|
type="keyword"
|
||||||
|
>
|
||||||
|
<span class="tag-label">关键词</span>
|
||||||
|
<span class="tag-value">{{ settings.keyword }}</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="queue-footer-tag"
|
||||||
|
type="allow"
|
||||||
|
>
|
||||||
|
<span class="tag-label">允许</span>
|
||||||
|
<span class="tag-value">{{ settings.allowAllDanmaku ? '所有弹幕' : allowGuardTypes.length > 0 ? allowGuardTypes.join('/') : '无' }}</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="queue-footer-tag"
|
||||||
|
type="gift"
|
||||||
|
>
|
||||||
|
<span class="tag-label">礼物</span>
|
||||||
|
<span class="tag-value">{{ settings.allowGift ? '允许' : '不允许' }}</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="queue-footer-tag"
|
||||||
|
type="price"
|
||||||
|
>
|
||||||
|
<span class="tag-label">最低价格</span>
|
||||||
|
<span class="tag-value">{{ settings.minGiftPrice ? '> ¥' + settings.minGiftPrice : '任意' }}</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="settings.giftNames && settings.giftNames.length > 0"
|
||||||
|
class="queue-footer-tag"
|
||||||
|
type="gift-names"
|
||||||
|
>
|
||||||
|
<span class="tag-label">礼物名</span>
|
||||||
|
<span class="tag-value">{{ settings.giftNames ? settings.giftNames.join(', ') : '无' }}</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="queue-footer-tag"
|
||||||
|
type="medal"
|
||||||
|
>
|
||||||
|
<span class="tag-label">粉丝牌</span>
|
||||||
|
<span class="tag-value">
|
||||||
|
{{
|
||||||
|
settings.fanMedalMinLevel != undefined && !settings.allowAllDanmaku
|
||||||
|
? settings.fanMedalMinLevel > 0
|
||||||
|
? '> ' + settings.fanMedalMinLevel
|
||||||
|
: '佩戴'
|
||||||
|
: '无需'
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -383,11 +448,9 @@ onUnmounted(() => {
|
|||||||
.queue-singing-avatar {
|
.queue-singing-avatar {
|
||||||
height: 30px;
|
height: 30px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
/* 添加无限旋转动画 */
|
|
||||||
animation: rotate 20s linear infinite;
|
animation: rotate 20s linear infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 网页点歌 */
|
|
||||||
.queue-singing-container[from='3'] .queue-singing-avatar {
|
.queue-singing-container[from='3'] .queue-singing-avatar {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@@ -417,50 +480,62 @@ onUnmounted(() => {
|
|||||||
.queue-content {
|
.queue-content {
|
||||||
background-color: #0f0f0f4f;
|
background-color: #0f0f0f4f;
|
||||||
margin: 10px;
|
margin: 10px;
|
||||||
padding: 10px;
|
padding: 8px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
overflow-x: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.marquee {
|
.queue-list {
|
||||||
justify-items: left;
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes vertical-ping-pong {
|
||||||
|
0% {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateY(v-bind(animationTranslateYCss));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.queue-list.animating {
|
||||||
|
animation-name: vertical-ping-pong;
|
||||||
|
animation-duration: v-bind(animationDurationCss);
|
||||||
|
animation-timing-function: ease-in-out;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
animation-direction: alternate;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.queue-list.animating:hover {
|
||||||
|
animation-play-state: paused;
|
||||||
}
|
}
|
||||||
|
|
||||||
.queue-list-item {
|
.queue-list-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
|
||||||
align-self: flex-start;
|
align-self: flex-start;
|
||||||
position: relative;
|
position: relative;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: left;
|
justify-content: left;
|
||||||
gap: 10px;
|
gap: 5px;
|
||||||
|
padding: 4px 6px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
background-color: rgba(0, 0, 0, 0.2);
|
||||||
|
border-radius: 6px;
|
||||||
|
min-height: 36px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.queue-list-item-user-name {
|
.queue-list-item-user-name {
|
||||||
font-size: 18px;
|
font-size: 16px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
max-width: 80%;
|
max-width: 50%;
|
||||||
}
|
flex-grow: 1;
|
||||||
|
|
||||||
/* 手动添加 */
|
|
||||||
.queue-list-item[from='0'] .queue-list-item-payment {
|
|
||||||
font-style: italic;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #d2d8d6;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.queue-list-item[from='0'] .queue-list-item-avatar {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 弹幕点歌 */
|
|
||||||
.queue-list-item[payment='0'] .queue-list-item-payment {
|
|
||||||
display: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.queue-list-item-payment {
|
.queue-list-item-payment {
|
||||||
@@ -469,8 +544,23 @@ onUnmounted(() => {
|
|||||||
color: rgba(233, 165, 165, 0.993);
|
color: rgba(233, 165, 165, 0.993);
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
|
background-color: rgba(0, 0, 0, 0.2);
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
min-width: 50px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.queue-list-item-level {
|
||||||
|
text-align: center;
|
||||||
|
height: 18px;
|
||||||
|
padding: 2px 6px;
|
||||||
|
min-width: 15px;
|
||||||
|
border-radius: 5px;
|
||||||
|
background-color: rgba(0, 0, 0, 0.3);
|
||||||
|
color: rgba(204, 204, 204, 0.993);
|
||||||
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.queue-list-item-index {
|
.queue-list-item-index {
|
||||||
@@ -518,36 +608,103 @@ onUnmounted(() => {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 底部信息区域样式优化 */
|
||||||
.queue-footer {
|
.queue-footer {
|
||||||
margin: 0 5px 5px 5px;
|
margin: 0 5px 5px 5px;
|
||||||
height: 60px;
|
background-color: rgba(0, 0, 0, 0.25);
|
||||||
border-radius: 5px;
|
border-radius: 8px;
|
||||||
background-color: #0f0f0f4f;
|
padding: 8px 6px;
|
||||||
|
overflow: hidden;
|
||||||
|
height: auto;
|
||||||
|
min-height: 40px;
|
||||||
|
max-height: 60px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.queue-tag {
|
.queue-footer-info {
|
||||||
display: flex;
|
width: 100%;
|
||||||
margin: 5px 0 5px 5px;
|
overflow: hidden;
|
||||||
height: 40px;
|
position: relative;
|
||||||
border-radius: 3px;
|
}
|
||||||
background-color: #0f0f0f4f;
|
|
||||||
padding: 4px;
|
.queue-footer-tags {
|
||||||
padding-right: 6px;
|
display: inline-flex;
|
||||||
display: flex;
|
flex-wrap: nowrap;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 2px;
|
||||||
|
white-space: nowrap;
|
||||||
|
animation: scrollTags 25s linear infinite;
|
||||||
|
padding-right: 16px; /* 确保最后一个标签有足够间距 */
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes scrollTags {
|
||||||
|
0% {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateX(-50%); /* 移动一半距离,因为我们复制了标签 */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.queue-footer-tags:hover {
|
||||||
|
animation-play-state: paused;
|
||||||
|
}
|
||||||
|
|
||||||
|
.queue-footer-tag {
|
||||||
|
display: inline-flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: left;
|
padding: 5px 8px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background-color: rgba(255, 255, 255, 0.12);
|
||||||
|
min-width: max-content;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||||
|
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.queue-tag-key {
|
.queue-footer-tag[type="keyword"] {
|
||||||
font-style: italic;
|
background: linear-gradient(135deg, rgba(59, 130, 246, 0.12), rgba(37, 99, 235, 0.18));
|
||||||
color: rgb(211, 211, 211);
|
}
|
||||||
|
|
||||||
|
.queue-footer-tag[type="allow"] {
|
||||||
|
background: linear-gradient(135deg, rgba(16, 185, 129, 0.12), rgba(5, 150, 105, 0.18));
|
||||||
|
}
|
||||||
|
|
||||||
|
.queue-footer-tag[type="gift"] {
|
||||||
|
background: linear-gradient(135deg, rgba(244, 114, 182, 0.12), rgba(219, 39, 119, 0.18));
|
||||||
|
}
|
||||||
|
|
||||||
|
.queue-footer-tag[type="price"] {
|
||||||
|
background: linear-gradient(135deg, rgba(251, 191, 36, 0.12), rgba(245, 158, 11, 0.18));
|
||||||
|
}
|
||||||
|
|
||||||
|
.queue-footer-tag[type="gift-names"] {
|
||||||
|
background: linear-gradient(135deg, rgba(139, 92, 246, 0.12), rgba(124, 58, 237, 0.18));
|
||||||
|
}
|
||||||
|
|
||||||
|
.queue-footer-tag[type="medal"] {
|
||||||
|
background: linear-gradient(135deg, rgba(239, 68, 68, 0.12), rgba(220, 38, 38, 0.18));
|
||||||
|
}
|
||||||
|
|
||||||
|
.queue-footer-tag:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-label {
|
||||||
|
font-size: 10px;
|
||||||
|
opacity: 0.8;
|
||||||
|
color: #e5e7eb;
|
||||||
|
font-weight: normal;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-value {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
font-weight: 600;
|
||||||
|
color: white;
|
||||||
.queue-tag-value {
|
line-height: 1.2;
|
||||||
font-size: 14px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes animated-border {
|
@keyframes animated-border {
|
||||||
@@ -559,4 +716,19 @@ onUnmounted(() => {
|
|||||||
box-shadow: 0 0 0 6px rgba(255, 255, 255, 0);
|
box-shadow: 0 0 0 6px rgba(255, 255, 255, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.queue-list-item[from='0'] .queue-list-item-payment {
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #d2d8d6;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.queue-list-item[from='0'] .queue-list-item-avatar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.queue-list-item[payment='0'] .queue-list-item-payment {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -125,6 +125,7 @@ const isReverse = useStorage('Queue.Settings.Reverse', false); // 本地存储
|
|||||||
|
|
||||||
const isLoading = ref(false); // 加载状态
|
const isLoading = ref(false); // 加载状态
|
||||||
const showOBSModal = ref(false); // OBS 组件模态框显示状态
|
const showOBSModal = ref(false); // OBS 组件模态框显示状态
|
||||||
|
const obsScrollSpeed = ref(1.0); // OBS 组件滚动速度
|
||||||
|
|
||||||
const filterName = ref(''); // 历史记录筛选用户名
|
const filterName = ref(''); // 历史记录筛选用户名
|
||||||
const filterNameContains = ref(false); // 历史记录筛选是否包含
|
const filterNameContains = ref(false); // 历史记录筛选是否包含
|
||||||
@@ -1862,6 +1863,24 @@ function getIndexStyle(status: QueueStatus): CSSProperties {
|
|||||||
复制
|
复制
|
||||||
</NButton>
|
</NButton>
|
||||||
</NInputGroup>
|
</NInputGroup>
|
||||||
|
<!-- 添加速度控制 -->
|
||||||
|
<NInputGroup style="margin-bottom: 15px;">
|
||||||
|
<NInputGroupLabel>滚动速度</NInputGroupLabel>
|
||||||
|
<NInputNumber
|
||||||
|
v-model:value="obsScrollSpeed"
|
||||||
|
:min="0.5"
|
||||||
|
:max="5"
|
||||||
|
:step="0.1"
|
||||||
|
placeholder="默认1.0"
|
||||||
|
/>
|
||||||
|
<NButton
|
||||||
|
type="primary"
|
||||||
|
ghost
|
||||||
|
@click="copyToClipboard(`${CURRENT_HOST}obs/queue?id=${accountInfo?.id}&speed=${obsScrollSpeed}`)"
|
||||||
|
>
|
||||||
|
复制带速度URL
|
||||||
|
</NButton>
|
||||||
|
</NInputGroup>
|
||||||
<NDivider> 预览 (尺寸可能与实际不同) </NDivider>
|
<NDivider> 预览 (尺寸可能与实际不同) </NDivider>
|
||||||
<div
|
<div
|
||||||
style="height: 450px; width: 280px; position: relative; margin: 0 auto; border: 1px dashed #ccc; overflow: hidden;"
|
style="height: 450px; width: 280px; position: relative; margin: 0 auto; border: 1px dashed #ccc; overflow: hidden;"
|
||||||
@@ -1869,6 +1888,7 @@ function getIndexStyle(status: QueueStatus): CSSProperties {
|
|||||||
<QueueOBS
|
<QueueOBS
|
||||||
v-if="accountInfo?.id"
|
v-if="accountInfo?.id"
|
||||||
:id="accountInfo.id"
|
:id="accountInfo.id"
|
||||||
|
:speed-multiplier="obsScrollSpeed"
|
||||||
/>
|
/>
|
||||||
<NEmpty
|
<NEmpty
|
||||||
v-else
|
v-else
|
||||||
|
|||||||
Reference in New Issue
Block a user