mirror of
https://github.com/Megghy/vtsuru.live.git
synced 2025-12-07 02:46:55 +08:00
678 lines
20 KiB
Vue
678 lines
20 KiB
Vue
<!-- eslint-disable @typescript-eslint/no-explicit-any -->
|
|
<script setup lang="ts">
|
|
import { SaveSetting, useAccount } from '@/api/account'
|
|
import { QuestionDisplayAlign, Setting_QuestionDisplay } from '@/api/api-models'
|
|
import QuestionItem from '@/components/QuestionItem.vue'
|
|
import QuestionItems from '@/components/QuestionItems.vue'
|
|
import { CURRENT_HOST } from '@/data/constants'
|
|
import { useQuestionBox } from '@/store/useQuestionBox'
|
|
import { useWebRTC } from '@/store/useRTC'
|
|
import QuestionDisplayCard from '@/views/manage/QuestionDisplayCard.vue'
|
|
import {
|
|
ArrowCircleLeft12Filled,
|
|
ArrowCircleRight12Filled,
|
|
Info24Filled,
|
|
TextAlignCenter16Filled,
|
|
TextAlignLeft16Filled,
|
|
TextAlignRight16Filled,
|
|
} from '@vicons/fluent'
|
|
import { Heart, HeartOutline } from '@vicons/ionicons5'
|
|
import { useDebounceFn, useElementSize, useStorage, useThrottleFn } from '@vueuse/core'
|
|
import {
|
|
NButton,
|
|
NCard,
|
|
NCheckbox,
|
|
NColorPicker,
|
|
NDivider,
|
|
NDrawer,
|
|
NDrawerContent,
|
|
NFlex,
|
|
NIcon,
|
|
NInput,
|
|
NInputGroup,
|
|
NInputGroupLabel,
|
|
NInputNumber,
|
|
NModal,
|
|
NRadioButton,
|
|
NRadioGroup,
|
|
NScrollbar,
|
|
NSelect,
|
|
NTooltip,
|
|
useMessage
|
|
} from 'naive-ui'
|
|
import { computed, onMounted, ref, watch } from 'vue'
|
|
|
|
const message = useMessage()
|
|
const accountInfo = useAccount()
|
|
const defaultSettings = {} as Setting_QuestionDisplay
|
|
const useQB = useQuestionBox()
|
|
const rtc = await useWebRTC().Init('master')
|
|
|
|
const showSettingDrawer = ref(false)
|
|
const showGreenBorder = ref(false)
|
|
const showOBSModal = ref(false)
|
|
|
|
const isLoading = ref(false)
|
|
|
|
const cardRef = ref<HTMLElement>()
|
|
const cardSize = useElementSize(cardRef)
|
|
const savedCardSize = useStorage<{ width: number; height: number }>('Settings.QuestionDisplay.CardSize', {
|
|
width: 400,
|
|
height: 400,
|
|
})
|
|
const customCss = useStorage<string>('Settings.QuestionDisplay.CustomCss', '')
|
|
|
|
const debouncedSize = useDebounceFn(() => {
|
|
savedCardSize.value = { width: cardSize.width.value, height: cardSize.height.value }
|
|
}, 500)
|
|
watch([cardSize.width, cardSize.height], () => {
|
|
if (cardSize.width.value > 300 && cardSize.height.value > 300) {
|
|
debouncedSize()
|
|
}
|
|
})
|
|
const scrollInfo = ref<{ clientHeight: number; scrollHeight: number; scrollTop: number }>()
|
|
const debouncedScroll = useDebounceFn(() => {
|
|
rtc?.send('function.question.sync-scroll', scrollInfo.value)
|
|
}, 200)
|
|
|
|
|
|
const setting = computed({
|
|
get: () => {
|
|
if (accountInfo.value && accountInfo.value.settings) {
|
|
return accountInfo.value.settings.questionDisplay
|
|
}
|
|
return defaultSettings
|
|
},
|
|
set: (value) => {
|
|
if (accountInfo.value) {
|
|
accountInfo.value.settings.questionDisplay = value
|
|
}
|
|
},
|
|
})
|
|
async function updateSettings() {
|
|
if (accountInfo.value) {
|
|
isLoading.value = true
|
|
await SaveSetting('QuestionDisplay', setting.value)
|
|
.then((msg) => {
|
|
if (msg) {
|
|
message.success('已保存')
|
|
return true
|
|
} else {
|
|
message.error('保存失败: ' + msg)
|
|
}
|
|
})
|
|
.finally(() => {
|
|
isLoading.value = false
|
|
})
|
|
} else {
|
|
message.success('完成')
|
|
}
|
|
}
|
|
const fontsOptions = useStorage<{ label: string; value: string }[]>('Settings.Fonts', [])
|
|
async function loadFonts() {
|
|
if ('queryLocalFonts' in window) {
|
|
//@ts-expect-error 不知道为啥不存在
|
|
const status = await navigator.permissions.query({ name: 'local-fonts' })
|
|
if (status.state === 'granted') {
|
|
console.log('Permission was granted 👍')
|
|
} else if (status.state === 'prompt') {
|
|
console.log('Permission will be requested')
|
|
} else {
|
|
console.log('Permission was denied 👎')
|
|
message.error('你没有授予本地字体权限, 无法读取本地字体')
|
|
}
|
|
//@ts-expect-error 不知道为啥不存在
|
|
const fonts = await window.queryLocalFonts()
|
|
fontsOptions.value = fonts.map((f: any) => {
|
|
return { label: f.fullName, value: f.fullName }
|
|
})
|
|
message.success('已获取字体列表, 共' + fontsOptions.value.length + '个字体')
|
|
} else {
|
|
message.error('你的浏览器不支持获取字体列表')
|
|
}
|
|
}
|
|
function syncScroll(value: { clientHeight: number; scrollHeight: number; scrollTop: number }) {
|
|
if (!setting.value.syncScroll) {
|
|
return
|
|
}
|
|
scrollInfo.value = value
|
|
debouncedScroll()
|
|
}
|
|
|
|
onMounted(() => {
|
|
useQB.GetRecieveQAInfo()
|
|
|
|
useQB.displayQuestion = useQB.recieveQuestions.find(
|
|
(s) => s.id == accountInfo.value?.settings.questionDisplay.currentQuestion,
|
|
)
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<NFlex
|
|
:wrap="false"
|
|
style="margin: 20px"
|
|
>
|
|
<NFlex
|
|
style="height: calc(100vh - 40px)"
|
|
:wrap="false"
|
|
vertical
|
|
>
|
|
<NCard
|
|
size="small"
|
|
title="内容设置"
|
|
>
|
|
<template #header-extra>
|
|
<NButton
|
|
size="tiny"
|
|
secondary
|
|
@click="$router.push({ name: 'manage-questionBox' })"
|
|
>
|
|
回到控制台
|
|
</NButton>
|
|
</template>
|
|
<NFlex align="center">
|
|
<NSelect
|
|
v-model:value="useQB.displayTag"
|
|
placeholder="选择当前话题"
|
|
filterable
|
|
clearable
|
|
:options="useQB.tags.map((s) => ({ label: s.name, value: s.name }))"
|
|
style="width: 200px"
|
|
/>
|
|
<NButton
|
|
type="primary"
|
|
@click="useQB.GetRecieveQAInfo"
|
|
>
|
|
刷新
|
|
</NButton>
|
|
<NCheckbox v-model:checked="useQB.onlyFavorite">
|
|
只显示收藏
|
|
</NCheckbox>
|
|
<NCheckbox v-model:checked="useQB.onlyUnread">
|
|
只显示未读
|
|
</NCheckbox>
|
|
</NFlex>
|
|
</NCard>
|
|
<template v-if="useQB.displayQuestion">
|
|
<NDivider style="margin: 10px 0 10px 0" />
|
|
<NCard
|
|
size="small"
|
|
title="当前展示"
|
|
embedded
|
|
>
|
|
<template #header-extra>
|
|
<NFlex>
|
|
<NButton
|
|
size="small"
|
|
secondary
|
|
type="info"
|
|
@click="useQB.setCurrentQuestion(useQB.displayQuestion)"
|
|
>
|
|
取消展示
|
|
</NButton>
|
|
<NButton
|
|
size="small"
|
|
secondary
|
|
type="success"
|
|
@click="useQB.read(useQB.displayQuestion, true)"
|
|
>
|
|
已读
|
|
</NButton>
|
|
</NFlex>
|
|
</template>
|
|
<QuestionItem
|
|
:item="useQB.displayQuestion"
|
|
style="max-height: 200px;overflow-y: auto"
|
|
/>
|
|
</NCard>
|
|
<NDivider style="margin: 10px 0 10px 0" />
|
|
</template>
|
|
|
|
<NScrollbar :style="{ flex: 1, minWidth: '400px', overflow: 'auto' }">
|
|
<QuestionItems :questions="useQB.recieveQuestionsFiltered">
|
|
<template #footer="{ item }">
|
|
<NFlex>
|
|
<NTooltip>
|
|
<template #trigger>
|
|
<NButton
|
|
size="small"
|
|
:type="item.id != useQB.displayQuestion?.id ? 'default' : 'primary'"
|
|
:secondary="item.id != useQB.displayQuestion?.id"
|
|
@click="useQB.setCurrentQuestion(item)"
|
|
>
|
|
<template #icon>
|
|
<NIcon
|
|
:component="item.id != useQB.displayQuestion?.id ? ArrowCircleRight12Filled : ArrowCircleLeft12Filled
|
|
"
|
|
/>
|
|
</template>
|
|
</NButton>
|
|
</template>
|
|
{{ item.id == useQB.displayQuestion?.id ? '取消' : '' }}设为当前展示的提问
|
|
</NTooltip>
|
|
<NButton
|
|
v-if="!item.isReaded"
|
|
size="small"
|
|
type="success"
|
|
secondary
|
|
@click="useQB.read(item, true)"
|
|
>
|
|
设为已读
|
|
</NButton>
|
|
<NButton
|
|
v-else
|
|
size="small"
|
|
type="warning"
|
|
secondary
|
|
@click="useQB.read(item, false)"
|
|
>
|
|
重设为未读
|
|
</NButton>
|
|
<NButton
|
|
size="small"
|
|
@click="useQB.favorite(item, !item.isFavorite)"
|
|
>
|
|
<template #icon>
|
|
<NIcon
|
|
:component="item.isFavorite ? Heart : HeartOutline"
|
|
:color="item.isFavorite ? '#dd484f' : ''"
|
|
/>
|
|
</template>
|
|
收藏
|
|
</NButton>
|
|
</NFlex>
|
|
</template>
|
|
</QuestionItems>
|
|
</NScrollbar>
|
|
</NFlex>
|
|
<NCard style="min-height: 600px; min-width: 50vw;">
|
|
<NFlex
|
|
vertical
|
|
:size="0"
|
|
style="height: 100%"
|
|
>
|
|
<NFlex align="center">
|
|
<NButton
|
|
type="primary"
|
|
@click="showSettingDrawer = true"
|
|
>
|
|
打开设置
|
|
</NButton>
|
|
<NButton
|
|
type="primary"
|
|
secondary
|
|
@click="showOBSModal = true"
|
|
>
|
|
预览OBS组件
|
|
</NButton>
|
|
<NCheckbox v-model:checked="showGreenBorder">
|
|
显示边框
|
|
<NTooltip>
|
|
<template #trigger>
|
|
<NIcon :component="Info24Filled" />
|
|
</template>
|
|
用于使用 OBS 直接捕获浏览器窗口时消除背景
|
|
</NTooltip>
|
|
</NCheckbox>
|
|
|
|
<NCheckbox
|
|
v-model:checked="setting.syncScroll"
|
|
@update:checked="updateSettings"
|
|
>
|
|
同步滚动
|
|
<NTooltip>
|
|
<template #trigger>
|
|
<NIcon :component="Info24Filled" />
|
|
</template>
|
|
实验性功能, 当前页面组件内容滚动时也会同步到OBS组件, 当组件大小不同时可能会发生无法预料的问题
|
|
</NTooltip>
|
|
</NCheckbox>
|
|
<template v-if="useQB.displayQuestion">
|
|
<NDivider vertical />
|
|
<NButton
|
|
type="success"
|
|
@click="useQB.read(useQB.displayQuestion, true)"
|
|
>
|
|
将当前问题设为已读
|
|
</NButton>
|
|
</template>
|
|
</NFlex>
|
|
<NDivider style="margin-top: 10px">
|
|
{{ cardSize.width.value.toFixed(0) }} x {{ cardSize.height.value.toFixed(0) }}
|
|
<NTooltip>
|
|
<template #trigger>
|
|
<NIcon :component="Info24Filled" />
|
|
</template>
|
|
宽x高, 展示框右下角可以调整尺寸. 如果用的 OBS 组件的话尺寸不会同步到obs, 得你自己调
|
|
</NTooltip>
|
|
</NDivider>
|
|
<NFlex
|
|
justify="center"
|
|
align="center"
|
|
style="height: 100%"
|
|
>
|
|
<div
|
|
ref="cardRef"
|
|
class="resize-box"
|
|
:style="{
|
|
border: showGreenBorder ? '24px solid green' : '',
|
|
background: showGreenBorder ? 'green' : '',
|
|
padding: '10px',
|
|
width: savedCardSize.width + 'px',
|
|
height: savedCardSize.height + 'px',
|
|
}"
|
|
>
|
|
<QuestionDisplayCard
|
|
:question="useQB.displayQuestion"
|
|
:setting="setting"
|
|
:css="customCss"
|
|
@scroll="syncScroll"
|
|
/>
|
|
</div>
|
|
</NFlex>
|
|
</NFlex>
|
|
</NCard>
|
|
</NFlex>
|
|
<NDrawer
|
|
v-model:show="showSettingDrawer"
|
|
:title="`设置`"
|
|
:width="400"
|
|
placement="left"
|
|
>
|
|
<NDrawerContent
|
|
title="设置"
|
|
closable
|
|
>
|
|
<NFlex>
|
|
<NTooltip>
|
|
<template #trigger>
|
|
<NRadioGroup
|
|
v-model:value="setting.align"
|
|
@update:value="updateSettings"
|
|
>
|
|
<NRadioButton :value="QuestionDisplayAlign.Left">
|
|
<NIcon :component="TextAlignLeft16Filled" />
|
|
</NRadioButton>
|
|
<NRadioButton :value="QuestionDisplayAlign.Center">
|
|
<NIcon :component="TextAlignCenter16Filled" />
|
|
</NRadioButton>
|
|
<NRadioButton :value="QuestionDisplayAlign.Right">
|
|
<NIcon :component="TextAlignRight16Filled" />
|
|
</NRadioButton>
|
|
</NRadioGroup>
|
|
</template>
|
|
文字对齐
|
|
</NTooltip>
|
|
<NFlex>
|
|
<NCheckbox
|
|
v-model:checked="setting.showImage"
|
|
@update:checked="updateSettings"
|
|
>
|
|
显示图片
|
|
</NCheckbox>
|
|
<NCheckbox
|
|
v-model:checked="setting.showUserName"
|
|
@update:checked="updateSettings"
|
|
>
|
|
显示投稿用户名
|
|
</NCheckbox>
|
|
</NFlex>
|
|
<NCard
|
|
size="small"
|
|
title="内容设置"
|
|
>
|
|
<NFlex>
|
|
<NInputGroup style="max-width: 230px">
|
|
<NInputGroupLabel>字体大小</NInputGroupLabel>
|
|
<NInputNumber
|
|
v-model:value="setting.fontSize"
|
|
:min="1"
|
|
:max="1000"
|
|
/>
|
|
<NButton
|
|
type="info"
|
|
@click="updateSettings"
|
|
>
|
|
保存
|
|
</NButton>
|
|
</NInputGroup>
|
|
<NInputGroup style="max-width: 230px">
|
|
<NInputGroupLabel>边框宽度</NInputGroupLabel>
|
|
<NInputNumber
|
|
v-model:value="setting.borderWidth"
|
|
:min="1"
|
|
:max="1000"
|
|
/>
|
|
<NButton
|
|
type="info"
|
|
@click="updateSettings"
|
|
>
|
|
保存
|
|
</NButton>
|
|
</NInputGroup>
|
|
<NInputGroup style="max-width: 300px">
|
|
<NInputGroupLabel>字重</NInputGroupLabel>
|
|
<NInputNumber
|
|
v-model:value="setting.fontWeight"
|
|
:min="1"
|
|
:max="10000"
|
|
step="100"
|
|
placeholder="只有部分字体支持"
|
|
/>
|
|
<NButton
|
|
type="info"
|
|
@click="updateSettings"
|
|
>
|
|
保存
|
|
</NButton>
|
|
</NInputGroup>
|
|
<NFlex>
|
|
<NSelect
|
|
v-model:value="setting.font"
|
|
:options="fontsOptions"
|
|
filterable
|
|
placeholder="选择内容字体"
|
|
@update:value="updateSettings"
|
|
/>
|
|
<NTooltip>
|
|
<template #trigger>
|
|
<NButton
|
|
type="info"
|
|
secondary
|
|
@click="loadFonts"
|
|
>
|
|
获取字体列表
|
|
</NButton>
|
|
</template>
|
|
如果选用了本地字体且使用了obs组件的话请确保运行obs的电脑上也有这个字体
|
|
</NTooltip>
|
|
</NFlex>
|
|
<NFlex
|
|
justify="space-around"
|
|
style="width: 100%"
|
|
>
|
|
<NFlex style="min-width: 80px">
|
|
字体颜色
|
|
<NColorPicker
|
|
:value="setting.fontColor ? '#' + setting.fontColor : undefined"
|
|
show-preview
|
|
:modes="['hex']"
|
|
:actions="['clear', 'confirm']"
|
|
:show-alpha="false"
|
|
@update:value="(c: string | null | undefined) => {
|
|
setting.fontColor = c?.replace('#', '')
|
|
}
|
|
"
|
|
@confirm="updateSettings"
|
|
/>
|
|
</NFlex>
|
|
<NFlex style="min-width: 80px">
|
|
背景颜色
|
|
<NColorPicker
|
|
:value="setting.backgroundColor ? '#' + setting.backgroundColor : undefined"
|
|
show-preview
|
|
:modes="['hex']"
|
|
:actions="['clear', 'confirm']"
|
|
:show-alpha="false"
|
|
@update:value="(c: string | null | undefined) => {
|
|
setting.backgroundColor = c?.replace('#', '')
|
|
}
|
|
"
|
|
@confirm="updateSettings"
|
|
/>
|
|
</NFlex>
|
|
<NFlex style="min-width: 80px">
|
|
边框颜色
|
|
<NColorPicker
|
|
:value="setting.borderColor ? '#' + setting.borderColor : undefined"
|
|
show-preview
|
|
:modes="['hex']"
|
|
:actions="['clear', 'confirm']"
|
|
:show-alpha="false"
|
|
@update:value="(c: string | null | undefined) => {
|
|
setting.borderColor = c?.replace('#', '')
|
|
}
|
|
"
|
|
@confirm="updateSettings"
|
|
/>
|
|
</NFlex>
|
|
</NFlex>
|
|
</NFlex>
|
|
</NCard>
|
|
<NCard
|
|
size="small"
|
|
title="用户名设置"
|
|
>
|
|
<NFlex>
|
|
<NInputGroup style="max-width: 230px">
|
|
<NInputGroupLabel>字体大小</NInputGroupLabel>
|
|
<NInputNumber
|
|
v-model:value="setting.nameFontSize"
|
|
:min="1"
|
|
:max="1000"
|
|
/>
|
|
<NButton
|
|
type="info"
|
|
@click="updateSettings"
|
|
>
|
|
保存
|
|
</NButton>
|
|
</NInputGroup>
|
|
<NInputGroup style="max-width: 300px">
|
|
<NInputGroupLabel>字重</NInputGroupLabel>
|
|
<NInputNumber
|
|
v-model:value="setting.nameFontWeight"
|
|
:min="1"
|
|
:max="10000"
|
|
step="100"
|
|
placeholder="只有部分字体支持"
|
|
/>
|
|
<NButton
|
|
type="info"
|
|
@click="updateSettings"
|
|
>
|
|
保存
|
|
</NButton>
|
|
</NInputGroup>
|
|
<NFlex>
|
|
<NSelect
|
|
v-model:value="setting.nameFont"
|
|
:options="fontsOptions"
|
|
filterable
|
|
placeholder="选择用户名字体"
|
|
@update:value="updateSettings"
|
|
/>
|
|
<NTooltip>
|
|
<template #trigger>
|
|
<NButton
|
|
type="info"
|
|
secondary
|
|
@click="loadFonts"
|
|
>
|
|
获取字体列表
|
|
</NButton>
|
|
</template>
|
|
如果选用了本地字体且使用了obs组件的话请确保运行obs的电脑上也有这个字体
|
|
</NTooltip>
|
|
</NFlex>
|
|
<NFlex style="min-width: 80px">
|
|
字体颜色
|
|
<NColorPicker
|
|
:value="setting.nameFontColor ? '#' + setting.nameFontColor : undefined"
|
|
show-preview
|
|
:modes="['hex']"
|
|
:actions="['clear', 'confirm']"
|
|
:show-alpha="false"
|
|
@update:value="(c: string | null | undefined) => {
|
|
setting.nameFontColor = c?.replace('#', '')
|
|
}
|
|
"
|
|
@confirm="updateSettings"
|
|
/>
|
|
</NFlex>
|
|
</NFlex>
|
|
</NCard>
|
|
<NDivider style="margin: 10px 0 10px 0">
|
|
自定义样式 (CSS)
|
|
<NTooltip>
|
|
<template #trigger>
|
|
<NIcon :component="Info24Filled" />
|
|
</template>
|
|
只会在当前页面生效, 要想在OBS中也生效的话需要自己粘贴到创建浏览器源时的css栏中
|
|
</NTooltip>
|
|
</NDivider>
|
|
<NInput
|
|
v-model:value="customCss"
|
|
type="textarea"
|
|
placeholder="写上css"
|
|
style="max-height: 500px"
|
|
/>
|
|
</NFlex>
|
|
</NDrawerContent>
|
|
</NDrawer>
|
|
<NModal
|
|
v-model:show="showOBSModal"
|
|
preset="card"
|
|
closable
|
|
style="max-width: 90vw; width: auto"
|
|
title="OBS组件"
|
|
content-style="display: flex; align-items: center; justify-content: center; flex-direction: column"
|
|
>
|
|
<div
|
|
:style="{
|
|
width: savedCardSize.width + 'px',
|
|
height: savedCardSize.height + 'px',
|
|
}"
|
|
>
|
|
<QuestionDisplayCard
|
|
:question="useQB.displayQuestion"
|
|
:setting="setting"
|
|
/>
|
|
</div>
|
|
<NDivider />
|
|
<NInput
|
|
readonly
|
|
:value="`${CURRENT_HOST}obs/question-display?token=` + accountInfo?.token"
|
|
/>
|
|
</NModal>
|
|
</template>
|
|
|
|
<style>
|
|
.resize-box {
|
|
display: flex;
|
|
justify-content: center;
|
|
overflow-y: visible;
|
|
min-width: 300px;
|
|
min-height: 100px;
|
|
resize: both;
|
|
overflow: auto;
|
|
overflow-y: hidden;
|
|
padding: 10px;
|
|
}
|
|
|
|
.n-drawer-mask {
|
|
background-color: rgba(0, 0, 0, 0);
|
|
}
|
|
</style>
|