mirror of
https://github.com/Megghy/vtsuru.live.git
synced 2025-12-06 18:36:55 +08:00
fix: no voice in speech page; custom personal page redirect notworking. feat: sync sroll bar between question display page an obs component
This commit is contained in:
@@ -34,6 +34,7 @@
|
||||
"html2canvas": "^1.4.1",
|
||||
"linqts": "^2.0.0",
|
||||
"mitt": "^3.0.1",
|
||||
"monaco-editor": "^0.52.0",
|
||||
"music-metadata-browser": "^2.5.11",
|
||||
"peerjs": "^1.5.4",
|
||||
"pinia": "^2.2.6",
|
||||
@@ -43,6 +44,7 @@
|
||||
"unplugin-vue-markdown": "^0.26.2",
|
||||
"uuid": "^11.0.2",
|
||||
"vite": "^5.4.10",
|
||||
"vite-plugin-monaco-editor": "^1.1.0",
|
||||
"vite-svg-loader": "^5.1.0",
|
||||
"vue": "3.5.12",
|
||||
"vue-echarts": "^7.0.3",
|
||||
|
||||
@@ -229,6 +229,7 @@ export interface Setting_QuestionDisplay {
|
||||
|
||||
borderColor?: string
|
||||
borderWidth?: number
|
||||
syncScroll: boolean
|
||||
|
||||
currentQuestion?: number
|
||||
}
|
||||
|
||||
32
src/components/MonacoEditorComponent.vue
Normal file
32
src/components/MonacoEditorComponent.vue
Normal file
@@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<div ref="editorContainer" :style="`height: ${height}px;`"></div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { editor } from 'monaco-editor'; // 全部导入
|
||||
import { ref, onMounted } from 'vue';
|
||||
|
||||
const value = defineModel<string>('value')
|
||||
|
||||
const { language, height = 400 } = defineProps<{
|
||||
language: string
|
||||
height?: number
|
||||
}>()
|
||||
|
||||
const editorContainer = ref()
|
||||
|
||||
onMounted(() => {
|
||||
const e = editor.create(editorContainer.value, {
|
||||
value: value.value,
|
||||
language: language,
|
||||
minimap: {
|
||||
enabled: true
|
||||
},
|
||||
colorDecorators: true,
|
||||
automaticLayout: true
|
||||
})
|
||||
e.onDidChangeModelContent(() => {
|
||||
value.value = e.getValue()
|
||||
})
|
||||
})
|
||||
</script>
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { VideoCollectTable } from '@/api/api-models'
|
||||
import { CURRENT_HOST } from '@/data/constants';
|
||||
import router from '@/router'
|
||||
import { Clock24Regular, NumberRow24Regular } from '@vicons/fluent'
|
||||
import {
|
||||
@@ -28,7 +29,7 @@ const renderCountdown: CountdownProps['render'] = (info: { hours: number; minute
|
||||
function onClick() {
|
||||
if (props.canClick == true) {
|
||||
if (props.from == 'user') {
|
||||
window.open('https://vtsuru.live/video-collect/' + props.item.shortId, '_blank')
|
||||
window.open(`${CURRENT_HOST}video-collect/` + props.item.shortId, '_blank')
|
||||
} else {
|
||||
router.push({ name: 'manage-videoCollect-Detail', params: { id: props.item.id } })
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ export interface RTCData {
|
||||
Data: any
|
||||
}
|
||||
|
||||
abstract class BaseRTCClient {
|
||||
export abstract class BaseRTCClient {
|
||||
constructor(user: string, pass: string) {
|
||||
this.user = user
|
||||
this.pass = pass
|
||||
|
||||
@@ -28,6 +28,8 @@ export const BASE_HUB_URL = {
|
||||
|
||||
export const TURNSTILE_KEY = '0x4AAAAAAAETUSAKbds019h0'
|
||||
|
||||
export const CURRENT_HOST = `${window.location.protocol}//${window.location.host}/`
|
||||
|
||||
export const USER_API_URL = { toString: () => `${BASE_API_URL}user/` }
|
||||
export const ACCOUNT_API_URL = { toString: () => `${BASE_API_URL}account/` }
|
||||
export const BILI_API_URL = { toString: () => `${BASE_API_URL}bili/` }
|
||||
|
||||
18
src/main.ts
18
src/main.ts
@@ -13,13 +13,6 @@ import { useVTsuruHub } from './store/useVTsuruHub'
|
||||
|
||||
const pinia = createPinia()
|
||||
|
||||
const app = createApp(App)
|
||||
app.use(router).use(pinia).mount('#app')
|
||||
|
||||
let currentVersion: string
|
||||
let isHaveNewVersion = false
|
||||
|
||||
const { notification } = createDiscreteApi(['notification'])
|
||||
QueryGetAPI<string>(BASE_API_URL + 'vtsuru/version')
|
||||
.then((version) => {
|
||||
if (version.code == 200) {
|
||||
@@ -99,6 +92,7 @@ QueryGetAPI<string>(BASE_API_URL + 'vtsuru/version')
|
||||
})
|
||||
.finally(async () => {
|
||||
//加载其他数据
|
||||
InitTTS()
|
||||
await GetSelfAccount()
|
||||
const account = useAccount()
|
||||
const useAuth = useAuthStore()
|
||||
@@ -110,8 +104,16 @@ QueryGetAPI<string>(BASE_API_URL + 'vtsuru/version')
|
||||
useAuth.getAuthInfo()
|
||||
GetNotifactions()
|
||||
UpdateAccountLoop()
|
||||
InitTTS()
|
||||
})
|
||||
|
||||
const app = createApp(App)
|
||||
app.use(router).use(pinia).mount('#app')
|
||||
|
||||
let currentVersion: string
|
||||
let isHaveNewVersion = false
|
||||
|
||||
const { notification } = createDiscreteApi(['notification'])
|
||||
|
||||
function InitTTS() {
|
||||
try {
|
||||
const result = EasySpeech.detect()
|
||||
|
||||
@@ -8,8 +8,8 @@ export default //管理页面
|
||||
name: 'manage-index',
|
||||
component: () => import('@/views/manage/DashboardView.vue'),
|
||||
meta: {
|
||||
title: '面板',
|
||||
},
|
||||
title: '面板'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'song-list',
|
||||
@@ -17,8 +17,8 @@ export default //管理页面
|
||||
component: () => import('@/views/manage/SongListManageView.vue'),
|
||||
meta: {
|
||||
title: '歌单',
|
||||
keepAlive: true,
|
||||
},
|
||||
keepAlive: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'question-box',
|
||||
@@ -26,8 +26,8 @@ export default //管理页面
|
||||
component: () => import('@/views/manage/QuestionBoxManageView.vue'),
|
||||
meta: {
|
||||
title: '提问箱',
|
||||
keepAlive: true,
|
||||
},
|
||||
keepAlive: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'lottery',
|
||||
@@ -35,8 +35,8 @@ export default //管理页面
|
||||
component: () => import('@/views/manage/LotteryView.vue'),
|
||||
meta: {
|
||||
title: '动态抽奖',
|
||||
keepAlive: true,
|
||||
},
|
||||
keepAlive: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'history',
|
||||
@@ -44,8 +44,8 @@ export default //管理页面
|
||||
component: () => import('@/views/manage/HistoryView.vue'),
|
||||
meta: {
|
||||
title: '数据跟踪',
|
||||
keepAlive: true,
|
||||
},
|
||||
keepAlive: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'schedule',
|
||||
@@ -53,8 +53,8 @@ export default //管理页面
|
||||
component: () => import('@/views/manage/ScheduleManageView.vue'),
|
||||
meta: {
|
||||
title: '日程',
|
||||
keepAlive: true,
|
||||
},
|
||||
keepAlive: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'event',
|
||||
@@ -62,8 +62,8 @@ export default //管理页面
|
||||
component: () => import('@/views/manage/EventView.vue'),
|
||||
meta: {
|
||||
title: '事件记录',
|
||||
keepAlive: true,
|
||||
},
|
||||
keepAlive: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'video-collect',
|
||||
@@ -71,8 +71,8 @@ export default //管理页面
|
||||
component: () => import('@/views/manage/VideoCollectManageView.vue'),
|
||||
meta: {
|
||||
title: '视频征集',
|
||||
keepAlive: true,
|
||||
},
|
||||
keepAlive: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'video-collect/:id',
|
||||
@@ -80,8 +80,8 @@ export default //管理页面
|
||||
component: () => import('@/views/manage/VideoCollectDetailView.vue'),
|
||||
meta: {
|
||||
title: '详情 · 视频征集',
|
||||
parent: 'manage-videoCollect',
|
||||
},
|
||||
parent: 'manage-videoCollect'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'live-lottery',
|
||||
@@ -90,8 +90,8 @@ export default //管理页面
|
||||
meta: {
|
||||
title: '直播抽奖',
|
||||
keepAlive: true,
|
||||
danmaku: true,
|
||||
},
|
||||
danmaku: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'queue',
|
||||
@@ -100,8 +100,8 @@ export default //管理页面
|
||||
meta: {
|
||||
title: '排队',
|
||||
keepAlive: true,
|
||||
danmaku: true,
|
||||
},
|
||||
danmaku: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'speech',
|
||||
@@ -110,8 +110,8 @@ export default //管理页面
|
||||
meta: {
|
||||
title: '读弹幕',
|
||||
keepAlive: true,
|
||||
danmaku: true,
|
||||
},
|
||||
danmaku: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'live-request',
|
||||
@@ -120,8 +120,8 @@ export default //管理页面
|
||||
meta: {
|
||||
title: '点播',
|
||||
keepAlive: true,
|
||||
danmaku: true,
|
||||
},
|
||||
danmaku: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'music-request',
|
||||
@@ -130,8 +130,19 @@ export default //管理页面
|
||||
meta: {
|
||||
title: '点歌',
|
||||
keepAlive: true,
|
||||
danmaku: true,
|
||||
danmaku: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'danmuji',
|
||||
name: 'manage-danmuji',
|
||||
component: () => import('@/views/manage/DanmujiManageView.vue'),
|
||||
meta: {
|
||||
title: '点歌',
|
||||
keepAlive: true,
|
||||
danmaku: true,
|
||||
isNew: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'live',
|
||||
@@ -139,40 +150,40 @@ export default //管理页面
|
||||
component: () => import('@/views/manage/LiveManager.vue'),
|
||||
meta: {
|
||||
title: '直播记录',
|
||||
keepAlive: true,
|
||||
},
|
||||
keepAlive: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'live/:id',
|
||||
name: 'manage-liveDetail',
|
||||
component: () => import('@/views/manage/LiveDetailManage.vue'),
|
||||
meta: {
|
||||
title: '直播详情',
|
||||
},
|
||||
title: '直播详情'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'feedback',
|
||||
name: 'manage-feedback',
|
||||
component: () => import('@/views/FeedbackManage.vue'),
|
||||
meta: {
|
||||
title: '反馈',
|
||||
},
|
||||
title: '反馈'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'point',
|
||||
name: 'manage-point',
|
||||
component: () => import('@/views/manage/point/PointManage.vue'),
|
||||
meta: {
|
||||
title: '积分',
|
||||
},
|
||||
title: '积分'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'forum',
|
||||
name: 'manage-forum',
|
||||
component: () => import('@/views/manage/ForumManage.vue'),
|
||||
meta: {
|
||||
title: '粉丝讨论区',
|
||||
},
|
||||
},
|
||||
],
|
||||
title: '粉丝讨论区'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,55 +1,68 @@
|
||||
import { useAccount } from '@/api/account'
|
||||
import { MasterRTCClient, SlaveRTCClient } from '@/data/RTCClient'
|
||||
import {
|
||||
BaseRTCClient,
|
||||
MasterRTCClient,
|
||||
SlaveRTCClient
|
||||
} from '@/data/RTCClient'
|
||||
import { nonFunctionArgSeparator } from 'html2canvas/dist/types/css/syntax/parser'
|
||||
import { acceptHMRUpdate, defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
|
||||
export const useWebRTC = defineStore('WebRTC', () => {
|
||||
const masterClient = ref<MasterRTCClient>()
|
||||
const slaveClient = ref<SlaveRTCClient>()
|
||||
const client = ref<BaseRTCClient>()
|
||||
const accountInfo = useAccount()
|
||||
let isInitializing = false
|
||||
|
||||
function Init(
|
||||
type: 'master' | 'slave'
|
||||
): MasterRTCClient | SlaveRTCClient | undefined {
|
||||
function on(event: string, callback: (...args: any[]) => void) {
|
||||
client.value?.on(event, callback)
|
||||
}
|
||||
|
||||
function off(event: string, callback: (...args: any[]) => void) {
|
||||
client.value?.off(event, callback)
|
||||
}
|
||||
|
||||
function send(event: string, data: any) {
|
||||
client.value?.send(event, data)
|
||||
}
|
||||
|
||||
async function Init(type: 'master' | 'slave') {
|
||||
if (isInitializing) {
|
||||
return
|
||||
return useWebRTC()
|
||||
}
|
||||
try {
|
||||
isInitializing = true
|
||||
navigator.locks.request(
|
||||
await navigator.locks.request(
|
||||
'rtcClientInit',
|
||||
{
|
||||
ifAvailable: true
|
||||
},
|
||||
async (lock) => {
|
||||
if (lock) {
|
||||
while (!accountInfo.value.id) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 500))
|
||||
}
|
||||
if (client.value) {
|
||||
return client.value
|
||||
}
|
||||
if (type == 'master') {
|
||||
if (masterClient.value) {
|
||||
return masterClient
|
||||
} else {
|
||||
masterClient.value = new MasterRTCClient(
|
||||
client.value = new MasterRTCClient(
|
||||
accountInfo.value.id.toString(),
|
||||
accountInfo.value.token
|
||||
)
|
||||
await masterClient.value.Init()
|
||||
return masterClient
|
||||
}
|
||||
} else {
|
||||
if (slaveClient.value) {
|
||||
return slaveClient
|
||||
} else {
|
||||
slaveClient.value = new SlaveRTCClient(
|
||||
client.value = new SlaveRTCClient(
|
||||
accountInfo.value.id?.toString(),
|
||||
accountInfo.value.token
|
||||
)
|
||||
await slaveClient.value.Init()
|
||||
return slaveClient
|
||||
}
|
||||
}
|
||||
await client.value.Init()
|
||||
return useWebRTC()
|
||||
} else {
|
||||
return useWebRTC()
|
||||
}
|
||||
}
|
||||
)
|
||||
return useWebRTC()
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
throw e
|
||||
@@ -59,7 +72,10 @@ export const useWebRTC = defineStore('WebRTC', () => {
|
||||
}
|
||||
|
||||
return {
|
||||
Init
|
||||
Init,
|
||||
send,
|
||||
on,
|
||||
off
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { GetSelfAccount, useAccount } from '@/api/account'
|
||||
import { QueryGetAPI } from '@/api/query'
|
||||
import { BILI_API_URL, BILI_AUTH_API_URL } from '@/data/constants'
|
||||
import { BILI_API_URL, BILI_AUTH_API_URL, CURRENT_HOST } from '@/data/constants'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import { randomUUID } from 'crypto'
|
||||
import {
|
||||
@@ -187,7 +187,7 @@ onMounted(async () => {
|
||||
<NText> 你的登陆链接为: </NText>
|
||||
<NInputGroup>
|
||||
<NInput
|
||||
:value="`https://vtsuru.live/bili-user?auth=${currentToken}`"
|
||||
:value="`${CURRENT_HOST}bili-user?auth=${currentToken}`"
|
||||
type="textarea"
|
||||
:allow-input="() => false"
|
||||
/>
|
||||
|
||||
@@ -130,7 +130,7 @@ const iconColor = 'white'
|
||||
</NButton>
|
||||
</NSpace>
|
||||
</NSpace>
|
||||
<NButton size="large" @click="$router.push('/user/Megghy')"> 展示 </NButton>
|
||||
<NButton size="large" @click="$router.push('/@Megghy')"> 展示 </NButton>
|
||||
<NButton
|
||||
size="large"
|
||||
tag="a"
|
||||
|
||||
@@ -26,9 +26,11 @@ import { useElementSize, useStorage } from '@vueuse/core'
|
||||
import {
|
||||
NAlert,
|
||||
NBackTop,
|
||||
NBadge,
|
||||
NButton,
|
||||
NCountdown,
|
||||
NDivider,
|
||||
NFlex,
|
||||
NIcon,
|
||||
NLayout,
|
||||
NLayoutContent,
|
||||
@@ -318,6 +320,23 @@ const menuOptions = [
|
||||
icon: renderIcon(Chat24Filled),
|
||||
disabled: accountInfo.value?.isEmailVerified == false,
|
||||
children: [
|
||||
{
|
||||
label: () =>
|
||||
h(NBadge, { value: '新', offset: [15, 12], type: 'info' }, () => h(NTooltip, {}, {
|
||||
trigger: () => h(
|
||||
RouterLink,
|
||||
{
|
||||
to: {
|
||||
name: 'manage-danmuji',
|
||||
},
|
||||
},
|
||||
{ default: () => '弹幕机' },
|
||||
),
|
||||
default: () => '兼容 blivechat 样式 (其实就是直接用的 blivechat 组件',
|
||||
})),
|
||||
key: 'manage-danmuji',
|
||||
icon: renderIcon(Lottery24Filled),
|
||||
},
|
||||
{
|
||||
label: () =>
|
||||
h(
|
||||
@@ -464,12 +483,8 @@ onMounted(() => {
|
||||
</template>
|
||||
<template #extra>
|
||||
<NSpace align="center" justify="center">
|
||||
<NSwitch
|
||||
:default-value="!isDarkMode"
|
||||
@update:value="
|
||||
(value: string & number & boolean) => (themeType = value ? ThemeType.Light : ThemeType.Dark)
|
||||
"
|
||||
>
|
||||
<NSwitch :default-value="!isDarkMode" @update:value="(value: string & number & boolean) => (themeType = value ? ThemeType.Light : ThemeType.Dark)
|
||||
">
|
||||
<template #checked>
|
||||
<NIcon :component="Sunny" />
|
||||
</template>
|
||||
@@ -477,12 +492,8 @@ onMounted(() => {
|
||||
<NIcon :component="Moon" />
|
||||
</template>
|
||||
</NSwitch>
|
||||
<NButton
|
||||
size="small"
|
||||
style="right: 0px; position: relative"
|
||||
type="primary"
|
||||
@click="$router.push({ name: 'user-index', params: { id: accountInfo?.name } })"
|
||||
>
|
||||
<NButton size="small" style="right: 0px; position: relative" type="primary"
|
||||
@click="$router.push({ name: 'user-index', params: { id: accountInfo?.name } })">
|
||||
回到展示页
|
||||
</NButton>
|
||||
</NSpace>
|
||||
@@ -490,17 +501,8 @@ onMounted(() => {
|
||||
</NPageHeader>
|
||||
</NLayoutHeader>
|
||||
<NLayout has-sider style="height: calc(100vh - 50px)">
|
||||
<NLayoutSider
|
||||
ref="sider"
|
||||
bordered
|
||||
show-trigger
|
||||
collapse-mode="width"
|
||||
:default-collapsed="windowWidth < 750"
|
||||
:collapsed-width="64"
|
||||
:width="180"
|
||||
:native-scrollbar="false"
|
||||
:scrollbar-props="{ trigger: 'none', style: {} }"
|
||||
>
|
||||
<NLayoutSider ref="sider" bordered show-trigger collapse-mode="width" :default-collapsed="windowWidth < 750"
|
||||
:collapsed-width="64" :width="180" :native-scrollbar="false" :scrollbar-props="{ trigger: 'none', style: {} }">
|
||||
<NSpace vertical style="margin-top: 16px" align="center">
|
||||
<NSpace justify="center">
|
||||
<NButton @click="$router.push({ name: 'manage-index' })" type="info" style="width: 100%">
|
||||
@@ -527,14 +529,9 @@ onMounted(() => {
|
||||
<template v-if="width >= 180"> 认证用户主页 </template>
|
||||
</NButton>
|
||||
</NSpace>
|
||||
<NMenu
|
||||
style="margin-top: 12px"
|
||||
:disabled="accountInfo?.isEmailVerified != true"
|
||||
:default-value="($route.meta.parent as string) ?? $route.name?.toString()"
|
||||
:collapsed-width="64"
|
||||
:collapsed-icon-size="22"
|
||||
:options="menuOptions"
|
||||
/>
|
||||
<NMenu style="margin-top: 12px" :disabled="accountInfo?.isEmailVerified != true"
|
||||
:default-value="($route.meta.parent as string) ?? $route.name?.toString()" :collapsed-width="64"
|
||||
:collapsed-icon-size="22" :options="menuOptions" />
|
||||
<NSpace v-if="width > 150" justify="center" align="center" vertical>
|
||||
<NText depth="3">
|
||||
有更多功能建议请
|
||||
@@ -544,10 +541,18 @@ onMounted(() => {
|
||||
<NButton text type="info" @click="$router.push({ name: 'about' })"> 关于本站 </NButton>
|
||||
</NText>
|
||||
</NSpace>
|
||||
<NDivider style="margin-bottom: 8px;" />
|
||||
<NFlex justify="center" align="center">
|
||||
<NText
|
||||
:style="`font-size: 12px; text-align: center;color: ${isDarkMode ? '#555' : '#c0c0c0'};visibility: ${width < 180 ? 'hidden' : 'visible'}`">
|
||||
By Megghy
|
||||
</NText>
|
||||
</NFlex>
|
||||
</NLayoutSider>
|
||||
<NLayout>
|
||||
<NScrollbar :style="`height: calc(100vh - 50px - ${aplayerHeight}px)`">
|
||||
<NLayoutContent style="box-sizing: border-box; padding: 20px; min-width: 300px; height: 100%">
|
||||
<NLayoutContent
|
||||
:style="`box-sizing: border-box; padding: 20px; min-width: 300px; height: calc(100vh - 50px - ${aplayerHeight}px);`">
|
||||
<RouterView v-if="accountInfo?.isEmailVerified" v-slot="{ Component, route }">
|
||||
<KeepAlive>
|
||||
<Suspense>
|
||||
@@ -566,11 +571,8 @@ onMounted(() => {
|
||||
<NButton size="small" type="info" :disabled="!canResendEmail" @click="resendEmail">
|
||||
重新发送验证邮件
|
||||
</NButton>
|
||||
<NCountdown
|
||||
v-if="!canResendEmail"
|
||||
:duration="(accountInfo?.nextSendEmailTime ?? 0) - Date.now()"
|
||||
@finish="canResendEmail = true"
|
||||
/>
|
||||
<NCountdown v-if="!canResendEmail" :duration="(accountInfo?.nextSendEmailTime ?? 0) - Date.now()"
|
||||
@finish="canResendEmail = true" />
|
||||
|
||||
<NPopconfirm @positive-click="logout" size="small">
|
||||
<template #trigger>
|
||||
@@ -586,21 +588,12 @@ onMounted(() => {
|
||||
</NScrollbar>
|
||||
<NLayoutFooter :style="`height: ${aplayerHeight}px;overflow: auto`">
|
||||
<div style="display: flex; align-items: center; margin: 0 10px 0 10px">
|
||||
<APlayer
|
||||
v-if="musicRquestStore.aplayerMusics.length > 0"
|
||||
ref="aplayer"
|
||||
:list="musicRquestStore.aplayerMusics"
|
||||
v-model:music="musicRquestStore.currentMusic"
|
||||
v-model:volume="musicRquestStore.settings.volume"
|
||||
v-model:shuffle="musicRquestStore.settings.shuffle"
|
||||
v-model:repeat="musicRquestStore.settings.repeat"
|
||||
:listMaxHeight="'200'"
|
||||
mutex
|
||||
listFolded
|
||||
@ended="musicRquestStore.onMusicEnd"
|
||||
@play="musicRquestStore.onMusicPlay"
|
||||
style="flex: 1; min-width: 400px"
|
||||
/>
|
||||
<APlayer v-if="musicRquestStore.aplayerMusics.length > 0" ref="aplayer"
|
||||
:list="musicRquestStore.aplayerMusics" v-model:music="musicRquestStore.currentMusic"
|
||||
v-model:volume="musicRquestStore.settings.volume" v-model:shuffle="musicRquestStore.settings.shuffle"
|
||||
v-model:repeat="musicRquestStore.settings.repeat" :listMaxHeight="'200'" mutex listFolded
|
||||
@ended="musicRquestStore.onMusicEnd" @play="musicRquestStore.onMusicPlay"
|
||||
style="flex: 1; min-width: 400px" />
|
||||
<NSpace vertical>
|
||||
<NTag :bordered="false" type="info" size="small">
|
||||
队列: {{ musicRquestStore.waitingMusics.length }}
|
||||
@@ -613,8 +606,7 @@ onMounted(() => {
|
||||
</NLayout>
|
||||
</NLayout>
|
||||
<template v-else>
|
||||
<NLayoutContent
|
||||
style="
|
||||
<NLayoutContent style="
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
@@ -622,8 +614,7 @@ onMounted(() => {
|
||||
padding: 50px;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
"
|
||||
>
|
||||
">
|
||||
<template v-if="!isLoadingAccount">
|
||||
<NSpace vertical justify="center" align="center">
|
||||
<NText> 请登录或注册后使用 </NText>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { useAccount } from '@/api/account';
|
||||
import { MasterRTCClient, SlaveRTCClient } from '@/data/RTCClient';
|
||||
import { BaseRTCClient, MasterRTCClient, SlaveRTCClient } from '@/data/RTCClient';
|
||||
import { useDanmakuClient } from '@/store/useDanmakuClient';
|
||||
import { useWebRTC } from '@/store/useRTC';
|
||||
import { NButton, NInput, NSpin } from 'naive-ui';
|
||||
@@ -19,11 +19,11 @@ const isMaster = computed(() => {
|
||||
const dc = useDanmakuClient()
|
||||
const customCss = ref('')
|
||||
|
||||
let rtc: Ref<MasterRTCClient | SlaveRTCClient | undefined> = ref()
|
||||
let rtc= useWebRTC()
|
||||
const danmujiRef = ref()
|
||||
|
||||
function mount() {
|
||||
rtc.value = useWebRTC().Init(isMaster.value ? 'master' : 'slave')
|
||||
async function mount() {
|
||||
rtc.Init(isMaster.value ? 'master' : 'slave')
|
||||
dc.initClient()
|
||||
}
|
||||
</script>
|
||||
|
||||
37
src/views/manage/DanmujiManageView.vue
Normal file
37
src/views/manage/DanmujiManageView.vue
Normal file
@@ -0,0 +1,37 @@
|
||||
<script setup lang="ts">
|
||||
import { NFlex, NInput } from 'naive-ui';
|
||||
import DanmujiOBS from '../obs/DanmujiOBS.vue';
|
||||
import { useAccount } from '@/api/account';
|
||||
import MonacoEditorComponent from '@/components/MonacoEditorComponent.vue';
|
||||
import { ref } from 'vue';
|
||||
import { CURRENT_HOST } from '@/data/constants';
|
||||
|
||||
const accountInfo = useAccount()
|
||||
const css = ref('')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NFlex wrap style="height: 100%">
|
||||
<NFlex style="flex: 1;" vertical>
|
||||
<NInput :allowInput="() => false" :value="`${CURRENT_HOST}obs/danmuji?token=${accountInfo.token}`" />
|
||||
<MonacoEditorComponent language="css" :height="500" v-model:value="css" />
|
||||
</NFlex>
|
||||
<div class="danmuji-obs" style="width: 300px; height: calc(100% - 2px); min-height: 500px;border: 1px solid #adadad;border-radius: 8px;
|
||||
overflow: hidden;">
|
||||
<DanmujiOBS :isOBS="false" style="height: 100%; width: 100%;" :customCss="css" />
|
||||
</div>
|
||||
</NFlex>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.danmuji-obs {
|
||||
--danmuji-bg: #333;
|
||||
background-color: #222;
|
||||
background-image: linear-gradient(45deg, var(--danmuji-bg) 25%, transparent 25%),
|
||||
linear-gradient(-45deg, var(--danmuji-bg) 25%, transparent 25%),
|
||||
linear-gradient(45deg, transparent 75%, var(--danmuji-bg) 75%),
|
||||
linear-gradient(-45deg, transparent 75%, var(--danmuji-bg) 75%);
|
||||
background-size: 20px 20px;
|
||||
background-position: 0 0, 0 10px, 10px -10px, -10px 0;
|
||||
}
|
||||
</style>
|
||||
@@ -1,11 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { copyToClipboard, downloadImage } from '@/Utils'
|
||||
import { DisableFunction, EnableFunction, SaveAccountSettings, SaveSetting, useAccount } from '@/api/account'
|
||||
import { FunctionTypes, QAInfo } from '@/api/api-models'
|
||||
import { FunctionTypes, QAInfo, Setting_QuestionDisplay } from '@/api/api-models'
|
||||
import { QueryGetAPI } from '@/api/query'
|
||||
import { QUESTION_API_URL } from '@/data/constants'
|
||||
import { CURRENT_HOST, QUESTION_API_URL } from '@/data/constants'
|
||||
import router from '@/router'
|
||||
import { Heart, HeartOutline, SwapHorizontal } from '@vicons/ionicons5'
|
||||
// @ts-ignore
|
||||
import { saveAs } from 'file-saver'
|
||||
import html2canvas from 'html2canvas'
|
||||
import {
|
||||
@@ -47,6 +48,8 @@ import { useRoute } from 'vue-router'
|
||||
import QuestionItem from '@/components/QuestionItems.vue'
|
||||
import { Delete24Filled, Delete24Regular, Eye24Filled, EyeOff24Filled, Info24Filled } from '@vicons/fluent'
|
||||
import { useQuestionBox } from '@/store/useQuestionBox'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import QuestionDisplayCard from './QuestionDisplayCard.vue'
|
||||
|
||||
const accountInfo = useAccount()
|
||||
const route = useRoute()
|
||||
@@ -62,15 +65,34 @@ const replyMessage = ref()
|
||||
const addTagName = ref('')
|
||||
|
||||
const showSettingCard = ref(true)
|
||||
const showOBSModal = ref(false)
|
||||
const defaultSettings = {} as Setting_QuestionDisplay
|
||||
const setting = computed({
|
||||
get: () => {
|
||||
if (accountInfo.value && accountInfo.value.settings) {
|
||||
return accountInfo.value.settings.questionDisplay
|
||||
}
|
||||
return defaultSettings
|
||||
},
|
||||
set: (value) => {
|
||||
if (accountInfo.value) {
|
||||
accountInfo.value.settings.questionDisplay = value
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const shareCardRef = ref()
|
||||
const shareUrl = computed(() => 'https://vtsuru.live/@' + accountInfo.value?.name + '/question-box')
|
||||
const shareUrl = computed(() => `${CURRENT_HOST}@` + accountInfo.value?.name + '/question-box')
|
||||
|
||||
const ps = ref(20)
|
||||
const pn = ref(1)
|
||||
const pagedQuestions = computed(() =>
|
||||
useQB.recieveQuestionsFiltered.slice((pn.value - 1) * ps.value, pn.value * ps.value),
|
||||
)
|
||||
const savedCardSize = useStorage<{ width: number; height: number }>('Settings.QuestionDisplay.CardSize', {
|
||||
width: 400,
|
||||
height: 400,
|
||||
})
|
||||
|
||||
let isRevieveGetted = false
|
||||
let isSendGetted = false
|
||||
@@ -168,26 +190,20 @@ onMounted(() => {
|
||||
|
||||
<template>
|
||||
<NSpace align="center">
|
||||
<NAlert
|
||||
:type="accountInfo.settings.enableFunctions.includes(FunctionTypes.QuestionBox) ? 'success' : 'warning'"
|
||||
style="max-width: 200px"
|
||||
>
|
||||
<NAlert :type="accountInfo.settings.enableFunctions.includes(FunctionTypes.QuestionBox) ? 'success' : 'warning'"
|
||||
style="max-width: 200px">
|
||||
启用提问箱
|
||||
<NDivider vertical />
|
||||
<NSwitch
|
||||
:value="accountInfo?.settings.enableFunctions.includes(FunctionTypes.QuestionBox)"
|
||||
@update:value="setFunctionEnable"
|
||||
/>
|
||||
<NSwitch :value="accountInfo?.settings.enableFunctions.includes(FunctionTypes.QuestionBox)"
|
||||
@update:value="setFunctionEnable" />
|
||||
</NAlert>
|
||||
<NButton type="primary" @click="refresh"> 刷新 </NButton>
|
||||
<NButton type="primary" @click="shareModalVisiable = true" secondary> 分享 </NButton>
|
||||
<NButton
|
||||
type="primary"
|
||||
@click="$router.push({ name: 'user-questionBox', params: { id: accountInfo.name } })"
|
||||
secondary
|
||||
>
|
||||
<NButton type="primary" @click="$router.push({ name: 'user-questionBox', params: { id: accountInfo.name } })"
|
||||
secondary>
|
||||
前往提问页
|
||||
</NButton>
|
||||
<NButton @click="showOBSModal = true" type="primary" secondary> 预览OBS组件 </NButton>
|
||||
</NSpace>
|
||||
<NDivider style="margin: 10px 0 10px 0" />
|
||||
<NSpin v-if="useQB.isLoading" show />
|
||||
@@ -195,15 +211,11 @@ onMounted(() => {
|
||||
<NTabPane tab="我收到的" name="0" display-directive="show:lazy">
|
||||
<NFlex align="center">
|
||||
<NButton @click="$router.push({ name: 'question-display' })" type="primary"> 打开展示页 </NButton>
|
||||
<NSelect
|
||||
v-model:value="useQB.displayTag"
|
||||
placeholder="选择当前话题"
|
||||
filterable
|
||||
clearable
|
||||
:options="useQB.tags.map((s) => ({ label: s.name, value: s.name }))"
|
||||
style="width: 200px"
|
||||
>
|
||||
<template #header> <NText strong depth="3"> 在设置选项卡中添加或删除话题 </NText> </template>
|
||||
<NSelect v-model:value="useQB.displayTag" placeholder="选择当前话题" filterable clearable
|
||||
:options="useQB.tags.map((s) => ({ label: s.name, value: s.name }))" style="width: 200px">
|
||||
<template #header>
|
||||
<NText strong depth="3"> 在设置选项卡中添加或删除话题 </NText>
|
||||
</template>
|
||||
</NSelect>
|
||||
<NCheckbox v-model:checked="useQB.onlyFavorite"> 只显示收藏 </NCheckbox>
|
||||
<NCheckbox v-model:checked="useQB.onlyPublic"> 只显示公开 </NCheckbox>
|
||||
@@ -212,14 +224,8 @@ onMounted(() => {
|
||||
<NDivider style="margin: 10px 0 10px 0" />
|
||||
<NEmpty v-if="useQB.recieveQuestionsFiltered.length == 0" description="暂无收到的提问" />
|
||||
<div v-else>
|
||||
<NPagination
|
||||
v-model:page="pn"
|
||||
v-model:page-size="ps"
|
||||
:item-count="useQB.recieveQuestionsFiltered.length"
|
||||
show-quick-jumper
|
||||
show-size-picker
|
||||
:page-sizes="[20, 50, 100]"
|
||||
/>
|
||||
<NPagination v-model:page="pn" v-model:page-size="ps" :item-count="useQB.recieveQuestionsFiltered.length"
|
||||
show-quick-jumper show-size-picker :page-sizes="[20, 50, 100]" />
|
||||
<NDivider style="margin: 10px 0 10px 0" />
|
||||
<QuestionItem :questions="pagedQuestions">
|
||||
<template #footer="{ item }">
|
||||
@@ -230,10 +236,8 @@ onMounted(() => {
|
||||
<NButton v-else size="small" @click="useQB.read(item, false)" type="warning">重设为未读</NButton>
|
||||
<NButton size="small" @click="useQB.favorite(item, !item.isFavorite)">
|
||||
<template #icon>
|
||||
<NIcon
|
||||
:component="item.isFavorite ? Heart : HeartOutline"
|
||||
:color="item.isFavorite ? '#dd484f' : ''"
|
||||
/>
|
||||
<NIcon :component="item.isFavorite ? Heart : HeartOutline"
|
||||
:color="item.isFavorite ? '#dd484f' : ''" />
|
||||
</template>
|
||||
收藏
|
||||
</NButton>
|
||||
@@ -264,14 +268,8 @@ onMounted(() => {
|
||||
</template>
|
||||
</QuestionItem>
|
||||
<NDivider style="margin: 10px 0 10px 0" />
|
||||
<NPagination
|
||||
v-model:page="pn"
|
||||
v-model:page-size="ps"
|
||||
:item-count="useQB.recieveQuestionsFiltered.length"
|
||||
show-quick-jumper
|
||||
show-size-picker
|
||||
:page-sizes="[20, 50, 100]"
|
||||
/>
|
||||
<NPagination v-model:page="pn" v-model:page-size="ps" :item-count="useQB.recieveQuestionsFiltered.length"
|
||||
show-quick-jumper show-size-picker :page-sizes="[20, 50, 100]" />
|
||||
</div>
|
||||
</NTabPane>
|
||||
<NTabPane ref="parentRef" tab="我发送的" name="1" display-directive="show:lazy">
|
||||
@@ -320,10 +318,8 @@ onMounted(() => {
|
||||
<NTabPane tab="设置" name="2" display-directive="show:lazy">
|
||||
<NDivider> 设定 </NDivider>
|
||||
<NSpin :show="useQB.isLoading">
|
||||
<NCheckbox
|
||||
v-model:checked="accountInfo.settings.questionBox.allowUnregistedUser"
|
||||
@update:checked="saveSettings"
|
||||
>
|
||||
<NCheckbox v-model:checked="accountInfo.settings.questionBox.allowUnregistedUser"
|
||||
@update:checked="saveSettings">
|
||||
允许未注册用户进行提问
|
||||
</NCheckbox>
|
||||
<NDivider>
|
||||
@@ -395,14 +391,7 @@ onMounted(() => {
|
||||
<NModal preset="card" v-model:show="replyModalVisiable" style="max-width: 90vw; width: 500px">
|
||||
<template #header> 回复 </template>
|
||||
<NSpace vertical>
|
||||
<NInput
|
||||
placeholder="请输入回复"
|
||||
type="textarea"
|
||||
v-model:value="replyMessage"
|
||||
maxlength="1000"
|
||||
show-count
|
||||
clearable
|
||||
/>
|
||||
<NInput placeholder="请输入回复" type="textarea" v-model:value="replyMessage" maxlength="1000" show-count clearable />
|
||||
<NSpin :show="useQB.isChangingPublic">
|
||||
<NCheckbox @update:checked="(v) => useQB.setPublic(v)" :default-checked="useQB.currentQuestion?.isPublic">
|
||||
公开可见
|
||||
@@ -410,12 +399,8 @@ onMounted(() => {
|
||||
</NSpin>
|
||||
</NSpace>
|
||||
<NDivider style="margin: 10px 0 10px 0" />
|
||||
<NButton
|
||||
:loading="useQB.isRepling"
|
||||
@click="useQB.reply(useQB.currentQuestion?.id ?? -1, replyMessage)"
|
||||
type="primary"
|
||||
:secondary="useQB.currentQuestion?.answer ? true : false"
|
||||
>
|
||||
<NButton :loading="useQB.isRepling" @click="useQB.reply(useQB.currentQuestion?.id ?? -1, replyMessage)"
|
||||
type="primary" :secondary="useQB.currentQuestion?.answer ? true : false">
|
||||
{{ useQB.currentQuestion?.answer ? '修改' : '发送' }}
|
||||
</NButton>
|
||||
</NModal>
|
||||
@@ -428,15 +413,8 @@ onMounted(() => {
|
||||
</NText>
|
||||
<NDivider class="share-card divider-1" />
|
||||
<NText class="share-card site"> VTSURU.LIVE </NText>
|
||||
<QrcodeVue
|
||||
class="share-card qrcode"
|
||||
:value="shareUrl"
|
||||
level="Q"
|
||||
:size="100"
|
||||
background="#00000000"
|
||||
foreground="#ffffff"
|
||||
:margin="1"
|
||||
/>
|
||||
<QrcodeVue class="share-card qrcode" :value="shareUrl" level="Q" :size="100" background="#00000000"
|
||||
foreground="#ffffff" :margin="1" />
|
||||
</div>
|
||||
<NDivider style="margin: 10px" />
|
||||
<NInputGroup>
|
||||
@@ -449,6 +427,25 @@ onMounted(() => {
|
||||
<NButton type="primary" @click="saveQRCode"> 保存二维码 </NButton>
|
||||
</NSpace>
|
||||
</NModal>
|
||||
|
||||
<NModal preset="card" v-model:show="showOBSModal" closable style="max-width: 90vw; width: auto" title="OBS组件"
|
||||
content-style="display: flex; align-items: center; justify-content: center; flex-direction: column">
|
||||
<NAlert type="info">
|
||||
操作显示的内容请前往
|
||||
<NButton text @click="$router.push({ name: 'question-display' })"> 展示管理页 </NButton>
|
||||
</NAlert>
|
||||
<br />
|
||||
<div :style="{
|
||||
width: savedCardSize.width + 'px',
|
||||
height: savedCardSize.height + 'px',
|
||||
}">
|
||||
<QuestionDisplayCard :question="useQB.displayQuestion" :setting="setting" />
|
||||
</div>
|
||||
<NDivider />
|
||||
<NInput readonly :value="CURRENT_HOST + 'obs/question-display?token=' + accountInfo?.token" />
|
||||
<NDivider />
|
||||
<NButton type="primary" @click="$router.push({ name: 'question-display' })"> 前往展示管理页 </NButton>
|
||||
</NModal>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
@@ -463,6 +460,7 @@ onMounted(() => {
|
||||
border-radius: 10px;
|
||||
background: linear-gradient(to right, #66bea3, #9179be);
|
||||
}
|
||||
|
||||
.share-card.qrcode {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
@@ -470,6 +468,7 @@ onMounted(() => {
|
||||
border-radius: 4px;
|
||||
background: linear-gradient(to right, #3d554e, #503e74);
|
||||
}
|
||||
|
||||
.share-card.title {
|
||||
position: absolute;
|
||||
font-size: 80px;
|
||||
@@ -478,6 +477,7 @@ onMounted(() => {
|
||||
color: #e6e6e662;
|
||||
font-weight: 550;
|
||||
}
|
||||
|
||||
/* .share-card.type {
|
||||
position: absolute;
|
||||
font-size: 20px;
|
||||
@@ -494,6 +494,7 @@ onMounted(() => {
|
||||
font-weight: 550;
|
||||
color: #e6e6e662;
|
||||
}
|
||||
|
||||
.share-card.name {
|
||||
position: absolute;
|
||||
font-size: 30px;
|
||||
@@ -505,6 +506,7 @@ onMounted(() => {
|
||||
color: #e6e6e6;
|
||||
font-weight: 550;
|
||||
}
|
||||
|
||||
.share-card.site {
|
||||
position: absolute;
|
||||
font-size: 12px;
|
||||
@@ -513,6 +515,7 @@ onMounted(() => {
|
||||
color: #e6e6e6a4;
|
||||
font-weight: 550;
|
||||
}
|
||||
|
||||
.share-card.divider-1 {
|
||||
position: absolute;
|
||||
width: 400px;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, onUnmounted, watch } from 'vue'
|
||||
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
import { QAInfo, QuestionDisplayAlign, Setting_QuestionDisplay } from '@/api/api-models'
|
||||
import { useDebounceFn, useStorage } from '@vueuse/core'
|
||||
import { useDebounceFn, useScroll, useStorage } from '@vueuse/core'
|
||||
|
||||
const props = defineProps<{
|
||||
question: QAInfo | undefined
|
||||
@@ -10,6 +10,9 @@ const props = defineProps<{
|
||||
showGreenBorder?: boolean
|
||||
css?: string
|
||||
}>()
|
||||
defineExpose({ setScroll, setScrollTop })
|
||||
const emit = defineEmits<{ scroll: [value: { clientHeight: number, scrollHeight: number, scrollTop: number }] }>()
|
||||
|
||||
let styleElement: HTMLStyleElement
|
||||
const cssDebounce = useDebounceFn(() => {
|
||||
if (styleElement) {
|
||||
@@ -19,6 +22,10 @@ const cssDebounce = useDebounceFn(() => {
|
||||
}, 1000)
|
||||
watch(() => props.css, cssDebounce)
|
||||
|
||||
const contentRef = ref()
|
||||
const { x, y, isScrolling, arrivedState, directions } = useScroll(contentRef)
|
||||
|
||||
|
||||
const align = computed(() => {
|
||||
switch (props.setting.align) {
|
||||
case QuestionDisplayAlign.Left:
|
||||
@@ -30,6 +37,28 @@ const align = computed(() => {
|
||||
}
|
||||
return 'left'
|
||||
})
|
||||
function setScrollTop(top: number) {
|
||||
contentRef.value?.scrollTo({
|
||||
top: top,
|
||||
behavior: 'smooth',
|
||||
})
|
||||
}
|
||||
function setScroll(value: { clientHeight: number, scrollHeight: number, scrollTop: number }) {
|
||||
if (contentRef.value.clientHeight == contentRef.value.scrollHeight) {
|
||||
setScrollTop(value.scrollTop)
|
||||
} else {
|
||||
const scrollRatio1 = value.scrollTop / (value.scrollHeight - value.clientHeight);
|
||||
const scrollTop = scrollRatio1 * (contentRef.value.scrollHeight - contentRef.value.clientHeight);
|
||||
setScrollTop(scrollTop)
|
||||
}
|
||||
}
|
||||
function scrollCallback(e: Event) {
|
||||
emit('scroll', {
|
||||
clientHeight: contentRef.value?.clientHeight ?? 0,
|
||||
scrollHeight: contentRef.value?.scrollHeight ?? 0,
|
||||
scrollTop: contentRef.value?.scrollTop ?? 0
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 创建<style>元素并添加到<head>中
|
||||
@@ -37,62 +66,50 @@ onMounted(() => {
|
||||
// 可能需要对 userStyleString 做安全处理以避免XSS攻击
|
||||
styleElement.textContent = props.css ?? ''
|
||||
document.head.appendChild(styleElement)
|
||||
|
||||
contentRef.value?.addEventListener('scroll', scrollCallback)
|
||||
})
|
||||
onUnmounted(() => {
|
||||
if (styleElement && styleElement.parentNode) {
|
||||
styleElement.parentNode.removeChild(styleElement)
|
||||
}
|
||||
contentRef.value?.removeEventListener('scroll', scrollCallback)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="question-display-root"
|
||||
:style="{
|
||||
<div class="question-display-root" :style="{
|
||||
backgroundColor: '#' + setting.borderColor,
|
||||
borderColor: setting.borderColor ? '#' + setting.borderColor : undefined,
|
||||
borderWidth: setting.borderWidth ? setting.borderWidth + 'px' : undefined,
|
||||
borderTopWidth: setting.showUserName && question ? 0 : setting.borderWidth,
|
||||
}"
|
||||
:display="question ? 1 : 0"
|
||||
>
|
||||
}" :display="question ? 1 : 0">
|
||||
<Transition name="scale" mode="out-in">
|
||||
<div
|
||||
v-if="setting.showUserName && question"
|
||||
class="question-display-user-name"
|
||||
:style="{
|
||||
<div v-if="setting.showUserName && question" class="question-display-user-name" :style="{
|
||||
color: '#' + setting.nameFontColor,
|
||||
fontSize: setting.nameFontSize + 'px',
|
||||
fontWeight: setting.nameFontWeight ? setting.nameFontWeight : undefined,
|
||||
fontFamily: setting.nameFont,
|
||||
}"
|
||||
>
|
||||
}">
|
||||
{{ question?.sender?.name ?? '匿名用户' }}
|
||||
</div>
|
||||
</Transition>
|
||||
<div
|
||||
class="question-display-content"
|
||||
:style="{
|
||||
<div class="question-display-content" @scroll="scrollCallback" ref="contentRef" :style="{
|
||||
color: '#' + setting.fontColor,
|
||||
backgroundColor: '#' + setting.backgroundColor,
|
||||
fontSize: setting.fontSize + 'px',
|
||||
fontWeight: setting.fontWeight ? setting.fontWeight : undefined,
|
||||
textAlign: align,
|
||||
fontFamily: setting.font,
|
||||
}"
|
||||
:is-empty="question ? 0 : 1"
|
||||
>
|
||||
}" :is-empty="question ? 0 : 1">
|
||||
<Transition name="fade" mode="out-in">
|
||||
<template v-if="question">
|
||||
<div>
|
||||
<div class="question-display-text">
|
||||
{{ question?.question.message }}
|
||||
</div>
|
||||
<img
|
||||
class="question-display-image"
|
||||
v-if="setting.showImage && question?.question.image"
|
||||
:src="question?.question.image"
|
||||
/>
|
||||
<img class="question-display-image" v-if="setting.showImage && question?.question.image"
|
||||
:src="question?.question.image" />
|
||||
</div>
|
||||
</template>
|
||||
<div v-else class="question-display-loading loading" :style="{ color: '#' + setting.fontColor }">
|
||||
@@ -118,6 +135,7 @@ onUnmounted(() => {
|
||||
box-sizing: border-box;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.question-display-content {
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
@@ -127,6 +145,7 @@ onUnmounted(() => {
|
||||
padding: 24px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.question-display-user-name {
|
||||
text-align: center;
|
||||
margin: 5px;
|
||||
@@ -134,9 +153,11 @@ onUnmounted(() => {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.question-display-text {
|
||||
min-height: 50px;
|
||||
}
|
||||
|
||||
.question-display-image {
|
||||
max-width: 40%;
|
||||
max-height: 40%;
|
||||
@@ -172,7 +193,7 @@ onUnmounted(() => {
|
||||
|
||||
<style scoped>
|
||||
.loading,
|
||||
.loading > div {
|
||||
.loading>div {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
@@ -187,7 +208,7 @@ onUnmounted(() => {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.loading > div {
|
||||
.loading>div {
|
||||
display: inline-block;
|
||||
float: none;
|
||||
background-color: currentColor;
|
||||
@@ -199,18 +220,18 @@ onUnmounted(() => {
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
.loading > div {
|
||||
.loading>div {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
.loading > div:first-child {
|
||||
.loading>div:first-child {
|
||||
transform: translateX(0%);
|
||||
animation: ball-newton-cradle-left 1.5s 0s ease-out infinite;
|
||||
}
|
||||
|
||||
.loading > div:last-child {
|
||||
.loading>div:last-child {
|
||||
transform: translateX(0%);
|
||||
animation: ball-newton-cradle-right 1.5s 0s ease-out infinite;
|
||||
}
|
||||
@@ -220,7 +241,7 @@ onUnmounted(() => {
|
||||
height: 4px;
|
||||
}
|
||||
|
||||
.loading.la-sm > div {
|
||||
.loading.la-sm>div {
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
}
|
||||
@@ -230,7 +251,7 @@ onUnmounted(() => {
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.loading.la-2x > div {
|
||||
.loading.la-2x>div {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
@@ -240,7 +261,7 @@ onUnmounted(() => {
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.loading.la-3x > div {
|
||||
.loading.la-3x>div {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
@@ -270,11 +291,12 @@ onUnmounted(() => {
|
||||
transform: translateX(0%);
|
||||
}
|
||||
}
|
||||
.loading > div:first-child {
|
||||
|
||||
.loading>div:first-child {
|
||||
animation: ball-newton-cradle-left 1.5s 0s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.loading > div:last-child {
|
||||
.loading>div:last-child {
|
||||
animation: ball-newton-cradle-right 1.5s 0s ease-in-out infinite;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
} from '@/api/api-models'
|
||||
import { QueryGetAPI, QueryPostAPI } from '@/api/query'
|
||||
import VideoCollectInfoCard from '@/components/VideoCollectInfoCard.vue'
|
||||
import { VIDEO_COLLECT_API_URL } from '@/data/constants'
|
||||
import { CURRENT_HOST, VIDEO_COLLECT_API_URL } from '@/data/constants'
|
||||
import router from '@/router'
|
||||
import { Clock24Filled, Person24Filled } from '@vicons/fluent'
|
||||
import { useWindowSize } from '@vueuse/core'
|
||||
@@ -395,10 +395,8 @@ onActivated(async () => {
|
||||
<NButton type="warning" size="small" @click="closeTable">
|
||||
{{ videoDetail.table.isFinish ? '开启表' : '关闭表' }}
|
||||
</NButton>
|
||||
<NButton
|
||||
size="small"
|
||||
@click="$router.push({ name: 'video-collect-list', params: { id: videoDetail.table.id } })"
|
||||
>
|
||||
<NButton size="small"
|
||||
@click="$router.push({ name: 'video-collect-list', params: { id: videoDetail.table.id } })">
|
||||
结果表
|
||||
</NButton>
|
||||
<NPopconfirm :on-positive-click="deleteTable">
|
||||
@@ -447,14 +445,9 @@ onActivated(async () => {
|
||||
</NTabs>
|
||||
</template>
|
||||
<NModal v-model:show="shareModalVisiable" title="分享" preset="card" style="width: 600px; max-width: 90vw">
|
||||
<Qrcode
|
||||
:value="'https://vtsuru.live/video-collect/' + videoDetail.table.shortId"
|
||||
level="Q"
|
||||
:size="100"
|
||||
background="#fff"
|
||||
:margin="1"
|
||||
/>
|
||||
<NInput :value="'https://vtsuru.live/video-collect/' + videoDetail.table.shortId" />
|
||||
<Qrcode :value="`${CURRENT_HOST}video-collect/` + videoDetail.table.shortId" level="Q" :size="100" background="#fff"
|
||||
:margin="1" />
|
||||
<NInput :value="`${CURRENT_HOST}video-collect/` + videoDetail.table.shortId" />
|
||||
<NDivider />
|
||||
<NSpace justify="center">
|
||||
<NButton type="primary" @click="saveQRCode"> 保存二维码 </NButton>
|
||||
@@ -469,20 +462,12 @@ onActivated(async () => {
|
||||
<NInput v-model:value="updateModel.description" placeholder="可以是备注之类的" maxlength="300" show-count />
|
||||
</NFormItem>
|
||||
<NFormItem label="视频数量" path="maxVideoCount">
|
||||
<NInputNumber
|
||||
v-model:value="updateModel.maxVideoCount"
|
||||
placeholder="最大数量"
|
||||
type="number"
|
||||
style="max-width: 150px"
|
||||
/>
|
||||
<NInputNumber v-model:value="updateModel.maxVideoCount" placeholder="最大数量" type="number"
|
||||
style="max-width: 150px" />
|
||||
</NFormItem>
|
||||
<NFormItem label="结束时间" path="endAt">
|
||||
<NDatePicker
|
||||
v-model:value="updateModel.endAt"
|
||||
type="datetime"
|
||||
placeholder="结束征集的时间"
|
||||
:isDateDisabled="dateDisabled"
|
||||
/>
|
||||
<NDatePicker v-model:value="updateModel.endAt" type="datetime" placeholder="结束征集的时间"
|
||||
:isDateDisabled="dateDisabled" />
|
||||
<NDivider vertical />
|
||||
<NText depth="3"> 最低为一小时 </NText>
|
||||
</NFormItem>
|
||||
|
||||
@@ -76,7 +76,7 @@ const defaultConfig: DanmujiConfig = {
|
||||
} as DanmujiConfig
|
||||
let textEmoticons: { keyword: string, url: string }[] = []
|
||||
const config = ref<DanmujiConfig>(JSON.parse(JSON.stringify(defaultConfig)))
|
||||
const rtc = useWebRTC().Init('slave')
|
||||
const rtc = await useWebRTC().Init('slave')
|
||||
|
||||
const emoticonsTrie = computed(() => {
|
||||
let res = new trie.Trie()
|
||||
|
||||
@@ -23,7 +23,7 @@ const route = useRoute()
|
||||
const currentId = computed(() => {
|
||||
return props.id ?? route.query.id
|
||||
})
|
||||
const rtc = useWebRTC().Init('slave')
|
||||
const rtc = await useWebRTC().Init('slave')
|
||||
|
||||
const listContainerRef = ref()
|
||||
const footerRef = ref()
|
||||
|
||||
@@ -9,11 +9,13 @@ import { useWebRTC } from '@/store/useRTC'
|
||||
|
||||
const hash = ref('')
|
||||
const token = useRouteQuery('token')
|
||||
const rtc = useWebRTC().Init('slave')
|
||||
const rtc = await useWebRTC().Init('slave')
|
||||
|
||||
const question = ref<QAInfo>()
|
||||
const setting = ref<Setting_QuestionDisplay>({} as Setting_QuestionDisplay)
|
||||
|
||||
const cardRef = ref()
|
||||
|
||||
async function checkIfChanged() {
|
||||
try {
|
||||
const data = await QueryGetAPI<string>(QUESTION_API_URL + 'get-hash', {
|
||||
@@ -45,6 +47,9 @@ async function getQuestionAndSetting() {
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
function handleScroll(value: { clientHeight: number, scrollHeight: number, scrollTop: number }) {
|
||||
cardRef.value?.setScroll(value)
|
||||
}
|
||||
|
||||
const visiable = ref(true)
|
||||
const active = ref(true)
|
||||
@@ -66,12 +71,16 @@ onMounted(() => {
|
||||
active.value = a
|
||||
}
|
||||
}
|
||||
|
||||
rtc?.on('function.question.sync-scroll', handleScroll)
|
||||
})
|
||||
onUnmounted(() => {
|
||||
clearInterval(timer)
|
||||
|
||||
rtc?.off('function.question.sync-scroll', handleScroll)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<QuestionDisplayCard :question="question" :setting="setting" />
|
||||
<QuestionDisplayCard ref="cardRef" :question="question" :setting="setting" />
|
||||
</template>
|
||||
|
||||
@@ -29,7 +29,7 @@ const route = useRoute()
|
||||
const currentId = computed(() => {
|
||||
return props.id ?? route.query.id
|
||||
})
|
||||
const rtc = useWebRTC().Init('slave')
|
||||
const rtc = await useWebRTC().Init('slave')
|
||||
|
||||
const listContainerRef = ref()
|
||||
const footerRef = ref()
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
import { QueryGetAPI, QueryPostAPI, QueryPostAPIWithParams } from '@/api/query'
|
||||
import SongPlayer from '@/components/SongPlayer.vue'
|
||||
import { RoomAuthInfo } from '@/data/DanmakuClient'
|
||||
import { SONG_REQUEST_API_URL } from '@/data/constants'
|
||||
import { CURRENT_HOST, SONG_REQUEST_API_URL } from '@/data/constants'
|
||||
import { useDanmakuClient } from '@/store/useDanmakuClient'
|
||||
import {
|
||||
Checkmark12Regular,
|
||||
@@ -731,7 +731,7 @@ async function updateActive() {
|
||||
message.error('无法获取点播队列: ' + data.message)
|
||||
return []
|
||||
}
|
||||
} catch (err) {}
|
||||
} catch (err) { }
|
||||
}
|
||||
function blockUser(item: SongRequestInfo) {
|
||||
if (item.from != SongRequestFrom.Danmaku) {
|
||||
@@ -793,15 +793,11 @@ onUnmounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NAlert
|
||||
:type="accountInfo.settings.enableFunctions.includes(FunctionTypes.SongRequest) ? 'success' : 'warning'"
|
||||
v-if="accountInfo.id"
|
||||
>
|
||||
<NAlert :type="accountInfo.settings.enableFunctions.includes(FunctionTypes.SongRequest) ? 'success' : 'warning'"
|
||||
v-if="accountInfo.id">
|
||||
启用弹幕点播功能
|
||||
<NSwitch
|
||||
:value="accountInfo?.settings.enableFunctions.includes(FunctionTypes.SongRequest)"
|
||||
@update:value="onUpdateFunctionEnable"
|
||||
/>
|
||||
<NSwitch :value="accountInfo?.settings.enableFunctions.includes(FunctionTypes.SongRequest)"
|
||||
@update:value="onUpdateFunctionEnable" />
|
||||
|
||||
<br />
|
||||
<NText depth="3">
|
||||
@@ -812,11 +808,7 @@ onUnmounted(() => {
|
||||
则其需要保持此页面开启才能点播, 也不要同时开多个页面, 会导致点播重复 !(部署了则不影响)
|
||||
</NText>
|
||||
</NAlert>
|
||||
<NAlert
|
||||
type="warning"
|
||||
v-else
|
||||
title="你尚未注册并登录 VTsuru.live, 大部分规则设置将不可用 (因为我懒得在前段重写一遍逻辑"
|
||||
>
|
||||
<NAlert type="warning" v-else title="你尚未注册并登录 VTsuru.live, 大部分规则设置将不可用 (因为我懒得在前段重写一遍逻辑">
|
||||
<NButton tag="a" href="/manage" target="_blank" type="primary"> 前往登录或注册 </NButton>
|
||||
</NAlert>
|
||||
<br />
|
||||
@@ -832,11 +824,8 @@ onUnmounted(() => {
|
||||
</NCard>
|
||||
<br />
|
||||
<NCard>
|
||||
<NTabs
|
||||
v-if="!accountInfo || accountInfo.settings.enableFunctions.includes(FunctionTypes.SongRequest)"
|
||||
animated
|
||||
display-directive="show:lazy"
|
||||
>
|
||||
<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">
|
||||
@@ -861,12 +850,8 @@ onUnmounted(() => {
|
||||
<NInput placeholder="手动添加" v-model:value="newSongName" />
|
||||
<NButton type="primary" @click="addSongManual"> 添加 </NButton>
|
||||
</NInputGroup>
|
||||
<NRadioGroup
|
||||
v-model:value="settings.sortType"
|
||||
:disabled="!configCanEdit"
|
||||
@update:value="updateSettings"
|
||||
type="button"
|
||||
>
|
||||
<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>
|
||||
@@ -893,17 +878,13 @@ onUnmounted(() => {
|
||||
</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%;`"
|
||||
>
|
||||
<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>
|
||||
:style="`border-radius: 4px; background-color: ${song.status == SongRequestStatus.Singing ? '#75c37f' : '#577fb8'}; width: 10px; height: 20px`">
|
||||
</div>
|
||||
<NText strong style="font-size: 18px">
|
||||
{{ song.songName }}
|
||||
</NText>
|
||||
@@ -922,12 +903,10 @@ onUnmounted(() => {
|
||||
{{ song.user?.uid }}
|
||||
</NTooltip>
|
||||
</template>
|
||||
<NSpace
|
||||
v-if="
|
||||
<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">
|
||||
@@ -939,26 +918,16 @@ 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>
|
||||
@@ -973,13 +942,8 @@ onUnmounted(() => {
|
||||
<NSpace justify="end" align="center">
|
||||
<NTooltip v-if="song.song">
|
||||
<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>
|
||||
@@ -989,14 +953,8 @@ 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
|
||||
" @click="
|
||||
updateSongStatus(
|
||||
song,
|
||||
song.status == SongRequestStatus.Singing
|
||||
@@ -1005,9 +963,7 @@ onUnmounted(() => {
|
||||
)
|
||||
"
|
||||
:style="`animation: ${song.status == SongRequestStatus.Waiting ? '' : 'loading 5s linear infinite'}`"
|
||||
:secondary="song.status == SongRequestStatus.Singing"
|
||||
:loading="isLoading"
|
||||
>
|
||||
:secondary="song.status == SongRequestStatus.Singing" :loading="isLoading">
|
||||
<template #icon>
|
||||
<NIcon :component="Mic24Filled" />
|
||||
</template>
|
||||
@@ -1023,13 +979,8 @@ 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>
|
||||
@@ -1054,13 +1005,8 @@ 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>
|
||||
@@ -1096,20 +1042,14 @@ onUnmounted(() => {
|
||||
</NInputGroup>
|
||||
</NSpace>
|
||||
</NCard>
|
||||
<NDataTable
|
||||
size="small"
|
||||
ref="table"
|
||||
:columns="columns"
|
||||
:data="songs"
|
||||
:pagination="{
|
||||
<NDataTable size="small" ref="table" :columns="columns" :data="songs" :pagination="{
|
||||
itemCount: songs.length,
|
||||
pageSizes: [20, 50, 100],
|
||||
showSizePicker: true,
|
||||
prefix({ itemCount }) {
|
||||
return `共 ${itemCount} 条记录`
|
||||
},
|
||||
}"
|
||||
/>
|
||||
}" />
|
||||
</NTabPane>
|
||||
<NTabPane name="setting" tab="设置">
|
||||
<NSpin :show="isLoading">
|
||||
@@ -1132,26 +1072,17 @@ onUnmounted(() => {
|
||||
<NButton @click="updateSettings" type="info" :disabled="!configCanEdit">确定</NButton>
|
||||
</NInputGroup>
|
||||
<NSpace align="center">
|
||||
<NCheckbox
|
||||
v-model:checked="settings.enableOnStreaming"
|
||||
@update:checked="updateSettings"
|
||||
:disabled="!configCanEdit"
|
||||
>
|
||||
<NCheckbox v-model:checked="settings.enableOnStreaming" @update:checked="updateSettings"
|
||||
:disabled="!configCanEdit">
|
||||
仅在直播时才允许加入
|
||||
</NCheckbox>
|
||||
<NCheckbox
|
||||
v-model:checked="settings.allowAllDanmaku"
|
||||
@update:checked="updateSettings"
|
||||
:disabled="!configCanEdit"
|
||||
>
|
||||
<NCheckbox v-model:checked="settings.allowAllDanmaku" @update:checked="updateSettings"
|
||||
:disabled="!configCanEdit">
|
||||
允许所有弹幕点播
|
||||
</NCheckbox>
|
||||
<template v-if="!settings.allowAllDanmaku">
|
||||
<NCheckbox
|
||||
v-model:checked="settings.needWearFanMedal"
|
||||
@update:checked="updateSettings"
|
||||
:disabled="!configCanEdit"
|
||||
>
|
||||
<NCheckbox v-model:checked="settings.needWearFanMedal" @update:checked="updateSettings"
|
||||
:disabled="!configCanEdit">
|
||||
需要拥有粉丝牌
|
||||
</NCheckbox>
|
||||
<NInputGroup v-if="settings.needWearFanMedal" style="width: 250px">
|
||||
@@ -1159,28 +1090,16 @@ onUnmounted(() => {
|
||||
<NInputNumber v-model:value="settings.fanMedalMinLevel" :disabled="!configCanEdit" />
|
||||
<NButton @click="updateSettings" type="info" :disabled="!configCanEdit">确定</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"
|
||||
@update:checked="updateSettings" :disabled="!configCanEdit">
|
||||
只允许舰长
|
||||
</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"
|
||||
@update:checked="updateSettings" :disabled="!configCanEdit">
|
||||
只允许提督
|
||||
</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"
|
||||
@update:checked="updateSettings" :disabled="!configCanEdit">
|
||||
只允许总督
|
||||
</NCheckbox>
|
||||
</template>
|
||||
@@ -1190,11 +1109,8 @@ onUnmounted(() => {
|
||||
允许通过 SuperChat 点播
|
||||
</NCheckbox>
|
||||
<span v-if="settings.allowSC">
|
||||
<NCheckbox
|
||||
v-model:checked="settings.allowSC"
|
||||
@update:checked="updateSettings"
|
||||
:disabled="!configCanEdit"
|
||||
>
|
||||
<NCheckbox v-model:checked="settings.allowSC" @update:checked="updateSettings"
|
||||
:disabled="!configCanEdit">
|
||||
SC 点播无视限制
|
||||
</NCheckbox>
|
||||
<NTooltip>
|
||||
@@ -1273,29 +1189,20 @@ onUnmounted(() => {
|
||||
</NSpace> -->
|
||||
<NDivider> 点歌 </NDivider>
|
||||
<NSpace>
|
||||
<NCheckbox
|
||||
v-model:checked="settings.onlyAllowSongList"
|
||||
@update:checked="updateSettings"
|
||||
:disabled="!configCanEdit"
|
||||
>
|
||||
<NCheckbox v-model:checked="settings.onlyAllowSongList" @update:checked="updateSettings"
|
||||
:disabled="!configCanEdit">
|
||||
仅允许点
|
||||
<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" @update:checked="updateSettings"
|
||||
:disabled="!configCanEdit">
|
||||
允许通过网页点歌
|
||||
</NCheckbox>
|
||||
</NSpace>
|
||||
<NDivider> 冷却 (单位: 秒) </NDivider>
|
||||
<NCheckbox
|
||||
v-model:checked="settings.enableCooldown"
|
||||
@update:checked="updateSettings"
|
||||
:disabled="!configCanEdit"
|
||||
>
|
||||
<NCheckbox v-model:checked="settings.enableCooldown" @update:checked="updateSettings"
|
||||
:disabled="!configCanEdit">
|
||||
启用点播冷却
|
||||
</NCheckbox>
|
||||
<NSpace v-if="settings.enableCooldown">
|
||||
@@ -1329,25 +1236,16 @@ onUnmounted(() => {
|
||||
<NButton @click="updateSettings" type="primary">确定</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>
|
||||
@@ -1368,7 +1266,7 @@ onUnmounted(() => {
|
||||
<LiveRequestOBS :id="accountInfo?.id" />
|
||||
</div>
|
||||
<br />
|
||||
<NInput :value="'https://vtsuru.live/obs/live-request?id=' + accountInfo?.id" />
|
||||
<NInput :value="`${CURRENT_HOST}obs/live-request?id=` + accountInfo?.id" />
|
||||
<NDivider />
|
||||
<NCollapse>
|
||||
<NCollapseItem title="使用说明">
|
||||
@@ -1385,15 +1283,18 @@ onUnmounted(() => {
|
||||
|
||||
<style>
|
||||
@keyframes loading {
|
||||
|
||||
/*以百分比来规定改变发生的时间 也可以通过"from"和"to",等价于0% 和 100%*/
|
||||
0% {
|
||||
/*rotate(2D旋转) scale(放大或者缩小) translate(移动) skew(翻转)*/
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes animated-border {
|
||||
0% {
|
||||
box-shadow: 0 0 0px #589580;
|
||||
@@ -1403,6 +1304,7 @@ onUnmounted(() => {
|
||||
box-shadow: 0 0 0 4px rgba(255, 255, 255, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes animated-border-round {
|
||||
0% {
|
||||
box-shadow: 0 0 0px #589580;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { DownloadConfig, UploadConfig, useAccount } from '@/api/account'
|
||||
import { DanmakuUserInfo, EventModel, SongFrom, SongsInfo } from '@/api/api-models'
|
||||
import { QueryGetAPI, QueryPostAPI } from '@/api/query'
|
||||
import { RoomAuthInfo } from '@/data/DanmakuClient'
|
||||
import { MUSIC_REQUEST_API_URL, SONG_API_URL } from '@/data/constants'
|
||||
import { CURRENT_HOST, MUSIC_REQUEST_API_URL, SONG_API_URL } from '@/data/constants'
|
||||
import { useDanmakuClient } from '@/store/useDanmakuClient'
|
||||
import { MusicRequestSettings, useMusicRequestProvider } from '@/store/useMusicRequest'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
@@ -103,10 +103,10 @@ const neteaseSongListId = computed(() => {
|
||||
return Number(match[1])
|
||||
}
|
||||
}
|
||||
} catch (err) {}
|
||||
} catch (err) { }
|
||||
try {
|
||||
return Number(neteaseIdInput.value)
|
||||
} catch {}
|
||||
} catch { }
|
||||
return null
|
||||
})
|
||||
const neteaseSongs = ref<SongsInfo[]>([])
|
||||
@@ -149,7 +149,7 @@ async function searchMusic(keyword: string) {
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
function switchTo() {}
|
||||
function switchTo() { }
|
||||
async function getNeteaseSongList() {
|
||||
isLoading.value = true
|
||||
await QueryGetAPI<SongsInfo[]>(SONG_API_URL + 'get-netease-list', {
|
||||
@@ -387,14 +387,9 @@ onUnmounted(() => {
|
||||
</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 @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">
|
||||
{{ listening ? '停止监听' : '开始监听' }}
|
||||
</NButton>
|
||||
<NButton @click="showOBSModal = true" type="info" size="small"> OBS组件 </NButton>
|
||||
@@ -420,12 +415,8 @@ onUnmounted(() => {
|
||||
<NButton @click="musicRquestStore.playMusic(item.music)" type="primary" secondary size="small">
|
||||
播放
|
||||
</NButton>
|
||||
<NButton
|
||||
@click="musicRquestStore.waitingMusics.splice(musicRquestStore.waitingMusics.indexOf(item), 1)"
|
||||
type="error"
|
||||
secondary
|
||||
size="small"
|
||||
>
|
||||
<NButton @click="musicRquestStore.waitingMusics.splice(musicRquestStore.waitingMusics.indexOf(item), 1)"
|
||||
type="error" secondary size="small">
|
||||
取消
|
||||
</NButton>
|
||||
<NButton @click="blockMusic(item.music)" type="warning" secondary size="small"> 拉黑 </NButton>
|
||||
@@ -455,26 +446,18 @@ onUnmounted(() => {
|
||||
<NInputGroupLabel> 点歌弹幕前缀 </NInputGroupLabel>
|
||||
<NInput v-model:value="settings.orderPrefix" />
|
||||
</NInputGroup>
|
||||
<NCheckbox
|
||||
:checked="settings.orderCooldown != undefined"
|
||||
@update:checked="
|
||||
(checked: boolean) => {
|
||||
<NCheckbox :checked="settings.orderCooldown != undefined" @update:checked="(checked: boolean) => {
|
||||
settings.orderCooldown = checked ? 300 : undefined
|
||||
}
|
||||
"
|
||||
>
|
||||
">
|
||||
是否启用点歌冷却
|
||||
</NCheckbox>
|
||||
<NInputGroup v-if="settings.orderCooldown" style="width: 200px">
|
||||
<NInputGroupLabel> 冷却时间 (秒) </NInputGroupLabel>
|
||||
<NInputNumber
|
||||
v-model:value="settings.orderCooldown"
|
||||
@update:value="
|
||||
(value) => {
|
||||
<NInputNumber v-model:value="settings.orderCooldown" @update:value="(value) => {
|
||||
if (!value || value <= 0) settings.orderCooldown = undefined
|
||||
}
|
||||
"
|
||||
/>
|
||||
" />
|
||||
</NInputGroup>
|
||||
</NSpace>
|
||||
<NSpace>
|
||||
@@ -488,13 +471,9 @@ onUnmounted(() => {
|
||||
</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>
|
||||
@@ -532,12 +511,8 @@ onUnmounted(() => {
|
||||
<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"
|
||||
>
|
||||
<NButton @click="settings.blacklist.splice(settings.blacklist.indexOf(item), 1)" type="error" secondary
|
||||
size="small">
|
||||
删除
|
||||
</NButton>
|
||||
<NText> {{ item }} </NText>
|
||||
@@ -548,14 +523,8 @@ onUnmounted(() => {
|
||||
</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或者网页链接"
|
||||
>
|
||||
<NInput clearable style="width: 100%" autosize :status="neteaseSongListId ? 'success' : 'error'"
|
||||
v-model:value="neteaseIdInput" placeholder="直接输入歌单Id或者网页链接">
|
||||
<template #suffix>
|
||||
<NTag v-if="neteaseSongListId" type="success" size="small"> 歌单Id: {{ neteaseSongListId }} </NTag>
|
||||
</template>
|
||||
@@ -566,13 +535,8 @@ onUnmounted(() => {
|
||||
</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 style="height: 500px" ref="transfer" v-model:value="selectedNeteaseSongs"
|
||||
:options="neteaseSongsOptions" source-filterable />
|
||||
<NDivider style="margin: 10px" />
|
||||
<NButton type="primary" @click="addNeteaseSongs" :loading="isLoading">
|
||||
添加到歌单 | {{ selectedNeteaseSongs.length }} 首
|
||||
@@ -586,7 +550,7 @@ onUnmounted(() => {
|
||||
<MusicRequestOBS :id="accountInfo?.id" />
|
||||
</div>
|
||||
<br />
|
||||
<NInput :value="'https://vtsuru.live/obs/music-request?id=' + accountInfo?.id" />
|
||||
<NInput :value="`${CURRENT_HOST}obs/music-request?id=` + accountInfo?.id" />
|
||||
<NDivider />
|
||||
<NCollapse>
|
||||
<NCollapseItem title="使用说明">
|
||||
@@ -606,6 +570,7 @@ onUnmounted(() => {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
@keyframes animated-border {
|
||||
0% {
|
||||
box-shadow: 0 0 0px #589580;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useAccount } from '@/api/account'
|
||||
import { OpenLiveLotteryType, OpenLiveLotteryUserInfo, UpdateLiveLotteryUsersModel } from '@/api/api-models'
|
||||
import { QueryGetAPI, QueryPostAPI } from '@/api/query'
|
||||
import { DanmakuInfo, GiftInfo, RoomAuthInfo } from '@/data/DanmakuClient'
|
||||
import { LOTTERY_API_URL } from '@/data/constants'
|
||||
import { CURRENT_HOST, LOTTERY_API_URL } from '@/data/constants'
|
||||
import { useDanmakuClient } from '@/store/useDanmakuClient'
|
||||
import { Delete24Filled, Info24Filled } from '@vicons/fluent'
|
||||
import { useLocalStorage, useStorage } from '@vueuse/core'
|
||||
@@ -108,7 +108,7 @@ async function getUsers() {
|
||||
if (data.code == 200) {
|
||||
return data.data
|
||||
}
|
||||
} catch (err) {}
|
||||
} catch (err) { }
|
||||
return null
|
||||
}
|
||||
function updateUsers() {
|
||||
@@ -342,12 +342,7 @@ 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>
|
||||
@@ -391,13 +386,8 @@ onUnmounted(() => {
|
||||
<NCollapseTransition>
|
||||
<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'">
|
||||
@@ -405,21 +395,14 @@ onUnmounted(() => {
|
||||
<template #trigger>
|
||||
<NInputGroup style="max-width: 250px">
|
||||
<NInputGroupLabel> 弹幕内容 </NInputGroupLabel>
|
||||
<NInput
|
||||
:disabled="isStartLottery"
|
||||
v-model:value="lotteryOption.danmakuKeyword"
|
||||
placeholder="留空则任何弹幕都可以"
|
||||
/>
|
||||
<NInput :disabled="isStartLottery" v-model:value="lotteryOption.danmakuKeyword"
|
||||
placeholder="留空则任何弹幕都可以" />
|
||||
</NInputGroup>
|
||||
</template>
|
||||
符合规则的弹幕才会被添加到抽奖队列中
|
||||
</NTooltip>
|
||||
<NRadioGroup
|
||||
v-model:value="lotteryOption.danmakuFilterType"
|
||||
name="判定类型"
|
||||
:disabled="isLottering"
|
||||
size="small"
|
||||
>
|
||||
<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>
|
||||
@@ -428,11 +411,8 @@ onUnmounted(() => {
|
||||
<template v-else-if="lotteryOption.type == 'gift'">
|
||||
<NInputGroup style="max-width: 250px">
|
||||
<NInputGroupLabel> 最低价格 </NInputGroupLabel>
|
||||
<NInputNumber
|
||||
:disabled="isStartLottery"
|
||||
v-model:value="lotteryOption.giftMinPrice"
|
||||
placeholder="留空则不限制"
|
||||
/>
|
||||
<NInputNumber :disabled="isStartLottery" v-model:value="lotteryOption.giftMinPrice"
|
||||
placeholder="留空则不限制" />
|
||||
</NInputGroup>
|
||||
<NInputGroup style="max-width: 200px">
|
||||
<NInputGroupLabel> 礼物名称 </NInputGroupLabel>
|
||||
@@ -467,12 +447,8 @@ onUnmounted(() => {
|
||||
</NCard>
|
||||
<NCard v-if="originUsers" size="small">
|
||||
<NSpace justify="center" align="center">
|
||||
<NButton
|
||||
type="primary"
|
||||
@click="continueLottery"
|
||||
:loading="isStartLottery"
|
||||
:disabled="isStartLottery || isLotteried || !client"
|
||||
>
|
||||
<NButton type="primary" @click="continueLottery" :loading="isStartLottery"
|
||||
:disabled="isStartLottery || isLotteried || !client">
|
||||
开始
|
||||
</NButton>
|
||||
<NButton type="warning" :disabled="!isStartLottery" @click="pause"> 停止 </NButton>
|
||||
@@ -482,15 +458,9 @@ onUnmounted(() => {
|
||||
<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 @click="startLottery" :loading="isLottering"
|
||||
:disabled="isStartLottery || isLotteried" data-umami-event="Open-Live Use Lottery"
|
||||
:data-umami-event-uid="client?.authInfo?.anchor_info?.uid">
|
||||
进行抽取
|
||||
</NButton>
|
||||
<NButton type="info" secondary :disabled="isStartLottery || isLottering || !isLotteried" @click="reset">
|
||||
@@ -503,15 +473,8 @@ onUnmounted(() => {
|
||||
<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)"
|
||||
/>
|
||||
<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">
|
||||
@@ -526,12 +489,8 @@ onUnmounted(() => {
|
||||
{{ 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" @click="removeUser(item)"
|
||||
size="small" circle>
|
||||
<template #icon>
|
||||
<NIcon :component="Delete24Filled" />
|
||||
</template>
|
||||
@@ -572,21 +531,15 @@ onUnmounted(() => {
|
||||
</NScrollbar>
|
||||
<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"
|
||||
>
|
||||
<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 />
|
||||
<NInput :value="'https://vtsuru.live/obs/live-lottery?code=' + code" />
|
||||
<NInput :value="`${CURRENT_HOST}obs/live-lottery?code=` + code" />
|
||||
<NDivider />
|
||||
<NCollapse>
|
||||
<NCollapseItem title="使用说明">
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
} from '@/api/api-models'
|
||||
import { QueryGetAPI, QueryPostAPI, QueryPostAPIWithParams } from '@/api/query'
|
||||
import { RoomAuthInfo } from '@/data/DanmakuClient'
|
||||
import { QUEUE_API_URL } from '@/data/constants'
|
||||
import { CURRENT_HOST, QUEUE_API_URL } from '@/data/constants'
|
||||
import { useDanmakuClient } from '@/store/useDanmakuClient'
|
||||
import {
|
||||
Checkmark12Regular,
|
||||
@@ -702,7 +702,7 @@ async function updateActive() {
|
||||
message.error('无法获取队列: ' + data.message)
|
||||
return []
|
||||
}
|
||||
} catch (err) {}
|
||||
} catch (err) { }
|
||||
}
|
||||
function blockUser(item: ResponseQueueModel) {
|
||||
if (item.from != QueueFrom.Danmaku) {
|
||||
@@ -762,15 +762,11 @@ onUnmounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NAlert
|
||||
:type="accountInfo.settings.enableFunctions.includes(FunctionTypes.Queue) ? 'success' : 'warning'"
|
||||
v-if="accountInfo.id"
|
||||
>
|
||||
<NAlert :type="accountInfo.settings.enableFunctions.includes(FunctionTypes.Queue) ? 'success' : 'warning'"
|
||||
v-if="accountInfo.id">
|
||||
启用弹幕队列功能
|
||||
<NSwitch
|
||||
:value="accountInfo?.settings.enableFunctions.includes(FunctionTypes.Queue)"
|
||||
@update:value="onUpdateFunctionEnable"
|
||||
/>
|
||||
<NSwitch :value="accountInfo?.settings.enableFunctions.includes(FunctionTypes.Queue)"
|
||||
@update:value="onUpdateFunctionEnable" />
|
||||
|
||||
<br />
|
||||
<NText depth="3">
|
||||
@@ -781,11 +777,7 @@ onUnmounted(() => {
|
||||
则其需要保持此页面开启才能使用, 也不要同时开多个页面, 会导致重复 !(部署了则不影响)
|
||||
</NText>
|
||||
</NAlert>
|
||||
<NAlert
|
||||
type="warning"
|
||||
v-else
|
||||
title="你尚未注册并登录 VTsuru.live, 大部分规则设置将不可用 (因为我懒得在前段重写一遍逻辑"
|
||||
>
|
||||
<NAlert type="warning" v-else title="你尚未注册并登录 VTsuru.live, 大部分规则设置将不可用 (因为我懒得在前段重写一遍逻辑">
|
||||
<NButton tag="a" href="/manage" target="_blank" type="primary"> 前往登录或注册 </NButton>
|
||||
</NAlert>
|
||||
<br />
|
||||
@@ -801,11 +793,8 @@ onUnmounted(() => {
|
||||
</NCard>
|
||||
<br />
|
||||
<NCard>
|
||||
<NTabs
|
||||
v-if="!accountInfo || accountInfo.settings.enableFunctions.includes(FunctionTypes.Queue)"
|
||||
animated
|
||||
display-directive="show:lazy"
|
||||
>
|
||||
<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">
|
||||
@@ -833,12 +822,8 @@ onUnmounted(() => {
|
||||
</template>
|
||||
确定全部取消吗?
|
||||
</NPopconfirm>
|
||||
<NRadioGroup
|
||||
v-model:value="settings.sortType"
|
||||
:disabled="!configCanEdit"
|
||||
@update:value="updateSettings"
|
||||
type="button"
|
||||
>
|
||||
<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>
|
||||
@@ -853,17 +838,12 @@ onUnmounted(() => {
|
||||
<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%;`"
|
||||
>
|
||||
<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">
|
||||
@@ -877,12 +857,10 @@ onUnmounted(() => {
|
||||
<template v-if="queueData.from == QueueFrom.Manual">
|
||||
<NTag size="small" :bordered="false"> 手动添加 </NTag>
|
||||
</template>
|
||||
<NSpace
|
||||
v-if="
|
||||
<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">
|
||||
@@ -894,12 +872,8 @@ 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">
|
||||
@@ -933,23 +907,15 @@ onUnmounted(() => {
|
||||
<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
|
||||
" @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"
|
||||
>
|
||||
:secondary="queueData.status == QueueStatus.Progressing" :loading="isLoading">
|
||||
<template #icon>
|
||||
<NIcon :component="ClipboardTextLtr24Filled" />
|
||||
</template>
|
||||
@@ -965,13 +931,8 @@ 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>
|
||||
@@ -996,13 +957,8 @@ 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>
|
||||
@@ -1030,20 +986,14 @@ onUnmounted(() => {
|
||||
</NInputGroup>
|
||||
</NSpace>
|
||||
</NCard>
|
||||
<NDataTable
|
||||
size="small"
|
||||
ref="table"
|
||||
:columns="columns"
|
||||
:data="originQueue"
|
||||
:pagination="{
|
||||
<NDataTable size="small" ref="table" :columns="columns" :data="originQueue" :pagination="{
|
||||
itemCount: originQueue.length,
|
||||
pageSizes: [20, 50, 100],
|
||||
showSizePicker: true,
|
||||
prefix({ itemCount }) {
|
||||
return `共 ${itemCount} 条记录`
|
||||
},
|
||||
}"
|
||||
/>
|
||||
}" />
|
||||
</NTabPane>
|
||||
<NTabPane name="setting" tab="设置">
|
||||
<NSpin :show="isLoading">
|
||||
@@ -1058,12 +1008,8 @@ onUnmounted(() => {
|
||||
</template>
|
||||
<NInput v-else v-model:value="defaultKeyword" />
|
||||
</NInputGroup>
|
||||
<NRadioGroup
|
||||
v-model:value="settings.matchType"
|
||||
:disabled="!configCanEdit"
|
||||
@update:value="updateSettings"
|
||||
type="button"
|
||||
>
|
||||
<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>
|
||||
@@ -1075,18 +1021,12 @@ onUnmounted(() => {
|
||||
<NButton @click="updateSettings" type="info" :disabled="!configCanEdit">确定</NButton>
|
||||
</NInputGroup>
|
||||
<NSpace align="center">
|
||||
<NCheckbox
|
||||
v-model:checked="settings.enableOnStreaming"
|
||||
@update:checked="updateSettings"
|
||||
:disabled="!configCanEdit"
|
||||
>
|
||||
<NCheckbox v-model:checked="settings.enableOnStreaming" @update:checked="updateSettings"
|
||||
:disabled="!configCanEdit">
|
||||
仅在直播时才允许加入
|
||||
</NCheckbox>
|
||||
<NCheckbox
|
||||
v-model:checked="settings.allowAllDanmaku"
|
||||
@update:checked="updateSettings"
|
||||
:disabled="!configCanEdit"
|
||||
>
|
||||
<NCheckbox v-model:checked="settings.allowAllDanmaku" @update:checked="updateSettings"
|
||||
:disabled="!configCanEdit">
|
||||
允许所有用户加入
|
||||
</NCheckbox>
|
||||
<template v-if="!settings.allowAllDanmaku">
|
||||
@@ -1095,38 +1035,23 @@ onUnmounted(() => {
|
||||
<NInputNumber v-model:value="settings.fanMedalMinLevel" :disabled="!configCanEdit" min="0" />
|
||||
<NButton @click="updateSettings" type="info" :disabled="!configCanEdit">确定</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"
|
||||
@update:checked="updateSettings" :disabled="!configCanEdit">
|
||||
允许舰长
|
||||
</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"
|
||||
@update:checked="updateSettings" :disabled="!configCanEdit">
|
||||
允许提督
|
||||
</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"
|
||||
@update:checked="updateSettings" :disabled="!configCanEdit">
|
||||
允许总督
|
||||
</NCheckbox>
|
||||
</template>
|
||||
</NSpace>
|
||||
<NSpace align="center">
|
||||
<NCheckbox
|
||||
v-model:checked="settings.allowGift"
|
||||
@update:checked="updateSettings"
|
||||
:disabled="!configCanEdit"
|
||||
>
|
||||
<NCheckbox v-model:checked="settings.allowGift" @update:checked="updateSettings"
|
||||
:disabled="!configCanEdit">
|
||||
允许通过发送礼物加入队列
|
||||
</NCheckbox>
|
||||
<template v-if="settings.allowGift">
|
||||
@@ -1137,34 +1062,19 @@ onUnmounted(() => {
|
||||
</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 style="width: 250px" v-model:value="settings.giftNames" :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" @update:checked="updateSettings"
|
||||
:disabled="!configCanEdit">
|
||||
赠送礼物后自动加入队列
|
||||
<NTooltip>
|
||||
<template #trigger>
|
||||
@@ -1174,36 +1084,24 @@ onUnmounted(() => {
|
||||
</NTooltip>
|
||||
</NCheckbox>
|
||||
|
||||
<NCheckbox
|
||||
v-model:checked="settings.sendGiftIgnoreLimit"
|
||||
@update:checked="updateSettings"
|
||||
:disabled="!configCanEdit"
|
||||
>
|
||||
<NCheckbox v-model:checked="settings.sendGiftIgnoreLimit" @update:checked="updateSettings"
|
||||
:disabled="!configCanEdit">
|
||||
赠送礼物后无视用户等级限制
|
||||
</NCheckbox>
|
||||
</template>
|
||||
<NCheckbox
|
||||
v-model:checked="settings.allowIncreasePaymentBySendGift"
|
||||
@update:checked="updateSettings"
|
||||
:disabled="!configCanEdit"
|
||||
>
|
||||
<NCheckbox v-model:checked="settings.allowIncreasePaymentBySendGift" @update:checked="updateSettings"
|
||||
:disabled="!configCanEdit">
|
||||
在队列中时允许继续发送礼物累计付费量 (仅限上方设定的礼物)
|
||||
</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" @update:checked="updateSettings"
|
||||
:disabled="!configCanEdit">
|
||||
允许发送任意礼物来叠加付费量
|
||||
</NCheckbox>
|
||||
</NSpace>
|
||||
<NDivider> 冷却 (单位: 秒) </NDivider>
|
||||
<NCheckbox
|
||||
v-model:checked="settings.enableCooldown"
|
||||
@update:checked="updateSettings"
|
||||
:disabled="!configCanEdit"
|
||||
>
|
||||
<NCheckbox v-model:checked="settings.enableCooldown" @update:checked="updateSettings"
|
||||
:disabled="!configCanEdit">
|
||||
启用排队冷却
|
||||
</NCheckbox>
|
||||
<NSpace v-if="settings.enableCooldown">
|
||||
@@ -1229,25 +1127,16 @@ onUnmounted(() => {
|
||||
</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>
|
||||
@@ -1267,7 +1156,7 @@ onUnmounted(() => {
|
||||
<QueueOBS :id="accountInfo?.id" />
|
||||
</div>
|
||||
<br />
|
||||
<NInput :value="'https://vtsuru.live/obs/queue?id=' + accountInfo?.id" />
|
||||
<NInput :value="`${CURRENT_HOST}obs/queue?id=` + accountInfo?.id" />
|
||||
<NDivider />
|
||||
<NCollapse>
|
||||
<NCollapseItem title="使用说明">
|
||||
@@ -1284,15 +1173,18 @@ onUnmounted(() => {
|
||||
|
||||
<style>
|
||||
@keyframes loading {
|
||||
|
||||
/*以百分比来规定改变发生的时间 也可以通过"from"和"to",等价于0% 和 100%*/
|
||||
0% {
|
||||
/*rotate(2D旋转) scale(放大或者缩小) translate(移动) skew(翻转)*/
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes animated-border {
|
||||
0% {
|
||||
box-shadow: 0 0 0px #589580;
|
||||
@@ -1302,6 +1194,7 @@ onUnmounted(() => {
|
||||
box-shadow: 0 0 0 4px rgba(255, 255, 255, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes animated-border-round {
|
||||
0% {
|
||||
box-shadow: 0 0 0px #589580;
|
||||
|
||||
@@ -111,8 +111,6 @@ const speechSynthesisInfo = ref<{
|
||||
}>()
|
||||
const languageDisplayName = new Intl.DisplayNames(['zh'], { type: 'language' })
|
||||
const voiceOptions = computed(() => {
|
||||
const status = EasySpeech.status()
|
||||
if (status.status != 'init: complete') return []
|
||||
return new List(EasySpeech.voices())
|
||||
.Select((v) => {
|
||||
return {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { AddressInfo } from '@/api/api-models'
|
||||
import AddressDisplay from '@/components/manage/AddressDisplay.vue'
|
||||
import { POINT_API_URL, THINGS_URL } from '@/data/constants'
|
||||
import { CURRENT_HOST, POINT_API_URL, THINGS_URL } from '@/data/constants'
|
||||
import { useAuthStore } from '@/store/useAuthStore'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import {
|
||||
@@ -242,16 +242,11 @@ function logout() {
|
||||
<NListItem v-for="address in biliAuth.address" :key="address.id">
|
||||
<AddressDisplay :address="address">
|
||||
<template #actions>
|
||||
<NButton
|
||||
size="small"
|
||||
@click="
|
||||
() => {
|
||||
<NButton size="small" @click="() => {
|
||||
currentAddress = address
|
||||
showAddressModal = true
|
||||
}
|
||||
"
|
||||
type="info"
|
||||
>
|
||||
" type="info">
|
||||
修改
|
||||
</NButton>
|
||||
<NPopconfirm @positive-click="() => deleteAddress(address?.id ?? '')">
|
||||
@@ -267,7 +262,7 @@ function logout() {
|
||||
</NFlex>
|
||||
</NCollapseItem>
|
||||
<NCollapseItem title="登录链接" name="2">
|
||||
<NInput type="textarea" :value="'https://vtsuru.live/bili-user?auth=' + useAuth.biliToken" readonly />
|
||||
<NInput type="textarea" :value="`${CURRENT_HOST}bili-user?auth=` + useAuth.biliToken" readonly />
|
||||
</NCollapseItem>
|
||||
</NCollapse>
|
||||
</NCard>
|
||||
@@ -285,72 +280,40 @@ function logout() {
|
||||
<NListItem v-for="item in useAuth.biliTokens" :key="item.token" @click="switchAuth(item.token)">
|
||||
<NFlex align="center">
|
||||
<NTag v-if="useAuth.biliToken == item.token" type="info"> 当前账号 </NTag>
|
||||
{{ item.name }} <NDivider vertical style="margin: 0" /><NText depth="3"> {{ item.uId }} </NText>
|
||||
{{ item.name }}
|
||||
<NDivider vertical style="margin: 0" />
|
||||
<NText depth="3"> {{ item.uId }} </NText>
|
||||
</NFlex>
|
||||
</NListItem>
|
||||
</NList>
|
||||
</NCard>
|
||||
</NFlex>
|
||||
</NSpin>
|
||||
<NModal
|
||||
v-model:show="showAddressModal"
|
||||
preset="card"
|
||||
style="width: 800px; max-width: 90vw; height: auto"
|
||||
title="添加/更新地址"
|
||||
>
|
||||
<NModal v-model:show="showAddressModal" preset="card" style="width: 800px; max-width: 90vw; height: auto"
|
||||
title="添加/更新地址">
|
||||
<NSpin v-if="currentAddress" :show="isLoading">
|
||||
<NForm ref="formRef" :model="currentAddress" :rules="rules">
|
||||
<NFormItem label="地址" path="area" required>
|
||||
<NFlex style="width: 100%">
|
||||
<NSelect
|
||||
v-model:value="currentAddress.province"
|
||||
:options="provinceOptions"
|
||||
@update:value="onAreaSelectChange(0)"
|
||||
placeholder="请选择省"
|
||||
style="width: 100px"
|
||||
filterable
|
||||
/>
|
||||
<NSelect
|
||||
v-model:value="currentAddress.city"
|
||||
:key="currentAddress.province"
|
||||
:options="cityOptions(currentAddress.province)"
|
||||
:disabled="!currentAddress?.province"
|
||||
@update:value="onAreaSelectChange(1)"
|
||||
placeholder="请选择市"
|
||||
style="width: 100px"
|
||||
filterable
|
||||
/>
|
||||
<NSelect
|
||||
v-model:value="currentAddress.district"
|
||||
:key="currentAddress.city"
|
||||
:options="districtOptions(currentAddress.province, currentAddress.city)"
|
||||
:disabled="!currentAddress?.city"
|
||||
@update:value="onAreaSelectChange(2)"
|
||||
placeholder="请选择区"
|
||||
style="width: 100px"
|
||||
filterable
|
||||
/>
|
||||
<NSelect
|
||||
v-model:value="currentAddress.street"
|
||||
:key="currentAddress.district"
|
||||
<NSelect v-model:value="currentAddress.province" :options="provinceOptions"
|
||||
@update:value="onAreaSelectChange(0)" placeholder="请选择省" style="width: 100px" filterable />
|
||||
<NSelect v-model:value="currentAddress.city" :key="currentAddress.province"
|
||||
:options="cityOptions(currentAddress.province)" :disabled="!currentAddress?.province"
|
||||
@update:value="onAreaSelectChange(1)" placeholder="请选择市" style="width: 100px" filterable />
|
||||
<NSelect v-model:value="currentAddress.district" :key="currentAddress.city"
|
||||
:options="districtOptions(currentAddress.province, currentAddress.city)" :disabled="!currentAddress?.city"
|
||||
@update:value="onAreaSelectChange(2)" placeholder="请选择区" style="width: 100px" filterable />
|
||||
<NSelect v-model:value="currentAddress.street" :key="currentAddress.district"
|
||||
:options="streetOptions(currentAddress.province, currentAddress.city, currentAddress.district)"
|
||||
:disabled="!currentAddress?.district"
|
||||
placeholder="请选择街道"
|
||||
style="width: 150px"
|
||||
filterable
|
||||
/>
|
||||
:disabled="!currentAddress?.district" placeholder="请选择街道" style="width: 150px" filterable />
|
||||
</NFlex>
|
||||
</NFormItem>
|
||||
<NFormItem label="详细地址" path="address" required>
|
||||
<NInput v-model:value="currentAddress.address" placeholder="详细地址" type="textarea" />
|
||||
</NFormItem>
|
||||
<NFormItem label="联系电话" path="phone" required>
|
||||
<NInputNumber
|
||||
v-model:value="currentAddress.phone"
|
||||
placeholder="联系电话"
|
||||
:show-button="false"
|
||||
style="width: 200px"
|
||||
/>
|
||||
<NInputNumber v-model:value="currentAddress.phone" placeholder="联系电话" :show-button="false"
|
||||
style="width: 200px" />
|
||||
</NFormItem>
|
||||
<NFormItem label="联系人" path="name" required>
|
||||
<NInput v-model:value="currentAddress.name" placeholder="联系人" style="max-width: 150px" />
|
||||
@@ -365,12 +328,10 @@ function logout() {
|
||||
</NForm>
|
||||
</NSpin>
|
||||
</NModal>
|
||||
<NModal
|
||||
v-model:show="showAgreementModal"
|
||||
title="用户协议"
|
||||
preset="card"
|
||||
style="width: 800px; max-width: 90vw; height: 90vh"
|
||||
>
|
||||
<NScrollbar style="height: 80vh"> <UserAgreement /></NScrollbar>
|
||||
<NModal v-model:show="showAgreementModal" title="用户协议" preset="card"
|
||||
style="width: 800px; max-width: 90vw; height: 90vh">
|
||||
<NScrollbar style="height: 80vh">
|
||||
<UserAgreement />
|
||||
</NScrollbar>
|
||||
</NModal>
|
||||
</template>
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
<script setup lang="ts">
|
||||
import { SaveSetting, useAccount } from '@/api/account'
|
||||
import { QuestionDisplayAlign, Setting_QuestionDisplay } from '@/api/api-models'
|
||||
import { QueryPostAPI } from '@/api/query'
|
||||
import QuestionItem from '@/components/QuestionItem.vue'
|
||||
import QuestionItems from '@/components/QuestionItems.vue'
|
||||
import { QUESTION_API_URL } from '@/data/constants'
|
||||
import { CURRENT_HOST } from '@/data/constants'
|
||||
import { useQuestionBox } from '@/store/useQuestionBox'
|
||||
import { useWebRTC } from '@/store/useRTC'
|
||||
import QuestionDisplayCard from '@/views/manage/QuestionDisplayCard.vue'
|
||||
import {
|
||||
ArrowCircleLeft12Filled,
|
||||
@@ -16,8 +16,8 @@ import {
|
||||
TextAlignLeft16Filled,
|
||||
TextAlignRight16Filled,
|
||||
} from '@vicons/fluent'
|
||||
import { Heart, HeartOutline, Delete24Filled } from '@vicons/ionicons5'
|
||||
import { useDebounceFn, useElementSize, useStorage } from '@vueuse/core'
|
||||
import { Heart, HeartOutline } from '@vicons/ionicons5'
|
||||
import { useDebounceFn, useElementSize, useStorage, useThrottleFn } from '@vueuse/core'
|
||||
import {
|
||||
NButton,
|
||||
NCard,
|
||||
@@ -33,13 +33,12 @@ import {
|
||||
NInputGroupLabel,
|
||||
NInputNumber,
|
||||
NModal,
|
||||
NPopconfirm,
|
||||
NRadioButton,
|
||||
NRadioGroup,
|
||||
NScrollbar,
|
||||
NSelect,
|
||||
NTooltip,
|
||||
useMessage,
|
||||
useMessage
|
||||
} from 'naive-ui'
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
|
||||
@@ -47,6 +46,7 @@ const message = useMessage()
|
||||
const accountInfo = useAccount()
|
||||
const defaultSettings = {} as Setting_QuestionDisplay
|
||||
const useQB = useQuestionBox()
|
||||
const rtc = await useWebRTC().Init('master')
|
||||
|
||||
const showSettingDrawer = ref(false)
|
||||
const showGreenBorder = ref(false)
|
||||
@@ -70,6 +70,11 @@ watch([cardSize.width, cardSize.height], () => {
|
||||
debouncedSize()
|
||||
}
|
||||
})
|
||||
const scrollInfo = ref<{ clientHeight: number; scrollHeight: number; scrollTop: number }>()
|
||||
const debouncedScroll = useDebounceFn(() => {
|
||||
rtc?.send('function.question.sync-scroll', scrollInfo.value)
|
||||
}, 200)
|
||||
|
||||
|
||||
const setting = computed({
|
||||
get: () => {
|
||||
@@ -126,6 +131,13 @@ async function loadFonts() {
|
||||
message.error('你的浏览器不支持获取字体列表')
|
||||
}
|
||||
}
|
||||
function syncScroll(value: { clientHeight: number; scrollHeight: number; scrollTop: number }) {
|
||||
if (!setting.value.syncScroll) {
|
||||
return
|
||||
}
|
||||
scrollInfo.value = value
|
||||
debouncedScroll()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
useQB.GetRecieveQAInfo()
|
||||
@@ -144,14 +156,8 @@ onMounted(() => {
|
||||
<NButton @click="$router.push({ name: 'manage-questionBox' })" size="tiny" secondary> 回到控制台 </NButton>
|
||||
</template>
|
||||
<NFlex align="center">
|
||||
<NSelect
|
||||
v-model:value="useQB.displayTag"
|
||||
placeholder="选择当前话题"
|
||||
filterable
|
||||
clearable
|
||||
:options="useQB.tags.map((s) => ({ label: s.name, value: s.name }))"
|
||||
style="width: 200px"
|
||||
/>
|
||||
<NSelect v-model:value="useQB.displayTag" placeholder="选择当前话题" filterable clearable
|
||||
:options="useQB.tags.map((s) => ({ label: s.name, value: s.name }))" style="width: 200px" />
|
||||
<NButton @click="useQB.GetRecieveQAInfo" type="primary"> 刷新 </NButton>
|
||||
<NCheckbox v-model:checked="useQB.onlyFavorite"> 只显示收藏 </NCheckbox>
|
||||
<NCheckbox v-model:checked="useQB.onlyUnread"> 只显示未读 </NCheckbox>
|
||||
@@ -170,7 +176,7 @@ onMounted(() => {
|
||||
</NButton>
|
||||
</NFlex>
|
||||
</template>
|
||||
<QuestionItem :item="useQB.displayQuestion" />
|
||||
<QuestionItem :item="useQB.displayQuestion" style="max-height: 200px;overflow-y: auto" />
|
||||
</NCard>
|
||||
<NDivider style="margin: 10px 0 10px 0" />
|
||||
</template>
|
||||
@@ -181,18 +187,12 @@ onMounted(() => {
|
||||
<NFlex>
|
||||
<NTooltip>
|
||||
<template #trigger>
|
||||
<NButton
|
||||
@click="useQB.setCurrentQuestion(item)"
|
||||
size="small"
|
||||
<NButton @click="useQB.setCurrentQuestion(item)" size="small"
|
||||
:type="item.id != useQB.displayQuestion?.id ? 'default' : 'primary'"
|
||||
:secondary="item.id != useQB.displayQuestion?.id"
|
||||
>
|
||||
:secondary="item.id != useQB.displayQuestion?.id">
|
||||
<template #icon>
|
||||
<NIcon
|
||||
:component="
|
||||
item.id != useQB.displayQuestion?.id ? ArrowCircleRight12Filled : ArrowCircleLeft12Filled
|
||||
"
|
||||
/>
|
||||
<NIcon :component="item.id != useQB.displayQuestion?.id ? ArrowCircleRight12Filled : ArrowCircleLeft12Filled
|
||||
" />
|
||||
</template>
|
||||
</NButton>
|
||||
</template>
|
||||
@@ -201,15 +201,11 @@ onMounted(() => {
|
||||
<NButton v-if="!item.isReaded" size="small" @click="useQB.read(item, true)" type="success" secondary>
|
||||
设为已读
|
||||
</NButton>
|
||||
<NButton v-else size="small" @click="useQB.read(item, false)" type="warning" secondary
|
||||
>重设为未读</NButton
|
||||
>
|
||||
<NButton v-else size="small" @click="useQB.read(item, false)" type="warning" secondary>重设为未读</NButton>
|
||||
<NButton size="small" @click="useQB.favorite(item, !item.isFavorite)">
|
||||
<template #icon>
|
||||
<NIcon
|
||||
:component="item.isFavorite ? Heart : HeartOutline"
|
||||
:color="item.isFavorite ? '#dd484f' : ''"
|
||||
/>
|
||||
<NIcon :component="item.isFavorite ? Heart : HeartOutline"
|
||||
:color="item.isFavorite ? '#dd484f' : ''" />
|
||||
</template>
|
||||
收藏
|
||||
</NButton>
|
||||
@@ -218,7 +214,7 @@ onMounted(() => {
|
||||
</QuestionItems>
|
||||
</NScrollbar>
|
||||
</NFlex>
|
||||
<NCard style="min-height: 600px">
|
||||
<NCard style="min-height: 600px; min-width: 50vw;">
|
||||
<NFlex vertical :size="0" style="height: 100%">
|
||||
<NFlex align="center">
|
||||
<NButton @click="showSettingDrawer = true" type="primary"> 打开设置 </NButton>
|
||||
@@ -232,6 +228,16 @@ onMounted(() => {
|
||||
用于使用 OBS 直接捕获浏览器窗口时消除背景
|
||||
</NTooltip>
|
||||
</NCheckbox>
|
||||
|
||||
<NCheckbox v-model:checked="setting.syncScroll" @update:checked="updateSettings">
|
||||
同步滚动
|
||||
<NTooltip>
|
||||
<template #trigger>
|
||||
<NIcon :component="Info24Filled" />
|
||||
</template>
|
||||
实验性功能, 当前页面组件内容滚动时也会同步到OBS组件, 当组件大小不同时可能会发生无法预料的问题
|
||||
</NTooltip>
|
||||
</NCheckbox>
|
||||
<template v-if="useQB.displayQuestion">
|
||||
<NDivider vertical />
|
||||
<NButton @click="useQB.read(useQB.displayQuestion, true)" type="success"> 将当前问题设为已读 </NButton>
|
||||
@@ -247,18 +253,15 @@ onMounted(() => {
|
||||
</NTooltip>
|
||||
</NDivider>
|
||||
<NFlex justify="center" align="center" style="height: 100%">
|
||||
<div
|
||||
ref="cardRef"
|
||||
class="resize-box"
|
||||
:style="{
|
||||
<div ref="cardRef" class="resize-box" :style="{
|
||||
border: showGreenBorder ? '24px solid green' : '',
|
||||
background: showGreenBorder ? 'green' : '',
|
||||
padding: '10px',
|
||||
width: savedCardSize.width + 'px',
|
||||
height: savedCardSize.height + 'px',
|
||||
}"
|
||||
>
|
||||
<QuestionDisplayCard :question="useQB.displayQuestion" :setting="setting" :css="customCss" />
|
||||
}">
|
||||
<QuestionDisplayCard :question="useQB.displayQuestion" :setting="setting" :css="customCss"
|
||||
@scroll="syncScroll" />
|
||||
</div>
|
||||
</NFlex>
|
||||
</NFlex>
|
||||
@@ -303,23 +306,13 @@ onMounted(() => {
|
||||
</NInputGroup>
|
||||
<NInputGroup style="max-width: 300px">
|
||||
<NInputGroupLabel>字重</NInputGroupLabel>
|
||||
<NInputNumber
|
||||
v-model:value="setting.fontWeight"
|
||||
:min="1"
|
||||
:max="10000"
|
||||
step="100"
|
||||
placeholder="只有部分字体支持"
|
||||
/>
|
||||
<NInputNumber v-model:value="setting.fontWeight" :min="1" :max="10000" step="100"
|
||||
placeholder="只有部分字体支持" />
|
||||
<NButton @click="updateSettings" type="info">保存</NButton>
|
||||
</NInputGroup>
|
||||
<NFlex>
|
||||
<NSelect
|
||||
v-model:value="setting.font"
|
||||
:options="fontsOptions"
|
||||
filterable
|
||||
@update:value="updateSettings"
|
||||
placeholder="选择内容字体"
|
||||
/>
|
||||
<NSelect v-model:value="setting.font" :options="fontsOptions" filterable @update:value="updateSettings"
|
||||
placeholder="选择内容字体" />
|
||||
<NTooltip>
|
||||
<template #trigger>
|
||||
<NButton @click="loadFonts" type="info" secondary> 获取字体列表 </NButton>
|
||||
@@ -330,51 +323,27 @@ onMounted(() => {
|
||||
<NFlex justify="space-around" style="width: 100%">
|
||||
<NFlex style="min-width: 80px">
|
||||
字体颜色
|
||||
<NColorPicker
|
||||
:value="setting.fontColor ? '#' + setting.fontColor : undefined"
|
||||
show-preview
|
||||
:modes="['hex']"
|
||||
:actions="['clear', 'confirm']"
|
||||
:show-alpha="false"
|
||||
@update:value="
|
||||
(c: string | null | undefined) => {
|
||||
<NColorPicker :value="setting.fontColor ? '#' + setting.fontColor : undefined" show-preview
|
||||
:modes="['hex']" :actions="['clear', 'confirm']" :show-alpha="false" @update:value="(c: string | null | undefined) => {
|
||||
setting.fontColor = c?.replace('#', '')
|
||||
}
|
||||
"
|
||||
@confirm="updateSettings"
|
||||
/>
|
||||
" @confirm="updateSettings" />
|
||||
</NFlex>
|
||||
<NFlex style="min-width: 80px">
|
||||
背景颜色
|
||||
<NColorPicker
|
||||
:value="setting.backgroundColor ? '#' + setting.backgroundColor : undefined"
|
||||
show-preview
|
||||
:modes="['hex']"
|
||||
:actions="['clear', 'confirm']"
|
||||
:show-alpha="false"
|
||||
@update:value="
|
||||
(c: string | null | undefined) => {
|
||||
<NColorPicker :value="setting.backgroundColor ? '#' + setting.backgroundColor : undefined" show-preview
|
||||
:modes="['hex']" :actions="['clear', 'confirm']" :show-alpha="false" @update:value="(c: string | null | undefined) => {
|
||||
setting.backgroundColor = c?.replace('#', '')
|
||||
}
|
||||
"
|
||||
@confirm="updateSettings"
|
||||
/>
|
||||
" @confirm="updateSettings" />
|
||||
</NFlex>
|
||||
<NFlex style="min-width: 80px">
|
||||
边框颜色
|
||||
<NColorPicker
|
||||
:value="setting.borderColor ? '#' + setting.borderColor : undefined"
|
||||
show-preview
|
||||
:modes="['hex']"
|
||||
:actions="['clear', 'confirm']"
|
||||
:show-alpha="false"
|
||||
@update:value="
|
||||
(c: string | null | undefined) => {
|
||||
<NColorPicker :value="setting.borderColor ? '#' + setting.borderColor : undefined" show-preview
|
||||
:modes="['hex']" :actions="['clear', 'confirm']" :show-alpha="false" @update:value="(c: string | null | undefined) => {
|
||||
setting.borderColor = c?.replace('#', '')
|
||||
}
|
||||
"
|
||||
@confirm="updateSettings"
|
||||
/>
|
||||
" @confirm="updateSettings" />
|
||||
</NFlex>
|
||||
</NFlex>
|
||||
</NFlex>
|
||||
@@ -388,23 +357,13 @@ onMounted(() => {
|
||||
</NInputGroup>
|
||||
<NInputGroup style="max-width: 300px">
|
||||
<NInputGroupLabel>字重</NInputGroupLabel>
|
||||
<NInputNumber
|
||||
v-model:value="setting.nameFontWeight"
|
||||
:min="1"
|
||||
:max="10000"
|
||||
step="100"
|
||||
placeholder="只有部分字体支持"
|
||||
/>
|
||||
<NInputNumber v-model:value="setting.nameFontWeight" :min="1" :max="10000" step="100"
|
||||
placeholder="只有部分字体支持" />
|
||||
<NButton @click="updateSettings" type="info">保存</NButton>
|
||||
</NInputGroup>
|
||||
<NFlex>
|
||||
<NSelect
|
||||
v-model:value="setting.nameFont"
|
||||
:options="fontsOptions"
|
||||
filterable
|
||||
@update:value="updateSettings"
|
||||
placeholder="选择用户名字体"
|
||||
/>
|
||||
<NSelect v-model:value="setting.nameFont" :options="fontsOptions" filterable
|
||||
@update:value="updateSettings" placeholder="选择用户名字体" />
|
||||
<NTooltip>
|
||||
<template #trigger>
|
||||
<NButton @click="loadFonts" type="info" secondary> 获取字体列表 </NButton>
|
||||
@@ -414,19 +373,11 @@ onMounted(() => {
|
||||
</NFlex>
|
||||
<NFlex style="min-width: 80px">
|
||||
字体颜色
|
||||
<NColorPicker
|
||||
:value="setting.nameFontColor ? '#' + setting.nameFontColor : undefined"
|
||||
show-preview
|
||||
:modes="['hex']"
|
||||
:actions="['clear', 'confirm']"
|
||||
:show-alpha="false"
|
||||
@update:value="
|
||||
(c: string | null | undefined) => {
|
||||
<NColorPicker :value="setting.nameFontColor ? '#' + setting.nameFontColor : undefined" show-preview
|
||||
:modes="['hex']" :actions="['clear', 'confirm']" :show-alpha="false" @update:value="(c: string | null | undefined) => {
|
||||
setting.nameFontColor = c?.replace('#', '')
|
||||
}
|
||||
"
|
||||
@confirm="updateSettings"
|
||||
/>
|
||||
" @confirm="updateSettings" />
|
||||
</NFlex>
|
||||
</NFlex>
|
||||
</NCard>
|
||||
@@ -443,24 +394,16 @@ onMounted(() => {
|
||||
</NFlex>
|
||||
</NDrawerContent>
|
||||
</NDrawer>
|
||||
<NModal
|
||||
preset="card"
|
||||
v-model:show="showOBSModal"
|
||||
closable
|
||||
style="max-width: 90vw; width: auto"
|
||||
title="OBS组件"
|
||||
content-style="display: flex; align-items: center; justify-content: center; flex-direction: column"
|
||||
>
|
||||
<div
|
||||
:style="{
|
||||
<NModal preset="card" v-model:show="showOBSModal" closable style="max-width: 90vw; width: auto" title="OBS组件"
|
||||
content-style="display: flex; align-items: center; justify-content: center; flex-direction: column">
|
||||
<div :style="{
|
||||
width: savedCardSize.width + 'px',
|
||||
height: savedCardSize.height + 'px',
|
||||
}"
|
||||
>
|
||||
}">
|
||||
<QuestionDisplayCard :question="useQB.displayQuestion" :setting="setting" />
|
||||
</div>
|
||||
<NDivider />
|
||||
<NInput readonly :value="'https://vtsuru.live/obs/question-display?token=' + accountInfo?.token" />
|
||||
<NInput readonly :value="`${CURRENT_HOST}obs/question-display?token=` + accountInfo?.token" />
|
||||
</NModal>
|
||||
</template>
|
||||
|
||||
@@ -476,6 +419,7 @@ onMounted(() => {
|
||||
overflow-y: hidden;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.n-drawer-mask {
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
@@ -193,7 +193,7 @@ onUnmounted(() => {
|
||||
<NInput
|
||||
:disabled="isSelf"
|
||||
show-count
|
||||
maxlength="1000"
|
||||
maxlength="5000"
|
||||
type="textarea"
|
||||
:count-graphemes="countGraphemes"
|
||||
v-model:value="questionMessage"
|
||||
|
||||
@@ -72,7 +72,8 @@ export const Config: TemplateConfig<ConfigType> = {
|
||||
<NDivider />
|
||||
<template v-if="userInfo?.biliId">
|
||||
<template v-if="userInfo?.id == accountInfo?.id">
|
||||
<NButton type="primary" @click="$router.push({ name: 'manage-index', query: { tab: 'index' } })">
|
||||
<NButton type="primary"
|
||||
@click="$router.push({ name: 'manage-index', query: { tab: 'setting', setting: 'index' } })">
|
||||
自定义个人主页
|
||||
</NButton>
|
||||
<NDivider />
|
||||
@@ -85,17 +86,10 @@ export const Config: TemplateConfig<ConfigType> = {
|
||||
</template>
|
||||
|
||||
<NSpace justify="center" align="center" vertical>
|
||||
<NAvatar
|
||||
v-if="biliInfo"
|
||||
:src="biliInfo?.face"
|
||||
:size="width > 750 ? 175 : 100"
|
||||
round
|
||||
bordered
|
||||
:img-props="{
|
||||
<NAvatar v-if="biliInfo" :src="biliInfo?.face" :size="width > 750 ? 175 : 100" round bordered :img-props="{
|
||||
referrerpolicy: 'no-referrer',
|
||||
}"
|
||||
:style="{ boxShadow: isDarkMode ? 'rgb(195 192 192 / 35%) 0px 5px 20px' : '0 5px 15px rgba(0, 0, 0, 0.2)' }"
|
||||
/>
|
||||
:style="{ boxShadow: isDarkMode ? 'rgb(195 192 192 / 35%) 0px 5px 20px' : '0 5px 15px rgba(0, 0, 0, 0.2)' }" />
|
||||
<NSpace align="baseline" justify="center">
|
||||
<NText strong style="font-size: 32px"> {{ biliInfo?.name }} </NText>
|
||||
<NText strong style="font-size: 20px" depth="3"> ({{ userInfo?.name }}) </NText>
|
||||
@@ -116,15 +110,8 @@ export const Config: TemplateConfig<ConfigType> = {
|
||||
<temlate v-if="Object.keys(indexInfo.links || {}).length > 0">
|
||||
<NFlex align="center">
|
||||
<NDivider vertical />
|
||||
<NButton
|
||||
type="info"
|
||||
secondary
|
||||
tag="a"
|
||||
:href="link[1]"
|
||||
target="_blank"
|
||||
v-for="link in Object.entries(indexInfo.links || {})"
|
||||
:key="link[0] + link[1]"
|
||||
>
|
||||
<NButton type="info" secondary tag="a" :href="link[1]" target="_blank"
|
||||
v-for="link in Object.entries(indexInfo.links || {})" :key="link[0] + link[1]">
|
||||
{{ link[0] }}
|
||||
</NButton>
|
||||
</NFlex>
|
||||
|
||||
@@ -4,17 +4,17 @@ import { SongListConfigType, SongListConfigTypeWithConfig } from '@/data/Templat
|
||||
import { TemplateConfig } from '@/data/VTsuruTypes';
|
||||
import { FILE_BASE_URL } from '@/data/constants';
|
||||
|
||||
const props = defineProps<SongListConfigTypeWithConfig<ConfigType>>()
|
||||
const props = defineProps<SongListConfigTypeWithConfig<TraditionalConfigType>>()
|
||||
defineExpose({ Config, DefaultConfig })
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export type ConfigType = {
|
||||
export type TraditionalConfigType = {
|
||||
background: string[],
|
||||
notice: string,
|
||||
}
|
||||
export const DefaultConfig = {} as ConfigType
|
||||
export const Config: TemplateConfig<ConfigType> = {
|
||||
export const DefaultConfig = {} as TraditionalConfigType
|
||||
export const Config: TemplateConfig<TraditionalConfigType> = {
|
||||
name: 'Template.SongList.Traditional',
|
||||
items: [
|
||||
{
|
||||
|
||||
@@ -6,6 +6,7 @@ import { defineConfig } from 'vite'
|
||||
import svgLoader from 'vite-svg-loader'
|
||||
import Markdown from 'unplugin-vue-markdown/vite'
|
||||
import caddyTls from './plugins/vite-plugin-caddy'
|
||||
import ViteMonacoPlugin from 'vite-plugin-monaco-editor'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
@@ -26,7 +27,8 @@ export default defineConfig({
|
||||
Markdown({
|
||||
/* options */
|
||||
}),
|
||||
caddyTls()
|
||||
caddyTls(),
|
||||
ViteMonacoPlugin({ languageWorkers: ['css'] })
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
@@ -39,5 +41,5 @@ export default defineConfig({
|
||||
},
|
||||
optimizeDeps: {
|
||||
include: ['@vicons/fluent', '@vicons/ionicons5', 'vue', 'vue-router']
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user