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