mirror of
https://github.com/Megghy/vtsuru.live.git
synced 2025-12-06 18:36:55 +08:00
add switch
This commit is contained in:
@@ -3,7 +3,13 @@ module.exports = {
|
|||||||
env: {
|
env: {
|
||||||
node: true,
|
node: true,
|
||||||
},
|
},
|
||||||
extends: ['@vue/typescript/recommended', 'plugin:vue/vue3-essential', 'prettier', '@vue/eslint-config-typescript'],
|
extends: [
|
||||||
|
'@vue/typescript/recommended',
|
||||||
|
'plugin:vue/vue3-essential',
|
||||||
|
'prettier',
|
||||||
|
'@vue/eslint-config-typescript',
|
||||||
|
'plugin:oxlint/recommended',
|
||||||
|
],
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
ecmaVersion: 'latest',
|
ecmaVersion: 'latest',
|
||||||
},
|
},
|
||||||
@@ -11,6 +17,7 @@ module.exports = {
|
|||||||
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||||
'vue/component-name-in-template-casing': ['error', 'PascalCase'],
|
'vue/component-name-in-template-casing': ['error', 'PascalCase'],
|
||||||
|
'vue/no-mutating-props': ['off'],
|
||||||
'@typescript-eslint/no-explicit-any': ['off'],
|
'@typescript-eslint/no-explicit-any': ['off'],
|
||||||
'@typescript-eslint/no-var-requires': ['warn'],
|
'@typescript-eslint/no-var-requires': ['warn'],
|
||||||
},
|
},
|
||||||
@@ -24,6 +31,7 @@ module.exports = {
|
|||||||
],
|
],
|
||||||
rules: {
|
rules: {
|
||||||
// TypeScript specific rules...
|
// TypeScript specific rules...
|
||||||
|
'@typescript-eslint/no-explicit-any': ['off'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
1
.husky/pre-commit
Normal file
1
.husky/pre-commit
Normal file
@@ -0,0 +1 @@
|
|||||||
|
yarn test
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
// eslint.config.js
|
|
||||||
import oxlint from 'eslint-plugin-oxlint'
|
|
||||||
export default [
|
|
||||||
oxlint, // oxlint should be the last one
|
|
||||||
]
|
|
||||||
@@ -2,10 +2,13 @@
|
|||||||
"name": "vtsuru.live",
|
"name": "vtsuru.live",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"lint": "vite lint"
|
"lint": "npx oxlint && vite lint",
|
||||||
|
"postinstall": "husky init",
|
||||||
|
"prepare": "husky"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": "^20.11.19",
|
"@types/node": "^20.11.19",
|
||||||
@@ -47,6 +50,7 @@
|
|||||||
"xlsx": "^0.18.5"
|
"xlsx": "^0.18.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@eslint/eslintrc": "^3.0.1",
|
||||||
"@types/eslint": "^8.56.2",
|
"@types/eslint": "^8.56.2",
|
||||||
"@types/uuid": "^9.0.8",
|
"@types/uuid": "^9.0.8",
|
||||||
"@typescript-eslint/parser": "^7.0.1",
|
"@typescript-eslint/parser": "^7.0.1",
|
||||||
@@ -55,6 +59,7 @@
|
|||||||
"@vue/eslint-config-typescript": "^12.0.0",
|
"@vue/eslint-config-typescript": "^12.0.0",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-plugin-vue": "^9.21.1",
|
"eslint-plugin-vue": "^9.21.1",
|
||||||
|
"husky": "^9.0.11",
|
||||||
"naive-ui": "^2.37.3",
|
"naive-ui": "^2.37.3",
|
||||||
"stylus": "^0.62.0",
|
"stylus": "^0.62.0",
|
||||||
"typescript": "^5.3.3"
|
"typescript": "^5.3.3"
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ export function getBase64(file: File | undefined | null): Promise<string | undef
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
export async function getImageUploadModel(files: UploadFileInfo[] | undefined | null, maxSize: number = 10 * 1024 * 1024) {
|
export async function getImageUploadModel(files: UploadFileInfo[] | undefined | null, maxSize: number = 10 * 1024 * 1024) {
|
||||||
let result = {
|
const result = {
|
||||||
existImages: [],
|
existImages: [],
|
||||||
newImagesBase64: [],
|
newImagesBase64: [],
|
||||||
} as { existImages: string[]; newImagesBase64: string[] }
|
} as { existImages: string[]; newImagesBase64: string[] }
|
||||||
|
|||||||
@@ -1,7 +1,18 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ResponseLiveInfoModel } from '@/api/api-models'
|
import { ResponseLiveInfoModel } from '@/api/api-models'
|
||||||
import { Info24Filled } from '@vicons/fluent'
|
import { Info24Filled } from '@vicons/fluent'
|
||||||
import { NButton, NDivider, NIcon, NNumberAnimation, NPopover, NSpace, NStatistic, NTag, NTime, NTooltip } from 'naive-ui'
|
import {
|
||||||
|
NButton,
|
||||||
|
NDivider,
|
||||||
|
NIcon,
|
||||||
|
NNumberAnimation,
|
||||||
|
NPopover,
|
||||||
|
NSpace,
|
||||||
|
NStatistic,
|
||||||
|
NTag,
|
||||||
|
NTime,
|
||||||
|
NTooltip,
|
||||||
|
} from 'naive-ui'
|
||||||
import { ref, watch } from 'vue'
|
import { ref, watch } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
|
||||||
@@ -12,7 +23,7 @@ const { live } = defineProps<{
|
|||||||
live: ResponseLiveInfoModel
|
live: ResponseLiveInfoModel
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
let defaultDanmakusCount = ref(0)
|
const defaultDanmakusCount = ref(0)
|
||||||
function OnClickCover() {
|
function OnClickCover() {
|
||||||
router.push({
|
router.push({
|
||||||
name: 'manage-liveDetail',
|
name: 'manage-liveDetail',
|
||||||
@@ -50,7 +61,14 @@ watch(
|
|||||||
</NButton>
|
</NButton>
|
||||||
<span>
|
<span>
|
||||||
<span v-if="!live.isFinish">
|
<span v-if="!live.isFinish">
|
||||||
<NTag size="tiny" :bordered="false" type="success" style="justify-items: center; box-shadow: 0 0 3px #589580"> 直播中 </NTag>
|
<NTag
|
||||||
|
size="tiny"
|
||||||
|
:bordered="false"
|
||||||
|
type="success"
|
||||||
|
style="justify-items: center; box-shadow: 0 0 3px #589580"
|
||||||
|
>
|
||||||
|
直播中
|
||||||
|
</NTag>
|
||||||
</span>
|
</span>
|
||||||
<span v-else style="color: gray">
|
<span v-else style="color: gray">
|
||||||
{{ (((live.stopAt ?? 0) - (live.startAt ?? 0)) / (3600 * 1000)).toFixed(1) }}
|
{{ (((live.stopAt ?? 0) - (live.startAt ?? 0)) / (3600 * 1000)).toFixed(1) }}
|
||||||
|
|||||||
@@ -4,7 +4,13 @@ import { QueryGetAPI, QueryPostAPI } from '@/api/query'
|
|||||||
import { SONG_API_URL } from '@/data/constants'
|
import { SONG_API_URL } from '@/data/constants'
|
||||||
import FiveSingIcon from '@/svgs/fivesing.svg'
|
import FiveSingIcon from '@/svgs/fivesing.svg'
|
||||||
import NeteaseIcon from '@/svgs/netease.svg'
|
import NeteaseIcon from '@/svgs/netease.svg'
|
||||||
import { Delete24Filled, Info24Filled, NotepadEdit20Filled, Play24Filled, SquareArrowForward24Filled } from '@vicons/fluent'
|
import {
|
||||||
|
Delete24Filled,
|
||||||
|
Info24Filled,
|
||||||
|
NotepadEdit20Filled,
|
||||||
|
Play24Filled,
|
||||||
|
SquareArrowForward24Filled,
|
||||||
|
} from '@vicons/fluent'
|
||||||
import { refDebounced, useLocalStorage } from '@vueuse/core'
|
import { refDebounced, useLocalStorage } from '@vueuse/core'
|
||||||
import { List } from 'linqts'
|
import { List } from 'linqts'
|
||||||
import {
|
import {
|
||||||
@@ -154,7 +160,11 @@ const authorColumn = ref<DataTableBaseColumn<SongsInfo>>({
|
|||||||
},
|
},
|
||||||
filterOptions: authorsOptions.value,
|
filterOptions: authorsOptions.value,
|
||||||
render(data) {
|
render(data) {
|
||||||
return h(NSpace, { size: 5 }, () => data.author.map((a) => h(NButton, { size: 'tiny', type: 'info', secondary: true, onClick: () => onAuthorClick(a) }, () => a)))
|
return h(NSpace, { size: 5 }, () =>
|
||||||
|
data.author.map((a) =>
|
||||||
|
h(NButton, { size: 'tiny', type: 'info', secondary: true, onClick: () => onAuthorClick(a) }, () => a),
|
||||||
|
),
|
||||||
|
)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
const onAuthorClick = (author: string) => {
|
const onAuthorClick = (author: string) => {
|
||||||
@@ -179,7 +189,10 @@ function createColumns(): DataTableColumns<SongsInfo> {
|
|||||||
width: 300,
|
width: 300,
|
||||||
sorter: 'default',
|
sorter: 'default',
|
||||||
render(data) {
|
render(data) {
|
||||||
return h(NSpace, { size: 5 }, () => [h(NText, { style: { color: data.options?.scMinPrice ? '#c36767' : '' } }, () => data.name), h(NText, { depth: '3' }, () => data.translateName)])
|
return h(NSpace, { size: 5 }, () => [
|
||||||
|
h(NText, { style: { color: data.options?.scMinPrice ? '#c36767' : '' } }, () => data.name),
|
||||||
|
h(NText, { depth: '3' }, () => data.translateName),
|
||||||
|
])
|
||||||
},
|
},
|
||||||
title: '曲名',
|
title: '曲名',
|
||||||
},
|
},
|
||||||
@@ -195,7 +208,11 @@ function createColumns(): DataTableColumns<SongsInfo> {
|
|||||||
},
|
},
|
||||||
render(data) {
|
render(data) {
|
||||||
return (data.language?.length ?? 0) > 0
|
return (data.language?.length ?? 0) > 0
|
||||||
? h(NSpace, { size: 5 }, () => data.language?.map((a) => h(NTag, { bordered: false, size: 'small' }, () => songSelectOption.find((s) => s.value == a)?.label)))
|
? h(NSpace, { size: 5 }, () =>
|
||||||
|
data.language?.map((a) =>
|
||||||
|
h(NTag, { bordered: false, size: 'small' }, () => songSelectOption.find((s) => s.value == a)?.label),
|
||||||
|
),
|
||||||
|
)
|
||||||
: null
|
: null
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -214,13 +231,40 @@ function createColumns(): DataTableColumns<SongsInfo> {
|
|||||||
render(data) {
|
render(data) {
|
||||||
return data.options
|
return data.options
|
||||||
? h(NSpace, {}, () => [
|
? h(NSpace, {}, () => [
|
||||||
data.options?.needJianzhang ? h(NTag, { color: { textColor: 'white', color: GetGuardColor(3), borderColor: 'white' }, size: 'small' }, () => '舰长') : null,
|
data.options?.needJianzhang
|
||||||
data.options?.needTidu ? h(NTag, { color: { textColor: 'white', color: GetGuardColor(2), borderColor: 'white' }, size: 'small' }, () => '提督') : null,
|
? h(
|
||||||
data.options?.needZongdu ? h(NTag, { color: { textColor: 'white', color: GetGuardColor(1), borderColor: 'white' }, size: 'small' }, () => '总督') : null,
|
NTag,
|
||||||
data.options?.scMinPrice
|
{ color: { textColor: 'white', color: GetGuardColor(3), borderColor: 'white' }, size: 'small' },
|
||||||
? h(NTag, { color: { textColor: 'white', color: GetSCColor(data.options.scMinPrice), borderColor: 'white' }, size: 'small' }, () => 'SC | ' + data.options?.scMinPrice)
|
() => '舰长',
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
data.options?.needTidu
|
||||||
|
? h(
|
||||||
|
NTag,
|
||||||
|
{ color: { textColor: 'white', color: GetGuardColor(2), borderColor: 'white' }, size: 'small' },
|
||||||
|
() => '提督',
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
data.options?.needZongdu
|
||||||
|
? h(
|
||||||
|
NTag,
|
||||||
|
{ color: { textColor: 'white', color: GetGuardColor(1), borderColor: 'white' }, size: 'small' },
|
||||||
|
() => '总督',
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
data.options?.scMinPrice
|
||||||
|
? h(
|
||||||
|
NTag,
|
||||||
|
{
|
||||||
|
color: { textColor: 'white', color: GetSCColor(data.options.scMinPrice), borderColor: 'white' },
|
||||||
|
size: 'small',
|
||||||
|
},
|
||||||
|
() => 'SC | ' + data.options?.scMinPrice,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
data.options?.fanMedalMinLevel
|
||||||
|
? h(NTag, { type: 'info', size: 'small' }, () => '粉丝牌 | ' + data.options?.fanMedalMinLevel)
|
||||||
: null,
|
: null,
|
||||||
data.options?.fanMedalMinLevel ? h(NTag, { type: 'info', size: 'small' }, () => '粉丝牌 | ' + data.options?.fanMedalMinLevel) : null,
|
|
||||||
])
|
])
|
||||||
: null
|
: null
|
||||||
},
|
},
|
||||||
@@ -234,7 +278,9 @@ function createColumns(): DataTableColumns<SongsInfo> {
|
|||||||
},
|
},
|
||||||
filterOptions: tagsSelectOption.value,
|
filterOptions: tagsSelectOption.value,
|
||||||
render(data) {
|
render(data) {
|
||||||
return (data.tags?.length ?? 0) > 0 ? h(NSpace, { size: 5 }, () => data.tags?.map((a) => h(NTag, { bordered: false, size: 'small' }, () => a))) : null
|
return (data.tags?.length ?? 0) > 0
|
||||||
|
? h(NSpace, { size: 5 }, () => data.tags?.map((a) => h(NTag, { bordered: false, size: 'small' }, () => a)))
|
||||||
|
: null
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -251,7 +297,11 @@ function createColumns(): DataTableColumns<SongsInfo> {
|
|||||||
},
|
},
|
||||||
() => [
|
() => [
|
||||||
GetPlayButton(data),
|
GetPlayButton(data),
|
||||||
data.url?.endsWith('mp3') || data.url?.endsWith('flac') || data.url?.endsWith('ogg') || data.url?.endsWith('wav') || data.url?.endsWith('m4a')
|
data.url?.endsWith('mp3') ||
|
||||||
|
data.url?.endsWith('flac') ||
|
||||||
|
data.url?.endsWith('ogg') ||
|
||||||
|
data.url?.endsWith('wav') ||
|
||||||
|
data.url?.endsWith('m4a')
|
||||||
? h(NTooltip, null, {
|
? h(NTooltip, null, {
|
||||||
trigger: () =>
|
trigger: () =>
|
||||||
h(
|
h(
|
||||||
@@ -571,8 +621,23 @@ onMounted(() => {
|
|||||||
<NCard embedded size="small">
|
<NCard embedded size="small">
|
||||||
<NSpace>
|
<NSpace>
|
||||||
<NInput placeholder="搜索歌曲" v-model:value="searchMusicKeyword" size="small" style="width: 150px" />
|
<NInput placeholder="搜索歌曲" v-model:value="searchMusicKeyword" size="small" style="width: 150px" />
|
||||||
<NSelect placeholder="选择歌手" v-model:value="authorColumn.filterOptionValue" :options="authorsOptions" clearable filterable size="small" style="width: 150px" />
|
<NSelect
|
||||||
<NButton v-if="authorColumn.filterOptionValue" type="error" @click="authorColumn.filterOptionValue = null" size="small"> 清除歌手选择 </NButton>
|
placeholder="选择歌手"
|
||||||
|
v-model:value="authorColumn.filterOptionValue"
|
||||||
|
:options="authorsOptions"
|
||||||
|
clearable
|
||||||
|
filterable
|
||||||
|
size="small"
|
||||||
|
style="width: 150px"
|
||||||
|
/>
|
||||||
|
<NButton
|
||||||
|
v-if="authorColumn.filterOptionValue"
|
||||||
|
type="error"
|
||||||
|
@click="authorColumn.filterOptionValue = null"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
清除歌手选择
|
||||||
|
</NButton>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
</NCard>
|
</NCard>
|
||||||
<NDivider style="margin: 5px 0 5px 0"> 共 {{ songsComputed.length }} 首 </NDivider>
|
<NDivider style="margin: 5px 0 5px 0"> 共 {{ songsComputed.length }} 首 </NDivider>
|
||||||
@@ -582,14 +647,22 @@ onMounted(() => {
|
|||||||
<NDivider style="margin: 15px 0 15px 0" />
|
<NDivider style="margin: 15px 0 15px 0" />
|
||||||
</div>
|
</div>
|
||||||
</Transition>
|
</Transition>
|
||||||
<NButton :disabled="selectedColumn.length <= 1 && isSelf" type="info" @click="showBatchModal = true" size="small"> 批量编辑 </NButton>
|
<NButton :disabled="selectedColumn.length <= 1 && isSelf" type="info" @click="showBatchModal = true" size="small">
|
||||||
|
批量编辑
|
||||||
|
</NButton>
|
||||||
<NDivider style="margin: 5px 0 5px 0" />
|
<NDivider style="margin: 5px 0 5px 0" />
|
||||||
<NDataTable
|
<NDataTable
|
||||||
v-model:checked-row-keys="selectedColumn"
|
v-model:checked-row-keys="selectedColumn"
|
||||||
size="small"
|
size="small"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:data="songsComputed"
|
:data="songsComputed"
|
||||||
:pagination="{ itemCount: songsInternal.length, defaultPageSize: 25, pageSizes: [25, 50, 200], size: 'small', showSizePicker: true }"
|
:pagination="{
|
||||||
|
itemCount: songsInternal.length,
|
||||||
|
defaultPageSize: 25,
|
||||||
|
pageSizes: [25, 50, 200],
|
||||||
|
size: 'small',
|
||||||
|
showSizePicker: true,
|
||||||
|
}"
|
||||||
/>
|
/>
|
||||||
<NModal v-model:show="showModal" style="max-width: 600px" preset="card">
|
<NModal v-model:show="showModal" style="max-width: 600px" preset="card">
|
||||||
<template #header> 修改信息 </template>
|
<template #header> 修改信息 </template>
|
||||||
@@ -598,16 +671,39 @@ onMounted(() => {
|
|||||||
<NInput v-model:value="updateSongModel.name" autosize style="min-width: 200px" placeholder="就是歌曲名称" />
|
<NInput v-model:value="updateSongModel.name" autosize style="min-width: 200px" placeholder="就是歌曲名称" />
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
<NFormItem path="author" label="作者">
|
<NFormItem path="author" label="作者">
|
||||||
<NSelect v-model:value="updateSongModel.author" filterable multiple tag placeholder="输入,按回车确认" :options="authorsOptions" />
|
<NSelect
|
||||||
|
v-model:value="updateSongModel.author"
|
||||||
|
filterable
|
||||||
|
multiple
|
||||||
|
tag
|
||||||
|
placeholder="输入,按回车确认"
|
||||||
|
:options="authorsOptions"
|
||||||
|
/>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
<NFormItem path="description" label="备注">
|
<NFormItem path="description" label="备注">
|
||||||
<NInput v-model:value="updateSongModel.description" placeholder="可选" :maxlength="250" show-count autosize style="min-width: 300px" clearable />
|
<NInput
|
||||||
|
v-model:value="updateSongModel.description"
|
||||||
|
placeholder="可选"
|
||||||
|
:maxlength="250"
|
||||||
|
show-count
|
||||||
|
autosize
|
||||||
|
style="min-width: 300px"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
<NFormItem path="language" label="语言">
|
<NFormItem path="language" label="语言">
|
||||||
<NSelect v-model:value="updateSongModel.language" multiple :options="songSelectOption" placeholder="可选" />
|
<NSelect v-model:value="updateSongModel.language" multiple :options="songSelectOption" placeholder="可选" />
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
<NFormItem path="tags" label="标签">
|
<NFormItem path="tags" label="标签">
|
||||||
<NSelect v-model:value="updateSongModel.tags" filterable multiple clearable tag placeholder="可选,按回车确认" :options="tagsSelectOption" />
|
<NSelect
|
||||||
|
v-model:value="updateSongModel.tags"
|
||||||
|
filterable
|
||||||
|
multiple
|
||||||
|
clearable
|
||||||
|
tag
|
||||||
|
placeholder="可选,按回车确认"
|
||||||
|
:options="tagsSelectOption"
|
||||||
|
/>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
<NFormItem path="options">
|
<NFormItem path="options">
|
||||||
<template #label>
|
<template #label>
|
||||||
@@ -684,21 +780,45 @@ onMounted(() => {
|
|||||||
</NSpace>
|
</NSpace>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
<NFormItem path="url" label="链接">
|
<NFormItem path="url" label="链接">
|
||||||
<NInput v-model:value="updateSongModel.url" placeholder="可选, 后缀为mp3、wav、ogg时将会尝试播放, 否则会在新页面打开" :disabled="updateSongModel.from != SongFrom.Custom" />
|
<NInput
|
||||||
|
v-model:value="updateSongModel.url"
|
||||||
|
placeholder="可选, 后缀为mp3、wav、ogg时将会尝试播放, 否则会在新页面打开"
|
||||||
|
:disabled="updateSongModel.from != SongFrom.Custom"
|
||||||
|
/>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
</NForm>
|
</NForm>
|
||||||
<NDivider style="margin: 10px" />
|
<NDivider style="margin: 10px" />
|
||||||
<NButton @click="updateSong" type="success"> 更新 </NButton>
|
<NButton @click="updateSong" type="success"> 更新 </NButton>
|
||||||
</NModal>
|
</NModal>
|
||||||
<NModal v-model:show="showBatchModal" preset="card" :title="`批量编辑 | 已选择: ${selectedColumn.length}`" style="max-width: 600px">
|
<NModal
|
||||||
|
v-model:show="showBatchModal"
|
||||||
|
preset="card"
|
||||||
|
:title="`批量编辑 | 已选择: ${selectedColumn.length}`"
|
||||||
|
style="max-width: 600px"
|
||||||
|
>
|
||||||
<NTabs>
|
<NTabs>
|
||||||
<NTabPane name="author" tab="作者">
|
<NTabPane name="author" tab="作者">
|
||||||
<NSelect v-model:value="batchUpdate_Author" filterable multiple tag placeholder="输入后按回车新增" :options="authorsOptions" />
|
<NSelect
|
||||||
|
v-model:value="batchUpdate_Author"
|
||||||
|
filterable
|
||||||
|
multiple
|
||||||
|
tag
|
||||||
|
placeholder="输入后按回车新增"
|
||||||
|
:options="authorsOptions"
|
||||||
|
/>
|
||||||
<NDivider />
|
<NDivider />
|
||||||
<NButton @click="batchUpdateAuthor" type="success"> 更新 </NButton>
|
<NButton @click="batchUpdateAuthor" type="success"> 更新 </NButton>
|
||||||
</NTabPane>
|
</NTabPane>
|
||||||
<NTabPane name="tag" tab="标签">
|
<NTabPane name="tag" tab="标签">
|
||||||
<NSelect v-model:value="batchUpdate_Tag" filterable multiple clearable tag placeholder="可选,按回车确认" :options="tagsSelectOption" />
|
<NSelect
|
||||||
|
v-model:value="batchUpdate_Tag"
|
||||||
|
filterable
|
||||||
|
multiple
|
||||||
|
clearable
|
||||||
|
tag
|
||||||
|
placeholder="可选,按回车确认"
|
||||||
|
:options="tagsSelectOption"
|
||||||
|
/>
|
||||||
<NDivider />
|
<NDivider />
|
||||||
<NButton @click="batchUpdateTag" type="success"> 更新 </NButton>
|
<NButton @click="batchUpdateTag" type="success"> 更新 </NButton>
|
||||||
</NTabPane>
|
</NTabPane>
|
||||||
|
|||||||
@@ -2,7 +2,19 @@
|
|||||||
import { VideoCollectTable } from '@/api/api-models'
|
import { VideoCollectTable } from '@/api/api-models'
|
||||||
import router from '@/router'
|
import router from '@/router'
|
||||||
import { Clock24Regular, NumberRow24Regular } from '@vicons/fluent'
|
import { Clock24Regular, NumberRow24Regular } from '@vicons/fluent'
|
||||||
import { CountdownProps, NCard, NCountdown, NDivider, NEllipsis, NIcon, NSpace, NTag, NText, NTime, NTooltip } from 'naive-ui'
|
import {
|
||||||
|
CountdownProps,
|
||||||
|
NCard,
|
||||||
|
NCountdown,
|
||||||
|
NDivider,
|
||||||
|
NEllipsis,
|
||||||
|
NIcon,
|
||||||
|
NSpace,
|
||||||
|
NTag,
|
||||||
|
NText,
|
||||||
|
NTime,
|
||||||
|
NTooltip,
|
||||||
|
} from 'naive-ui'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
item: VideoCollectTable
|
item: VideoCollectTable
|
||||||
@@ -34,7 +46,7 @@ function onClick() {
|
|||||||
<NText depth="3" style="font-size: 13px">
|
<NText depth="3" style="font-size: 13px">
|
||||||
<NTime :time="item.createAt" />
|
<NTime :time="item.createAt" />
|
||||||
</NText>
|
</NText>
|
||||||
<br/>
|
<br />
|
||||||
<NText depth="3" style="font-size: 13px">
|
<NText depth="3" style="font-size: 13px">
|
||||||
结束:
|
结束:
|
||||||
<NTime :time="item.endAt" />
|
<NTime :time="item.endAt" />
|
||||||
@@ -62,7 +74,9 @@ function onClick() {
|
|||||||
<NIcon :component="Clock24Regular" />
|
<NIcon :component="Clock24Regular" />
|
||||||
<NTooltip>
|
<NTooltip>
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<NText depth="3"> 剩余 <NCountdown :duration="item.endAt - Date.now()" :render="renderCountdown" /> </NText>
|
<NText depth="3">
|
||||||
|
剩余 <NCountdown :duration="item.endAt - Date.now()" :render="renderCountdown" />
|
||||||
|
</NText>
|
||||||
</template>
|
</template>
|
||||||
结束于 <NTime :time="item.endAt" />
|
结束于 <NTime :time="item.endAt" />
|
||||||
</NTooltip>
|
</NTooltip>
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { GoodsTypes, ResponsePointGoodModel } from '@/api/api-models'
|
import { GoodsTypes, ResponsePointGoodModel } from '@/api/api-models'
|
||||||
import { NButton, NCard, NDropdown, NEllipsis, NEmpty, NFlex, NIcon, NImage, NPopselect, NTag, NText } from 'naive-ui'
|
|
||||||
import { FILE_BASE_URL, IMGUR_URL } from '@/data/constants'
|
import { FILE_BASE_URL, IMGUR_URL } from '@/data/constants'
|
||||||
import { computed, ref } from 'vue'
|
import { NCard, NEllipsis, NEmpty, NFlex, NImage, NTag, NText } from 'naive-ui'
|
||||||
import { MoreHorizontal16Filled, MoreVertical16Filled } from '@vicons/fluent'
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
goods: ResponsePointGoodModel | undefined
|
goods: ResponsePointGoodModel | undefined
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { EventDataTypes, PointFrom, ResponsePointGoodModel, ResponsePointHisrotyModel } from '@/api/api-models'
|
||||||
import {
|
import {
|
||||||
DataTableColumns,
|
DataTableColumns,
|
||||||
NButton,
|
NButton,
|
||||||
@@ -13,7 +14,6 @@ import {
|
|||||||
NTooltip,
|
NTooltip,
|
||||||
} from 'naive-ui'
|
} from 'naive-ui'
|
||||||
import { h, ref } from 'vue'
|
import { h, ref } from 'vue'
|
||||||
import { EventDataTypes, PointFrom, ResponsePointGoodModel, ResponsePointHisrotyModel } from '@/api/api-models'
|
|
||||||
import PointGoodsItem from './PointGoodsItem.vue'
|
import PointGoodsItem from './PointGoodsItem.vue'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {
|
import {
|
||||||
GoodsStatus,
|
|
||||||
GoodsTypes,
|
GoodsTypes,
|
||||||
PointOrderStatus,
|
PointOrderStatus,
|
||||||
ResponsePointGoodModel,
|
ResponsePointGoodModel,
|
||||||
ResponsePointOrder2OwnerModel,
|
ResponsePointOrder2OwnerModel,
|
||||||
ResponsePointOrder2UserModel,
|
ResponsePointOrder2UserModel,
|
||||||
} from '@/api/api-models'
|
} from '@/api/api-models'
|
||||||
|
import { QueryPostAPI } from '@/api/query'
|
||||||
|
import { POINT_API_URL } from '@/data/constants'
|
||||||
|
import { Info24Filled } from '@vicons/fluent'
|
||||||
import {
|
import {
|
||||||
DataTableColumns,
|
DataTableColumns,
|
||||||
DataTableRowKey,
|
DataTableRowKey,
|
||||||
NAutoComplete,
|
NAutoComplete,
|
||||||
NButton,
|
NButton,
|
||||||
NCard,
|
|
||||||
NDataTable,
|
NDataTable,
|
||||||
NDivider,
|
NDivider,
|
||||||
NFlex,
|
NFlex,
|
||||||
@@ -34,9 +35,6 @@ import {
|
|||||||
import { computed, h, onMounted, ref, watch } from 'vue'
|
import { computed, h, onMounted, ref, watch } from 'vue'
|
||||||
import AddressDisplay from './AddressDisplay.vue'
|
import AddressDisplay from './AddressDisplay.vue'
|
||||||
import PointGoodsItem from './PointGoodsItem.vue'
|
import PointGoodsItem from './PointGoodsItem.vue'
|
||||||
import { Info24Filled } from '@vicons/fluent'
|
|
||||||
import { QueryPostAPI } from '@/api/query'
|
|
||||||
import { POINT_API_URL } from '@/data/constants'
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
order: ResponsePointOrder2UserModel[] | ResponsePointOrder2OwnerModel[]
|
order: ResponsePointOrder2UserModel[] | ResponsePointOrder2OwnerModel[]
|
||||||
|
|||||||
@@ -128,8 +128,8 @@ export default class ChatClientDirectOpenLive extends ChatClientOfficialBase {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let ids = []
|
const ids = []
|
||||||
for (let id of command.data.message_ids) {
|
for (const id of command.data.message_ids) {
|
||||||
ids.push(id.toString())
|
ids.push(id.toString())
|
||||||
}
|
}
|
||||||
this.onDelSuperChat({ ids })
|
this.onDelSuperChat({ ids })
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -36,8 +36,8 @@ export const AUTH_REPLY_CODE_TOKEN_ERROR = -101
|
|||||||
const HEARTBEAT_INTERVAL = 10 * 1000
|
const HEARTBEAT_INTERVAL = 10 * 1000
|
||||||
const RECEIVE_TIMEOUT = HEARTBEAT_INTERVAL + 5 * 1000
|
const RECEIVE_TIMEOUT = HEARTBEAT_INTERVAL + 5 * 1000
|
||||||
|
|
||||||
let textEncoder = new TextEncoder()
|
const textEncoder = new TextEncoder()
|
||||||
let textDecoder = new TextDecoder()
|
const textDecoder = new TextDecoder()
|
||||||
|
|
||||||
export default class ChatClientOfficialBase {
|
export default class ChatClientOfficialBase {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -83,8 +83,8 @@ export default class ChatClientOfficialBase {
|
|||||||
// string
|
// string
|
||||||
body = textEncoder.encode(data)
|
body = textEncoder.encode(data)
|
||||||
}
|
}
|
||||||
let header = new ArrayBuffer(HEADER_SIZE)
|
const header = new ArrayBuffer(HEADER_SIZE)
|
||||||
let headerView = new DataView(header)
|
const headerView = new DataView(header)
|
||||||
headerView.setUint32(0, HEADER_SIZE + body.byteLength) // pack_len
|
headerView.setUint32(0, HEADER_SIZE + body.byteLength) // pack_len
|
||||||
headerView.setUint16(4, HEADER_SIZE) // raw_header_size
|
headerView.setUint16(4, HEADER_SIZE) // raw_header_size
|
||||||
headerView.setUint16(6, 1) // ver
|
headerView.setUint16(6, 1) // ver
|
||||||
@@ -201,7 +201,7 @@ export default class ChatClientOfficialBase {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let data = new Uint8Array(event.data)
|
const data = new Uint8Array(event.data)
|
||||||
this.parseWsMessage(data)
|
this.parseWsMessage(data)
|
||||||
|
|
||||||
// 至少成功处理1条消息
|
// 至少成功处理1条消息
|
||||||
@@ -214,7 +214,7 @@ export default class ChatClientOfficialBase {
|
|||||||
let packLen = dataView.getUint32(0)
|
let packLen = dataView.getUint32(0)
|
||||||
let rawHeaderSize = dataView.getUint16(4)
|
let rawHeaderSize = dataView.getUint16(4)
|
||||||
// let ver = dataView.getUint16(6)
|
// let ver = dataView.getUint16(6)
|
||||||
let operation = dataView.getUint32(8)
|
const operation = dataView.getUint32(8)
|
||||||
// let seqId = dataView.getUint32(12)
|
// let seqId = dataView.getUint32(12)
|
||||||
|
|
||||||
switch (operation) {
|
switch (operation) {
|
||||||
@@ -223,7 +223,7 @@ export default class ChatClientOfficialBase {
|
|||||||
// 业务消息,可能有多个包一起发,需要分包
|
// 业务消息,可能有多个包一起发,需要分包
|
||||||
while (true) {
|
while (true) {
|
||||||
// eslint-disable-line no-constant-condition
|
// eslint-disable-line no-constant-condition
|
||||||
let body = new Uint8Array(data.buffer, offset + rawHeaderSize, packLen - rawHeaderSize)
|
const body = new Uint8Array(data.buffer, offset + rawHeaderSize, packLen - rawHeaderSize)
|
||||||
this.parseBusinessMessage(dataView, body)
|
this.parseBusinessMessage(dataView, body)
|
||||||
|
|
||||||
offset += packLen
|
offset += packLen
|
||||||
@@ -244,7 +244,7 @@ export default class ChatClientOfficialBase {
|
|||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
// 未知消息
|
// 未知消息
|
||||||
let body = new Uint8Array(data.buffer, offset + rawHeaderSize, packLen - rawHeaderSize)
|
const body = new Uint8Array(data.buffer, offset + rawHeaderSize, packLen - rawHeaderSize)
|
||||||
console.warn('未知包类型,operation=', operation, dataView, body)
|
console.warn('未知包类型,operation=', operation, dataView, body)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -252,8 +252,8 @@ export default class ChatClientOfficialBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
parseBusinessMessage(dataView, body) {
|
parseBusinessMessage(dataView, body) {
|
||||||
let ver = dataView.getUint16(6)
|
const ver = dataView.getUint16(6)
|
||||||
let operation = dataView.getUint32(8)
|
const operation = dataView.getUint32(8)
|
||||||
|
|
||||||
switch (operation) {
|
switch (operation) {
|
||||||
case OP_SEND_MSG_REPLY: {
|
case OP_SEND_MSG_REPLY: {
|
||||||
@@ -302,11 +302,11 @@ export default class ChatClientOfficialBase {
|
|||||||
|
|
||||||
handlerCommand(command) {
|
handlerCommand(command) {
|
||||||
let cmd = command.cmd || ''
|
let cmd = command.cmd || ''
|
||||||
let pos = cmd.indexOf(':')
|
const pos = cmd.indexOf(':')
|
||||||
if (pos != -1) {
|
if (pos != -1) {
|
||||||
cmd = cmd.substr(0, pos)
|
cmd = cmd.substr(0, pos)
|
||||||
}
|
}
|
||||||
let callback = this.CMD_CALLBACK_MAP[cmd]
|
const callback = this.CMD_CALLBACK_MAP[cmd]
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback.call(this, command)
|
callback.call(this, command)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import { defineStore } from 'pinia'
|
import { BiliAuthModel, ResponsePointGoodModel } from '@/api/api-models'
|
||||||
import { useMessage } from 'naive-ui'
|
|
||||||
import { computed, ref } from 'vue'
|
|
||||||
import { AddressInfo, BiliAuthModel, ResponsePointGoodModel } from '@/api/api-models'
|
|
||||||
import { QueryGetAPI, QueryPostAPI } from '@/api/query'
|
import { QueryGetAPI, QueryPostAPI } from '@/api/query'
|
||||||
import { useStorage } from '@vueuse/core'
|
|
||||||
import { BILI_AUTH_API_URL, POINT_API_URL } from '@/data/constants'
|
import { BILI_AUTH_API_URL, POINT_API_URL } from '@/data/constants'
|
||||||
|
import { useStorage } from '@vueuse/core'
|
||||||
import { MessageApiInjection } from 'naive-ui/es/message/src/MessageProvider'
|
import { MessageApiInjection } from 'naive-ui/es/message/src/MessageProvider'
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
export const useAuthStore = defineStore('BiliAuth', () => {
|
export const useAuthStore = defineStore('BiliAuth', () => {
|
||||||
const biliAuth = ref<BiliAuthModel>({} as BiliAuthModel)
|
const biliAuth = ref<BiliAuthModel>({} as BiliAuthModel)
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export const useQuestionBox = defineStore('QuestionBox', () => {
|
|||||||
const displayQuestion = ref<QAInfo>()
|
const displayQuestion = ref<QAInfo>()
|
||||||
|
|
||||||
let isRevieveGetted = false
|
let isRevieveGetted = false
|
||||||
let isSendGetted = false
|
//const isSendGetted = false
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
|
|
||||||
@@ -83,7 +83,7 @@ export const useQuestionBox = defineStore('QuestionBox', () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
message.error('发生错误')
|
message.error('发生错误: ' + err)
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
@@ -97,7 +97,7 @@ export const useQuestionBox = defineStore('QuestionBox', () => {
|
|||||||
})
|
})
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (data.code == 200) {
|
if (data.code == 200) {
|
||||||
var index = recieveQuestions.value.findIndex((q) => q.id == id)
|
const index = recieveQuestions.value.findIndex((q) => q.id == id)
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
recieveQuestions.value[index] = data.data
|
recieveQuestions.value[index] = data.data
|
||||||
}
|
}
|
||||||
@@ -109,7 +109,7 @@ export const useQuestionBox = defineStore('QuestionBox', () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
message.error('发送失败')
|
message.error('发送失败: ' + err)
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
isRepling.value = false
|
isRepling.value = false
|
||||||
@@ -131,7 +131,7 @@ export const useQuestionBox = defineStore('QuestionBox', () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
message.error('修改失败')
|
message.error('修改失败: ' + err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
async function favorite(question: QAInfo, fav: boolean) {
|
async function favorite(question: QAInfo, fav: boolean) {
|
||||||
@@ -147,7 +147,7 @@ export const useQuestionBox = defineStore('QuestionBox', () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
message.error('修改失败')
|
message.error('修改失败: ' + err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
async function setPublic(pub: boolean) {
|
async function setPublic(pub: boolean) {
|
||||||
@@ -165,7 +165,7 @@ export const useQuestionBox = defineStore('QuestionBox', () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
message.error('修改失败')
|
message.error('修改失败: ' + err)
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
isChangingPublic.value = false
|
isChangingPublic.value = false
|
||||||
@@ -191,7 +191,7 @@ export const useQuestionBox = defineStore('QuestionBox', () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
message.error('拉黑失败')
|
message.error('拉黑失败: ' + err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
async function setCurrentQuestion(item: QAInfo) {
|
async function setCurrentQuestion(item: QAInfo) {
|
||||||
|
|||||||
@@ -27,14 +27,24 @@ import { NButton, NCard, NDivider, NLayoutContent, NSpace, NText, NTimeline, NTi
|
|||||||
</NText>
|
</NText>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<NSpace vertical align="center">
|
<NSpace vertical align="center">
|
||||||
<span style="color: gray"> MADE WITH ❤️ BY <NButton type="primary" tag="a" href="https://space.bilibili.com/10021741" target="_blank" text style=""> Megghy </NButton> </span>
|
<span style="color: gray">
|
||||||
|
MADE WITH ❤️ BY
|
||||||
|
<NButton type="primary" tag="a" href="https://space.bilibili.com/10021741" target="_blank" text style="">
|
||||||
|
Megghy
|
||||||
|
</NButton>
|
||||||
|
</span>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
<NDivider title-placement="left"> 更新日志 </NDivider>
|
<NDivider title-placement="left"> 更新日志 </NDivider>
|
||||||
<NTimeline>
|
<NTimeline>
|
||||||
<NTimelineItem type="info" title="功能更新" content="棉花糖添加展示页面" time="2024-2-20" />
|
<NTimelineItem type="info" title="功能更新" content="棉花糖添加展示页面" time="2024-2-20" />
|
||||||
<NTimelineItem type="info" title="功能更新" content="歌单新增从文件导入" time="2024-2-10" />
|
<NTimelineItem type="info" title="功能更新" content="歌单新增从文件导入" time="2024-2-10" />
|
||||||
<NTimelineItem type="info" title="功能更新" content="排队的OBS组件添加设置项" time="2024-1-27" />
|
<NTimelineItem type="info" title="功能更新" content="排队的OBS组件添加设置项" time="2024-1-27" />
|
||||||
<NTimelineItem type="warning" title="Bug修复" content="修复点歌会直接跳到下一首的问题 (怎么没人跟我说" time="2024-1-22" />
|
<NTimelineItem
|
||||||
|
type="warning"
|
||||||
|
title="Bug修复"
|
||||||
|
content="修复点歌会直接跳到下一首的问题 (怎么没人跟我说"
|
||||||
|
time="2024-1-22"
|
||||||
|
/>
|
||||||
<NTimelineItem type="info" title="功能更新" content="读弹幕支持自定义API" time="2023-12-25" />
|
<NTimelineItem type="info" title="功能更新" content="读弹幕支持自定义API" time="2023-12-25" />
|
||||||
<NTimelineItem type="success" title="功能添加" content="弹幕点歌 (点播)" time="2023-12-24" />
|
<NTimelineItem type="success" title="功能添加" content="弹幕点歌 (点播)" time="2023-12-24" />
|
||||||
<NTimelineItem type="success" title="功能添加" content="读弹幕" time="2023-12-17" />
|
<NTimelineItem type="success" title="功能添加" content="读弹幕" time="2023-12-17" />
|
||||||
@@ -47,7 +57,13 @@ import { NButton, NCard, NDivider, NLayoutContent, NSpace, NText, NTimeline, NTi
|
|||||||
<NTimelineItem type="info" title="功能更新" content="日程表添加 '粉粉' 模板" time="2023-10-27" />
|
<NTimelineItem type="info" title="功能更新" content="日程表添加 '粉粉' 模板" time="2023-10-27" />
|
||||||
<NTimelineItem type="info" title="功能更新" content="提问箱新增公开选项" time="2023-10-26" />
|
<NTimelineItem type="info" title="功能更新" content="提问箱新增公开选项" time="2023-10-26" />
|
||||||
<NTimelineItem type="success" title="功能添加" content="提问箱分享卡片" time="2023-10-25" />
|
<NTimelineItem type="success" title="功能添加" content="提问箱分享卡片" time="2023-10-25" />
|
||||||
<NTimelineItem type="success" title="功能添加" content="舰长及SC记录" time="2023-10-24" line-type="dashed" />
|
<NTimelineItem
|
||||||
|
type="success"
|
||||||
|
title="功能添加"
|
||||||
|
content="舰长及SC记录"
|
||||||
|
time="2023-10-24"
|
||||||
|
line-type="dashed"
|
||||||
|
/>
|
||||||
<NTimelineItem type="info" content="开始运行" time="2023-10-23" />
|
<NTimelineItem type="info" content="开始运行" time="2023-10-23" />
|
||||||
</NTimeline>
|
</NTimeline>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -5,7 +5,20 @@ import { QueryGetAPI, QueryPostAPI } from '@/api/query'
|
|||||||
import FeedbackItem from '@/components/FeedbackItem.vue'
|
import FeedbackItem from '@/components/FeedbackItem.vue'
|
||||||
import { FEEDBACK_API_URL } from '@/data/constants'
|
import { FEEDBACK_API_URL } from '@/data/constants'
|
||||||
import { List } from 'linqts'
|
import { List } from 'linqts'
|
||||||
import { NButton, NCard, NCheckbox, NDivider, NEllipsis, NEmpty, NInput, NModal, NRadioButton, NRadioGroup, NSpace, NSpin, NTag, NText, NTime, NTooltip, useMessage } from 'naive-ui'
|
import {
|
||||||
|
NButton,
|
||||||
|
NCheckbox,
|
||||||
|
NDivider,
|
||||||
|
NEmpty,
|
||||||
|
NInput,
|
||||||
|
NModal,
|
||||||
|
NRadioButton,
|
||||||
|
NRadioGroup,
|
||||||
|
NSpace,
|
||||||
|
NText,
|
||||||
|
NTooltip,
|
||||||
|
useMessage,
|
||||||
|
} from 'naive-ui'
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
interface FeedbackModel {
|
interface FeedbackModel {
|
||||||
@@ -28,6 +41,7 @@ const order = {
|
|||||||
[FeedbackStatus.Developing]: 6,
|
[FeedbackStatus.Developing]: 6,
|
||||||
}
|
}
|
||||||
const selectedFeedback = computed(() => {
|
const selectedFeedback = computed(() => {
|
||||||
|
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
|
||||||
return feedbacks.value.sort((a, b) => {
|
return feedbacks.value.sort((a, b) => {
|
||||||
if (orderType.value == 'time') {
|
if (orderType.value == 'time') {
|
||||||
return b.createAt - a.createAt
|
return b.createAt - a.createAt
|
||||||
@@ -102,43 +116,77 @@ async function add() {
|
|||||||
</NDivider>
|
</NDivider>
|
||||||
<NEmpty v-if="feedbacks.length == 0" description="暂无反馈" />
|
<NEmpty v-if="feedbacks.length == 0" description="暂无反馈" />
|
||||||
<NSpace v-else-if="orderType == 'time'">
|
<NSpace v-else-if="orderType == 'time'">
|
||||||
<FeedbackItem v-for="item in selectedFeedback" :item="item" />
|
<FeedbackItem v-for="item in selectedFeedback" :item="item" :key="item.createAt" />
|
||||||
</NSpace>
|
</NSpace>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<NDivider> 开发计划 </NDivider>
|
<NDivider> 开发计划 </NDivider>
|
||||||
<NEmpty v-if="selectedFeedback.filter((f) => f.status == FeedbackStatus.Developing).length == 0" description="无" />
|
<NEmpty v-if="selectedFeedback.filter((f) => f.status == FeedbackStatus.Developing).length == 0" description="无" />
|
||||||
<NSpace v-else>
|
<NSpace v-else>
|
||||||
<FeedbackItem v-for="item in selectedFeedback.filter((f) => f.status == FeedbackStatus.Developing)" :item="item" />
|
<FeedbackItem
|
||||||
|
v-for="item in selectedFeedback.filter((f) => f.status == FeedbackStatus.Developing)"
|
||||||
|
:item="item"
|
||||||
|
:key="item.createAt"
|
||||||
|
/>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
<NDivider> 处理中 </NDivider>
|
<NDivider> 处理中 </NDivider>
|
||||||
<NEmpty v-if="selectedFeedback.filter((f) => f.status == FeedbackStatus.Progressing).length == 0" description="无" />
|
<NEmpty
|
||||||
|
v-if="selectedFeedback.filter((f) => f.status == FeedbackStatus.Progressing).length == 0"
|
||||||
|
description="无"
|
||||||
|
/>
|
||||||
<NSpace v-else>
|
<NSpace v-else>
|
||||||
<FeedbackItem v-for="item in selectedFeedback.filter((f) => f.status == FeedbackStatus.Progressing)" :item="item" />
|
<FeedbackItem
|
||||||
|
v-for="item in selectedFeedback.filter((f) => f.status == FeedbackStatus.Progressing)"
|
||||||
|
:item="item"
|
||||||
|
:key="item.createAt"
|
||||||
|
/>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
<NDivider> 等待回复 </NDivider>
|
<NDivider> 等待回复 </NDivider>
|
||||||
<NEmpty v-if="selectedFeedback.filter((f) => f.status == FeedbackStatus.Padding).length == 0" description="无" />
|
<NEmpty v-if="selectedFeedback.filter((f) => f.status == FeedbackStatus.Padding).length == 0" description="无" />
|
||||||
<NSpace v-else>
|
<NSpace v-else>
|
||||||
<FeedbackItem v-for="item in selectedFeedback.filter((f) => f.status == FeedbackStatus.Padding)" :item="item" />
|
<FeedbackItem
|
||||||
|
v-for="item in selectedFeedback.filter((f) => f.status == FeedbackStatus.Padding)"
|
||||||
|
:item="item"
|
||||||
|
:key="item.createAt"
|
||||||
|
/>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
<NDivider> 计划中 </NDivider>
|
<NDivider> 计划中 </NDivider>
|
||||||
<NEmpty v-if="selectedFeedback.filter((f) => f.status == FeedbackStatus.Todo).length == 0" description="无" />
|
<NEmpty v-if="selectedFeedback.filter((f) => f.status == FeedbackStatus.Todo).length == 0" description="无" />
|
||||||
<NSpace v-else>
|
<NSpace v-else>
|
||||||
<FeedbackItem v-for="item in selectedFeedback.filter((f) => f.status == FeedbackStatus.Todo)" :item="item" />
|
<FeedbackItem
|
||||||
|
v-for="item in selectedFeedback.filter((f) => f.status == FeedbackStatus.Todo)"
|
||||||
|
:item="item"
|
||||||
|
:key="item.createAt"
|
||||||
|
/>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
<NDivider> 已完成 </NDivider>
|
<NDivider> 已完成 </NDivider>
|
||||||
<NEmpty v-if="selectedFeedback.filter((f) => f.status == FeedbackStatus.Finish).length == 0" description="无" />
|
<NEmpty v-if="selectedFeedback.filter((f) => f.status == FeedbackStatus.Finish).length == 0" description="无" />
|
||||||
<NSpace v-else>
|
<NSpace v-else>
|
||||||
<FeedbackItem v-for="item in selectedFeedback.filter((f) => f.status == FeedbackStatus.Finish)" :item="item" />
|
<FeedbackItem
|
||||||
|
v-for="item in selectedFeedback.filter((f) => f.status == FeedbackStatus.Finish)"
|
||||||
|
:item="item"
|
||||||
|
:key="item.createAt"
|
||||||
|
/>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
<NDivider> 搁置 </NDivider>
|
<NDivider> 搁置 </NDivider>
|
||||||
<NEmpty v-if="selectedFeedback.filter((f) => f.status == FeedbackStatus.Reject).length == 0" description="无" />
|
<NEmpty v-if="selectedFeedback.filter((f) => f.status == FeedbackStatus.Reject).length == 0" description="无" />
|
||||||
<NSpace v-else>
|
<NSpace v-else>
|
||||||
<FeedbackItem v-for="item in selectedFeedback.filter((f) => f.status == FeedbackStatus.Reject)" :item="item" />
|
<FeedbackItem
|
||||||
|
v-for="item in selectedFeedback.filter((f) => f.status == FeedbackStatus.Reject)"
|
||||||
|
:item="item"
|
||||||
|
:key="item.createAt"
|
||||||
|
/>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
</template>
|
</template>
|
||||||
<NModal v-model:show="showAddModal" preset="card" title="添加反馈" style="width: 600px; max-width: 90vw">
|
<NModal v-model:show="showAddModal" preset="card" title="添加反馈" style="width: 600px; max-width: 90vw">
|
||||||
<NSpace vertical>
|
<NSpace vertical>
|
||||||
<NInput v-model:value="newFeedback.message" type="textarea" placeholder="请输入反馈内容" clearable show-count maxlength="1000" />
|
<NInput
|
||||||
|
v-model:value="newFeedback.message"
|
||||||
|
type="textarea"
|
||||||
|
placeholder="请输入反馈内容"
|
||||||
|
clearable
|
||||||
|
show-count
|
||||||
|
maxlength="1000"
|
||||||
|
/>
|
||||||
<NRadioGroup v-model:value="newFeedback.type" name="radiogroup">
|
<NRadioGroup v-model:value="newFeedback.type" name="radiogroup">
|
||||||
<NRadioButton :value="FeedbackType.Opinion">建议 / 意见</NRadioButton>
|
<NRadioButton :value="FeedbackType.Opinion">建议 / 意见</NRadioButton>
|
||||||
<NRadioButton :value="FeedbackType.Bug">Bug</NRadioButton>
|
<NRadioButton :value="FeedbackType.Bug">Bug</NRadioButton>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { ThemeType } from '@/api/api-models'
|
|||||||
import { QueryGetAPI } from '@/api/query'
|
import { QueryGetAPI } from '@/api/query'
|
||||||
import RegisterAndLogin from '@/components/RegisterAndLogin.vue'
|
import RegisterAndLogin from '@/components/RegisterAndLogin.vue'
|
||||||
import { ACCOUNT_API_URL } from '@/data/constants'
|
import { ACCOUNT_API_URL } from '@/data/constants'
|
||||||
|
import { useAuthStore } from '@/store/useAuthStore'
|
||||||
import { useMusicRequestProvider } from '@/store/useMusicRequest'
|
import { useMusicRequestProvider } from '@/store/useMusicRequest'
|
||||||
import {
|
import {
|
||||||
BookCoins20Filled,
|
BookCoins20Filled,
|
||||||
@@ -49,7 +50,6 @@ import { computed, h, onMounted, ref, watch } from 'vue'
|
|||||||
import { RouterLink, useRoute } from 'vue-router'
|
import { RouterLink, useRoute } from 'vue-router'
|
||||||
import APlayer from 'vue3-aplayer'
|
import APlayer from 'vue3-aplayer'
|
||||||
import DanmakuLayout from './manage/DanmakuLayout.vue'
|
import DanmakuLayout from './manage/DanmakuLayout.vue'
|
||||||
import { useAuthStore } from '@/store/useAuthStore'
|
|
||||||
|
|
||||||
const accountInfo = useAccount()
|
const accountInfo = useAccount()
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
|
|||||||
@@ -4,7 +4,12 @@ import { NButton, NLayoutContent, NResult } from 'naive-ui'
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NLayoutContent tag="div" style="height: 100vh; position: relative">
|
<NLayoutContent tag="div" style="height: 100vh; position: relative">
|
||||||
<NResult status="404" title="404" description="页面不存在" style="position: absolute; top: 40%; left: 50%; transform: translate(-50%, -50%)">
|
<NResult
|
||||||
|
status="404"
|
||||||
|
title="404"
|
||||||
|
description="页面不存在"
|
||||||
|
style="position: absolute; top: 40%; left: 50%; transform: translate(-50%, -50%)"
|
||||||
|
>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<NButton type="primary" size="large" @click="$router.push({ name: 'index' })">返回首页</NButton>
|
<NButton type="primary" size="large" @click="$router.push({ name: 'index' })">返回首页</NButton>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -130,7 +130,15 @@ onUnmounted(() => {
|
|||||||
<NResult status="error" title="无效访问">
|
<NResult status="error" title="无效访问">
|
||||||
<template #footer>
|
<template #footer>
|
||||||
请前往
|
请前往
|
||||||
<NButton text type="primary" tag="a" href="https://play-live.bilibili.com/details/1698742711771" target="_blank"> 幻星平台 | VTsuru </NButton>
|
<NButton
|
||||||
|
text
|
||||||
|
type="primary"
|
||||||
|
tag="a"
|
||||||
|
href="https://play-live.bilibili.com/details/1698742711771"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
幻星平台 | VTsuru
|
||||||
|
</NButton>
|
||||||
并点击 获取 , 再点击 获取 H5 插件链接来获取可用链接
|
并点击 获取 , 再点击 获取 H5 插件链接来获取可用链接
|
||||||
<br />
|
<br />
|
||||||
或者直接在那个页面用也可以, 虽然并不推荐
|
或者直接在那个页面用也可以, 虽然并不推荐
|
||||||
@@ -142,8 +150,15 @@ onUnmounted(() => {
|
|||||||
<NPageHeader :subtitle="($route.meta.title as string) ?? ''">
|
<NPageHeader :subtitle="($route.meta.title as string) ?? ''">
|
||||||
<template #extra>
|
<template #extra>
|
||||||
<NSpace align="center">
|
<NSpace align="center">
|
||||||
<NTag :type="client?.roomAuthInfo.value ? 'success' : 'warning'"> {{ client?.roomAuthInfo.value ? `已连接 | ${client.roomAuthInfo.value?.anchor_info.uname}` : '未连接' }} </NTag>
|
<NTag :type="client?.roomAuthInfo.value ? 'success' : 'warning'">
|
||||||
<NSwitch :default-value="!isDarkMode()" @update:value="(value: string & number & boolean) => (themeType = value ? ThemeType.Light : ThemeType.Dark)">
|
{{ client?.roomAuthInfo.value ? `已连接 | ${client.roomAuthInfo.value?.anchor_info.uname}` : '未连接' }}
|
||||||
|
</NTag>
|
||||||
|
<NSwitch
|
||||||
|
: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>
|
||||||
@@ -155,13 +170,25 @@ onUnmounted(() => {
|
|||||||
</template>
|
</template>
|
||||||
<template #title>
|
<template #title>
|
||||||
<NButton text tag="a" @click="$router.push({ name: 'open-live-index', query: $route.query })">
|
<NButton text tag="a" @click="$router.push({ name: 'open-live-index', query: $route.query })">
|
||||||
<NText strong style="font-size: 1.4rem; text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); text-justify: auto"> VTSURU | 开放平台 </NText>
|
<NText strong style="font-size: 1.4rem; text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); text-justify: auto">
|
||||||
|
VTSURU | 开放平台
|
||||||
|
</NText>
|
||||||
</NButton>
|
</NButton>
|
||||||
</template>
|
</template>
|
||||||
</NPageHeader>
|
</NPageHeader>
|
||||||
</NLayoutHeader>
|
</NLayoutHeader>
|
||||||
<NLayout has-sider style="height: calc(100vh - 45px - 30px)">
|
<NLayout has-sider style="height: calc(100vh - 45px - 30px)">
|
||||||
<NLayoutSider bordered ref="sider" show-trigger default-collapsed collapse-mode="width" :collapsed-width="64" :width="180" :native-scrollbar="false" style="height: 100%">
|
<NLayoutSider
|
||||||
|
bordered
|
||||||
|
ref="sider"
|
||||||
|
show-trigger
|
||||||
|
default-collapsed
|
||||||
|
collapse-mode="width"
|
||||||
|
:collapsed-width="64"
|
||||||
|
:width="180"
|
||||||
|
:native-scrollbar="false"
|
||||||
|
style="height: 100%"
|
||||||
|
>
|
||||||
<Transition>
|
<Transition>
|
||||||
<div v-if="client?.roomAuthInfo" style="margin-top: 8px">
|
<div v-if="client?.roomAuthInfo" style="margin-top: 8px">
|
||||||
<NSpace vertical justify="center" align="center">
|
<NSpace vertical justify="center" align="center">
|
||||||
@@ -170,7 +197,9 @@ onUnmounted(() => {
|
|||||||
:img-props="{ referrerpolicy: 'no-referrer' }"
|
:img-props="{ referrerpolicy: 'no-referrer' }"
|
||||||
round
|
round
|
||||||
bordered
|
bordered
|
||||||
:style="{ boxShadow: isDarkMode() ? 'rgb(195 192 192 / 35%) 0px 0px 8px' : '0 2px 3px rgba(0, 0, 0, 0.1)' }"
|
:style="{
|
||||||
|
boxShadow: isDarkMode() ? 'rgb(195 192 192 / 35%) 0px 0px 8px' : '0 2px 3px rgba(0, 0, 0, 0.1)',
|
||||||
|
}"
|
||||||
/>
|
/>
|
||||||
<NEllipsis v-if="width > 100" style="max-width: 100%">
|
<NEllipsis v-if="width > 100" style="max-width: 100%">
|
||||||
<NText strong>
|
<NText strong>
|
||||||
@@ -180,7 +209,12 @@ onUnmounted(() => {
|
|||||||
</NSpace>
|
</NSpace>
|
||||||
</div>
|
</div>
|
||||||
</Transition>
|
</Transition>
|
||||||
<NMenu :default-value="$route.name?.toString()" :collapsed-width="64" :collapsed-icon-size="22" :options="menuOptions" />
|
<NMenu
|
||||||
|
:default-value="$route.name?.toString()"
|
||||||
|
:collapsed-width="64"
|
||||||
|
:collapsed-icon-size="22"
|
||||||
|
:options="menuOptions"
|
||||||
|
/>
|
||||||
<NSpace justify="center">
|
<NSpace justify="center">
|
||||||
<NText depth="3" v-if="width > 150">
|
<NText depth="3" v-if="width > 150">
|
||||||
有更多功能建议请
|
有更多功能建议请
|
||||||
|
|||||||
@@ -6,7 +6,24 @@ import { VIDEO_COLLECT_API_URL } from '@/data/constants'
|
|||||||
import { Clock24Regular, Person24Regular, Question24Regular } from '@vicons/fluent'
|
import { Clock24Regular, Person24Regular, Question24Regular } from '@vicons/fluent'
|
||||||
import { useElementSize } from '@vueuse/core'
|
import { useElementSize } from '@vueuse/core'
|
||||||
import { List } from 'linqts'
|
import { List } from 'linqts'
|
||||||
import { NAlert, NButton, NCard, NDivider, NEllipsis, NIcon, NImage, NLayoutContent, NList, NListItem, NProgress, NResult, NSpace, NText, NTooltip, useMessage } from 'naive-ui'
|
import {
|
||||||
|
NAlert,
|
||||||
|
NButton,
|
||||||
|
NCard,
|
||||||
|
NDivider,
|
||||||
|
NEllipsis,
|
||||||
|
NIcon,
|
||||||
|
NImage,
|
||||||
|
NLayoutContent,
|
||||||
|
NList,
|
||||||
|
NListItem,
|
||||||
|
NProgress,
|
||||||
|
NResult,
|
||||||
|
NSpace,
|
||||||
|
NText,
|
||||||
|
NTooltip,
|
||||||
|
useMessage,
|
||||||
|
} from 'naive-ui'
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
|
|
||||||
@@ -121,10 +138,22 @@ function formatSecondsToTime(seconds: number): string {
|
|||||||
<NListItem v-for="item in acceptVideos" v-bind:key="item.info.bvid">
|
<NListItem v-for="item in acceptVideos" v-bind:key="item.info.bvid">
|
||||||
<NCard size="small" :hoverable="!item.video.watched" :embedded="!item.video.watched">
|
<NCard size="small" :hoverable="!item.video.watched" :embedded="!item.video.watched">
|
||||||
<NSpace>
|
<NSpace>
|
||||||
<NImage :src="item.video.cover + '@100h'" lazy :img-props="{ referrerpolicy: 'no-referrer' }" height="75" @click="onClick(item.video)" preview-disabled style="cursor: pointer" />
|
<NImage
|
||||||
|
:src="item.video.cover + '@100h'"
|
||||||
|
lazy
|
||||||
|
:img-props="{ referrerpolicy: 'no-referrer' }"
|
||||||
|
height="75"
|
||||||
|
@click="onClick(item.video)"
|
||||||
|
preview-disabled
|
||||||
|
style="cursor: pointer"
|
||||||
|
/>
|
||||||
<NSpace vertical :size="5">
|
<NSpace vertical :size="5">
|
||||||
<NButton style="width: 100%; max-width: 100px" @click="onClick(item.video)" text>
|
<NButton style="width: 100%; max-width: 100px" @click="onClick(item.video)" text>
|
||||||
<NText :title="item.video.title" :delete="item.video.watched" :style="`color: ${item.video.watched ? '#a54e4e' : ''};width: ${width - 20}px;`">
|
<NText
|
||||||
|
:title="item.video.title"
|
||||||
|
:delete="item.video.watched"
|
||||||
|
:style="`color: ${item.video.watched ? '#a54e4e' : ''};width: ${width - 20}px;`"
|
||||||
|
>
|
||||||
{{ item.video.title }}
|
{{ item.video.title }}
|
||||||
</NText>
|
</NText>
|
||||||
</NButton>
|
</NButton>
|
||||||
|
|||||||
@@ -3,7 +3,19 @@ import { VideoCollectDetail, VideoCollectTable } 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 { TURNSTILE_KEY, VIDEO_COLLECT_API_URL } from '@/data/constants'
|
import { TURNSTILE_KEY, VIDEO_COLLECT_API_URL } from '@/data/constants'
|
||||||
import { NAlert, NButton, NCard, NDivider, NInput, NInputNumber, NLayoutContent, NResult, NSpace, NText, useMessage } from 'naive-ui'
|
import {
|
||||||
|
NAlert,
|
||||||
|
NButton,
|
||||||
|
NCard,
|
||||||
|
NDivider,
|
||||||
|
NInput,
|
||||||
|
NInputNumber,
|
||||||
|
NLayoutContent,
|
||||||
|
NResult,
|
||||||
|
NSpace,
|
||||||
|
NText,
|
||||||
|
useMessage,
|
||||||
|
} from 'naive-ui'
|
||||||
import { onUnmounted, ref } from 'vue'
|
import { onUnmounted, ref } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import VueTurnstile from 'vue-turnstile'
|
import VueTurnstile from 'vue-turnstile'
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { NLayoutContent } from 'naive-ui'
|
|||||||
import FeedbackManage from './FeedbackManage.vue'
|
import FeedbackManage from './FeedbackManage.vue'
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div style="margin: 16px;">
|
<div style="margin: 16px">
|
||||||
<FeedbackManage />
|
<FeedbackManage />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { useUser } from '@/api/user'
|
|||||||
import RegisterAndLogin from '@/components/RegisterAndLogin.vue'
|
import RegisterAndLogin from '@/components/RegisterAndLogin.vue'
|
||||||
import { FETCH_API } from '@/data/constants'
|
import { FETCH_API } from '@/data/constants'
|
||||||
import { useAuthStore } from '@/store/useAuthStore'
|
import { useAuthStore } from '@/store/useAuthStore'
|
||||||
import { BookCoins20Filled, CalendarClock24Filled, Wallet24Filled } from '@vicons/fluent'
|
import { BookCoins20Filled, CalendarClock24Filled } from '@vicons/fluent'
|
||||||
import { Chatbox, Home, Moon, MusicalNote, Sunny } from '@vicons/ionicons5'
|
import { Chatbox, Home, Moon, MusicalNote, Sunny } from '@vicons/ionicons5'
|
||||||
import { useElementSize, useStorage } from '@vueuse/core'
|
import { useElementSize, useStorage } from '@vueuse/core'
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -2,7 +2,19 @@
|
|||||||
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 } from '@/data/constants'
|
import { BILI_API_URL } from '@/data/constants'
|
||||||
import { NAlert, NButton, NCard, NCountdown, NInput, NInputGroup, NInputNumber, NSpace, NSpin, NText, useMessage } from 'naive-ui'
|
import {
|
||||||
|
NAlert,
|
||||||
|
NButton,
|
||||||
|
NCard,
|
||||||
|
NCountdown,
|
||||||
|
NInput,
|
||||||
|
NInputGroup,
|
||||||
|
NInputNumber,
|
||||||
|
NSpace,
|
||||||
|
NSpin,
|
||||||
|
NText,
|
||||||
|
useMessage,
|
||||||
|
} from 'naive-ui'
|
||||||
import { onMounted, ref } from 'vue'
|
import { onMounted, ref } from 'vue'
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
@@ -102,7 +114,9 @@ onMounted(async () => {
|
|||||||
<NInput :allow-input="() => false" v-model:value="accountInfo.biliVerifyCode" />
|
<NInput :allow-input="() => false" v-model:value="accountInfo.biliVerifyCode" />
|
||||||
<NButton @click="copyCode"> 复制认证码 </NButton>
|
<NButton @click="copyCode"> 复制认证码 </NButton>
|
||||||
</NInputGroup>
|
</NInputGroup>
|
||||||
<NButton v-if="roomId" type="primary" tag="a" :href="'https://live.bilibili.com/' + roomId" target="_blank"> 前往直播间 </NButton>
|
<NButton v-if="roomId" type="primary" tag="a" :href="'https://live.bilibili.com/' + roomId" target="_blank">
|
||||||
|
前往直播间
|
||||||
|
</NButton>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
|
|||||||
@@ -30,6 +30,11 @@ onUnmounted(() => {
|
|||||||
<NAlert v-if="accountInfo?.isBiliVerified != true" type="info"> 尚未进行Bilibili认证 </NAlert>
|
<NAlert v-if="accountInfo?.isBiliVerified != true" type="info"> 尚未进行Bilibili认证 </NAlert>
|
||||||
<NSpin v-else-if="isClientLoading" show />
|
<NSpin v-else-if="isClientLoading" show />
|
||||||
<KeepAlive v-else>
|
<KeepAlive v-else>
|
||||||
<component :is="component" :client="client" :room-info="client.roomAuthInfo?.value" :code="accountInfo?.biliAuthCode" />
|
<component
|
||||||
|
:is="component"
|
||||||
|
:client="client"
|
||||||
|
:room-info="client.roomAuthInfo?.value"
|
||||||
|
:code="accountInfo?.biliAuthCode"
|
||||||
|
/>
|
||||||
</KeepAlive>
|
</KeepAlive>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ import {
|
|||||||
NUl,
|
NUl,
|
||||||
useMessage,
|
useMessage,
|
||||||
} from 'naive-ui'
|
} from 'naive-ui'
|
||||||
import { computed, onMounted, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
enum EventType {
|
enum EventType {
|
||||||
Guard,
|
Guard,
|
||||||
@@ -57,7 +57,10 @@ const rangeShortcuts = {
|
|||||||
上个月: () => {
|
上个月: () => {
|
||||||
const cur = new Date()
|
const cur = new Date()
|
||||||
const lastMonth = new Date(cur.getFullYear(), cur.getMonth() - 1)
|
const lastMonth = new Date(cur.getFullYear(), cur.getMonth() - 1)
|
||||||
return [new Date(cur.getFullYear(), cur.getMonth() - 1, 1).getTime(), new Date(cur.getFullYear(), cur.getMonth(), 1).getTime()] as const
|
return [
|
||||||
|
new Date(cur.getFullYear(), cur.getMonth() - 1, 1).getTime(),
|
||||||
|
new Date(cur.getFullYear(), cur.getMonth(), 1).getTime(),
|
||||||
|
] as const
|
||||||
},
|
},
|
||||||
本月: () => {
|
本月: () => {
|
||||||
const cur = new Date()
|
const cur = new Date()
|
||||||
@@ -143,8 +146,9 @@ function exportData() {
|
|||||||
}
|
}
|
||||||
saveAs(
|
saveAs(
|
||||||
new Blob([text], { type: 'text/plain;charset=utf-8' }),
|
new Blob([text], { type: 'text/plain;charset=utf-8' }),
|
||||||
`${format(Date.now(), 'yyyy-MM-dd HH:mm:ss')}_${format(selectedDate.value[0], 'yyyy-MM-dd HH:mm:ss')}_${format(selectedDate.value[1], 'yyyy-MM-dd HH:mm:ss')}}_${accountInfo.value
|
`${format(Date.now(), 'yyyy-MM-dd HH:mm:ss')}_${format(selectedDate.value[0], 'yyyy-MM-dd HH:mm:ss')}_${format(selectedDate.value[1], 'yyyy-MM-dd HH:mm:ss')}}_${
|
||||||
?.id}_${accountInfo.value?.name}_${selectedType.value}.${exportType.value}`,
|
accountInfo.value?.id
|
||||||
|
}_${accountInfo.value?.name}_${selectedType.value}.${exportType.value}`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
function objectsToCSV(arr: any[]) {
|
function objectsToCSV(arr: any[]) {
|
||||||
@@ -163,12 +167,19 @@ function objectsToCSV(arr: any[]) {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NSpace vertical>
|
<NSpace vertical>
|
||||||
<NAlert v-if="!accountInfo?.isBiliVerified" type="warning"> 使用此功能前你需要先<NButton type="info" text @click="$router.push({ name: 'manage-biliVerify' })">认证Bilibili账号</NButton> </NAlert>
|
<NAlert v-if="!accountInfo?.isBiliVerified" type="warning">
|
||||||
|
使用此功能前你需要先<NButton type="info" text @click="$router.push({ name: 'manage-biliVerify' })"
|
||||||
|
>认证Bilibili账号</NButton
|
||||||
|
>
|
||||||
|
</NAlert>
|
||||||
<NAlert type="info">
|
<NAlert type="info">
|
||||||
当前本站正在测试为粉丝数大于 1000 或至少拥有一位舰长的主播直接从服务端记录并储存弹幕数据, 不过并不清楚B站的风控策略, 此功能不一定会长期启用
|
当前本站正在测试为粉丝数大于 1000 或至少拥有一位舰长的主播直接从服务端记录并储存弹幕数据,
|
||||||
|
不过并不清楚B站的风控策略, 此功能不一定会长期启用
|
||||||
<br />
|
<br />
|
||||||
在我们被限制连接之前满足以上条件的主播无需部署
|
在我们被限制连接之前满足以上条件的主播无需部署
|
||||||
<NButton tag="a" href="https://www.yuque.com/megghy/dez70g/vfvcyv3024xvaa1p" target="_blank" type="primary" text> VtsuruEventFetcher </NButton>
|
<NButton tag="a" href="https://www.yuque.com/megghy/dez70g/vfvcyv3024xvaa1p" target="_blank" type="primary" text>
|
||||||
|
VtsuruEventFetcher
|
||||||
|
</NButton>
|
||||||
即可使用相关功能 (如记录上舰和SC, 直播场记录等) 😊
|
即可使用相关功能 (如记录上舰和SC, 直播场记录等) 😊
|
||||||
</NAlert>
|
</NAlert>
|
||||||
<EventFetcherStatusCard />
|
<EventFetcherStatusCard />
|
||||||
@@ -177,7 +188,14 @@ function objectsToCSV(arr: any[]) {
|
|||||||
<NCard size="small" style="witdh: 100%">
|
<NCard size="small" style="witdh: 100%">
|
||||||
<template v-if="accountInfo?.isBiliVerified">
|
<template v-if="accountInfo?.isBiliVerified">
|
||||||
<NSpace justify="center" align="center">
|
<NSpace justify="center" align="center">
|
||||||
<NDatePicker v-model:value="selectedDate" @update:value="onDateChange" type="datetimerange" :shortcuts="rangeShortcuts" start-placeholder="开始时间" end-placeholder="结束时间" />
|
<NDatePicker
|
||||||
|
v-model:value="selectedDate"
|
||||||
|
@update:value="onDateChange"
|
||||||
|
type="datetimerange"
|
||||||
|
:shortcuts="rangeShortcuts"
|
||||||
|
start-placeholder="开始时间"
|
||||||
|
end-placeholder="结束时间"
|
||||||
|
/>
|
||||||
<NRadioGroup v-model:value="selectedType">
|
<NRadioGroup v-model:value="selectedType">
|
||||||
<NRadioButton :value="EventType.Guard">舰长</NRadioButton>
|
<NRadioButton :value="EventType.Guard">舰长</NRadioButton>
|
||||||
<NRadioButton :value="EventType.SC">Superchat</NRadioButton>
|
<NRadioButton :value="EventType.SC">Superchat</NRadioButton>
|
||||||
@@ -209,15 +227,32 @@ function objectsToCSV(arr: any[]) {
|
|||||||
<div v-if="displayMode == 'grid'">
|
<div v-if="displayMode == 'grid'">
|
||||||
<NGrid cols="1 500:2 800:3 1000:4" :x-gap="12" :y-gap="8">
|
<NGrid cols="1 500:2 800:3 1000:4" :x-gap="12" :y-gap="8">
|
||||||
<NGridItem v-for="item in selectedEvents" v-bind:key="item.time">
|
<NGridItem v-for="item in selectedEvents" v-bind:key="item.time">
|
||||||
<NCard size="small" :style="`height: ${selectedType == EventType.Guard ? '175px' : '220'}px`" embedded hoverable>
|
<NCard
|
||||||
|
size="small"
|
||||||
|
:style="`height: ${selectedType == EventType.Guard ? '175px' : '220'}px`"
|
||||||
|
embedded
|
||||||
|
hoverable
|
||||||
|
>
|
||||||
<NSpace align="center" vertical :size="5">
|
<NSpace align="center" vertical :size="5">
|
||||||
<NAvatar round lazy borderd :size="64" :src="AVATAR_URL + item.uid" :img-props="{ referrerpolicy: 'no-referrer' }" style="box-shadow: 0 3px 5px rgba(0, 0, 0, 0.2)" />
|
<NAvatar
|
||||||
|
round
|
||||||
|
lazy
|
||||||
|
borderd
|
||||||
|
:size="64"
|
||||||
|
:src="AVATAR_URL + item.uid"
|
||||||
|
:img-props="{ referrerpolicy: 'no-referrer' }"
|
||||||
|
style="box-shadow: 0 3px 5px rgba(0, 0, 0, 0.2)"
|
||||||
|
/>
|
||||||
<NSpace>
|
<NSpace>
|
||||||
<NTag size="tiny" v-if="selectedType == EventType.Guard" :bordered="false"> {{ item.msg }} </NTag>
|
<NTag size="tiny" v-if="selectedType == EventType.Guard" :bordered="false"> {{ item.msg }} </NTag>
|
||||||
<NTag
|
<NTag
|
||||||
size="tiny"
|
size="tiny"
|
||||||
round
|
round
|
||||||
:color="{ color: selectedType == EventType.Guard ? GetGuardColor(item.price) : GetSCColor(item.price), textColor: 'white', borderColor: isDarkMode() ? 'white' : '#00000000' }"
|
:color="{
|
||||||
|
color: selectedType == EventType.Guard ? GetGuardColor(item.price) : GetSCColor(item.price),
|
||||||
|
textColor: 'white',
|
||||||
|
borderColor: isDarkMode() ? 'white' : '#00000000',
|
||||||
|
}"
|
||||||
>
|
>
|
||||||
{{ item.price }}
|
{{ item.price }}
|
||||||
</NTag>
|
</NTag>
|
||||||
@@ -252,7 +287,15 @@ function objectsToCSV(arr: any[]) {
|
|||||||
<td><NTime :time="item.time" /></td>
|
<td><NTime :time="item.time" /></td>
|
||||||
<td v-if="selectedType == EventType.Guard">{{ item.msg }}</td>
|
<td v-if="selectedType == EventType.Guard">{{ item.msg }}</td>
|
||||||
<td>
|
<td>
|
||||||
<NTag :color="{ color: selectedType == EventType.Guard ? GetGuardColor(item.price) : GetSCColor(item.price), textColor: 'white', borderColor: 'white' }"> {{ item.price }} </NTag>
|
<NTag
|
||||||
|
:color="{
|
||||||
|
color: selectedType == EventType.Guard ? GetGuardColor(item.price) : GetSCColor(item.price),
|
||||||
|
textColor: 'white',
|
||||||
|
borderColor: 'white',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ item.price }}
|
||||||
|
</NTag>
|
||||||
</td>
|
</td>
|
||||||
<td v-if="selectedType == EventType.SC">
|
<td v-if="selectedType == EventType.SC">
|
||||||
<NEllipsis style="max-width: 300px">{{ item.msg }}</NEllipsis>
|
<NEllipsis style="max-width: 300px">{{ item.msg }}</NEllipsis>
|
||||||
@@ -265,8 +308,13 @@ function objectsToCSV(arr: any[]) {
|
|||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<NCollapse :default-expanded-names="['1']">
|
<NCollapse :default-expanded-names="['1']">
|
||||||
<NCollapseItem title="这是什么?" name="1"> 可以查看曾经收到的Superchat以及上舰记录, 并导出为 CSV 之类的表格 </NCollapseItem>
|
<NCollapseItem title="这是什么?" name="1">
|
||||||
<NCollapseItem title="可以直接用吗"> 遗憾的是并不能, 使用这个功能需要你拥有一个可以7*24小时运行 Docker 容器或者 Node.js 脚本的环境, 并且可以访问互联网 </NCollapseItem>
|
可以查看曾经收到的Superchat以及上舰记录, 并导出为 CSV 之类的表格
|
||||||
|
</NCollapseItem>
|
||||||
|
<NCollapseItem title="可以直接用吗">
|
||||||
|
遗憾的是并不能, 使用这个功能需要你拥有一个可以7*24小时运行 Docker 容器或者 Node.js 脚本的环境,
|
||||||
|
并且可以访问互联网
|
||||||
|
</NCollapseItem>
|
||||||
<NCollapseItem title="有没有什么要求?">
|
<NCollapseItem title="有没有什么要求?">
|
||||||
关于环境的话理论上能够运行 Docker 或者 Node.js 的环境都能可以
|
关于环境的话理论上能够运行 Docker 或者 Node.js 的环境都能可以
|
||||||
<br /><br />
|
<br /><br />
|
||||||
@@ -278,13 +326,18 @@ function objectsToCSV(arr: any[]) {
|
|||||||
<NLi>拥有掌握以上技能的 stf 或者朋友</NLi>
|
<NLi>拥有掌握以上技能的 stf 或者朋友</NLi>
|
||||||
</NUl>
|
</NUl>
|
||||||
<NH3>
|
<NH3>
|
||||||
<NText strong> 即使你对相关知识一窍不通也不用担心, 跟着后面的傻瓜教程中的 Koyeb 也可以完成部署. 理论上这玩意里头的免费套餐就够用了, 当然如果你想要更稳一点上个付费套餐也不影响 </NText>
|
<NText strong>
|
||||||
|
即使你对相关知识一窍不通也不用担心, 跟着后面的傻瓜教程中的 Koyeb 也可以完成部署.
|
||||||
|
理论上这玩意里头的免费套餐就够用了, 当然如果你想要更稳一点上个付费套餐也不影响
|
||||||
|
</NText>
|
||||||
</NH3>
|
</NH3>
|
||||||
</NCollapseItem>
|
</NCollapseItem>
|
||||||
</NCollapse>
|
</NCollapse>
|
||||||
<NDivider style="margin-bottom: 10px" />
|
<NDivider style="margin-bottom: 10px" />
|
||||||
<NSpace justify="center">
|
<NSpace justify="center">
|
||||||
<NButton tag="a" href="https://www.yuque.com/megghy/dez70g/vfvcyv3024xvaa1p" target="_blank" type="primary"> 部署指南 </NButton>
|
<NButton tag="a" href="https://www.yuque.com/megghy/dez70g/vfvcyv3024xvaa1p" target="_blank" type="primary">
|
||||||
|
部署指南
|
||||||
|
</NButton>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
</template>
|
</template>
|
||||||
</NCard>
|
</NCard>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import {
|
|||||||
} from 'echarts/components'
|
} from 'echarts/components'
|
||||||
import { use } from 'echarts/core'
|
import { use } from 'echarts/core'
|
||||||
import { CanvasRenderer } from 'echarts/renderers'
|
import { CanvasRenderer } from 'echarts/renderers'
|
||||||
import { NAlert, NButton, NCard, NDivider, NIcon, NSpace, NSpin, NText, NTime, NTooltip, useMessage } from 'naive-ui'
|
import { NAlert, NButton, NCard, NDivider, NIcon, NSpace, NSpin, NTime, NTooltip, useMessage } from 'naive-ui'
|
||||||
import { onMounted, ref } from 'vue'
|
import { onMounted, ref } from 'vue'
|
||||||
import VChart from 'vue-echarts'
|
import VChart from 'vue-echarts'
|
||||||
|
|
||||||
@@ -97,8 +97,8 @@ function isSameDaySimple(time1: number, time2: number) {
|
|||||||
const statisticStartDate = new Date(2023, 10, 4)
|
const statisticStartDate = new Date(2023, 10, 4)
|
||||||
const statisticStartDateTime = statisticStartDate.getTime()
|
const statisticStartDateTime = statisticStartDate.getTime()
|
||||||
function getOptions() {
|
function getOptions() {
|
||||||
let fansIncreacement = [] as { time: Date; count: number }[]
|
const fansIncreacement = [] as { time: Date; count: number }[]
|
||||||
let completeTimeSeries: {
|
const completeTimeSeries: {
|
||||||
time: Date
|
time: Date
|
||||||
count: number
|
count: number
|
||||||
change: boolean
|
change: boolean
|
||||||
@@ -147,7 +147,7 @@ function getOptions() {
|
|||||||
completeTimeSeries.forEach((entry, index, array) => {
|
completeTimeSeries.forEach((entry, index, array) => {
|
||||||
if (index === 0 || !isSameDay(entry.time, array[index - 1].time)) {
|
if (index === 0 || !isSameDay(entry.time, array[index - 1].time)) {
|
||||||
if (index > 0) {
|
if (index > 0) {
|
||||||
let dailyIncrement = entry.count - previousDayCount
|
const dailyIncrement = entry.count - previousDayCount
|
||||||
fansIncreacement.push({
|
fansIncreacement.push({
|
||||||
time: startOfHour(array[index - 1].time),
|
time: startOfHour(array[index - 1].time),
|
||||||
count: dailyIncrement,
|
count: dailyIncrement,
|
||||||
@@ -156,7 +156,7 @@ function getOptions() {
|
|||||||
}
|
}
|
||||||
previousDayCount = entry.count
|
previousDayCount = entry.count
|
||||||
} else if (index === array.length - 1) {
|
} else if (index === array.length - 1) {
|
||||||
let dailyIncrement = entry.count - previousDayCount
|
const dailyIncrement = entry.count - previousDayCount
|
||||||
fansIncreacement.push({
|
fansIncreacement.push({
|
||||||
time: startOfHour(entry.time),
|
time: startOfHour(entry.time),
|
||||||
count: dailyIncrement,
|
count: dailyIncrement,
|
||||||
@@ -168,19 +168,19 @@ function getOptions() {
|
|||||||
|
|
||||||
let lastDayGuards = 0
|
let lastDayGuards = 0
|
||||||
let lastDay = 0
|
let lastDay = 0
|
||||||
let guardsIncreacement = [] as { time: number; count: number; timeString: string }[]
|
const guardsIncreacement = [] as { time: number; count: number; timeString: string }[]
|
||||||
let guards = [] as { time: number; count: number; timeString: string }[]
|
const guards = [] as { time: number; count: number; timeString: string }[]
|
||||||
|
|
||||||
// 生成完整的天序列
|
// 生成完整的天序列
|
||||||
let currentGuardTime = startTime
|
let currentGuardTime = startTime
|
||||||
let lastDayGuardCount = 0
|
let lastDayGuardCount = 0
|
||||||
let completeGuardTimeSeries: {
|
const completeGuardTimeSeries: {
|
||||||
time: Date
|
time: Date
|
||||||
count: number
|
count: number
|
||||||
}[] = []
|
}[] = []
|
||||||
while (currentGuardTime <= endTime) {
|
while (currentGuardTime <= endTime) {
|
||||||
let found = guardHistory.value?.find((f) => isSameDay(currentGuardTime, f.time))
|
const found = guardHistory.value?.find((f) => isSameDay(currentGuardTime, f.time))
|
||||||
let count = found ? found.count : lastDayGuardCount
|
const count = found ? found.count : lastDayGuardCount
|
||||||
lastDayGuardCount = count
|
lastDayGuardCount = count
|
||||||
|
|
||||||
completeGuardTimeSeries.push({
|
completeGuardTimeSeries.push({
|
||||||
@@ -208,8 +208,8 @@ function getOptions() {
|
|||||||
lastDayGuards = g.count
|
lastDayGuards = g.count
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
let upstatViewIncreace: { time: number; value: number }[] = []
|
const upstatViewIncreace: { time: number; value: number }[] = []
|
||||||
let upstatLikeIncreace: { time: number; value: number }[] = []
|
const upstatLikeIncreace: { time: number; value: number }[] = []
|
||||||
if (upstatHistory.value && upstatHistory.value.length > 0) {
|
if (upstatHistory.value && upstatHistory.value.length > 0) {
|
||||||
let lastUpstatView = upstatHistory.value[0].stats.views
|
let lastUpstatView = upstatHistory.value[0].stats.views
|
||||||
let lastUpstatLike = upstatHistory.value[0].stats.likes
|
let lastUpstatLike = upstatHistory.value[0].stats.likes
|
||||||
@@ -247,7 +247,7 @@ function getOptions() {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
formatter: (param: any) => {
|
formatter: (param: any) => {
|
||||||
let name = param[0].name + '<br>'
|
const name = param[0].name + '<br>'
|
||||||
let str = ''
|
let str = ''
|
||||||
for (var i = 0; i < param.length; i++) {
|
for (var i = 0; i < param.length; i++) {
|
||||||
const status =
|
const status =
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { onMounted, onUnmounted } from 'vue'
|
|||||||
import OpenLottery from '../open_live/OpenLottery.vue'
|
import OpenLottery from '../open_live/OpenLottery.vue'
|
||||||
|
|
||||||
const accountInfo = useAccount()
|
const accountInfo = useAccount()
|
||||||
let client = new DanmakuClient(null)
|
const client = new DanmakuClient(null)
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
client.Start()
|
client.Start()
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { copyToClipboard, downloadImage } from '@/Utils'
|
import { copyToClipboard, downloadImage } from '@/Utils'
|
||||||
import { SaveAccountSettings, useAccount } from '@/api/account'
|
import { DisableFunction, EnableFunction, SaveAccountSettings, useAccount } from '@/api/account'
|
||||||
import { QAInfo } from '@/api/api-models'
|
import { FunctionTypes, QAInfo } from '@/api/api-models'
|
||||||
import { QueryGetAPI } from '@/api/query'
|
import { QueryGetAPI } from '@/api/query'
|
||||||
import { QUESTION_API_URL } from '@/data/constants'
|
import { QUESTION_API_URL } from '@/data/constants'
|
||||||
import router from '@/router'
|
import router from '@/router'
|
||||||
@@ -10,6 +10,7 @@ import { saveAs } from 'file-saver'
|
|||||||
import html2canvas from 'html2canvas'
|
import html2canvas from 'html2canvas'
|
||||||
import {
|
import {
|
||||||
NAffix,
|
NAffix,
|
||||||
|
NAlert,
|
||||||
NButton,
|
NButton,
|
||||||
NCard,
|
NCard,
|
||||||
NCheckbox,
|
NCheckbox,
|
||||||
@@ -123,6 +124,20 @@ async function saveSettings() {
|
|||||||
|
|
||||||
const parentRef = ref<HTMLElement | null>(null)
|
const parentRef = ref<HTMLElement | null>(null)
|
||||||
|
|
||||||
|
async function setFunctionEnable(enable: boolean) {
|
||||||
|
let success = false
|
||||||
|
if (enable) {
|
||||||
|
success = await EnableFunction(FunctionTypes.QuestionBox)
|
||||||
|
} else {
|
||||||
|
success = await DisableFunction(FunctionTypes.QuestionBox)
|
||||||
|
}
|
||||||
|
if (success) {
|
||||||
|
message.success('已' + (enable ? '启用' : '禁用'))
|
||||||
|
} else {
|
||||||
|
message.error('无法' + (enable ? '启用' : '禁用'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (selectedTabItem.value == '0') {
|
if (selectedTabItem.value == '0') {
|
||||||
useQB.GetRecieveQAInfo()
|
useQB.GetRecieveQAInfo()
|
||||||
@@ -137,7 +152,15 @@ onMounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NSpace>
|
<NSpace align="center">
|
||||||
|
<NAlert type="info" style="max-width: 200px">
|
||||||
|
启用提问箱
|
||||||
|
<NDivider vertical />
|
||||||
|
<NSwitch
|
||||||
|
:value="accountInfo?.settings.enableFunctions.includes(FunctionTypes.QuestionBox)"
|
||||||
|
@update:value="setFunctionEnable"
|
||||||
|
/>
|
||||||
|
</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>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
@@ -145,9 +168,7 @@ onMounted(() => {
|
|||||||
<NSpin v-if="useQB.isLoading" show />
|
<NSpin v-if="useQB.isLoading" show />
|
||||||
<NTabs v-else animated @update:value="onTabChange" v-model:value="selectedTabItem">
|
<NTabs v-else animated @update:value="onTabChange" v-model:value="selectedTabItem">
|
||||||
<NTabPane tab="我收到的" name="0">
|
<NTabPane tab="我收到的" name="0">
|
||||||
<NButton @click="$router.push({ name: 'question-display' })" type="primary">
|
<NButton @click="$router.push({ name: 'question-display' })" type="primary"> 打开展示页 </NButton>
|
||||||
打开展示页
|
|
||||||
</NButton>
|
|
||||||
<NDivider vertical />
|
<NDivider vertical />
|
||||||
<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>
|
||||||
|
|||||||
@@ -1,11 +1,27 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useAccount } from '@/api/account'
|
import { DisableFunction, EnableFunction, useAccount } from '@/api/account'
|
||||||
import { ScheduleWeekInfo } from '@/api/api-models'
|
import { FunctionTypes, ScheduleWeekInfo } from '@/api/api-models'
|
||||||
import { QueryGetAPI, QueryPostAPI } from '@/api/query'
|
import { QueryGetAPI, QueryPostAPI } from '@/api/query'
|
||||||
import ScheduleList from '@/components/ScheduleList.vue'
|
import ScheduleList from '@/components/ScheduleList.vue'
|
||||||
import { SCHEDULE_API_URL } from '@/data/constants'
|
import { SCHEDULE_API_URL } from '@/data/constants'
|
||||||
import { addWeeks, endOfWeek, endOfYear, format, isBefore, startOfWeek, startOfYear } from 'date-fns'
|
import { addWeeks, endOfWeek, endOfYear, format, isBefore, startOfWeek, startOfYear } from 'date-fns'
|
||||||
import { NAlert, NBadge, NButton, NColorPicker, NDivider, NInput, NInputGroup, NInputGroupLabel, NModal, NSelect, NSpace, NSpin, NTimePicker, useMessage } from 'naive-ui'
|
import {
|
||||||
|
NAlert,
|
||||||
|
NBadge,
|
||||||
|
NButton,
|
||||||
|
NColorPicker,
|
||||||
|
NDivider,
|
||||||
|
NInput,
|
||||||
|
NInputGroup,
|
||||||
|
NInputGroupLabel,
|
||||||
|
NModal,
|
||||||
|
NSelect,
|
||||||
|
NSpace,
|
||||||
|
NSpin,
|
||||||
|
NSwitch,
|
||||||
|
NTimePicker,
|
||||||
|
useMessage,
|
||||||
|
} from 'naive-ui'
|
||||||
import { SelectMixedOption, SelectOption } from 'naive-ui/es/select/src/interface'
|
import { SelectMixedOption, SelectOption } from 'naive-ui/es/select/src/interface'
|
||||||
import { VNode, computed, h, onMounted, ref } from 'vue'
|
import { VNode, computed, h, onMounted, ref } from 'vue'
|
||||||
|
|
||||||
@@ -40,10 +56,11 @@ const yearOptions = [
|
|||||||
},
|
},
|
||||||
] as SelectMixedOption[]
|
] as SelectMixedOption[]
|
||||||
const weekOptions = computed(() => {
|
const weekOptions = computed(() => {
|
||||||
let weeks = [] as SelectMixedOption[]
|
const weeks = [] as SelectMixedOption[]
|
||||||
const all = getAllWeeks(selectedScheduleYear.value)
|
const all = getAllWeeks(selectedScheduleYear.value)
|
||||||
all.forEach((week) => {
|
all.forEach((week) => {
|
||||||
const isExist = (schedules.value?.findIndex((s) => s.year == selectedScheduleYear.value && s.week == week[0] + 1) ?? -1) > -1
|
const isExist =
|
||||||
|
(schedules.value?.findIndex((s) => s.year == selectedScheduleYear.value && s.week == week[0] + 1) ?? -1) > -1
|
||||||
weeks.push({
|
weeks.push({
|
||||||
label: `${isExist ? '(已安排)' : ''} 第${week[0] + 1}周 (${week[1]})`,
|
label: `${isExist ? '(已安排)' : ''} 第${week[0] + 1}周 (${week[1]})`,
|
||||||
value: week[0] + 1,
|
value: week[0] + 1,
|
||||||
@@ -53,7 +70,7 @@ const weekOptions = computed(() => {
|
|||||||
return weeks
|
return weeks
|
||||||
})
|
})
|
||||||
const dayOptions = computed(() => {
|
const dayOptions = computed(() => {
|
||||||
let days = [] as SelectMixedOption[]
|
const days = [] as SelectMixedOption[]
|
||||||
for (let i = 0; i < 7; i++) {
|
for (let i = 0; i < 7; i++) {
|
||||||
try {
|
try {
|
||||||
days.push({
|
days.push({
|
||||||
@@ -67,7 +84,7 @@ const dayOptions = computed(() => {
|
|||||||
return days
|
return days
|
||||||
})
|
})
|
||||||
const existTagOptions = computed(() => {
|
const existTagOptions = computed(() => {
|
||||||
let colors = [] as SelectMixedOption[]
|
const colors = [] as SelectMixedOption[]
|
||||||
schedules.value?.forEach((s) => {
|
schedules.value?.forEach((s) => {
|
||||||
s.days.forEach((d) => {
|
s.days.forEach((d) => {
|
||||||
if (d.tag && !colors.find((c) => c.value == d.tagColor && c.label == d.tag)) {
|
if (d.tag && !colors.find((c) => c.value == d.tagColor && c.label == d.tag)) {
|
||||||
@@ -85,7 +102,7 @@ function getAllWeeks(year: number) {
|
|||||||
const endDate = endOfYear(new Date(year, 11, 31))
|
const endDate = endOfYear(new Date(year, 11, 31))
|
||||||
|
|
||||||
let date = startOfWeek(startDate, { weekStartsOn: 1 })
|
let date = startOfWeek(startDate, { weekStartsOn: 1 })
|
||||||
let weeks: [number, string][] = []
|
const weeks: [number, string][] = []
|
||||||
|
|
||||||
let index = 0
|
let index = 0
|
||||||
|
|
||||||
@@ -175,7 +192,9 @@ async function onUpdateSchedule() {
|
|||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (data.code == 200) {
|
if (data.code == 200) {
|
||||||
message.success('成功')
|
message.success('成功')
|
||||||
const s = schedules.value?.find((s) => s.year == selectedScheduleYear.value && s.week == selectedScheduleWeek.value)
|
const s = schedules.value?.find(
|
||||||
|
(s) => s.year == selectedScheduleYear.value && s.week == selectedScheduleWeek.value,
|
||||||
|
)
|
||||||
if (s) {
|
if (s) {
|
||||||
s.days[selectedDay.value] = updateScheduleModel.value.days[selectedDay.value]
|
s.days[selectedDay.value] = updateScheduleModel.value.days[selectedDay.value]
|
||||||
} else {
|
} else {
|
||||||
@@ -218,7 +237,23 @@ function onSelectChange(value: string | null, option: SelectMixedOption) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const renderOption = ({ node, option }: { node: VNode; option: SelectOption }) =>
|
const renderOption = ({ node, option }: { node: VNode; option: SelectOption }) =>
|
||||||
h(NSpace, { align: 'center', size: 3, style: 'margin-left: 5px' }, () => [option.value ? h(NBadge, { dot: true, color: option.value?.toString() }) : null, node])
|
h(NSpace, { align: 'center', size: 3, style: 'margin-left: 5px' }, () => [
|
||||||
|
option.value ? h(NBadge, { dot: true, color: option.value?.toString() }) : null,
|
||||||
|
node,
|
||||||
|
])
|
||||||
|
async function setFunctionEnable(enable: boolean) {
|
||||||
|
let success = false
|
||||||
|
if (enable) {
|
||||||
|
success = await EnableFunction(FunctionTypes.Schedule)
|
||||||
|
} else {
|
||||||
|
success = await DisableFunction(FunctionTypes.Schedule)
|
||||||
|
}
|
||||||
|
if (success) {
|
||||||
|
message.success('已' + (enable ? '启用' : '禁用'))
|
||||||
|
} else {
|
||||||
|
message.error('无法' + (enable ? '启用' : '禁用'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
get()
|
get()
|
||||||
@@ -226,9 +261,19 @@ onMounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NSpace>
|
<NSpace align="center">
|
||||||
|
<NAlert type="info" style="max-width: 200px">
|
||||||
|
启用日程表
|
||||||
|
<NDivider vertical />
|
||||||
|
<NSwitch
|
||||||
|
:value="accountInfo?.settings.enableFunctions.includes(FunctionTypes.Schedule)"
|
||||||
|
@update:value="setFunctionEnable"
|
||||||
|
/>
|
||||||
|
</NAlert>
|
||||||
<NButton @click="showAddModal = true" type="primary"> 添加周程 </NButton>
|
<NButton @click="showAddModal = true" type="primary"> 添加周程 </NButton>
|
||||||
<NButton @click="$router.push({ name: 'manage-index', query: { tab: 'template', template: 'schedule' } })"> 修改模板 </NButton>
|
<NButton @click="$router.push({ name: 'manage-index', query: { tab: 'template', template: 'schedule' } })">
|
||||||
|
修改模板
|
||||||
|
</NButton>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
<NDivider />
|
<NDivider />
|
||||||
<NModal v-model:show="showAddModal" style="width: 600px; max-width: 90vw" preset="card" title="添加周程">
|
<NModal v-model:show="showAddModal" style="width: 600px; max-width: 90vw" preset="card" title="添加周程">
|
||||||
@@ -260,7 +305,13 @@ onMounted(() => {
|
|||||||
<NSpace>
|
<NSpace>
|
||||||
<NInputGroup>
|
<NInputGroup>
|
||||||
<NInputGroupLabel type="primary"> 标签 </NInputGroupLabel>
|
<NInputGroupLabel type="primary"> 标签 </NInputGroupLabel>
|
||||||
<NInput v-model:value="updateScheduleModel.days[selectedDay].tag" placeholder="标签 | 留空视为无安排" style="max-width: 300px" maxlength="10" show-count />
|
<NInput
|
||||||
|
v-model:value="updateScheduleModel.days[selectedDay].tag"
|
||||||
|
placeholder="标签 | 留空视为无安排"
|
||||||
|
style="max-width: 300px"
|
||||||
|
maxlength="10"
|
||||||
|
show-count
|
||||||
|
/>
|
||||||
</NInputGroup>
|
</NInputGroup>
|
||||||
<NSelect
|
<NSelect
|
||||||
v-model:value="selectedExistTag"
|
v-model:value="selectedExistTag"
|
||||||
@@ -275,9 +326,19 @@ onMounted(() => {
|
|||||||
</NSpace>
|
</NSpace>
|
||||||
<NInputGroup>
|
<NInputGroup>
|
||||||
<NInputGroupLabel> 内容 </NInputGroupLabel>
|
<NInputGroupLabel> 内容 </NInputGroupLabel>
|
||||||
<NInput v-model:value="updateScheduleModel.days[selectedDay].title" placeholder="内容" style="max-width: 200px" maxlength="30" show-count />
|
<NInput
|
||||||
|
v-model:value="updateScheduleModel.days[selectedDay].title"
|
||||||
|
placeholder="内容"
|
||||||
|
style="max-width: 200px"
|
||||||
|
maxlength="30"
|
||||||
|
show-count
|
||||||
|
/>
|
||||||
</NInputGroup>
|
</NInputGroup>
|
||||||
<NTimePicker default-formatted-value="20:00" v-model:formatted-value="updateScheduleModel.days[selectedDay].time" format="HH:mm" />
|
<NTimePicker
|
||||||
|
default-formatted-value="20:00"
|
||||||
|
v-model:formatted-value="updateScheduleModel.days[selectedDay].time"
|
||||||
|
format="HH:mm"
|
||||||
|
/>
|
||||||
<NColorPicker
|
<NColorPicker
|
||||||
v-model:value="updateScheduleModel.days[selectedDay].tagColor"
|
v-model:value="updateScheduleModel.days[selectedDay].tagColor"
|
||||||
:swatches="['#FFFFFF', '#18A058', '#2080F0', '#F0A020', 'rgba(208, 48, 80, 1)']"
|
:swatches="['#FFFFFF', '#18A058', '#2080F0', '#F0A020', 'rgba(208, 48, 80, 1)']"
|
||||||
@@ -290,5 +351,12 @@ onMounted(() => {
|
|||||||
</template>
|
</template>
|
||||||
</NModal>
|
</NModal>
|
||||||
<NSpin v-if="isLoading" show />
|
<NSpin v-if="isLoading" show />
|
||||||
<ScheduleList v-else :schedules="schedules ?? []" @on-update="onOpenUpdateModal" @on-delete="onDeleteSchedule" @on-copy="onOpenCopyModal" is-self />
|
<ScheduleList
|
||||||
|
v-else
|
||||||
|
:schedules="schedules ?? []"
|
||||||
|
@on-update="onOpenUpdateModal"
|
||||||
|
@on-delete="onDeleteSchedule"
|
||||||
|
@on-copy="onOpenCopyModal"
|
||||||
|
is-self
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,10 +1,35 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { DelBiliBlackList, SaveAccountSettings, SaveEnableFunctions, downloadConfigDirect, useAccount } from '@/api/account'
|
import {
|
||||||
|
DelBiliBlackList,
|
||||||
|
SaveAccountSettings,
|
||||||
|
SaveEnableFunctions,
|
||||||
|
downloadConfigDirect,
|
||||||
|
useAccount,
|
||||||
|
} from '@/api/account'
|
||||||
import { FunctionTypes, ScheduleWeekInfo, SongFrom, SongLanguage, SongRequestOption, SongsInfo } from '@/api/api-models'
|
import { FunctionTypes, ScheduleWeekInfo, SongFrom, SongLanguage, SongRequestOption, SongsInfo } from '@/api/api-models'
|
||||||
import DynamicForm from '@/components/DynamicForm.vue'
|
import DynamicForm from '@/components/DynamicForm.vue'
|
||||||
import { TemplateConfig } from '@/data/VTsuruTypes'
|
import { TemplateConfig } from '@/data/VTsuruTypes'
|
||||||
import { FETCH_API, IndexTemplateMap, ScheduleTemplateMap, SongListTemplateMap } from '@/data/constants'
|
import { FETCH_API, IndexTemplateMap, ScheduleTemplateMap, SongListTemplateMap } from '@/data/constants'
|
||||||
import { NAlert, NButton, NCard, NCheckbox, NCheckboxGroup, NDivider, NEmpty, NList, NListItem, NModal, NSelect, NSpace, NSpin, NTabPane, NTabs, NText, SelectOption, useMessage } from 'naive-ui'
|
import {
|
||||||
|
NAlert,
|
||||||
|
NButton,
|
||||||
|
NCard,
|
||||||
|
NCheckbox,
|
||||||
|
NCheckboxGroup,
|
||||||
|
NDivider,
|
||||||
|
NEmpty,
|
||||||
|
NList,
|
||||||
|
NListItem,
|
||||||
|
NModal,
|
||||||
|
NSelect,
|
||||||
|
NSpace,
|
||||||
|
NSpin,
|
||||||
|
NTabPane,
|
||||||
|
NTabs,
|
||||||
|
NText,
|
||||||
|
SelectOption,
|
||||||
|
useMessage,
|
||||||
|
} from 'naive-ui'
|
||||||
import { computed, h, nextTick, onActivated, onMounted, ref } from 'vue'
|
import { computed, h, nextTick, onActivated, onMounted, ref } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
|
|
||||||
@@ -188,7 +213,9 @@ const selectedTab = ref(route.query.tab?.toString() ?? 'general')
|
|||||||
|
|
||||||
const dynamicConfigRef = ref()
|
const dynamicConfigRef = ref()
|
||||||
const selectedTemplateData = computed(() => templates.value[selectedOption.value])
|
const selectedTemplateData = computed(() => templates.value[selectedOption.value])
|
||||||
const selectedComponent = computed(() => selectedTemplateData.value?.TemplateMap[selectedTemplateData.value.Selected].compoent)
|
const selectedComponent = computed(
|
||||||
|
() => selectedTemplateData.value?.TemplateMap[selectedTemplateData.value.Selected].compoent,
|
||||||
|
)
|
||||||
const selectedTemplateConfig = computed(() => {
|
const selectedTemplateConfig = computed(() => {
|
||||||
if (dynamicConfigRef.value?.Config) {
|
if (dynamicConfigRef.value?.Config) {
|
||||||
return dynamicConfigRef.value?.Config as TemplateConfig<any>
|
return dynamicConfigRef.value?.Config as TemplateConfig<any>
|
||||||
@@ -201,7 +228,7 @@ const settingModalVisiable = ref(false)
|
|||||||
|
|
||||||
async function RequestBiliUserData() {
|
async function RequestBiliUserData() {
|
||||||
await fetch(FETCH_API + `https://account.bilibili.com/api/member/getCardByMid?mid=10021741`).then(async (respone) => {
|
await fetch(FETCH_API + `https://account.bilibili.com/api/member/getCardByMid?mid=10021741`).then(async (respone) => {
|
||||||
let data = await respone.json()
|
const data = await respone.json()
|
||||||
if (data.code == 0) {
|
if (data.code == 0) {
|
||||||
biliUserInfo.value = data.card
|
biliUserInfo.value = data.card
|
||||||
} else {
|
} else {
|
||||||
@@ -209,7 +236,10 @@ async function RequestBiliUserData() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
async function SaveComboGroupSetting(value: (string | number)[], meta: { actionType: 'check' | 'uncheck'; value: string | number }) {
|
async function SaveComboGroupSetting(
|
||||||
|
value: (string | number)[],
|
||||||
|
meta: { actionType: 'check' | 'uncheck'; value: string | number },
|
||||||
|
) {
|
||||||
if (accountInfo.value) {
|
if (accountInfo.value) {
|
||||||
isSaving.value = true
|
isSaving.value = true
|
||||||
//UpdateEnableFunction(meta.value as FunctionTypes, meta.actionType == 'check')
|
//UpdateEnableFunction(meta.value as FunctionTypes, meta.actionType == 'check')
|
||||||
@@ -220,12 +250,14 @@ async function SaveComboGroupSetting(value: (string | number)[], meta: { actionT
|
|||||||
} else {
|
} else {
|
||||||
message.error('修改失败')
|
message.error('修改失败')
|
||||||
if (accountInfo.value) {
|
if (accountInfo.value) {
|
||||||
accountInfo.value.settings.enableFunctions = accountInfo.value.settings.enableFunctions.filter((f) => f != (meta.value as FunctionTypes))
|
accountInfo.value.settings.enableFunctions = accountInfo.value.settings.enableFunctions.filter(
|
||||||
|
(f) => f != (meta.value as FunctionTypes),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
message.error('修改失败')
|
message.error('修改失败: ' + err)
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
isSaving.value = false
|
isSaving.value = false
|
||||||
@@ -245,7 +277,7 @@ async function SaveComboSetting() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
message.error('修改失败')
|
message.error('修改失败: ' + err)
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
isSaving.value = false
|
isSaving.value = false
|
||||||
@@ -300,7 +332,10 @@ async function getTemplateConfig() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const buttonGroup = computed(() => {
|
const buttonGroup = computed(() => {
|
||||||
return h(NSpace, () => [h(NButton, { type: 'primary', onClick: () => SaveTemplateSetting() }, () => '设为展示模板'), h(NButton, { type: 'info', onClick: onOpenTemplateSettings }, () => '模板设置')])
|
return h(NSpace, () => [
|
||||||
|
h(NButton, { type: 'primary', onClick: () => SaveTemplateSetting() }, () => '设为展示模板'),
|
||||||
|
h(NButton, { type: 'info', onClick: onOpenTemplateSettings }, () => '模板设置'),
|
||||||
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
function unblockUser(id: number) {
|
function unblockUser(id: number) {
|
||||||
@@ -348,12 +383,24 @@ onMounted(async () => {
|
|||||||
</NCheckboxGroup>
|
</NCheckboxGroup>
|
||||||
<NDivider> 通知 </NDivider>
|
<NDivider> 通知 </NDivider>
|
||||||
<NSpace>
|
<NSpace>
|
||||||
<NCheckbox v-model:checked="accountInfo.settings.sendEmail.recieveQA" @update:checked="SaveComboSetting"> 收到新提问时发送邮件 </NCheckbox>
|
<NCheckbox v-model:checked="accountInfo.settings.sendEmail.recieveQA" @update:checked="SaveComboSetting">
|
||||||
<NCheckbox v-model:checked="accountInfo.settings.sendEmail.recieveQAReply" @update:checked="SaveComboSetting"> 提问收到回复时发送邮件 </NCheckbox>
|
收到新提问时发送邮件
|
||||||
|
</NCheckbox>
|
||||||
|
<NCheckbox
|
||||||
|
v-model:checked="accountInfo.settings.sendEmail.recieveQAReply"
|
||||||
|
@update:checked="SaveComboSetting"
|
||||||
|
>
|
||||||
|
提问收到回复时发送邮件
|
||||||
|
</NCheckbox>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
<NDivider> 提问箱 </NDivider>
|
<NDivider> 提问箱 </NDivider>
|
||||||
<NSpace>
|
<NSpace>
|
||||||
<NCheckbox v-model:checked="accountInfo.settings.questionBox.allowUnregistedUser" @update:checked="SaveComboSetting"> 允许未注册用户提问 </NCheckbox>
|
<NCheckbox
|
||||||
|
v-model:checked="accountInfo.settings.questionBox.allowUnregistedUser"
|
||||||
|
@update:checked="SaveComboSetting"
|
||||||
|
>
|
||||||
|
允许未注册用户提问
|
||||||
|
</NCheckbox>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
</NTabPane>
|
</NTabPane>
|
||||||
<NTabPane tab="黑名单" name="blacklist">
|
<NTabPane tab="黑名单" name="blacklist">
|
||||||
@@ -373,22 +420,32 @@ onMounted(async () => {
|
|||||||
<NEmpty v-else />
|
<NEmpty v-else />
|
||||||
</NTabPane>
|
</NTabPane>
|
||||||
<NTabPane tab="模板" name="template">
|
<NTabPane tab="模板" name="template">
|
||||||
<NAlert type="success">
|
<NAlert type="success"> 如果有合适的设计稿或者想法可以给我说然后做成模板捏 </NAlert>
|
||||||
如果有合适的设计稿或者想法可以给我说然后做成模板捏
|
<br />
|
||||||
</NAlert>
|
|
||||||
<br/>
|
|
||||||
<NSpace vertical>
|
<NSpace vertical>
|
||||||
<NSpace align="center"> 页面 <NSelect :options="templateOptions" v-model:value="selectedOption" style="width: 150px" /> </NSpace>
|
<NSpace align="center">
|
||||||
|
页面 <NSelect :options="templateOptions" v-model:value="selectedOption" style="width: 150px" />
|
||||||
|
</NSpace>
|
||||||
<NDivider style="margin: 5px 0 5px 0" title-placement="left"> 模板 </NDivider>
|
<NDivider style="margin: 5px 0 5px 0" title-placement="left"> 模板 </NDivider>
|
||||||
<div>
|
<div>
|
||||||
<NSpace>
|
<NSpace>
|
||||||
<NSelect style="width: 150px" :options="selectedTemplateData.Options" v-model:value="selectedTemplateData.Selected" />
|
<NSelect
|
||||||
|
style="width: 150px"
|
||||||
|
:options="selectedTemplateData.Options"
|
||||||
|
v-model:value="selectedTemplateData.Selected"
|
||||||
|
/>
|
||||||
<component :is="buttonGroup" />
|
<component :is="buttonGroup" />
|
||||||
</NSpace>
|
</NSpace>
|
||||||
<NDivider />
|
<NDivider />
|
||||||
<Transition name="fade" mode="out-in">
|
<Transition name="fade" mode="out-in">
|
||||||
<div v-if="selectedComponent" :key="selectedTemplateData.Selected">
|
<div v-if="selectedComponent" :key="selectedTemplateData.Selected">
|
||||||
<component ref="dynamicConfigRef" :is="selectedComponent" :user-info="accountInfo" :bili-info="biliUserInfo" :current-data="selectedTemplateData.Data" />
|
<component
|
||||||
|
ref="dynamicConfigRef"
|
||||||
|
:is="selectedComponent"
|
||||||
|
:user-info="accountInfo"
|
||||||
|
:bili-info="biliUserInfo"
|
||||||
|
:current-data="selectedTemplateData.Data"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Transition>
|
</Transition>
|
||||||
</div>
|
</div>
|
||||||
@@ -397,9 +454,20 @@ onMounted(async () => {
|
|||||||
</NTabs>
|
</NTabs>
|
||||||
</NSpin>
|
</NSpin>
|
||||||
</NCard>
|
</NCard>
|
||||||
<NModal preset="card" v-model:show="settingModalVisiable" closable style="width: 600px; max-width: 90vw" title="模板设置">
|
<NModal
|
||||||
|
preset="card"
|
||||||
|
v-model:show="settingModalVisiable"
|
||||||
|
closable
|
||||||
|
style="width: 600px; max-width: 90vw"
|
||||||
|
title="模板设置"
|
||||||
|
>
|
||||||
只是测试, 没用
|
只是测试, 没用
|
||||||
<NSpin v-if="!selectedTemplateData.Config" show />
|
<NSpin v-if="!selectedTemplateData.Config" show />
|
||||||
<DynamicForm v-else :key="selectedTemplateData.Selected" :configData="selectedTemplateData.Config" :config="selectedTemplateConfig" />
|
<DynamicForm
|
||||||
|
v-else
|
||||||
|
:key="selectedTemplateData.Selected"
|
||||||
|
:configData="selectedTemplateData.Config"
|
||||||
|
:config="selectedTemplateConfig"
|
||||||
|
/>
|
||||||
</NModal>
|
</NModal>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { objectsToCSV } from '@/Utils'
|
import { objectsToCSV } from '@/Utils'
|
||||||
import { useAccount } from '@/api/account'
|
import { DisableFunction, EnableFunction, useAccount } from '@/api/account'
|
||||||
import { SongFrom, SongLanguage, SongRequestOption, SongsInfo } from '@/api/api-models'
|
import { FunctionTypes, SongFrom, SongLanguage, SongRequestOption, SongsInfo } from '@/api/api-models'
|
||||||
import { QueryGetAPI, QueryPostAPI } from '@/api/query'
|
import { QueryGetAPI, QueryPostAPI } from '@/api/query'
|
||||||
import SongList from '@/components/SongList.vue'
|
import SongList from '@/components/SongList.vue'
|
||||||
import { FETCH_API, SONG_API_URL } from '@/data/constants'
|
import { FETCH_API, SONG_API_URL } from '@/data/constants'
|
||||||
@@ -31,6 +31,7 @@ import {
|
|||||||
NSelect,
|
NSelect,
|
||||||
NSpace,
|
NSpace,
|
||||||
NSpin,
|
NSpin,
|
||||||
|
NSwitch,
|
||||||
NTabPane,
|
NTabPane,
|
||||||
NTable,
|
NTable,
|
||||||
NTabs,
|
NTabs,
|
||||||
@@ -545,6 +546,19 @@ function beforeUpload(data: { file: UploadFileInfo; fileList: UploadFileInfo[] }
|
|||||||
message.error('只能选择xlsx和xls和csv')
|
message.error('只能选择xlsx和xls和csv')
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
async function setFunctionEnable(enable: boolean) {
|
||||||
|
let success = false
|
||||||
|
if (enable) {
|
||||||
|
success = await EnableFunction(FunctionTypes.SongList)
|
||||||
|
} else {
|
||||||
|
success = await DisableFunction(FunctionTypes.SongList)
|
||||||
|
}
|
||||||
|
if (success) {
|
||||||
|
message.success('已' + (enable ? '启用' : '禁用'))
|
||||||
|
} else {
|
||||||
|
message.error('无法' + (enable ? '启用' : '禁用'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await getSongs()
|
await getSongs()
|
||||||
@@ -552,7 +566,15 @@ onMounted(async () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NSpace>
|
<NSpace align="center">
|
||||||
|
<NAlert type="info" style="max-width: 200px">
|
||||||
|
启用歌单
|
||||||
|
<NDivider vertical />
|
||||||
|
<NSwitch
|
||||||
|
:value="accountInfo?.settings.enableFunctions.includes(FunctionTypes.SongList)"
|
||||||
|
@update:value="setFunctionEnable"
|
||||||
|
/>
|
||||||
|
</NAlert>
|
||||||
<NButton @click="showModal = true" type="primary"> 添加歌曲 </NButton>
|
<NButton @click="showModal = true" type="primary"> 添加歌曲 </NButton>
|
||||||
<NButton @click="exportData" type="primary" secondary> 导出为 CSV </NButton>
|
<NButton @click="exportData" type="primary" secondary> 导出为 CSV </NButton>
|
||||||
<NButton
|
<NButton
|
||||||
@@ -833,9 +855,9 @@ onMounted(async () => {
|
|||||||
>
|
>
|
||||||
<NUploadDragger>
|
<NUploadDragger>
|
||||||
<div style="margin-bottom: 12px">
|
<div style="margin-bottom: 12px">
|
||||||
<n-icon size="48" :depth="3">
|
<NIcon size="48" :depth="3">
|
||||||
<ArchiveOutline />
|
<ArchiveOutline />
|
||||||
</n-icon>
|
</NIcon>
|
||||||
</div>
|
</div>
|
||||||
<NText style="font-size: 16px"> 点击或者拖动文件到该区域来上传 </NText>
|
<NText style="font-size: 16px"> 点击或者拖动文件到该区域来上传 </NText>
|
||||||
<NP depth="3" style="margin: 8px 0 0 0"> 仅限 Excel 文件(.xlsx和.xls) 以及 csv 文件 </NP>
|
<NP depth="3" style="margin: 8px 0 0 0"> 仅限 Excel 文件(.xlsx和.xls) 以及 csv 文件 </NP>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { onMounted, onUnmounted } from 'vue'
|
|||||||
import MusicRequest from '../open_live/MusicRequest.vue'
|
import MusicRequest from '../open_live/MusicRequest.vue'
|
||||||
|
|
||||||
const accountInfo = useAccount()
|
const accountInfo = useAccount()
|
||||||
let client = new DanmakuClient(null)
|
const client = new DanmakuClient(null)
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
client.Start()
|
client.Start()
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { downloadImage } from '@/Utils'
|
import { downloadImage } from '@/Utils'
|
||||||
import { VideoCollectCreateModel, VideoCollectDetail, VideoCollectTable, VideoCollectVideo, VideoInfo, VideoStatus } from '@/api/api-models'
|
import {
|
||||||
|
VideoCollectCreateModel,
|
||||||
|
VideoCollectDetail,
|
||||||
|
VideoCollectTable,
|
||||||
|
VideoCollectVideo,
|
||||||
|
VideoInfo,
|
||||||
|
VideoStatus,
|
||||||
|
} 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 { VIDEO_COLLECT_API_URL } from '@/data/constants'
|
||||||
@@ -140,10 +147,7 @@ const gridRender = (type: 'padding' | 'reject' | 'accept') => {
|
|||||||
}
|
}
|
||||||
return videos.length == 0
|
return videos.length == 0
|
||||||
? h(NEmpty)
|
? h(NEmpty)
|
||||||
: h(
|
: h(NGrid, { cols: '1 500:2 700:3 900:4 1200:5 ', xGap: '12', yGap: '12', responsive: 'self' }, () =>
|
||||||
NGrid,
|
|
||||||
{ cols: '1 500:2 700:3 900:4 1200:5 ', xGap: '12', yGap: '12', responsive: 'self' },
|
|
||||||
() =>
|
|
||||||
videos?.map((v) =>
|
videos?.map((v) =>
|
||||||
h(NGridItem, () =>
|
h(NGridItem, () =>
|
||||||
h(
|
h(
|
||||||
@@ -158,20 +162,50 @@ const gridRender = (type: 'padding' | 'reject' | 'accept') => {
|
|||||||
style: 'max-height: 100%; object-fit: contain;cursor: pointer',
|
style: 'max-height: 100%; object-fit: contain;cursor: pointer',
|
||||||
onClick: () => window.open('https://www.bilibili.com/video/' + v.info.bvid, '_blank'),
|
onClick: () => window.open('https://www.bilibili.com/video/' + v.info.bvid, '_blank'),
|
||||||
}),
|
}),
|
||||||
h(NSpace, { style: { position: 'relative', bottom: '20px', background: '#00000073' }, justify: 'space-around' }, () => [
|
h(
|
||||||
h('span', [h(NIcon, { component: Clock24Filled, color: 'lightgrey' }), h(NText, { style: 'color: lightgrey;size:small;' }, () => formatSeconds(v.video.length))]),
|
NSpace,
|
||||||
h('span', [h(NIcon, { component: Person24Filled, color: 'lightgrey' }), h(NText, { style: 'color: lightgrey;size:small;' }, () => v.video.ownerName)]),
|
{
|
||||||
|
style: { position: 'relative', bottom: '20px', background: '#00000073' },
|
||||||
|
justify: 'space-around',
|
||||||
|
},
|
||||||
|
() => [
|
||||||
|
h('span', [
|
||||||
|
h(NIcon, { component: Clock24Filled, color: 'lightgrey' }),
|
||||||
|
h(NText, { style: 'color: lightgrey;size:small;' }, () => formatSeconds(v.video.length)),
|
||||||
]),
|
]),
|
||||||
|
h('span', [
|
||||||
|
h(NIcon, { component: Person24Filled, color: 'lightgrey' }),
|
||||||
|
h(NText, { style: 'color: lightgrey;size:small;' }, () => v.video.ownerName),
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
),
|
||||||
]),
|
]),
|
||||||
header: () =>
|
header: () =>
|
||||||
h(NButton, { style: 'width: 100%;', text: true, onClick: () => window.open('https://www.bilibili.com/video/' + v.info.bvid, '_blank') }, () =>
|
h(
|
||||||
h(NEllipsis, { style: 'max-width: 100%;' }, { default: () => v.video.title, tooltip: () => h('div', { style: 'max-width: 300px' }, v.video.title) }),
|
NButton,
|
||||||
|
{
|
||||||
|
style: 'width: 100%;',
|
||||||
|
text: true,
|
||||||
|
onClick: () => window.open('https://www.bilibili.com/video/' + v.info.bvid, '_blank'),
|
||||||
|
},
|
||||||
|
() =>
|
||||||
|
h(
|
||||||
|
NEllipsis,
|
||||||
|
{ style: 'max-width: 100%;' },
|
||||||
|
{
|
||||||
|
default: () => v.video.title,
|
||||||
|
tooltip: () => h('div', { style: 'max-width: 300px' }, v.video.title),
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
default: () =>
|
default: () =>
|
||||||
h(NScrollbar, { style: 'height: 65px;' }, () =>
|
h(NScrollbar, { style: 'height: 65px;' }, () =>
|
||||||
h(NCard, { contentStyle: 'padding: 5px;' }, () =>
|
h(NCard, { contentStyle: 'padding: 5px;' }, () =>
|
||||||
v.info.senders.map((s) => [
|
v.info.senders.map((s) => [
|
||||||
h('div', { style: 'font-size: 12px;' }, [h('div', `推荐人: ${s.sender ?? '未填写'} [${s.senderId ?? '未填写'}]`), h('div', `推荐理由: ${s.description ?? '未填写'}`)]),
|
h('div', { style: 'font-size: 12px;' }, [
|
||||||
|
h('div', `推荐人: ${s.sender ?? '未填写'} [${s.senderId ?? '未填写'}]`),
|
||||||
|
h('div', `推荐理由: ${s.description ?? '未填写'}`),
|
||||||
|
]),
|
||||||
h(NSpace, { style: 'margin: 0;' }),
|
h(NSpace, { style: 'margin: 0;' }),
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
@@ -185,18 +219,42 @@ const gridRender = (type: 'padding' | 'reject' | 'accept') => {
|
|||||||
}
|
}
|
||||||
const paddingButtonGroup = (v: VideoInfo) =>
|
const paddingButtonGroup = (v: VideoInfo) =>
|
||||||
h(NSpace, { size: 'small', justify: 'space-around' }, () => [
|
h(NSpace, { size: 'small', justify: 'space-around' }, () => [
|
||||||
h(NButton, { type: 'success', loading: isLoading.value, onClick: () => setStatus(VideoStatus.Accepted, v) }, () => '通过'),
|
h(
|
||||||
h(NButton, { type: 'error', loading: isLoading.value, onClick: () => setStatus(VideoStatus.Rejected, v) }, () => '拒绝'),
|
NButton,
|
||||||
|
{ type: 'success', loading: isLoading.value, onClick: () => setStatus(VideoStatus.Accepted, v) },
|
||||||
|
() => '通过',
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
NButton,
|
||||||
|
{ type: 'error', loading: isLoading.value, onClick: () => setStatus(VideoStatus.Rejected, v) },
|
||||||
|
() => '拒绝',
|
||||||
|
),
|
||||||
])
|
])
|
||||||
const acceptButtonGroup = (v: VideoInfo) =>
|
const acceptButtonGroup = (v: VideoInfo) =>
|
||||||
h(NSpace, { size: 'small', justify: 'space-around' }, () => [
|
h(NSpace, { size: 'small', justify: 'space-around' }, () => [
|
||||||
h(NButton, { type: 'info', loading: isLoading.value, onClick: () => setStatus(VideoStatus.Pending, v) }, () => '重设为未审核'),
|
h(
|
||||||
h(NButton, { type: 'error', loading: isLoading.value, onClick: () => setStatus(VideoStatus.Rejected, v) }, () => '拒绝'),
|
NButton,
|
||||||
|
{ type: 'info', loading: isLoading.value, onClick: () => setStatus(VideoStatus.Pending, v) },
|
||||||
|
() => '重设为未审核',
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
NButton,
|
||||||
|
{ type: 'error', loading: isLoading.value, onClick: () => setStatus(VideoStatus.Rejected, v) },
|
||||||
|
() => '拒绝',
|
||||||
|
),
|
||||||
])
|
])
|
||||||
const rejectButtonGroup = (v: VideoInfo) =>
|
const rejectButtonGroup = (v: VideoInfo) =>
|
||||||
h(NSpace, { size: 'small', justify: 'space-around' }, () => [
|
h(NSpace, { size: 'small', justify: 'space-around' }, () => [
|
||||||
h(NButton, { type: 'success', loading: isLoading.value, onClick: () => setStatus(VideoStatus.Accepted, v) }, () => '通过'),
|
h(
|
||||||
h(NButton, { type: 'info', loading: isLoading.value, onClick: () => setStatus(VideoStatus.Pending, v) }, () => '重设为未审核'),
|
NButton,
|
||||||
|
{ type: 'success', loading: isLoading.value, onClick: () => setStatus(VideoStatus.Accepted, v) },
|
||||||
|
() => '通过',
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
NButton,
|
||||||
|
{ type: 'info', loading: isLoading.value, onClick: () => setStatus(VideoStatus.Pending, v) },
|
||||||
|
() => '重设为未审核',
|
||||||
|
),
|
||||||
])
|
])
|
||||||
function setStatus(status: VideoStatus, video: VideoInfo) {
|
function setStatus(status: VideoStatus, video: VideoInfo) {
|
||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
@@ -295,7 +353,10 @@ function closeTable() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
function saveQRCode() {
|
function saveQRCode() {
|
||||||
downloadImage(`https://api.qrserver.com/v1/create-qr-code/?data=${'https://vtsuru.live/video-collect/' + videoDetail.value.table.shortId}`, `vtsuru-视频征集二维码-${videoDetail.value.table.name}.png`)
|
downloadImage(
|
||||||
|
`https://api.qrserver.com/v1/create-qr-code/?data=${'https://vtsuru.live/video-collect/' + videoDetail.value.table.shortId}`,
|
||||||
|
`vtsuru-视频征集二维码-${videoDetail.value.table.name}.png`,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
@@ -306,8 +367,12 @@ function saveQRCode() {
|
|||||||
<template v-if="width <= 1000">
|
<template v-if="width <= 1000">
|
||||||
<NButton type="success" size="small" @click="shareModalVisiable = true"> 分享 </NButton>
|
<NButton type="success" size="small" @click="shareModalVisiable = true"> 分享 </NButton>
|
||||||
<NButton type="info" size="small" @click="editModalVisiable = true"> 更新 </NButton>
|
<NButton type="info" size="small" @click="editModalVisiable = true"> 更新 </NButton>
|
||||||
<NButton type="warning" size="small" @click="closeTable"> {{ videoDetail.table.isFinish ? '开启表' : '关闭表' }} </NButton>
|
<NButton type="warning" size="small" @click="closeTable">
|
||||||
<NButton size="small" @click="$router.push({ name: 'video-collect-list', params: { id: videoDetail.table.id } })"> 结果页面 </NButton>
|
{{ videoDetail.table.isFinish ? '开启表' : '关闭表' }}
|
||||||
|
</NButton>
|
||||||
|
<NButton size="small" @click="$router.push({ name: 'video-collect-list', params: { id: videoDetail.table.id } })">
|
||||||
|
结果页面
|
||||||
|
</NButton>
|
||||||
<NPopconfirm :on-positive-click="deleteTable">
|
<NPopconfirm :on-positive-click="deleteTable">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<NButton type="error" size="small"> 删除 </NButton>
|
<NButton type="error" size="small"> 删除 </NButton>
|
||||||
@@ -321,8 +386,15 @@ function saveQRCode() {
|
|||||||
<NSpace>
|
<NSpace>
|
||||||
<NButton type="success" size="small" @click="shareModalVisiable = true"> 分享 </NButton>
|
<NButton type="success" size="small" @click="shareModalVisiable = true"> 分享 </NButton>
|
||||||
<NButton type="info" size="small" @click="editModalVisiable = true"> 更新 </NButton>
|
<NButton type="info" size="small" @click="editModalVisiable = true"> 更新 </NButton>
|
||||||
<NButton type="warning" size="small" @click="closeTable"> {{ videoDetail.table.isFinish ? '开启表' : '关闭表' }} </NButton>
|
<NButton type="warning" size="small" @click="closeTable">
|
||||||
<NButton size="small" @click="$router.push({ name: 'video-collect-list', params: { id: videoDetail.table.id } })"> 结果表 </NButton>
|
{{ videoDetail.table.isFinish ? '开启表' : '关闭表' }}
|
||||||
|
</NButton>
|
||||||
|
<NButton
|
||||||
|
size="small"
|
||||||
|
@click="$router.push({ name: 'video-collect-list', params: { id: videoDetail.table.id } })"
|
||||||
|
>
|
||||||
|
结果表
|
||||||
|
</NButton>
|
||||||
<NPopconfirm :on-positive-click="deleteTable">
|
<NPopconfirm :on-positive-click="deleteTable">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<NButton type="error" size="small"> 删除 </NButton>
|
<NButton type="error" size="small"> 删除 </NButton>
|
||||||
@@ -369,9 +441,15 @@ function saveQRCode() {
|
|||||||
</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 :value="'https://vtsuru.live/video-collect/' + videoDetail.table.shortId" level="Q" :size="100" background="#fff" :margin="1" />
|
<Qrcode
|
||||||
|
:value="'https://vtsuru.live/video-collect/' + videoDetail.table.shortId"
|
||||||
|
level="Q"
|
||||||
|
:size="100"
|
||||||
|
background="#fff"
|
||||||
|
:margin="1"
|
||||||
|
/>
|
||||||
<NInput :value="'https://vtsuru.live/video-collect/' + videoDetail.table.shortId" />
|
<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>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
@@ -385,10 +463,20 @@ function saveQRCode() {
|
|||||||
<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 v-model:value="updateModel.maxVideoCount" placeholder="最大数量" type="number" style="max-width: 150px" />
|
<NInputNumber
|
||||||
|
v-model:value="updateModel.maxVideoCount"
|
||||||
|
placeholder="最大数量"
|
||||||
|
type="number"
|
||||||
|
style="max-width: 150px"
|
||||||
|
/>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
<NFormItem label="结束时间" path="endAt">
|
<NFormItem label="结束时间" path="endAt">
|
||||||
<NDatePicker v-model:value="updateModel.endAt" type="datetime" placeholder="结束征集的时间" :isDateDisabled="dateDisabled" />
|
<NDatePicker
|
||||||
|
v-model:value="updateModel.endAt"
|
||||||
|
type="datetime"
|
||||||
|
placeholder="结束征集的时间"
|
||||||
|
:isDateDisabled="dateDisabled"
|
||||||
|
/>
|
||||||
<NDivider vertical />
|
<NDivider vertical />
|
||||||
<NText depth="3"> 最低为一小时 </NText>
|
<NText depth="3"> 最低为一小时 </NText>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
|
|||||||
@@ -4,7 +4,24 @@ import { VideoCollectTable } 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 { VIDEO_COLLECT_API_URL } from '@/data/constants'
|
||||||
import { FormRules, NButton, NDatePicker, NDivider, NEmpty, NForm, NFormItem, NInput, NInputNumber, NList, NListItem, NModal, NSpace, NSpin, NText, useMessage } from 'naive-ui'
|
import {
|
||||||
|
FormRules,
|
||||||
|
NButton,
|
||||||
|
NDatePicker,
|
||||||
|
NDivider,
|
||||||
|
NEmpty,
|
||||||
|
NForm,
|
||||||
|
NFormItem,
|
||||||
|
NInput,
|
||||||
|
NInputNumber,
|
||||||
|
NList,
|
||||||
|
NListItem,
|
||||||
|
NModal,
|
||||||
|
NSpace,
|
||||||
|
NSpin,
|
||||||
|
NText,
|
||||||
|
useMessage,
|
||||||
|
} from 'naive-ui'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
|
||||||
const accountInfo = useAccount()
|
const accountInfo = useAccount()
|
||||||
@@ -128,13 +145,28 @@ function createTable() {
|
|||||||
<NInput v-model:value="createVideoModel.name" placeholder="征集表的标题" maxlength="30" show-count />
|
<NInput v-model:value="createVideoModel.name" placeholder="征集表的标题" maxlength="30" show-count />
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
<NFormItem label="描述" path="description">
|
<NFormItem label="描述" path="description">
|
||||||
<NInput v-model:value="createVideoModel.description" placeholder="可以是备注之类的" maxlength="300" show-count />
|
<NInput
|
||||||
|
v-model:value="createVideoModel.description"
|
||||||
|
placeholder="可以是备注之类的"
|
||||||
|
maxlength="300"
|
||||||
|
show-count
|
||||||
|
/>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
<NFormItem label="视频数量" path="maxVideoCount">
|
<NFormItem label="视频数量" path="maxVideoCount">
|
||||||
<NInputNumber v-model:value="createVideoModel.maxVideoCount" placeholder="最大数量" type="number" style="max-width: 150px" />
|
<NInputNumber
|
||||||
|
v-model:value="createVideoModel.maxVideoCount"
|
||||||
|
placeholder="最大数量"
|
||||||
|
type="number"
|
||||||
|
style="max-width: 150px"
|
||||||
|
/>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
<NFormItem label="结束时间" path="endAt">
|
<NFormItem label="结束时间" path="endAt">
|
||||||
<NDatePicker v-model:value="createVideoModel.endAt" type="datetime" placeholder="结束征集的时间" :isDateDisabled="dateDisabled" />
|
<NDatePicker
|
||||||
|
v-model:value="createVideoModel.endAt"
|
||||||
|
type="datetime"
|
||||||
|
placeholder="结束征集的时间"
|
||||||
|
:isDateDisabled="dateDisabled"
|
||||||
|
/>
|
||||||
<NDivider vertical />
|
<NDivider vertical />
|
||||||
<NText depth="3"> 最低为一小时 </NText>
|
<NText depth="3"> 最低为一小时 </NText>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
|
|||||||
@@ -1,62 +1,50 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { getBase64, getImageUploadModel } from '@/Utils'
|
import { getImageUploadModel } from '@/Utils'
|
||||||
import { DisableFunction, EnableFunction, useAccount } from '@/api/account'
|
import { DisableFunction, EnableFunction, useAccount } from '@/api/account'
|
||||||
import {
|
import { FunctionTypes, GoodsStatus, GoodsTypes, PointGoodsModel, ResponsePointGoodModel } from '@/api/api-models'
|
||||||
ResponsePointGoodModel,
|
|
||||||
FunctionTypes,
|
|
||||||
PointGoodsModel,
|
|
||||||
GoodsTypes,
|
|
||||||
GoodsStatus,
|
|
||||||
TagInfo,
|
|
||||||
} from '@/api/api-models'
|
|
||||||
import { QueryGetAPI, QueryPostAPI } from '@/api/query'
|
import { QueryGetAPI, QueryPostAPI } from '@/api/query'
|
||||||
|
import EventFetcherStatusCard from '@/components/EventFetcherStatusCard.vue'
|
||||||
import PointGoodsItem from '@/components/manage/PointGoodsItem.vue'
|
import PointGoodsItem from '@/components/manage/PointGoodsItem.vue'
|
||||||
import { FILE_BASE_URL, POINT_API_URL } from '@/data/constants'
|
import { FILE_BASE_URL, POINT_API_URL } from '@/data/constants'
|
||||||
import { Info24Filled, MoreVertical16Filled } from '@vicons/fluent'
|
import { useAuthStore } from '@/store/useAuthStore'
|
||||||
import { List } from 'linqts'
|
import { Info24Filled } from '@vicons/fluent'
|
||||||
|
import { useRouteHash } from '@vueuse/router'
|
||||||
import {
|
import {
|
||||||
|
FormItemRule,
|
||||||
NAlert,
|
NAlert,
|
||||||
NButton,
|
NButton,
|
||||||
|
NCheckbox,
|
||||||
NDivider,
|
NDivider,
|
||||||
|
NEmpty,
|
||||||
|
NFlex,
|
||||||
NForm,
|
NForm,
|
||||||
NFormItem,
|
NFormItem,
|
||||||
|
NGrid,
|
||||||
|
NGridItem,
|
||||||
|
NIcon,
|
||||||
|
NImage,
|
||||||
NInput,
|
NInput,
|
||||||
|
NInputNumber,
|
||||||
NModal,
|
NModal,
|
||||||
|
NPopconfirm,
|
||||||
|
NRadioButton,
|
||||||
|
NRadioGroup,
|
||||||
|
NScrollbar,
|
||||||
|
NSelect,
|
||||||
NSwitch,
|
NSwitch,
|
||||||
NTabPane,
|
NTabPane,
|
||||||
NTabs,
|
NTabs,
|
||||||
NText,
|
NText,
|
||||||
useMessage,
|
|
||||||
NFlex,
|
|
||||||
NInputNumber,
|
|
||||||
NRadioGroup,
|
|
||||||
NRadio,
|
|
||||||
NTooltip,
|
NTooltip,
|
||||||
NIcon,
|
|
||||||
NCheckbox,
|
|
||||||
NRadioButton,
|
|
||||||
NUpload,
|
NUpload,
|
||||||
UploadFileInfo,
|
UploadFileInfo,
|
||||||
FormItemRule,
|
|
||||||
NScrollbar,
|
|
||||||
FormValidationError,
|
|
||||||
NSelect,
|
|
||||||
NGrid,
|
|
||||||
NGridItem,
|
|
||||||
NDropdown,
|
|
||||||
NImage,
|
|
||||||
useDialog,
|
useDialog,
|
||||||
NPopconfirm,
|
useMessage,
|
||||||
NEmpty,
|
|
||||||
} from 'naive-ui'
|
} from 'naive-ui'
|
||||||
import { computed, onMounted, ref } from 'vue'
|
import { computed, onMounted, ref } from 'vue'
|
||||||
import PointOrderManage from './PointOrderManage.vue'
|
import PointOrderManage from './PointOrderManage.vue'
|
||||||
import PointUserManage from './PointUserManage.vue'
|
|
||||||
import { cloneFnJSON } from '@vueuse/core'
|
|
||||||
import { useAuthStore } from '@/store/useAuthStore'
|
|
||||||
import PointSettings from './PointSettings.vue'
|
import PointSettings from './PointSettings.vue'
|
||||||
import { useRouteHash } from '@vueuse/router'
|
import PointUserManage from './PointUserManage.vue'
|
||||||
import EventFetcherStatusCard from '@/components/EventFetcherStatusCard.vue'
|
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
const accountInfo = useAccount()
|
const accountInfo = useAccount()
|
||||||
@@ -240,7 +228,7 @@ async function updateGoods(e: MouseEvent) {
|
|||||||
}
|
}
|
||||||
function OnFileListChange(files: UploadFileInfo[]) {
|
function OnFileListChange(files: UploadFileInfo[]) {
|
||||||
if (files.length == 1) {
|
if (files.length == 1) {
|
||||||
var file = files[0]
|
const file = files[0]
|
||||||
if ((file.file?.size ?? 0) > 10 * 1024 * 1024) {
|
if ((file.file?.size ?? 0) > 10 * 1024 * 1024) {
|
||||||
message.error('文件大小不能超过10MB')
|
message.error('文件大小不能超过10MB')
|
||||||
currentGoodsModel.value.fileList = []
|
currentGoodsModel.value.fileList = []
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import { ResponsePointGoodModel, ResponsePointOrder2OwnerModel } from '@/api/api
|
|||||||
import { QueryGetAPI } from '@/api/query'
|
import { QueryGetAPI } from '@/api/query'
|
||||||
import PointOrderCard from '@/components/manage/PointOrderCard.vue'
|
import PointOrderCard from '@/components/manage/PointOrderCard.vue'
|
||||||
import { POINT_API_URL } from '@/data/constants'
|
import { POINT_API_URL } from '@/data/constants'
|
||||||
import { NButton, NCard, NEmpty, NList, NListItem, useMessage } from 'naive-ui'
|
import { NEmpty, useMessage } from 'naive-ui'
|
||||||
import { h, onMounted, ref } from 'vue'
|
import { onMounted, ref } from 'vue'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
goods: ResponsePointGoodModel[]
|
goods: ResponsePointGoodModel[]
|
||||||
|
|||||||
@@ -188,13 +188,20 @@ async function updateGift() {
|
|||||||
</template>
|
</template>
|
||||||
<NCard>
|
<NCard>
|
||||||
<NFlex vertical>
|
<NFlex vertical>
|
||||||
<NButton @click="showAddGiftModal = true" type="primary" :disabled="!canEdit" style="max-width: 200px"> 添加礼物 </NButton>
|
<NButton @click="showAddGiftModal = true" type="primary" :disabled="!canEdit" style="max-width: 200px">
|
||||||
|
添加礼物
|
||||||
|
</NButton>
|
||||||
<NList bordered>
|
<NList bordered>
|
||||||
<NListItem v-for="item in Object.entries(setting.giftPercentMap)" :key="item[0]">
|
<NListItem v-for="item in Object.entries(setting.giftPercentMap)" :key="item[0]">
|
||||||
<NFlex align="center">
|
<NFlex align="center">
|
||||||
<NTag :bordered="false" size="small" type="success"> {{ item[0] }} </NTag>
|
<NTag :bordered="false" size="small" type="success"> {{ item[0] }} </NTag>
|
||||||
<NInputGroup style="width: 200px" :disabled="!canEdit">
|
<NInputGroup style="width: 200px" :disabled="!canEdit">
|
||||||
<NInputNumber :value="setting.giftPercentMap[item[0]]" @update:value="(v) => (setting.giftPercentMap[item[0]] = v ?? 0)" :disabled="!canEdit" min="0" />
|
<NInputNumber
|
||||||
|
:value="setting.giftPercentMap[item[0]]"
|
||||||
|
@update:value="(v) => (setting.giftPercentMap[item[0]] = v ?? 0)"
|
||||||
|
:disabled="!canEdit"
|
||||||
|
min="0"
|
||||||
|
/>
|
||||||
<NButton @click="updateSettings" type="info" :disabled="!canEdit">确定</NButton>
|
<NButton @click="updateSettings" type="info" :disabled="!canEdit">确定</NButton>
|
||||||
</NInputGroup>
|
</NInputGroup>
|
||||||
<NPopconfirm @positive-click="deleteGift(item[0])">
|
<NPopconfirm @positive-click="deleteGift(item[0])">
|
||||||
|
|||||||
@@ -9,12 +9,9 @@ import { QueryGetAPI } from '@/api/query'
|
|||||||
import PointHistoryCard from '@/components/manage/PointHistoryCard.vue'
|
import PointHistoryCard from '@/components/manage/PointHistoryCard.vue'
|
||||||
import PointOrderCard from '@/components/manage/PointOrderCard.vue'
|
import PointOrderCard from '@/components/manage/PointOrderCard.vue'
|
||||||
import { POINT_API_URL } from '@/data/constants'
|
import { POINT_API_URL } from '@/data/constants'
|
||||||
import { useAuthStore } from '@/store/useAuthStore'
|
|
||||||
import {
|
import {
|
||||||
DataTableColumns,
|
|
||||||
NButton,
|
NButton,
|
||||||
NCard,
|
NCard,
|
||||||
NDataTable,
|
|
||||||
NDescriptions,
|
NDescriptions,
|
||||||
NDescriptionsItem,
|
NDescriptionsItem,
|
||||||
NDivider,
|
NDivider,
|
||||||
@@ -22,8 +19,6 @@ import {
|
|||||||
NFlex,
|
NFlex,
|
||||||
NInput,
|
NInput,
|
||||||
NInputNumber,
|
NInputNumber,
|
||||||
NList,
|
|
||||||
NListItem,
|
|
||||||
NModal,
|
NModal,
|
||||||
NSpin,
|
NSpin,
|
||||||
NTag,
|
NTag,
|
||||||
@@ -32,7 +27,7 @@ import {
|
|||||||
NTooltip,
|
NTooltip,
|
||||||
useMessage,
|
useMessage,
|
||||||
} from 'naive-ui'
|
} from 'naive-ui'
|
||||||
import { h, onMounted, ref } from 'vue'
|
import { onMounted, ref } from 'vue'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
user: ResponsePointUserModel
|
user: ResponsePointUserModel
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import { ResponsePointGoodModel, ResponsePointUserModel } from '@/api/api-models'
|
import { ResponsePointGoodModel, ResponsePointUserModel } from '@/api/api-models'
|
||||||
import { QueryGetAPI } from '@/api/query'
|
import { QueryGetAPI } from '@/api/query'
|
||||||
import { POINT_API_URL } from '@/data/constants'
|
import { POINT_API_URL } from '@/data/constants'
|
||||||
|
import { useStorage } from '@vueuse/core'
|
||||||
import {
|
import {
|
||||||
DataTableColumns,
|
DataTableColumns,
|
||||||
NButton,
|
NButton,
|
||||||
@@ -11,8 +12,6 @@ import {
|
|||||||
NDivider,
|
NDivider,
|
||||||
NEmpty,
|
NEmpty,
|
||||||
NFlex,
|
NFlex,
|
||||||
NList,
|
|
||||||
NListItem,
|
|
||||||
NModal,
|
NModal,
|
||||||
NPopconfirm,
|
NPopconfirm,
|
||||||
NScrollbar,
|
NScrollbar,
|
||||||
@@ -24,7 +23,6 @@ import {
|
|||||||
} from 'naive-ui'
|
} from 'naive-ui'
|
||||||
import { computed, h, onMounted, ref } from 'vue'
|
import { computed, h, onMounted, ref } from 'vue'
|
||||||
import PointUserDetailCard from './PointUserDetailCard.vue'
|
import PointUserDetailCard from './PointUserDetailCard.vue'
|
||||||
import { useStorage } from '@vueuse/core'
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
goods: ResponsePointGoodModel[]
|
goods: ResponsePointGoodModel[]
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { OpenLiveLotteryType, OpenLiveLotteryUserInfo, UpdateLiveLotteryUsersModel } from '@/api/api-models'
|
import { OpenLiveLotteryType, UpdateLiveLotteryUsersModel } from '@/api/api-models'
|
||||||
import { QueryGetAPI } from '@/api/query'
|
import { QueryGetAPI } from '@/api/query'
|
||||||
import { LOTTERY_API_URL } from '@/data/constants'
|
import { LOTTERY_API_URL } from '@/data/constants'
|
||||||
import { useElementSize } from '@vueuse/core'
|
import { useElementSize } from '@vueuse/core'
|
||||||
import { NCard, NDivider, NEmpty, NSpace, NText, useMessage } from 'naive-ui'
|
import { NDivider, NEmpty, NSpace, NText, useMessage } from 'naive-ui'
|
||||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import { Vue3Marquee } from 'vue3-marquee'
|
import { Vue3Marquee } from 'vue3-marquee'
|
||||||
@@ -66,8 +66,20 @@ onUnmounted(() => {
|
|||||||
</NDivider>
|
</NDivider>
|
||||||
<div class="lottery-content" ref="listContainerRef">
|
<div class="lottery-content" ref="listContainerRef">
|
||||||
<template v-if="users.length > 0">
|
<template v-if="users.length > 0">
|
||||||
<Vue3Marquee v-if="result.type == OpenLiveLotteryType.Waiting" vertical :pause="!isMoreThanContainer" :duration="20" :style="`height: ${height}px;`">
|
<Vue3Marquee
|
||||||
<span class="lottery-list-item" :id="index.toString()" v-for="(user, index) in users" :key="user.uId" style="height: 50px">
|
v-if="result.type == OpenLiveLotteryType.Waiting"
|
||||||
|
vertical
|
||||||
|
:pause="!isMoreThanContainer"
|
||||||
|
:duration="20"
|
||||||
|
:style="`height: ${height}px;`"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="lottery-list-item"
|
||||||
|
:id="index.toString()"
|
||||||
|
v-for="(user, index) in users"
|
||||||
|
:key="user.uId"
|
||||||
|
style="height: 50px"
|
||||||
|
>
|
||||||
<img class="lottery-avatar" :src="user.avatar + '@30h'" referrerpolicy="no-referrer" />
|
<img class="lottery-avatar" :src="user.avatar + '@30h'" referrerpolicy="no-referrer" />
|
||||||
<div>
|
<div>
|
||||||
<p class="lottery-name">{{ user.name }}</p>
|
<p class="lottery-name">{{ user.name }}</p>
|
||||||
@@ -85,10 +97,26 @@ onUnmounted(() => {
|
|||||||
v-for="user in result.resultUsers"
|
v-for="user in result.resultUsers"
|
||||||
:key="user.uId"
|
:key="user.uId"
|
||||||
title="抽奖结果"
|
title="抽奖结果"
|
||||||
style="height: 100px; width: 100px; display: flex; flex-direction: column; align-items: center; border-radius: 5px; border: #fff 1px solid; padding: 10px; margin: 10px"
|
style="
|
||||||
|
height: 100px;
|
||||||
|
width: 100px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 5px;
|
||||||
|
border: #fff 1px solid;
|
||||||
|
padding: 10px;
|
||||||
|
margin: 10px;
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<NSpace vertical>
|
<NSpace vertical>
|
||||||
<img height="50" width="50" style="border-radius: 50%" :src="user.avatar + '@50h_50w'" referrerpolicy="no-referrer" />
|
<img
|
||||||
|
height="50"
|
||||||
|
width="50"
|
||||||
|
style="border-radius: 50%"
|
||||||
|
:src="user.avatar + '@50h_50w'"
|
||||||
|
referrerpolicy="no-referrer"
|
||||||
|
/>
|
||||||
<NText style="font-size: large">
|
<NText style="font-size: large">
|
||||||
{{ user.name }}
|
{{ user.name }}
|
||||||
</NText>
|
</NText>
|
||||||
@@ -100,9 +128,25 @@ onUnmounted(() => {
|
|||||||
v-for="user in result.resultUsers"
|
v-for="user in result.resultUsers"
|
||||||
:key="user.uId"
|
:key="user.uId"
|
||||||
title="抽奖结果"
|
title="抽奖结果"
|
||||||
style="height: 100px; width: 100px; display: flex; flex-direction: column; align-items: center; border-radius: 5px; border: #fff 1px solid; padding: 10px; margin: 10px"
|
style="
|
||||||
|
height: 100px;
|
||||||
|
width: 100px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 5px;
|
||||||
|
border: #fff 1px solid;
|
||||||
|
padding: 10px;
|
||||||
|
margin: 10px;
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<img height="50" width="50" style="border-radius: 50%" :src="user.avatar + '@50h_50w'" referrerpolicy="no-referrer" />
|
<img
|
||||||
|
height="50"
|
||||||
|
width="50"
|
||||||
|
style="border-radius: 50%"
|
||||||
|
:src="user.avatar + '@50h_50w'"
|
||||||
|
referrerpolicy="no-referrer"
|
||||||
|
/>
|
||||||
<NText style="font-size: large; margin-top: 10px">
|
<NText style="font-size: large; margin-top: 10px">
|
||||||
{{ user.name }}
|
{{ user.name }}
|
||||||
</NText>
|
</NText>
|
||||||
@@ -131,7 +175,11 @@ onUnmounted(() => {
|
|||||||
text-align: center !important;
|
text-align: center !important;
|
||||||
font-size: 24px !important;
|
font-size: 24px !important;
|
||||||
font-weight: bold !important;
|
font-weight: bold !important;
|
||||||
text-shadow: 0 0 10px #ca7b7b6e, 0 0 20px #ffffff8e, 0 0 30px #61606086, 0 0 40px rgba(64, 156, 179, 0.555) !important;
|
text-shadow:
|
||||||
|
0 0 10px #ca7b7b6e,
|
||||||
|
0 0 20px #ffffff8e,
|
||||||
|
0 0 30px #61606086,
|
||||||
|
0 0 40px rgba(64, 156, 179, 0.555) !important;
|
||||||
}
|
}
|
||||||
.lottery-header-count {
|
.lottery-header-count {
|
||||||
color: #ffffffbd !important;
|
color: #ffffffbd !important;
|
||||||
@@ -156,7 +204,9 @@ onUnmounted(() => {
|
|||||||
display: flex !important;
|
display: flex !important;
|
||||||
align-items: center !important;
|
align-items: center !important;
|
||||||
gap: 10px !important;
|
gap: 10px !important;
|
||||||
transition: color 0.3s cubic-bezier(0.4, 0, 0.2, 1), background-color 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
|
transition:
|
||||||
|
color 0.3s cubic-bezier(0.4, 0, 0.2, 1),
|
||||||
|
background-color 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
|
||||||
}
|
}
|
||||||
.lottery-avatar {
|
.lottery-avatar {
|
||||||
height: 30px !important;
|
height: 30px !important;
|
||||||
|
|||||||
@@ -39,9 +39,12 @@ const originSongs = ref<{ playing?: WaitMusicInfo; waiting: WaitMusicInfo[] }>({
|
|||||||
|
|
||||||
async function get() {
|
async function get() {
|
||||||
try {
|
try {
|
||||||
const data = await QueryGetAPI<{ playing: WaitMusicInfo; waiting: WaitMusicInfo[] }>(MUSIC_REQUEST_API_URL + 'get-waiting', {
|
const data = await QueryGetAPI<{ playing: WaitMusicInfo; waiting: WaitMusicInfo[] }>(
|
||||||
|
MUSIC_REQUEST_API_URL + 'get-waiting',
|
||||||
|
{
|
||||||
id: currentId.value,
|
id: currentId.value,
|
||||||
})
|
},
|
||||||
|
)
|
||||||
if (data.code == 200) {
|
if (data.code == 200) {
|
||||||
return data.data
|
return data.data
|
||||||
}
|
}
|
||||||
@@ -74,10 +77,18 @@ onUnmounted(() => {
|
|||||||
<NDivider class="music-request-divider">
|
<NDivider class="music-request-divider">
|
||||||
<p class="music-request-header-count">已有 {{ originSongs.waiting.length ?? 0 }} 首</p>
|
<p class="music-request-header-count">已有 {{ originSongs.waiting.length ?? 0 }} 首</p>
|
||||||
</NDivider>
|
</NDivider>
|
||||||
<div class="music-request-singing-container" :playing="originSongs.playing ? 'true' : 'false'" :from="originSongs.playing?.music.from ?? -1">
|
<div
|
||||||
|
class="music-request-singing-container"
|
||||||
|
:playing="originSongs.playing ? 'true' : 'false'"
|
||||||
|
:from="originSongs.playing?.music.from ?? -1"
|
||||||
|
>
|
||||||
<div class="music-request-singing-prefix"></div>
|
<div class="music-request-singing-prefix"></div>
|
||||||
<template v-if="originSongs.playing">
|
<template v-if="originSongs.playing">
|
||||||
<img class="music-request-singing-avatar" :src="originSongs.playing.music.cover ?? AVATAR_URL + originSongs.playing.from?.uid" referrerpolicy="no-referrer" />
|
<img
|
||||||
|
class="music-request-singing-avatar"
|
||||||
|
:src="originSongs.playing.music.cover ?? AVATAR_URL + originSongs.playing.from?.uid"
|
||||||
|
referrerpolicy="no-referrer"
|
||||||
|
/>
|
||||||
<p class="music-request-singing-song-name">{{ originSongs.playing.music.name }}</p>
|
<p class="music-request-singing-song-name">{{ originSongs.playing.music.name }}</p>
|
||||||
<p class="music-request-singing-name">{{ originSongs.playing.from?.name }}</p>
|
<p class="music-request-singing-name">{{ originSongs.playing.from?.name }}</p>
|
||||||
</template>
|
</template>
|
||||||
@@ -86,8 +97,21 @@ onUnmounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
<div class="music-request-content" ref="listContainerRef">
|
<div class="music-request-content" ref="listContainerRef">
|
||||||
<template v-if="originSongs.waiting.length > 0">
|
<template v-if="originSongs.waiting.length > 0">
|
||||||
<Vue3Marquee class="music-request-list" :key="key" vertical :pause="!isMoreThanContainer" :duration="20" :style="`height: ${height}px;width: ${width}px;`">
|
<Vue3Marquee
|
||||||
<span class="music-request-list-item" :from="item.music.from as number" v-for="(item, index) in originSongs.waiting" :key="item.music.id" :style="`height: ${itemHeight}px`">
|
class="music-request-list"
|
||||||
|
:key="key"
|
||||||
|
vertical
|
||||||
|
:pause="!isMoreThanContainer"
|
||||||
|
:duration="20"
|
||||||
|
:style="`height: ${height}px;width: ${width}px;`"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="music-request-list-item"
|
||||||
|
:from="item.music.from as number"
|
||||||
|
v-for="(item, index) in originSongs.waiting"
|
||||||
|
:key="item.music.id"
|
||||||
|
:style="`height: ${itemHeight}px`"
|
||||||
|
>
|
||||||
<div class="music-request-list-item-index" :index="index + 1">
|
<div class="music-request-list-item-index" :index="index + 1">
|
||||||
{{ index + 1 }}
|
{{ index + 1 }}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,14 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { QueueFrom, QueueSortType, ResponseQueueModel, Setting_Queue, Setting_SongRequest, SongRequestFrom, SongRequestInfo, QueueStatus } from '@/api/api-models'
|
import {
|
||||||
|
QueueFrom,
|
||||||
|
QueueSortType,
|
||||||
|
ResponseQueueModel,
|
||||||
|
Setting_Queue,
|
||||||
|
Setting_SongRequest,
|
||||||
|
SongRequestFrom,
|
||||||
|
SongRequestInfo,
|
||||||
|
QueueStatus,
|
||||||
|
} from '@/api/api-models'
|
||||||
import { QueryGetAPI } from '@/api/query'
|
import { QueryGetAPI } from '@/api/query'
|
||||||
import { AVATAR_URL, QUEUE_API_URL, SONG_REQUEST_API_URL } from '@/data/constants'
|
import { AVATAR_URL, QUEUE_API_URL, SONG_REQUEST_API_URL } from '@/data/constants'
|
||||||
import { useElementSize } from '@vueuse/core'
|
import { useElementSize } from '@vueuse/core'
|
||||||
@@ -35,7 +44,9 @@ const progressing = computed(() => {
|
|||||||
return queue.value.find((s) => s.status == QueueStatus.Progressing)
|
return queue.value.find((s) => s.status == QueueStatus.Progressing)
|
||||||
})
|
})
|
||||||
const activeItems = computed(() => {
|
const activeItems = computed(() => {
|
||||||
let list = new List(queue.value).Where((q) => q?.status == QueueStatus.Waiting).OrderByDescending((q) => q.from == QueueFrom.Manual)
|
let list = new List(queue.value)
|
||||||
|
.Where((q) => q?.status == QueueStatus.Waiting)
|
||||||
|
.OrderByDescending((q) => q.from == QueueFrom.Manual)
|
||||||
switch (settings.value.sortType) {
|
switch (settings.value.sortType) {
|
||||||
case QueueSortType.TimeFirst: {
|
case QueueSortType.TimeFirst: {
|
||||||
list = list.ThenBy((q) => q.createAt)
|
list = list.ThenBy((q) => q.createAt)
|
||||||
@@ -57,9 +68,12 @@ const activeItems = computed(() => {
|
|||||||
|
|
||||||
async function get() {
|
async function get() {
|
||||||
try {
|
try {
|
||||||
const data = await QueryGetAPI<{ queue: ResponseQueueModel[]; setting: Setting_Queue }>(QUEUE_API_URL + 'get-active-and-settings', {
|
const data = await QueryGetAPI<{ queue: ResponseQueueModel[]; setting: Setting_Queue }>(
|
||||||
|
QUEUE_API_URL + 'get-active-and-settings',
|
||||||
|
{
|
||||||
id: currentId.value,
|
id: currentId.value,
|
||||||
})
|
},
|
||||||
|
)
|
||||||
if (data.code == 200) {
|
if (data.code == 200) {
|
||||||
return data.data
|
return data.data
|
||||||
}
|
}
|
||||||
@@ -108,7 +122,12 @@ onUnmounted(() => {
|
|||||||
<NDivider class="queue-divider">
|
<NDivider class="queue-divider">
|
||||||
<p class="queue-header-count">已有 {{ activeItems.length ?? 0 }} 人</p>
|
<p class="queue-header-count">已有 {{ activeItems.length ?? 0 }} 人</p>
|
||||||
</NDivider>
|
</NDivider>
|
||||||
<div class="queue-singing-container" :singing="queue.findIndex((s) => s.status == QueueStatus.Progressing) > -1" :from="progressing?.from as number" :status="progressing?.status as number">
|
<div
|
||||||
|
class="queue-singing-container"
|
||||||
|
:singing="queue.findIndex((s) => s.status == QueueStatus.Progressing) > -1"
|
||||||
|
:from="progressing?.from as number"
|
||||||
|
:status="progressing?.status as number"
|
||||||
|
>
|
||||||
<div class="queue-singing-prefix"></div>
|
<div class="queue-singing-prefix"></div>
|
||||||
<template v-if="progressing">
|
<template v-if="progressing">
|
||||||
<img class="queue-singing-avatar" :src="AVATAR_URL + progressing?.user?.uid" referrerpolicy="no-referrer" />
|
<img class="queue-singing-avatar" :src="AVATAR_URL + progressing?.user?.uid" referrerpolicy="no-referrer" />
|
||||||
@@ -119,7 +138,14 @@ onUnmounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
<div class="queue-content" ref="listContainerRef">
|
<div class="queue-content" ref="listContainerRef">
|
||||||
<template v-if="activeItems.length > 0">
|
<template v-if="activeItems.length > 0">
|
||||||
<Vue3Marquee class="queue-list" :key="key" vertical :pause="!isMoreThanContainer" :duration="20" :style="`height: ${height}px;width: ${width}px;`">
|
<Vue3Marquee
|
||||||
|
class="queue-list"
|
||||||
|
:key="key"
|
||||||
|
vertical
|
||||||
|
:pause="!isMoreThanContainer"
|
||||||
|
:duration="20"
|
||||||
|
:style="`height: ${height}px;width: ${width}px;`"
|
||||||
|
>
|
||||||
<span
|
<span
|
||||||
class="queue-list-item"
|
class="queue-list-item"
|
||||||
:from="item.from as number"
|
:from="item.from as number"
|
||||||
@@ -132,13 +158,21 @@ onUnmounted(() => {
|
|||||||
<div class="queue-list-item-index" :index="index + 1">
|
<div class="queue-list-item-index" :index="index + 1">
|
||||||
{{ index + 1 }}
|
{{ index + 1 }}
|
||||||
</div>
|
</div>
|
||||||
<div v-if="settings.showFanMadelInfo" class="queue-list-item-level" :has-level="(item.user?.fans_medal_level ?? 0) > 0">
|
<div
|
||||||
|
v-if="settings.showFanMadelInfo"
|
||||||
|
class="queue-list-item-level"
|
||||||
|
:has-level="(item.user?.fans_medal_level ?? 0) > 0"
|
||||||
|
>
|
||||||
{{ `${item.user?.fans_medal_name} ${item.user?.fans_medal_level}` }}
|
{{ `${item.user?.fans_medal_name} ${item.user?.fans_medal_level}` }}
|
||||||
</div>
|
</div>
|
||||||
<div class="queue-list-item-user-name">
|
<div class="queue-list-item-user-name">
|
||||||
{{ item.user?.name }}
|
{{ item.user?.name }}
|
||||||
</div>
|
</div>
|
||||||
<p v-if="settings.showPayment" class="queue-list-item-payment">{{ item.from == QueueFrom.Manual ? '主播添加' : item.giftPrice == undefined ? '无' : '¥ ' + item.giftPrice }}</p>
|
<p v-if="settings.showPayment" class="queue-list-item-payment">
|
||||||
|
{{
|
||||||
|
item.from == QueueFrom.Manual ? '主播添加' : item.giftPrice == undefined ? '无' : '¥ ' + item.giftPrice
|
||||||
|
}}
|
||||||
|
</p>
|
||||||
</span>
|
</span>
|
||||||
</Vue3Marquee>
|
</Vue3Marquee>
|
||||||
</template>
|
</template>
|
||||||
@@ -147,7 +181,13 @@ onUnmounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="queue-footer" ref="footerRef" v-if="settings.showRequireInfo">
|
<div class="queue-footer" ref="footerRef" v-if="settings.showRequireInfo">
|
||||||
<Vue3Marquee :key="key" ref="footerListRef" class="queue-footer-marquee" :pause="footerSize.width < footerListSize.width" :duration="20">
|
<Vue3Marquee
|
||||||
|
:key="key"
|
||||||
|
ref="footerListRef"
|
||||||
|
class="queue-footer-marquee"
|
||||||
|
:pause="footerSize.width < footerListSize.width"
|
||||||
|
:duration="20"
|
||||||
|
>
|
||||||
<span class="queue-tag" type="prefix">
|
<span class="queue-tag" type="prefix">
|
||||||
<div class="queue-tag-key">关键词</div>
|
<div class="queue-tag-key">关键词</div>
|
||||||
<div class="queue-tag-value">
|
<div class="queue-tag-value">
|
||||||
@@ -181,7 +221,13 @@ onUnmounted(() => {
|
|||||||
<span class="queue-tag" type="fan-madel">
|
<span class="queue-tag" type="fan-madel">
|
||||||
<div class="queue-tag-key">粉丝牌</div>
|
<div class="queue-tag-key">粉丝牌</div>
|
||||||
<div class="queue-tag-value">
|
<div class="queue-tag-value">
|
||||||
{{ settings.fanMedalMinLevel != undefined && !settings.allowAllDanmaku ? (settings.fanMedalMinLevel > 0 ? '> ' + settings.fanMedalMinLevel : '佩戴') : '无需' }}
|
{{
|
||||||
|
settings.fanMedalMinLevel != undefined && !settings.allowAllDanmaku
|
||||||
|
? settings.fanMedalMinLevel > 0
|
||||||
|
? '> ' + settings.fanMedalMinLevel
|
||||||
|
: '佩戴'
|
||||||
|
: '无需'
|
||||||
|
}}
|
||||||
</div>
|
</div>
|
||||||
</span></Vue3Marquee
|
</span></Vue3Marquee
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ const key = ref(Date.now())
|
|||||||
const originSongs = ref<SongRequestInfo[]>([])
|
const originSongs = ref<SongRequestInfo[]>([])
|
||||||
const songs = computed(() => {
|
const songs = computed(() => {
|
||||||
if (settings.value.isReverse) {
|
if (settings.value.isReverse) {
|
||||||
|
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
|
||||||
return originSongs.value.reverse()
|
return originSongs.value.reverse()
|
||||||
} else {
|
} else {
|
||||||
return originSongs.value
|
return originSongs.value
|
||||||
@@ -46,9 +47,12 @@ const activeSongs = computed(() => {
|
|||||||
|
|
||||||
async function get() {
|
async function get() {
|
||||||
try {
|
try {
|
||||||
const data = await QueryGetAPI<{ songs: SongRequestInfo[]; setting: Setting_SongRequest }>(SONG_REQUEST_API_URL + 'get-active-and-settings', {
|
const data = await QueryGetAPI<{ songs: SongRequestInfo[]; setting: Setting_SongRequest }>(
|
||||||
|
SONG_REQUEST_API_URL + 'get-active-and-settings',
|
||||||
|
{
|
||||||
id: currentId.value,
|
id: currentId.value,
|
||||||
})
|
},
|
||||||
|
)
|
||||||
if (data.code == 200) {
|
if (data.code == 200) {
|
||||||
return data.data
|
return data.data
|
||||||
}
|
}
|
||||||
@@ -100,8 +104,8 @@ onUnmounted(() => {
|
|||||||
<div
|
<div
|
||||||
class="song-request-singing-container"
|
class="song-request-singing-container"
|
||||||
:singing="songs.findIndex((s) => s.status == SongRequestStatus.Singing) > -1"
|
:singing="songs.findIndex((s) => s.status == SongRequestStatus.Singing) > -1"
|
||||||
:from="(singing?.from as number)"
|
:from="singing?.from as number"
|
||||||
:status="(singing?.status as number)"
|
:status="singing?.status as number"
|
||||||
>
|
>
|
||||||
<div class="song-request-singing-prefix"></div>
|
<div class="song-request-singing-prefix"></div>
|
||||||
<template v-if="singing">
|
<template v-if="singing">
|
||||||
@@ -114,16 +118,36 @@ onUnmounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
<div class="song-request-content" ref="listContainerRef">
|
<div class="song-request-content" ref="listContainerRef">
|
||||||
<template v-if="activeSongs.length > 0">
|
<template v-if="activeSongs.length > 0">
|
||||||
<Vue3Marquee class="song-request-list" :key="key" vertical :pause="!isMoreThanContainer" :duration="20" :style="`height: ${height}px;width: ${width}px;`">
|
<Vue3Marquee
|
||||||
<span class="song-request-list-item" :from="(song.from as number)" :status="(song.status as number)" v-for="(song, index) in activeSongs" :key="song.id" :style="`height: ${itemHeight}px`">
|
class="song-request-list"
|
||||||
|
:key="key"
|
||||||
|
vertical
|
||||||
|
:pause="!isMoreThanContainer"
|
||||||
|
:duration="20"
|
||||||
|
:style="`height: ${height}px;width: ${width}px;`"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="song-request-list-item"
|
||||||
|
:from="song.from as number"
|
||||||
|
:status="song.status as number"
|
||||||
|
v-for="(song, index) in activeSongs"
|
||||||
|
:key="song.id"
|
||||||
|
:style="`height: ${itemHeight}px`"
|
||||||
|
>
|
||||||
<div class="song-request-list-item-index" :index="index + 1">
|
<div class="song-request-list-item-index" :index="index + 1">
|
||||||
{{ index + 1 }}
|
{{ index + 1 }}
|
||||||
</div>
|
</div>
|
||||||
<div class="song-request-list-item-song-name">
|
<div class="song-request-list-item-song-name">
|
||||||
{{ song.songName }}
|
{{ song.songName }}
|
||||||
</div>
|
</div>
|
||||||
<p v-if="settings.showUserName" class="song-request-list-item-name">{{ song.from == SongRequestFrom.Manual ? '主播添加' : song.user?.name }}</p>
|
<p v-if="settings.showUserName" class="song-request-list-item-name">
|
||||||
<div v-if="settings.showFanMadelInfo" class="song-request-list-item-level" :has-level="(song.user?.fans_medal_level ?? 0) > 0">
|
{{ song.from == SongRequestFrom.Manual ? '主播添加' : song.user?.name }}
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
v-if="settings.showFanMadelInfo"
|
||||||
|
class="song-request-list-item-level"
|
||||||
|
:has-level="(song.user?.fans_medal_level ?? 0) > 0"
|
||||||
|
>
|
||||||
{{ `${song.user?.fans_medal_name} ${song.user?.fans_medal_level}` }}
|
{{ `${song.user?.fans_medal_name} ${song.user?.fans_medal_level}` }}
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
@@ -134,7 +158,13 @@ onUnmounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="song-request-footer" v-if="settings.showRequireInfo" ref="footerRef">
|
<div class="song-request-footer" v-if="settings.showRequireInfo" ref="footerRef">
|
||||||
<Vue3Marquee :key="key" ref="footerListRef" class="song-request-footer-marquee" :pause="footerSize.width < footerListSize.width" :duration="20">
|
<Vue3Marquee
|
||||||
|
:key="key"
|
||||||
|
ref="footerListRef"
|
||||||
|
class="song-request-footer-marquee"
|
||||||
|
:pause="footerSize.width < footerListSize.width"
|
||||||
|
:duration="20"
|
||||||
|
>
|
||||||
<span class="song-request-tag" type="prefix">
|
<span class="song-request-tag" type="prefix">
|
||||||
<div class="song-request-tag-key">前缀</div>
|
<div class="song-request-tag-key">前缀</div>
|
||||||
<div class="song-request-tag-value">
|
<div class="song-request-tag-value">
|
||||||
@@ -156,7 +186,13 @@ onUnmounted(() => {
|
|||||||
<span class="song-request-tag" type="fan-madel">
|
<span class="song-request-tag" type="fan-madel">
|
||||||
<div class="song-request-tag-key">粉丝牌</div>
|
<div class="song-request-tag-key">粉丝牌</div>
|
||||||
<div class="song-request-tag-value">
|
<div class="song-request-tag-value">
|
||||||
{{ settings.needWearFanMedal ? (settings.fanMedalMinLevel > 0 ? '> ' + settings.fanMedalMinLevel : '佩戴') : '无需' }}
|
{{
|
||||||
|
settings.needWearFanMedal
|
||||||
|
? settings.fanMedalMinLevel > 0
|
||||||
|
? '> ' + settings.fanMedalMinLevel
|
||||||
|
: '佩戴'
|
||||||
|
: '无需'
|
||||||
|
}}
|
||||||
</div>
|
</div>
|
||||||
</span></Vue3Marquee
|
</span></Vue3Marquee
|
||||||
>
|
>
|
||||||
@@ -182,7 +218,11 @@ onUnmounted(() => {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
text-shadow: 0 0 10px #ca7b7b6e, 0 0 20px #ffffff8e, 0 0 30px #61606086, 0 0 40px rgba(64, 156, 179, 0.555);
|
text-shadow:
|
||||||
|
0 0 10px #ca7b7b6e,
|
||||||
|
0 0 20px #ffffff8e,
|
||||||
|
0 0 30px #61606086,
|
||||||
|
0 0 40px rgba(64, 156, 179, 0.555);
|
||||||
}
|
}
|
||||||
.song-request-header-count {
|
.song-request-header-count {
|
||||||
color: #ffffffbd;
|
color: #ffffffbd;
|
||||||
|
|||||||
@@ -157,7 +157,9 @@ async function getNeteaseSongList() {
|
|||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (data.code == 200) {
|
if (data.code == 200) {
|
||||||
neteaseSongs.value = data.data
|
neteaseSongs.value = data.data
|
||||||
message.success(`成功获取歌曲信息, 共 ${data.data.length} 条, 歌单中已存在 ${neteaseSongsOptions.value.filter((s) => s.disabled).length} 首`)
|
message.success(
|
||||||
|
`成功获取歌曲信息, 共 ${data.data.length} 条, 歌单中已存在 ${neteaseSongsOptions.value.filter((s) => s.disabled).length} 首`,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
message.error('获取歌单失败: ' + data.message)
|
message.error('获取歌单失败: ' + data.message)
|
||||||
}
|
}
|
||||||
@@ -278,7 +280,9 @@ async function onGetEvent(data: EventModel) {
|
|||||||
if (settings.value.orderCooldown && cooldown.value[data.uid] && data.uid != (accountInfo.value?.biliId ?? -1)) {
|
if (settings.value.orderCooldown && cooldown.value[data.uid] && data.uid != (accountInfo.value?.biliId ?? -1)) {
|
||||||
const lastRequest = cooldown.value[data.uid]
|
const lastRequest = cooldown.value[data.uid]
|
||||||
if (Date.now() - lastRequest < settings.value.orderCooldown * 1000) {
|
if (Date.now() - lastRequest < settings.value.orderCooldown * 1000) {
|
||||||
message.info(`[${data.name}] 冷却中,距离下次点歌还有 ${((settings.value.orderCooldown * 1000 - (Date.now() - lastRequest)) / 1000).toFixed(1)} 秒`)
|
message.info(
|
||||||
|
`[${data.name}] 冷却中,距离下次点歌还有 ${((settings.value.orderCooldown * 1000 - (Date.now() - lastRequest)) / 1000).toFixed(1)} 秒`,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -326,7 +330,9 @@ async function getOutputDevice() {
|
|||||||
})
|
})
|
||||||
const list = await navigator.mediaDevices.enumerateDevices()
|
const list = await navigator.mediaDevices.enumerateDevices()
|
||||||
|
|
||||||
deviceList.value = list.filter((device) => device.kind === 'audiooutput').map((d) => ({ label: d.label, value: d.deviceId }))
|
deviceList.value = list
|
||||||
|
.filter((device) => device.kind === 'audiooutput')
|
||||||
|
.map((d) => ({ label: d.label, value: d.deviceId }))
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
message.error('获取音频输出设备失败, 获取你需要授予网页读取麦克风权限: ' + err)
|
message.error('获取音频输出设备失败, 获取你需要授予网页读取麦克风权限: ' + err)
|
||||||
@@ -393,7 +399,9 @@ onUnmounted(() => {
|
|||||||
<NButton @click="showOBSModal = true" type="info" size="small"> OBS组件 </NButton>
|
<NButton @click="showOBSModal = true" type="info" size="small"> OBS组件 </NButton>
|
||||||
<NButton @click="showNeteaseModal = true" size="small"> 从网易云歌单导入空闲歌单 </NButton>
|
<NButton @click="showNeteaseModal = true" size="small"> 从网易云歌单导入空闲歌单 </NButton>
|
||||||
|
|
||||||
<NButton @click="uploadConfig" type="primary" secondary :disabled="!accountInfo" size="small"> 保存配置到服务器 </NButton>
|
<NButton @click="uploadConfig" type="primary" secondary :disabled="!accountInfo" size="small">
|
||||||
|
保存配置到服务器
|
||||||
|
</NButton>
|
||||||
<NPopconfirm @positive-click="downloadConfig">
|
<NPopconfirm @positive-click="downloadConfig">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<NButton type="primary" secondary :disabled="!accountInfo" size="small"> 从服务器获取配置 </NButton>
|
<NButton type="primary" secondary :disabled="!accountInfo" size="small"> 从服务器获取配置 </NButton>
|
||||||
@@ -406,10 +414,19 @@ onUnmounted(() => {
|
|||||||
<NCollapseItem title="队列" name="1">
|
<NCollapseItem title="队列" name="1">
|
||||||
<NEmpty v-if="musicRquestStore.waitingMusics.length == 0"> 暂无 </NEmpty>
|
<NEmpty v-if="musicRquestStore.waitingMusics.length == 0"> 暂无 </NEmpty>
|
||||||
<NList v-else size="small" bordered>
|
<NList v-else size="small" bordered>
|
||||||
<NListItem v-for="item in musicRquestStore.waitingMusics">
|
<NListItem v-for="item in musicRquestStore.waitingMusics" :key="item.music.name">
|
||||||
<NSpace align="center">
|
<NSpace align="center">
|
||||||
<NButton @click="musicRquestStore.playMusic(item.music)" type="primary" secondary size="small"> 播放 </NButton>
|
<NButton @click="musicRquestStore.playMusic(item.music)" type="primary" secondary size="small">
|
||||||
<NButton @click="musicRquestStore.waitingMusics.splice(musicRquestStore.waitingMusics.indexOf(item), 1)" type="error" secondary size="small"> 取消 </NButton>
|
播放
|
||||||
|
</NButton>
|
||||||
|
<NButton
|
||||||
|
@click="musicRquestStore.waitingMusics.splice(musicRquestStore.waitingMusics.indexOf(item), 1)"
|
||||||
|
type="error"
|
||||||
|
secondary
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
取消
|
||||||
|
</NButton>
|
||||||
<NButton @click="blockMusic(item.music)" type="warning" secondary size="small"> 拉黑 </NButton>
|
<NButton @click="blockMusic(item.music)" type="warning" secondary size="small"> 拉黑 </NButton>
|
||||||
<span>
|
<span>
|
||||||
<NTag v-if="item.music.from == SongFrom.Netease" type="success" size="small"> 网易</NTag>
|
<NTag v-if="item.music.from == SongFrom.Netease" type="success" size="small"> 网易</NTag>
|
||||||
@@ -493,7 +510,7 @@ onUnmounted(() => {
|
|||||||
<NDivider style="margin: 15px 0 10px 0" />
|
<NDivider style="margin: 15px 0 10px 0" />
|
||||||
<NEmpty v-if="musicRquestStore.originMusics.length == 0"> 暂无 </NEmpty>
|
<NEmpty v-if="musicRquestStore.originMusics.length == 0"> 暂无 </NEmpty>
|
||||||
<NVirtualList v-else :style="`max-height: 1000px`" :item-size="30" :items="originMusics" item-resizable>
|
<NVirtualList v-else :style="`max-height: 1000px`" :item-size="30" :items="originMusics" item-resizable>
|
||||||
<template #default="{ item, index }">
|
<template #default="{ item }">
|
||||||
<p :style="`min-height: ${30}px;width:97%;display:flex;align-items:center;`">
|
<p :style="`min-height: ${30}px;width:97%;display:flex;align-items:center;`">
|
||||||
<NSpace align="center" style="width: 100%">
|
<NSpace align="center" style="width: 100%">
|
||||||
<NPopconfirm @positive-click="delMusic(item)">
|
<NPopconfirm @positive-click="delMusic(item)">
|
||||||
@@ -512,9 +529,16 @@ onUnmounted(() => {
|
|||||||
</NTabPane>
|
</NTabPane>
|
||||||
<NTabPane name="blacklist" tab="黑名单">
|
<NTabPane name="blacklist" tab="黑名单">
|
||||||
<NList>
|
<NList>
|
||||||
<NListItem v-for="item in settings.blacklist">
|
<NListItem v-for="item in settings.blacklist" :key="item">
|
||||||
<NSpace align="center" style="width: 100%">
|
<NSpace align="center" style="width: 100%">
|
||||||
<NButton @click="settings.blacklist.splice(settings.blacklist.indexOf(item), 1)" type="error" secondary size="small"> 删除 </NButton>
|
<NButton
|
||||||
|
@click="settings.blacklist.splice(settings.blacklist.indexOf(item), 1)"
|
||||||
|
type="error"
|
||||||
|
secondary
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</NButton>
|
||||||
<NText> {{ item }} </NText>
|
<NText> {{ item }} </NText>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
</NListItem>
|
</NListItem>
|
||||||
@@ -523,18 +547,35 @@ 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 clearable style="width: 100%" autosize :status="neteaseSongListId ? 'success' : 'error'" v-model:value="neteaseIdInput" placeholder="直接输入歌单Id或者网页链接">
|
<NInput
|
||||||
|
clearable
|
||||||
|
style="width: 100%"
|
||||||
|
autosize
|
||||||
|
:status="neteaseSongListId ? 'success' : 'error'"
|
||||||
|
v-model:value="neteaseIdInput"
|
||||||
|
placeholder="直接输入歌单Id或者网页链接"
|
||||||
|
>
|
||||||
<template #suffix>
|
<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>
|
||||||
</NInput>
|
</NInput>
|
||||||
<NDivider style="margin: 10px" />
|
<NDivider style="margin: 10px" />
|
||||||
<NButton type="primary" @click="getNeteaseSongList" :disabled="!neteaseSongListId" :loading="isLoading"> 获取 </NButton>
|
<NButton type="primary" @click="getNeteaseSongList" :disabled="!neteaseSongListId" :loading="isLoading">
|
||||||
|
获取
|
||||||
|
</NButton>
|
||||||
<template v-if="neteaseSongsOptions.length > 0">
|
<template v-if="neteaseSongsOptions.length > 0">
|
||||||
<NDivider style="margin: 10px" />
|
<NDivider style="margin: 10px" />
|
||||||
<NTransfer style="height: 500px" ref="transfer" v-model:value="selectedNeteaseSongs" :options="neteaseSongsOptions" source-filterable />
|
<NTransfer
|
||||||
|
style="height: 500px"
|
||||||
|
ref="transfer"
|
||||||
|
v-model:value="selectedNeteaseSongs"
|
||||||
|
:options="neteaseSongsOptions"
|
||||||
|
source-filterable
|
||||||
|
/>
|
||||||
<NDivider style="margin: 10px" />
|
<NDivider style="margin: 10px" />
|
||||||
<NButton type="primary" @click="addNeteaseSongs" :loading="isLoading"> 添加到歌单 | {{ selectedNeteaseSongs.length }} 首 </NButton>
|
<NButton type="primary" @click="addNeteaseSongs" :loading="isLoading">
|
||||||
|
添加到歌单 | {{ selectedNeteaseSongs.length }} 首
|
||||||
|
</NButton>
|
||||||
</template>
|
</template>
|
||||||
</NModal>
|
</NModal>
|
||||||
<NModal v-model:show="showOBSModal" title="OBS组件" preset="card" style="width: 800px">
|
<NModal v-model:show="showOBSModal" title="OBS组件" preset="card" style="width: 800px">
|
||||||
|
|||||||
@@ -18,37 +18,60 @@ const accountInfo = useAccount()
|
|||||||
<NCard hoverable embedded size="small" title="弹幕抽奖" style="width: 300px">
|
<NCard hoverable embedded size="small" title="弹幕抽奖" style="width: 300px">
|
||||||
通过弹幕或者礼物收集用户, 并进行抽取, 允许设置多种条件
|
通过弹幕或者礼物收集用户, 并进行抽取, 允许设置多种条件
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<NButton @click="$router.push({ name: 'open-live-lottery', query: $route.query })" type="primary"> 前往使用 </NButton>
|
<NButton @click="$router.push({ name: 'open-live-lottery', query: $route.query })" type="primary">
|
||||||
|
前往使用
|
||||||
|
</NButton>
|
||||||
</template>
|
</template>
|
||||||
</NCard>
|
</NCard>
|
||||||
<NCard hoverable embedded size="small" title="弹幕点歌" style="width: 300px">
|
<NCard hoverable embedded size="small" title="弹幕点歌" style="width: 300px">
|
||||||
通过弹幕或者SC进行点歌, 注册后可以保存和导出 (这个是歌势用的点歌, 不是拿来放歌的那种!)
|
通过弹幕或者SC进行点歌, 注册后可以保存和导出 (这个是歌势用的点歌, 不是拿来放歌的那种!)
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<NButton @click="$router.push({ name: 'open-live-song-request', query: $route.query })" type="primary"> 前往使用 </NButton>
|
<NButton @click="$router.push({ name: 'open-live-song-request', query: $route.query })" type="primary">
|
||||||
|
前往使用
|
||||||
|
</NButton>
|
||||||
</template>
|
</template>
|
||||||
</NCard>
|
</NCard>
|
||||||
<NCard hoverable embedded size="small" title="弹幕排队" style="width: 300px">
|
<NCard hoverable embedded size="small" title="弹幕排队" style="width: 300px">
|
||||||
通过发送弹幕或者礼物进行排队, 允许设置多种条件
|
通过发送弹幕或者礼物进行排队, 允许设置多种条件
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<NButton @click="$router.push({ name: 'open-live-queue', query: $route.query })" type="primary"> 前往使用 </NButton>
|
<NButton @click="$router.push({ name: 'open-live-queue', query: $route.query })" type="primary">
|
||||||
|
前往使用
|
||||||
|
</NButton>
|
||||||
</template>
|
</template>
|
||||||
</NCard>
|
</NCard>
|
||||||
|
|
||||||
<NCard hoverable embedded size="small" title="读弹幕" style="width: 300px">
|
<NCard hoverable embedded size="small" title="读弹幕" style="width: 300px">
|
||||||
通过浏览器自带的tts服务读弹幕 (此功能需要 Chrome, Edge 等现代浏览器!)
|
通过浏览器自带的tts服务读弹幕 (此功能需要 Chrome, Edge 等现代浏览器!)
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<NButton @click="$router.push({ name: 'open-live-speech', query: $route.query })" type="primary"> 前往使用 </NButton>
|
<NButton @click="$router.push({ name: 'open-live-speech', query: $route.query })" type="primary">
|
||||||
|
前往使用
|
||||||
|
</NButton>
|
||||||
</template>
|
</template>
|
||||||
</NCard>
|
</NCard>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
<br />
|
<br />
|
||||||
<NAlert v-if="accountInfo?.eventFetcherOnline != true" type="warning" title="可用性警告" style="max-width: 600px; margin: 0 auto">
|
<NAlert
|
||||||
|
v-if="accountInfo?.eventFetcherOnline != true"
|
||||||
|
type="warning"
|
||||||
|
title="可用性警告"
|
||||||
|
style="max-width: 600px; margin: 0 auto"
|
||||||
|
>
|
||||||
当浏览器在后台运行时, 定时器和 Websocket 连接将受到严格限制, 这会导致弹幕接收功能无法正常工作 (详见
|
当浏览器在后台运行时, 定时器和 Websocket 连接将受到严格限制, 这会导致弹幕接收功能无法正常工作 (详见
|
||||||
<NButton text tag="a" href="https://developer.chrome.com/blog/background_tabs/" target="_blank" type="info">此文章</NButton>), 虽然本站已经针对此问题做出了处理, 一般情况下即使掉线了也会重连,
|
<NButton text tag="a" href="https://developer.chrome.com/blog/background_tabs/" target="_blank" type="info"
|
||||||
不过还是有可能会遗漏事件
|
>此文章</NButton
|
||||||
|
>), 虽然本站已经针对此问题做出了处理, 一般情况下即使掉线了也会重连, 不过还是有可能会遗漏事件
|
||||||
<br />
|
<br />
|
||||||
为避免这种情况, 建议注册本站账后使用 <NButton type="primary" text size="tiny" tag="a" href="https://www.yuque.com/megghy/dez70g/vfvcyv3024xvaa1p" target="_blank"> VtsuruEventFetcher </NButton>,
|
为避免这种情况, 建议注册本站账后使用
|
||||||
否则请在使用功能时尽量保持网页在前台运行
|
<NButton
|
||||||
|
type="primary"
|
||||||
|
text
|
||||||
|
size="tiny"
|
||||||
|
tag="a"
|
||||||
|
href="https://www.yuque.com/megghy/dez70g/vfvcyv3024xvaa1p"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
VtsuruEventFetcher </NButton
|
||||||
|
>, 否则请在使用功能时尽量保持网页在前台运行
|
||||||
</NAlert>
|
</NAlert>
|
||||||
<NDivider> 还有更多 </NDivider>
|
<NDivider> 还有更多 </NDivider>
|
||||||
<NSpace justify="center" align="center" vertical>
|
<NSpace justify="center" align="center" vertical>
|
||||||
|
|||||||
@@ -178,7 +178,10 @@ function startLottery() {
|
|||||||
removeSingleUser()
|
removeSingleUser()
|
||||||
function removeSingleUser() {
|
function removeSingleUser() {
|
||||||
if (currentUsers.value.length > lotteryOption.value.resultCount) {
|
if (currentUsers.value.length > lotteryOption.value.resultCount) {
|
||||||
console.log(`[${currentUsers.value.length}] 移除` + currentUsers.value.splice(getRandomInt(currentUsers.value.length), 1)[0].name)
|
console.log(
|
||||||
|
`[${currentUsers.value.length}] 移除` +
|
||||||
|
currentUsers.value.splice(getRandomInt(currentUsers.value.length), 1)[0].name,
|
||||||
|
)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
removeSingleUser()
|
removeSingleUser()
|
||||||
}, 500)
|
}, 500)
|
||||||
@@ -193,7 +196,10 @@ function startLottery() {
|
|||||||
if (currentUsers.value.length / 2 <= lotteryOption.value.resultCount) {
|
if (currentUsers.value.length / 2 <= lotteryOption.value.resultCount) {
|
||||||
console.log(`[OPEN-LIVE-Lottery] 人数减半至${lotteryOption.value.resultCount}人`)
|
console.log(`[OPEN-LIVE-Lottery] 人数减半至${lotteryOption.value.resultCount}人`)
|
||||||
while (currentUsers.value.length > lotteryOption.value.resultCount) {
|
while (currentUsers.value.length > lotteryOption.value.resultCount) {
|
||||||
console.log(`[${currentUsers.value.length}] 移除` + currentUsers.value.splice(getRandomInt(currentUsers.value.length), 1)[0].name)
|
console.log(
|
||||||
|
`[${currentUsers.value.length}] 移除` +
|
||||||
|
currentUsers.value.splice(getRandomInt(currentUsers.value.length), 1)[0].name,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
onFinishLottery()
|
onFinishLottery()
|
||||||
} else {
|
} else {
|
||||||
@@ -201,7 +207,10 @@ function startLottery() {
|
|||||||
console.log(`[OPEN-LIVE-Lottery] 人数减半至${half}人`)
|
console.log(`[OPEN-LIVE-Lottery] 人数减半至${half}人`)
|
||||||
message.success('人数减半至 ' + half + ' 人')
|
message.success('人数减半至 ' + half + ' 人')
|
||||||
while (currentUsers.value.length > half) {
|
while (currentUsers.value.length > half) {
|
||||||
console.log(`[${currentUsers.value.length}] 移除` + currentUsers.value.splice(getRandomInt(currentUsers.value.length), 1)[0].name)
|
console.log(
|
||||||
|
`[${currentUsers.value.length}] 移除` +
|
||||||
|
currentUsers.value.splice(getRandomInt(currentUsers.value.length), 1)[0].name,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
isLottering.value = false
|
isLottering.value = false
|
||||||
@@ -224,10 +233,13 @@ function onFinishLottery() {
|
|||||||
description: '共' + resultUsers.value?.length + '人',
|
description: '共' + resultUsers.value?.length + '人',
|
||||||
duration: 3000,
|
duration: 3000,
|
||||||
content: () =>
|
content: () =>
|
||||||
h(
|
h(NSpace, { vertical: true }, () =>
|
||||||
NSpace,
|
resultUsers.value?.map((user) =>
|
||||||
{ vertical: true },
|
h(NSpace, null, () => [
|
||||||
() => resultUsers.value?.map((user) => h(NSpace, null, () => [h(NAvatar, { src: user.avatar + '@32w_32h', imgProps: { referrerpolicy: 'no-referrer' } }), h('span', user.name)])),
|
h(NAvatar, { src: user.avatar + '@32w_32h', imgProps: { referrerpolicy: 'no-referrer' } }),
|
||||||
|
h('span', user.name),
|
||||||
|
]),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
meta: format(new Date(), 'yyyy-MM-dd HH:mm:ss'),
|
meta: format(new Date(), 'yyyy-MM-dd HH:mm:ss'),
|
||||||
onAfterLeave: () => {
|
onAfterLeave: () => {
|
||||||
@@ -327,16 +339,25 @@ onUnmounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NResult v-if="!code && !accountInfo" status="403" title="403" description="该页面只能从幻星平台访问或者注册用户使用" />
|
<NResult
|
||||||
|
v-if="!code && !accountInfo"
|
||||||
|
status="403"
|
||||||
|
title="403"
|
||||||
|
description="该页面只能从幻星平台访问或者注册用户使用"
|
||||||
|
/>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<NCard>
|
<NCard>
|
||||||
<template #header>
|
<template #header>
|
||||||
直播抽奖
|
直播抽奖
|
||||||
<NDivider vertical />
|
<NDivider vertical />
|
||||||
<NButton text type="primary" tag="a" href="https://vtsuru.live" target="_blank"> 前往 VTsuru.live 主站 </NButton>
|
<NButton text type="primary" tag="a" href="https://vtsuru.live" target="_blank">
|
||||||
|
前往 VTsuru.live 主站
|
||||||
|
</NButton>
|
||||||
</template>
|
</template>
|
||||||
<NAlert v-if="!code && accountInfo && !accountInfo.isBiliVerified" type="error"> 请先绑定B站账号 </NAlert>
|
<NAlert v-if="!code && accountInfo && !accountInfo.isBiliVerified" type="error"> 请先绑定B站账号 </NAlert>
|
||||||
<NAlert v-else-if="!code && accountInfo && accountInfo.biliAuthCodeStatus != 1" type="error"> 身份码状态异常, 请重新绑定 </NAlert>
|
<NAlert v-else-if="!code && accountInfo && accountInfo.biliAuthCodeStatus != 1" type="error">
|
||||||
|
身份码状态异常, 请重新绑定
|
||||||
|
</NAlert>
|
||||||
<NCard>
|
<NCard>
|
||||||
<NSpace align="center">
|
<NSpace align="center">
|
||||||
<NButton type="info" @click="showModal = true" size="small"> 抽奖历史</NButton>
|
<NButton type="info" @click="showModal = true" size="small"> 抽奖历史</NButton>
|
||||||
@@ -345,7 +366,9 @@ onUnmounted(() => {
|
|||||||
</NCard>
|
</NCard>
|
||||||
<NCard size="small" embedded title="抽奖选项">
|
<NCard size="small" embedded title="抽奖选项">
|
||||||
<template #header-extra>
|
<template #header-extra>
|
||||||
<NButton size="small" secondary @click="lotteryOption = defaultOption" :disabled="isStartLottery"> 恢复默认 </NButton>
|
<NButton size="small" secondary @click="lotteryOption = defaultOption" :disabled="isStartLottery">
|
||||||
|
恢复默认
|
||||||
|
</NButton>
|
||||||
</template>
|
</template>
|
||||||
<NSpace justify="center" align="center">
|
<NSpace justify="center" align="center">
|
||||||
<NTag :bordered="false"> 抽奖类型 </NTag>
|
<NTag :bordered="false"> 抽奖类型 </NTag>
|
||||||
@@ -365,7 +388,13 @@ 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 v-model:value="lotteryOption.fanCardLevel" min="1" max="50" :default-value="1" :disabled="isLottering || isStartLottery" />
|
<NInputNumber
|
||||||
|
v-model:value="lotteryOption.fanCardLevel"
|
||||||
|
min="1"
|
||||||
|
max="50"
|
||||||
|
:default-value="1"
|
||||||
|
:disabled="isLottering || isStartLottery"
|
||||||
|
/>
|
||||||
</NInputGroup>
|
</NInputGroup>
|
||||||
</NCollapseTransition>
|
</NCollapseTransition>
|
||||||
<template v-if="lotteryOption.type == 'danmaku'">
|
<template v-if="lotteryOption.type == 'danmaku'">
|
||||||
@@ -373,12 +402,21 @@ onUnmounted(() => {
|
|||||||
<template #trigger>
|
<template #trigger>
|
||||||
<NInputGroup style="max-width: 250px">
|
<NInputGroup style="max-width: 250px">
|
||||||
<NInputGroupLabel> 弹幕内容 </NInputGroupLabel>
|
<NInputGroupLabel> 弹幕内容 </NInputGroupLabel>
|
||||||
<NInput :disabled="isStartLottery" v-model:value="lotteryOption.danmakuKeyword" placeholder="留空则任何弹幕都可以" />
|
<NInput
|
||||||
|
:disabled="isStartLottery"
|
||||||
|
v-model:value="lotteryOption.danmakuKeyword"
|
||||||
|
placeholder="留空则任何弹幕都可以"
|
||||||
|
/>
|
||||||
</NInputGroup>
|
</NInputGroup>
|
||||||
</template>
|
</template>
|
||||||
符合规则的弹幕才会被添加到抽奖队列中
|
符合规则的弹幕才会被添加到抽奖队列中
|
||||||
</NTooltip>
|
</NTooltip>
|
||||||
<NRadioGroup v-model:value="lotteryOption.danmakuFilterType" name="判定类型" :disabled="isLottering" size="small">
|
<NRadioGroup
|
||||||
|
v-model:value="lotteryOption.danmakuFilterType"
|
||||||
|
name="判定类型"
|
||||||
|
:disabled="isLottering"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
<NRadioButton :disabled="isStartLottery" value="all"> 完全一致 </NRadioButton>
|
<NRadioButton :disabled="isStartLottery" value="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>
|
||||||
@@ -387,7 +425,11 @@ 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 :disabled="isStartLottery" v-model:value="lotteryOption.giftMinPrice" placeholder="留空则不限制" />
|
<NInputNumber
|
||||||
|
:disabled="isStartLottery"
|
||||||
|
v-model:value="lotteryOption.giftMinPrice"
|
||||||
|
placeholder="留空则不限制"
|
||||||
|
/>
|
||||||
</NInputGroup>
|
</NInputGroup>
|
||||||
<NInputGroup style="max-width: 200px">
|
<NInputGroup style="max-width: 200px">
|
||||||
<NInputGroupLabel> 礼物名称 </NInputGroupLabel>
|
<NInputGroupLabel> 礼物名称 </NInputGroupLabel>
|
||||||
@@ -422,11 +464,20 @@ 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 type="primary" @click="continueLottery" :loading="isStartLottery" :disabled="isStartLottery || isLotteried || !client"> 开始 </NButton>
|
<NButton
|
||||||
|
type="primary"
|
||||||
|
@click="continueLottery"
|
||||||
|
:loading="isStartLottery"
|
||||||
|
:disabled="isStartLottery || isLotteried || !client"
|
||||||
|
>
|
||||||
|
开始
|
||||||
|
</NButton>
|
||||||
<NButton type="warning" :disabled="!isStartLottery" @click="pause"> 停止 </NButton>
|
<NButton type="warning" :disabled="!isStartLottery" @click="pause"> 停止 </NButton>
|
||||||
<NButton type="error" :disabled="isLottering || originUsers.length == 0" @click="clear"> 清空 </NButton>
|
<NButton type="error" :disabled="isLottering || originUsers.length == 0" @click="clear"> 清空 </NButton>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
<NDivider style="margin: 20px 0 20px 0"> <template v-if="isStartLottery"> 进行抽取前需要先停止 </template> </NDivider>
|
<NDivider style="margin: 20px 0 20px 0">
|
||||||
|
<template v-if="isStartLottery"> 进行抽取前需要先停止 </template>
|
||||||
|
</NDivider>
|
||||||
<NSpace justify="center">
|
<NSpace justify="center">
|
||||||
<NButton
|
<NButton
|
||||||
type="primary"
|
type="primary"
|
||||||
@@ -439,7 +490,9 @@ onUnmounted(() => {
|
|||||||
>
|
>
|
||||||
进行抽取
|
进行抽取
|
||||||
</NButton>
|
</NButton>
|
||||||
<NButton type="info" secondary :disabled="isStartLottery || isLottering || !isLotteried" @click="reset"> 重置 </NButton>
|
<NButton type="info" secondary :disabled="isStartLottery || isLottering || !isLotteried" @click="reset">
|
||||||
|
重置
|
||||||
|
</NButton>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
<NDivider style="margin: 10px 0 10px 0"> 共 {{ currentUsers?.length }} 人</NDivider>
|
<NDivider style="margin: 10px 0 10px 0"> 共 {{ currentUsers?.length }} 人</NDivider>
|
||||||
<NGrid v-if="currentUsers.length > 0" cols="1 500:2 800:3 1000:4" :x-gap="12" :y-gap="8">
|
<NGrid v-if="currentUsers.length > 0" cols="1 500:2 800:3 1000:4" :x-gap="12" :y-gap="8">
|
||||||
@@ -447,7 +500,15 @@ 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 round lazy borderd :size="64" :src="item.avatar + '@64w_64h'" :img-props="{ referrerpolicy: 'no-referrer' }" style="box-shadow: 0 3px 5px rgba(0, 0, 0, 0.2)" />
|
<NAvatar
|
||||||
|
round
|
||||||
|
lazy
|
||||||
|
borderd
|
||||||
|
:size="64"
|
||||||
|
:src="item.avatar + '@64w_64h'"
|
||||||
|
:img-props="{ referrerpolicy: 'no-referrer' }"
|
||||||
|
style="box-shadow: 0 3px 5px rgba(0, 0, 0, 0.2)"
|
||||||
|
/>
|
||||||
<NSpace v-if="item.fans_medal_wearing_status">
|
<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">
|
||||||
@@ -462,7 +523,12 @@ onUnmounted(() => {
|
|||||||
{{ item.name }}
|
{{ item.name }}
|
||||||
</NSpace>
|
</NSpace>
|
||||||
|
|
||||||
<NButton style="position: absolute; right: 5px; top: 5px; color: #753e3e" @click="removeUser(item)" size="small" circle>
|
<NButton
|
||||||
|
style="position: absolute; right: 5px; top: 5px; color: #753e3e"
|
||||||
|
@click="removeUser(item)"
|
||||||
|
size="small"
|
||||||
|
circle
|
||||||
|
>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<NIcon :component="Delete24Filled" />
|
<NIcon :component="Delete24Filled" />
|
||||||
</template>
|
</template>
|
||||||
@@ -487,7 +553,9 @@ onUnmounted(() => {
|
|||||||
<NTime :time="item.time" />
|
<NTime :time="item.time" />
|
||||||
</template>
|
</template>
|
||||||
<template #header-extra>
|
<template #header-extra>
|
||||||
<NButton type="error" size="small" @click="lotteryHistory.splice(lotteryHistory.indexOf(item), 1)"> 删除 </NButton>
|
<NButton type="error" size="small" @click="lotteryHistory.splice(lotteryHistory.indexOf(item), 1)">
|
||||||
|
删除
|
||||||
|
</NButton>
|
||||||
</template>
|
</template>
|
||||||
<NSpace vertical>
|
<NSpace vertical>
|
||||||
<NSpace v-for="user in item.users" :key="user.uId">
|
<NSpace v-for="user in item.users" :key="user.uId">
|
||||||
@@ -501,7 +569,14 @@ onUnmounted(() => {
|
|||||||
</NScrollbar>
|
</NScrollbar>
|
||||||
<NEmpty v-else description="暂无记录" />
|
<NEmpty v-else description="暂无记录" />
|
||||||
</NModal>
|
</NModal>
|
||||||
<NModal v-model:show="showOBSModal" preset="card" title="OBS 组件" style="max-width: 90%; width: 800px; max-height: 90vh" closable content-style="overflow: auto">
|
<NModal
|
||||||
|
v-model:show="showOBSModal"
|
||||||
|
preset="card"
|
||||||
|
title="OBS 组件"
|
||||||
|
style="max-width: 90%; width: 800px; max-height: 90vh"
|
||||||
|
closable
|
||||||
|
content-style="overflow: auto"
|
||||||
|
>
|
||||||
<NAlert title="这是什么? " type="info"> 将等待队列以及结果显示在OBS中 </NAlert>
|
<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">
|
||||||
|
|||||||
@@ -14,9 +14,16 @@ import {
|
|||||||
Setting_Queue,
|
Setting_Queue,
|
||||||
} from '@/api/api-models'
|
} from '@/api/api-models'
|
||||||
import { QueryGetAPI, QueryPostAPI, QueryPostAPIWithParams } from '@/api/query'
|
import { QueryGetAPI, QueryPostAPI, QueryPostAPIWithParams } from '@/api/query'
|
||||||
import DanmakuClient, { DanmakuInfo, GiftInfo, RoomAuthInfo } from '@/data/DanmakuClient'
|
import DanmakuClient, { RoomAuthInfo } from '@/data/DanmakuClient'
|
||||||
import { QUEUE_API_URL } from '@/data/constants'
|
import { QUEUE_API_URL } from '@/data/constants'
|
||||||
import { Checkmark12Regular, ClipboardTextLtr24Filled, Delete24Filled, Dismiss16Filled, PeopleQueue24Filled, PresenceBlocked16Regular } from '@vicons/fluent'
|
import {
|
||||||
|
Checkmark12Regular,
|
||||||
|
ClipboardTextLtr24Filled,
|
||||||
|
Delete24Filled,
|
||||||
|
Dismiss16Filled,
|
||||||
|
PeopleQueue24Filled,
|
||||||
|
PresenceBlocked16Regular,
|
||||||
|
} from '@vicons/fluent'
|
||||||
import { ReloadCircleSharp } from '@vicons/ionicons5'
|
import { ReloadCircleSharp } from '@vicons/ionicons5'
|
||||||
import { useStorage } from '@vueuse/core'
|
import { useStorage } from '@vueuse/core'
|
||||||
import { isSameDay } from 'date-fns'
|
import { isSameDay } from 'date-fns'
|
||||||
@@ -140,7 +147,10 @@ const queue = computed(() => {
|
|||||||
let list = new List(accountInfo ? originQueue.value : localQueues.value)
|
let list = new List(accountInfo ? originQueue.value : localQueues.value)
|
||||||
.Where(
|
.Where(
|
||||||
(q) =>
|
(q) =>
|
||||||
!filterName.value || (filterNameContains.value ? q?.user?.name.toLowerCase().includes(filterName.value.toLowerCase()) == true : q?.user?.name.toLowerCase() == filterName.value.toLowerCase()),
|
!filterName.value ||
|
||||||
|
(filterNameContains.value
|
||||||
|
? q?.user?.name.toLowerCase().includes(filterName.value.toLowerCase()) == true
|
||||||
|
: q?.user?.name.toLowerCase() == filterName.value.toLowerCase()),
|
||||||
)
|
)
|
||||||
.Where((q) => (q?.status ?? QueueStatus.Cancel) < QueueStatus.Finish)
|
.Where((q) => (q?.status ?? QueueStatus.Cancel) < QueueStatus.Finish)
|
||||||
.OrderByDescending((q) => q.from == QueueFrom.Manual)
|
.OrderByDescending((q) => q.from == QueueFrom.Manual)
|
||||||
@@ -342,7 +352,9 @@ function checkMessage(eventData: EventModel) {
|
|||||||
if (!settings.value.allowGift && !settings.value.allowIncreasePaymentBySendGift) {
|
if (!settings.value.allowGift && !settings.value.allowIncreasePaymentBySendGift) {
|
||||||
return false // { success: false, message: '不允许通过礼物排队' }
|
return false // { success: false, message: '不允许通过礼物排队' }
|
||||||
}
|
}
|
||||||
const nameNotMatch = (settings.value.giftNames?.length ?? 0) > 0 && settings.value.giftNames?.some((n) => eventData.msg.toLowerCase() === n.toLowerCase()) != true
|
const nameNotMatch =
|
||||||
|
(settings.value.giftNames?.length ?? 0) > 0 &&
|
||||||
|
settings.value.giftNames?.some((n) => eventData.msg.toLowerCase() === n.toLowerCase()) != true
|
||||||
const priceNotMatch = settings.value.minGiftPrice && eventData.price < settings.value.minGiftPrice
|
const priceNotMatch = settings.value.minGiftPrice && eventData.price < settings.value.minGiftPrice
|
||||||
if (settings.value.giftFilterType === QueueGiftFilterType.Or && (!nameNotMatch || !priceNotMatch)) {
|
if (settings.value.giftFilterType === QueueGiftFilterType.Or && (!nameNotMatch || !priceNotMatch)) {
|
||||||
return true // { success: true, message: '' }
|
return true // { success: true, message: '' }
|
||||||
@@ -371,7 +383,9 @@ async function onUpdateFunctionEnable() {
|
|||||||
if (accountInfo.value) {
|
if (accountInfo.value) {
|
||||||
const oldValue = JSON.parse(JSON.stringify(accountInfo.value.settings.enableFunctions))
|
const oldValue = JSON.parse(JSON.stringify(accountInfo.value.settings.enableFunctions))
|
||||||
if (accountInfo.value?.settings.enableFunctions.includes(FunctionTypes.Queue)) {
|
if (accountInfo.value?.settings.enableFunctions.includes(FunctionTypes.Queue)) {
|
||||||
accountInfo.value.settings.enableFunctions = accountInfo.value.settings.enableFunctions.filter((f) => f != FunctionTypes.Queue)
|
accountInfo.value.settings.enableFunctions = accountInfo.value.settings.enableFunctions.filter(
|
||||||
|
(f) => f != FunctionTypes.Queue,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
accountInfo.value.settings.enableFunctions.push(FunctionTypes.Queue)
|
accountInfo.value.settings.enableFunctions.push(FunctionTypes.Queue)
|
||||||
}
|
}
|
||||||
@@ -381,16 +395,22 @@ async function onUpdateFunctionEnable() {
|
|||||||
await SaveEnableFunctions(accountInfo.value?.settings.enableFunctions)
|
await SaveEnableFunctions(accountInfo.value?.settings.enableFunctions)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (data.code == 200) {
|
if (data.code == 200) {
|
||||||
message.success(`已${accountInfo.value?.settings.enableFunctions.includes(FunctionTypes.SongRequest) ? '启用' : '禁用'}队列功能`)
|
message.success(
|
||||||
|
`已${accountInfo.value?.settings.enableFunctions.includes(FunctionTypes.SongRequest) ? '启用' : '禁用'}队列功能`,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
if (accountInfo.value) {
|
if (accountInfo.value) {
|
||||||
accountInfo.value.settings.enableFunctions = oldValue
|
accountInfo.value.settings.enableFunctions = oldValue
|
||||||
}
|
}
|
||||||
message.error(`队列功能${accountInfo.value?.settings.enableFunctions.includes(FunctionTypes.SongRequest) ? '启用' : '禁用'}失败: ${data.message}`)
|
message.error(
|
||||||
|
`队列功能${accountInfo.value?.settings.enableFunctions.includes(FunctionTypes.SongRequest) ? '启用' : '禁用'}失败: ${data.message}`,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
message.error(`队列功能${accountInfo.value?.settings.enableFunctions.includes(FunctionTypes.SongRequest) ? '启用' : '禁用'}失败: ${err}`)
|
message.error(
|
||||||
|
`队列功能${accountInfo.value?.settings.enableFunctions.includes(FunctionTypes.SongRequest) ? '启用' : '禁用'}失败: ${err}`,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -544,7 +564,15 @@ const columns = [
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return h(NTag, { type: statusType, size: 'small', style: data.status == QueueStatus.Progressing ? 'animation: animated-border 2.5s infinite;' : '' }, () => STATUS_MAP[data.status])
|
return h(
|
||||||
|
NTag,
|
||||||
|
{
|
||||||
|
type: statusType,
|
||||||
|
size: 'small',
|
||||||
|
style: data.status == QueueStatus.Progressing ? 'animation: animated-border 2.5s infinite;' : '',
|
||||||
|
},
|
||||||
|
() => STATUS_MAP[data.status],
|
||||||
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -646,7 +674,9 @@ async function updateActive() {
|
|||||||
if (queueData.status != item.status) queueData.status = item.status
|
if (queueData.status != item.status) queueData.status = item.status
|
||||||
if (queueData.giftPrice != item.giftPrice) {
|
if (queueData.giftPrice != item.giftPrice) {
|
||||||
queueData.giftPrice = item.giftPrice
|
queueData.giftPrice = item.giftPrice
|
||||||
message.info(`${queueData.user?.name} 通过发送礼物再次付费: ¥ ${(item?.giftPrice ?? 0) - (queueData?.giftPrice ?? 0)}, 当前总计付费: ¥ ${item.giftPrice}`)
|
message.info(
|
||||||
|
`${queueData.user?.name} 通过发送礼物再次付费: ¥ ${(item?.giftPrice ?? 0) - (queueData?.giftPrice ?? 0)}, 当前总计付费: ¥ ${item.giftPrice}`,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
originQueue.value.unshift(item)
|
originQueue.value.unshift(item)
|
||||||
@@ -721,16 +751,25 @@ onUnmounted(() => {
|
|||||||
<template>
|
<template>
|
||||||
<NAlert type="info" v-if="accountInfo">
|
<NAlert type="info" v-if="accountInfo">
|
||||||
启用队列功能
|
启用队列功能
|
||||||
<NSwitch :value="accountInfo?.settings.enableFunctions.includes(FunctionTypes.Queue)" @update:value="onUpdateFunctionEnable" />
|
<NSwitch
|
||||||
|
:value="accountInfo?.settings.enableFunctions.includes(FunctionTypes.Queue)"
|
||||||
|
@update:value="onUpdateFunctionEnable"
|
||||||
|
/>
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
<NText depth="3">
|
<NText depth="3">
|
||||||
如果没有部署
|
如果没有部署
|
||||||
<NButton text type="primary" tag="a" href="https://www.yuque.com/megghy/dez70g/vfvcyv3024xvaa1p" target="_blank"> VtsuruEventFetcher </NButton>
|
<NButton text type="primary" tag="a" href="https://www.yuque.com/megghy/dez70g/vfvcyv3024xvaa1p" target="_blank">
|
||||||
|
VtsuruEventFetcher
|
||||||
|
</NButton>
|
||||||
则其需要保持此页面开启才能使用, 也不要同时开多个页面, 会导致重复 !(部署了则不影响)
|
则其需要保持此页面开启才能使用, 也不要同时开多个页面, 会导致重复 !(部署了则不影响)
|
||||||
</NText>
|
</NText>
|
||||||
</NAlert>
|
</NAlert>
|
||||||
<NAlert type="warning" v-else title="你尚未注册并登录 VTsuru.live, 大部分规则设置将不可用 (因为我懒得在前段重写一遍逻辑">
|
<NAlert
|
||||||
|
type="warning"
|
||||||
|
v-else
|
||||||
|
title="你尚未注册并登录 VTsuru.live, 大部分规则设置将不可用 (因为我懒得在前段重写一遍逻辑"
|
||||||
|
>
|
||||||
<NButton tag="a" href="/manage" target="_blank" type="primary"> 前往登录或注册 </NButton>
|
<NButton tag="a" href="/manage" target="_blank" type="primary"> 前往登录或注册 </NButton>
|
||||||
</NAlert>
|
</NAlert>
|
||||||
<br />
|
<br />
|
||||||
@@ -746,7 +785,11 @@ onUnmounted(() => {
|
|||||||
</NCard>
|
</NCard>
|
||||||
<br />
|
<br />
|
||||||
<NCard>
|
<NCard>
|
||||||
<NTabs v-if="!accountInfo || accountInfo.settings.enableFunctions.includes(FunctionTypes.Queue)" animated display-directive="show:lazy">
|
<NTabs
|
||||||
|
v-if="!accountInfo || accountInfo.settings.enableFunctions.includes(FunctionTypes.Queue)"
|
||||||
|
animated
|
||||||
|
display-directive="show:lazy"
|
||||||
|
>
|
||||||
<NTabPane name="list" tab="列表">
|
<NTabPane name="list" tab="列表">
|
||||||
<NCard size="small">
|
<NCard size="small">
|
||||||
<NSpace align="center">
|
<NSpace align="center">
|
||||||
@@ -760,7 +803,11 @@ onUnmounted(() => {
|
|||||||
<template #icon>
|
<template #icon>
|
||||||
<NIcon :component="Checkmark12Regular" />
|
<NIcon :component="Checkmark12Regular" />
|
||||||
</template>
|
</template>
|
||||||
今日已处理 | {{ queue.filter((s) => s.status == QueueStatus.Finish && isSameDay(s.finishAt ?? 0, Date.now())).length }} 位
|
今日已处理 |
|
||||||
|
{{
|
||||||
|
queue.filter((s) => s.status == QueueStatus.Finish && isSameDay(s.finishAt ?? 0, Date.now())).length
|
||||||
|
}}
|
||||||
|
位
|
||||||
</NTag>
|
</NTag>
|
||||||
<NInputGroup>
|
<NInputGroup>
|
||||||
<NInput placeholder="手动添加" v-model:value="newQueueName" />
|
<NInput placeholder="手动添加" v-model:value="newQueueName" />
|
||||||
@@ -772,19 +819,31 @@ onUnmounted(() => {
|
|||||||
</template>
|
</template>
|
||||||
确定全部取消吗?
|
确定全部取消吗?
|
||||||
</NPopconfirm>
|
</NPopconfirm>
|
||||||
<NRadioGroup v-model:value="settings.sortType" :disabled="!configCanEdit" @update:value="updateSettings" type="button">
|
<NRadioGroup
|
||||||
|
v-model:value="settings.sortType"
|
||||||
|
:disabled="!configCanEdit"
|
||||||
|
@update:value="updateSettings"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
<NRadioButton :value="QueueSortType.TimeFirst"> 加入时间优先 </NRadioButton>
|
<NRadioButton :value="QueueSortType.TimeFirst"> 加入时间优先 </NRadioButton>
|
||||||
<NRadioButton :value="QueueSortType.PaymentFist"> 付费价格优先 </NRadioButton>
|
<NRadioButton :value="QueueSortType.PaymentFist"> 付费价格优先 </NRadioButton>
|
||||||
<NRadioButton :value="QueueSortType.GuardFirst"> 舰长优先 (按等级) </NRadioButton>
|
<NRadioButton :value="QueueSortType.GuardFirst"> 舰长优先 (按等级) </NRadioButton>
|
||||||
</NRadioGroup>
|
</NRadioGroup>
|
||||||
<NCheckbox v-if="configCanEdit" v-model:checked="settings.isReverse" @update:checked="updateSettings"> 倒序 </NCheckbox>
|
<NCheckbox v-if="configCanEdit" v-model:checked="settings.isReverse" @update:checked="updateSettings">
|
||||||
|
倒序
|
||||||
|
</NCheckbox>
|
||||||
<NCheckbox v-else v-model:checked="isReverse"> 倒序 </NCheckbox>
|
<NCheckbox v-else v-model:checked="isReverse"> 倒序 </NCheckbox>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
</NCard>
|
</NCard>
|
||||||
<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 embedded size="small" content-style="padding: 5px;" :style="`${queueData.status == QueueStatus.Progressing ? 'animation: animated-border 2.5s infinite;' : ''};height: 100%;`">
|
<NCard
|
||||||
|
embedded
|
||||||
|
size="small"
|
||||||
|
content-style="padding: 5px;"
|
||||||
|
:style="`${queueData.status == QueueStatus.Progressing ? 'animation: animated-border 2.5s infinite;' : ''};height: 100%;`"
|
||||||
|
>
|
||||||
<NSpace justify="space-between" align="center" style="height: 100%; margin: 0 5px 0 5px">
|
<NSpace justify="space-between" align="center" style="height: 100%; margin: 0 5px 0 5px">
|
||||||
<NSpace align="center">
|
<NSpace align="center">
|
||||||
<div
|
<div
|
||||||
@@ -803,7 +862,12 @@ 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 v-if="(queueData.from == QueueFrom.Danmaku || queueData.from == QueueFrom.Gift) && queueData.user?.fans_medal_wearing_status">
|
<NSpace
|
||||||
|
v-if="
|
||||||
|
(queueData.from == QueueFrom.Danmaku || queueData.from == QueueFrom.Gift) &&
|
||||||
|
queueData.user?.fans_medal_wearing_status
|
||||||
|
"
|
||||||
|
>
|
||||||
<NTag size="tiny" round>
|
<NTag size="tiny" round>
|
||||||
<NTag size="tiny" round :bordered="false">
|
<NTag size="tiny" round :bordered="false">
|
||||||
<NText depth="3">
|
<NText depth="3">
|
||||||
@@ -815,10 +879,17 @@ onUnmounted(() => {
|
|||||||
</span>
|
</span>
|
||||||
</NTag>
|
</NTag>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
<NTag v-if="(queueData.user?.guard_level ?? 0) > 0" size="small" :bordered="false" :color="{ textColor: 'white', color: GetGuardColor(queueData.user?.guard_level) }">
|
<NTag
|
||||||
|
v-if="(queueData.user?.guard_level ?? 0) > 0"
|
||||||
|
size="small"
|
||||||
|
:bordered="false"
|
||||||
|
:color="{ textColor: 'white', color: GetGuardColor(queueData.user?.guard_level) }"
|
||||||
|
>
|
||||||
{{ queueData.user?.guard_level == 1 ? '总督' : queueData.user?.guard_level == 2 ? '提督' : '舰长' }}
|
{{ 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"> 付费 | {{ queueData.giftPrice }} </NTag>
|
<NTag v-if="(queueData.giftPrice ?? 0) > 0" size="small" :bordered="false" type="error">
|
||||||
|
付费 | {{ queueData.giftPrice }}
|
||||||
|
</NTag>
|
||||||
<NTooltip>
|
<NTooltip>
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<NText style="font-size: small">
|
<NText style="font-size: small">
|
||||||
@@ -835,8 +906,15 @@ onUnmounted(() => {
|
|||||||
circle
|
circle
|
||||||
type="primary"
|
type="primary"
|
||||||
style="height: 30px; width: 30px"
|
style="height: 30px; width: 30px"
|
||||||
:disabled="queue.findIndex((s) => s.id != queueData.id && s.status == QueueStatus.Progressing) > -1"
|
:disabled="
|
||||||
@click="updateStatus(queueData, queueData.status == QueueStatus.Progressing ? QueueStatus.Waiting : QueueStatus.Progressing)"
|
queue.findIndex((s) => s.id != queueData.id && s.status == QueueStatus.Progressing) > -1
|
||||||
|
"
|
||||||
|
@click="
|
||||||
|
updateStatus(
|
||||||
|
queueData,
|
||||||
|
queueData.status == QueueStatus.Progressing ? QueueStatus.Waiting : QueueStatus.Progressing,
|
||||||
|
)
|
||||||
|
"
|
||||||
:style="`animation: ${queueData.status == QueueStatus.Waiting ? '' : 'loading 5s linear infinite'}`"
|
:style="`animation: ${queueData.status == QueueStatus.Waiting ? '' : 'loading 5s linear infinite'}`"
|
||||||
:secondary="queueData.status == QueueStatus.Progressing"
|
:secondary="queueData.status == QueueStatus.Progressing"
|
||||||
:loading="isLoading"
|
:loading="isLoading"
|
||||||
@@ -856,7 +934,13 @@ onUnmounted(() => {
|
|||||||
</NTooltip>
|
</NTooltip>
|
||||||
<NTooltip>
|
<NTooltip>
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<NButton circle type="success" style="height: 30px; width: 30px" :loading="isLoading" @click="updateStatus(queueData, QueueStatus.Finish)">
|
<NButton
|
||||||
|
circle
|
||||||
|
type="success"
|
||||||
|
style="height: 30px; width: 30px"
|
||||||
|
:loading="isLoading"
|
||||||
|
@click="updateStatus(queueData, QueueStatus.Finish)"
|
||||||
|
>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<NIcon :component="Checkmark12Regular" />
|
<NIcon :component="Checkmark12Regular" />
|
||||||
</template>
|
</template>
|
||||||
@@ -881,7 +965,13 @@ onUnmounted(() => {
|
|||||||
</NTooltip>
|
</NTooltip>
|
||||||
<NTooltip>
|
<NTooltip>
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<NButton circle type="error" style="height: 30px; width: 30px" :loading="isLoading" @click="updateStatus(queueData, QueueStatus.Cancel)">
|
<NButton
|
||||||
|
circle
|
||||||
|
type="error"
|
||||||
|
style="height: 30px; width: 30px"
|
||||||
|
:loading="isLoading"
|
||||||
|
@click="updateStatus(queueData, QueueStatus.Cancel)"
|
||||||
|
>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<NIcon :component="Dismiss16Filled" />
|
<NIcon :component="Dismiss16Filled" />
|
||||||
</template>
|
</template>
|
||||||
@@ -937,7 +1027,12 @@ onUnmounted(() => {
|
|||||||
</template>
|
</template>
|
||||||
<NInput v-else v-model:value="defaultKeyword" />
|
<NInput v-else v-model:value="defaultKeyword" />
|
||||||
</NInputGroup>
|
</NInputGroup>
|
||||||
<NRadioGroup v-model:value="settings.matchType" :disabled="!configCanEdit" @update:value="updateSettings" type="button">
|
<NRadioGroup
|
||||||
|
v-model:value="settings.matchType"
|
||||||
|
:disabled="!configCanEdit"
|
||||||
|
@update:value="updateSettings"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
<NRadioButton :value="KeywordMatchType.Full"> 完全一致 </NRadioButton>
|
<NRadioButton :value="KeywordMatchType.Full"> 完全一致 </NRadioButton>
|
||||||
<NRadioButton :value="KeywordMatchType.Contains"> 包含 </NRadioButton>
|
<NRadioButton :value="KeywordMatchType.Contains"> 包含 </NRadioButton>
|
||||||
<NRadioButton :value="KeywordMatchType.Regex"> 正则 </NRadioButton>
|
<NRadioButton :value="KeywordMatchType.Regex"> 正则 </NRadioButton>
|
||||||
@@ -949,21 +1044,60 @@ 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 v-model:checked="settings.enableOnStreaming" @update:checked="updateSettings" :disabled="!configCanEdit"> 仅在直播时才允许加入 </NCheckbox>
|
<NCheckbox
|
||||||
<NCheckbox v-model:checked="settings.allowAllDanmaku" @update:checked="updateSettings" :disabled="!configCanEdit"> 允许所有用户加入 </NCheckbox>
|
v-model:checked="settings.enableOnStreaming"
|
||||||
|
@update:checked="updateSettings"
|
||||||
|
:disabled="!configCanEdit"
|
||||||
|
>
|
||||||
|
仅在直播时才允许加入
|
||||||
|
</NCheckbox>
|
||||||
|
<NCheckbox
|
||||||
|
v-model:checked="settings.allowAllDanmaku"
|
||||||
|
@update:checked="updateSettings"
|
||||||
|
:disabled="!configCanEdit"
|
||||||
|
>
|
||||||
|
允许所有用户加入
|
||||||
|
</NCheckbox>
|
||||||
<template v-if="!settings.allowAllDanmaku">
|
<template v-if="!settings.allowAllDanmaku">
|
||||||
<NInputGroup style="width: 270px">
|
<NInputGroup style="width: 270px">
|
||||||
<NInputGroupLabel> 最低粉丝牌等级 </NInputGroupLabel>
|
<NInputGroupLabel> 最低粉丝牌等级 </NInputGroupLabel>
|
||||||
<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 v-if="!settings.allowAllDanmaku" v-model:checked="settings.needJianzhang" @update:checked="updateSettings" :disabled="!configCanEdit"> 只允许舰长 </NCheckbox>
|
<NCheckbox
|
||||||
<NCheckbox v-if="!settings.allowAllDanmaku" v-model:checked="settings.needTidu" @update:checked="updateSettings" :disabled="!configCanEdit"> 只允许提督 </NCheckbox>
|
v-if="!settings.allowAllDanmaku"
|
||||||
<NCheckbox v-if="!settings.allowAllDanmaku" v-model:checked="settings.needZongdu" @update:checked="updateSettings" :disabled="!configCanEdit"> 只允许总督 </NCheckbox>
|
v-model:checked="settings.needJianzhang"
|
||||||
|
@update:checked="updateSettings"
|
||||||
|
:disabled="!configCanEdit"
|
||||||
|
>
|
||||||
|
只允许舰长
|
||||||
|
</NCheckbox>
|
||||||
|
<NCheckbox
|
||||||
|
v-if="!settings.allowAllDanmaku"
|
||||||
|
v-model:checked="settings.needTidu"
|
||||||
|
@update:checked="updateSettings"
|
||||||
|
:disabled="!configCanEdit"
|
||||||
|
>
|
||||||
|
只允许提督
|
||||||
|
</NCheckbox>
|
||||||
|
<NCheckbox
|
||||||
|
v-if="!settings.allowAllDanmaku"
|
||||||
|
v-model:checked="settings.needZongdu"
|
||||||
|
@update:checked="updateSettings"
|
||||||
|
:disabled="!configCanEdit"
|
||||||
|
>
|
||||||
|
只允许总督
|
||||||
|
</NCheckbox>
|
||||||
</template>
|
</template>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
<NSpace align="center">
|
<NSpace align="center">
|
||||||
<NCheckbox v-model:checked="settings.allowGift" @update:checked="updateSettings" :disabled="!configCanEdit"> 允许通过发送礼物加入队列 </NCheckbox>
|
<NCheckbox
|
||||||
|
v-model:checked="settings.allowGift"
|
||||||
|
@update:checked="updateSettings"
|
||||||
|
:disabled="!configCanEdit"
|
||||||
|
>
|
||||||
|
允许通过发送礼物加入队列
|
||||||
|
</NCheckbox>
|
||||||
<template v-if="settings.allowGift">
|
<template v-if="settings.allowGift">
|
||||||
<NInputGroup v-if="settings.allowGift" style="width: 250px">
|
<NInputGroup v-if="settings.allowGift" style="width: 250px">
|
||||||
<NInputGroupLabel> 最低价格 </NInputGroupLabel>
|
<NInputGroupLabel> 最低价格 </NInputGroupLabel>
|
||||||
@@ -986,21 +1120,40 @@ onUnmounted(() => {
|
|||||||
/>
|
/>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
<span>
|
<span>
|
||||||
<NRadioGroup v-model:value="settings.giftFilterType" :disabled="!configCanEdit" @update:value="updateSettings">
|
<NRadioGroup
|
||||||
|
v-model:value="settings.giftFilterType"
|
||||||
|
:disabled="!configCanEdit"
|
||||||
|
@update:value="updateSettings"
|
||||||
|
>
|
||||||
<NRadioButton :value="QueueGiftFilterType.And"> 需同时满足礼物名和价格 </NRadioButton>
|
<NRadioButton :value="QueueGiftFilterType.And"> 需同时满足礼物名和价格 </NRadioButton>
|
||||||
<NRadioButton :value="QueueGiftFilterType.Or"> 礼物名/价格 二选一 </NRadioButton>
|
<NRadioButton :value="QueueGiftFilterType.Or"> 礼物名/价格 二选一 </NRadioButton>
|
||||||
</NRadioGroup>
|
</NRadioGroup>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<NCheckbox v-model:checked="settings.allowIncreasePaymentBySendGift" @update:checked="updateSettings" :disabled="!configCanEdit">
|
<NCheckbox
|
||||||
|
v-model:checked="settings.allowIncreasePaymentBySendGift"
|
||||||
|
@update:checked="updateSettings"
|
||||||
|
:disabled="!configCanEdit"
|
||||||
|
>
|
||||||
在队列中时允许继续发送礼物累计付费量 (仅限上方设定的礼物)
|
在队列中时允许继续发送礼物累计付费量 (仅限上方设定的礼物)
|
||||||
</NCheckbox>
|
</NCheckbox>
|
||||||
<NCheckbox v-if="settings.allowIncreasePaymentBySendGift" v-model:checked="settings.allowIncreaseByAnyPayment" @update:checked="updateSettings" :disabled="!configCanEdit">
|
<NCheckbox
|
||||||
|
v-if="settings.allowIncreasePaymentBySendGift"
|
||||||
|
v-model:checked="settings.allowIncreaseByAnyPayment"
|
||||||
|
@update:checked="updateSettings"
|
||||||
|
:disabled="!configCanEdit"
|
||||||
|
>
|
||||||
允许发送任意礼物来叠加付费量
|
允许发送任意礼物来叠加付费量
|
||||||
</NCheckbox>
|
</NCheckbox>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
<NDivider> 冷却 (单位: 秒) </NDivider>
|
<NDivider> 冷却 (单位: 秒) </NDivider>
|
||||||
<NCheckbox v-model:checked="settings.enableCooldown" @update:checked="updateSettings" :disabled="!configCanEdit"> 启用排队冷却 </NCheckbox>
|
<NCheckbox
|
||||||
|
v-model:checked="settings.enableCooldown"
|
||||||
|
@update:checked="updateSettings"
|
||||||
|
:disabled="!configCanEdit"
|
||||||
|
>
|
||||||
|
启用排队冷却
|
||||||
|
</NCheckbox>
|
||||||
<NSpace v-if="settings.enableCooldown">
|
<NSpace v-if="settings.enableCooldown">
|
||||||
<NInputGroup style="width: 250px">
|
<NInputGroup style="width: 250px">
|
||||||
<NInputGroupLabel> 普通弹幕 </NInputGroupLabel>
|
<NInputGroupLabel> 普通弹幕 </NInputGroupLabel>
|
||||||
@@ -1024,9 +1177,27 @@ onUnmounted(() => {
|
|||||||
</NInputGroup>
|
</NInputGroup>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
<NDivider> OBS </NDivider>
|
<NDivider> OBS </NDivider>
|
||||||
<NCheckbox v-model:checked="settings.showRequireInfo" :disabled="!configCanEdit" @update:checked="updateSettings"> 显示底部的需求信息 </NCheckbox>
|
<NCheckbox
|
||||||
<NCheckbox v-model:checked="settings.showPayment" :disabled="!configCanEdit" @update:checked="updateSettings"> 显示付费信息 </NCheckbox>
|
v-model:checked="settings.showRequireInfo"
|
||||||
<NCheckbox v-model:checked="settings.showFanMadelInfo" :disabled="!configCanEdit" @update:checked="updateSettings"> 显示用户粉丝牌 </NCheckbox>
|
:disabled="!configCanEdit"
|
||||||
|
@update:checked="updateSettings"
|
||||||
|
>
|
||||||
|
显示底部的需求信息
|
||||||
|
</NCheckbox>
|
||||||
|
<NCheckbox
|
||||||
|
v-model:checked="settings.showPayment"
|
||||||
|
:disabled="!configCanEdit"
|
||||||
|
@update:checked="updateSettings"
|
||||||
|
>
|
||||||
|
显示付费信息
|
||||||
|
</NCheckbox>
|
||||||
|
<NCheckbox
|
||||||
|
v-model:checked="settings.showFanMadelInfo"
|
||||||
|
:disabled="!configCanEdit"
|
||||||
|
@update:checked="updateSettings"
|
||||||
|
>
|
||||||
|
显示用户粉丝牌
|
||||||
|
</NCheckbox>
|
||||||
<NDivider> 其他 </NDivider>
|
<NDivider> 其他 </NDivider>
|
||||||
<NCheckbox v-model:checked="isWarnMessageAutoClose"> 自动关闭加入队列失败时的提示消息 </NCheckbox>
|
<NCheckbox v-model:checked="isWarnMessageAutoClose"> 自动关闭加入队列失败时的提示消息 </NCheckbox>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
|
|||||||
@@ -241,9 +241,9 @@ function speakDirect(text: string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
synth.cancel()
|
synth.cancel()
|
||||||
let u = new SpeechSynthesisUtterance()
|
const u = new SpeechSynthesisUtterance()
|
||||||
u.text = text
|
u.text = text
|
||||||
let voices = synth.getVoices()
|
const voices = synth.getVoices()
|
||||||
const voice = voices.find((v) => v.name === settings.value.speechInfo.voice)
|
const voice = voices.find((v) => v.name === settings.value.speechInfo.voice)
|
||||||
if (voice) {
|
if (voice) {
|
||||||
u.voice = voice
|
u.voice = voice
|
||||||
@@ -670,7 +670,7 @@ onUnmounted(() => {
|
|||||||
<NCollapseItem title="队列" name="1">
|
<NCollapseItem title="队列" name="1">
|
||||||
<NEmpty v-if="speakQueue.length == 0"> 暂无 </NEmpty>
|
<NEmpty v-if="speakQueue.length == 0"> 暂无 </NEmpty>
|
||||||
<NList v-else size="small" bordered>
|
<NList v-else size="small" bordered>
|
||||||
<NListItem v-for="item in speakQueue">
|
<NListItem v-for="item in speakQueue" :key="item.data.time">
|
||||||
<NSpace align="center">
|
<NSpace align="center">
|
||||||
<NButton @click="forceSpeak(item.data)" type="primary" secondary size="small"> 读 </NButton>
|
<NButton @click="forceSpeak(item.data)" type="primary" secondary size="small"> 读 </NButton>
|
||||||
<NButton @click="speakQueue.splice(speakQueue.indexOf(item), 1)" type="error" secondary size="small">
|
<NButton @click="speakQueue.splice(speakQueue.indexOf(item), 1)" type="error" secondary size="small">
|
||||||
|
|||||||
@@ -1,11 +1,30 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { AddBiliBlackList, SaveEnableFunctions, useAccount } from '@/api/account'
|
import { AddBiliBlackList, SaveEnableFunctions, useAccount } from '@/api/account'
|
||||||
import { DanmakuUserInfo, EventDataTypes, EventModel, FunctionTypes, Setting_SongRequest, SongRequestFrom, SongRequestInfo, SongRequestStatus, SongsInfo } from '@/api/api-models'
|
import {
|
||||||
|
DanmakuUserInfo,
|
||||||
|
EventDataTypes,
|
||||||
|
EventModel,
|
||||||
|
FunctionTypes,
|
||||||
|
Setting_SongRequest,
|
||||||
|
SongRequestFrom,
|
||||||
|
SongRequestInfo,
|
||||||
|
SongRequestStatus,
|
||||||
|
SongsInfo,
|
||||||
|
} from '@/api/api-models'
|
||||||
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 DanmakuClient, { DanmakuInfo, RoomAuthInfo, SCInfo } from '@/data/DanmakuClient'
|
import DanmakuClient, { RoomAuthInfo } from '@/data/DanmakuClient'
|
||||||
import { SONG_REQUEST_API_URL } from '@/data/constants'
|
import { SONG_REQUEST_API_URL } from '@/data/constants'
|
||||||
import { Checkmark12Regular, Delete24Filled, Dismiss16Filled, Info24Filled, Mic24Filled, PeopleQueue24Filled, Play24Filled, PresenceBlocked16Regular } from '@vicons/fluent'
|
import {
|
||||||
|
Checkmark12Regular,
|
||||||
|
Delete24Filled,
|
||||||
|
Dismiss16Filled,
|
||||||
|
Info24Filled,
|
||||||
|
Mic24Filled,
|
||||||
|
PeopleQueue24Filled,
|
||||||
|
Play24Filled,
|
||||||
|
PresenceBlocked16Regular,
|
||||||
|
} from '@vicons/fluent'
|
||||||
import { ReloadCircleSharp } from '@vicons/ionicons5'
|
import { ReloadCircleSharp } from '@vicons/ionicons5'
|
||||||
import { useStorage } from '@vueuse/core'
|
import { useStorage } from '@vueuse/core'
|
||||||
import { isSameDay } from 'date-fns'
|
import { isSameDay } from 'date-fns'
|
||||||
@@ -190,7 +209,9 @@ async function getAllSong() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function addSong(danmaku: EventModel) {
|
async function addSong(danmaku: EventModel) {
|
||||||
console.log(`[OPEN-LIVE-Song-Request] 收到 [${danmaku.name}] 的点歌${danmaku.type == EventDataTypes.SC ? 'SC' : '弹幕'}: ${danmaku.msg}`)
|
console.log(
|
||||||
|
`[OPEN-LIVE-Song-Request] 收到 [${danmaku.name}] 的点歌${danmaku.type == EventDataTypes.SC ? 'SC' : '弹幕'}: ${danmaku.msg}`,
|
||||||
|
)
|
||||||
if (settings.value.enableOnStreaming && accountInfo.value?.streamerInfo?.isStreaming != true) {
|
if (settings.value.enableOnStreaming && accountInfo.value?.streamerInfo?.isStreaming != true) {
|
||||||
message.info('当前未在直播中, 无法添加点歌请求. 或者关闭设置中的仅允许直播时加入')
|
message.info('当前未在直播中, 无法添加点歌请求. 或者关闭设置中的仅允许直播时加入')
|
||||||
return
|
return
|
||||||
@@ -342,7 +363,9 @@ async function onUpdateFunctionEnable() {
|
|||||||
if (accountInfo.value) {
|
if (accountInfo.value) {
|
||||||
const oldValue = JSON.parse(JSON.stringify(accountInfo.value.settings.enableFunctions))
|
const oldValue = JSON.parse(JSON.stringify(accountInfo.value.settings.enableFunctions))
|
||||||
if (accountInfo.value?.settings.enableFunctions.includes(FunctionTypes.SongRequest)) {
|
if (accountInfo.value?.settings.enableFunctions.includes(FunctionTypes.SongRequest)) {
|
||||||
accountInfo.value.settings.enableFunctions = accountInfo.value.settings.enableFunctions.filter((f) => f != FunctionTypes.SongRequest)
|
accountInfo.value.settings.enableFunctions = accountInfo.value.settings.enableFunctions.filter(
|
||||||
|
(f) => f != FunctionTypes.SongRequest,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
accountInfo.value.settings.enableFunctions.push(FunctionTypes.SongRequest)
|
accountInfo.value.settings.enableFunctions.push(FunctionTypes.SongRequest)
|
||||||
}
|
}
|
||||||
@@ -352,16 +375,22 @@ async function onUpdateFunctionEnable() {
|
|||||||
await SaveEnableFunctions(accountInfo.value?.settings.enableFunctions)
|
await SaveEnableFunctions(accountInfo.value?.settings.enableFunctions)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (data.code == 200) {
|
if (data.code == 200) {
|
||||||
message.success(`已${accountInfo.value?.settings.enableFunctions.includes(FunctionTypes.SongRequest) ? '启用' : '禁用'}点歌功能`)
|
message.success(
|
||||||
|
`已${accountInfo.value?.settings.enableFunctions.includes(FunctionTypes.SongRequest) ? '启用' : '禁用'}点歌功能`,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
if (accountInfo.value) {
|
if (accountInfo.value) {
|
||||||
accountInfo.value.settings.enableFunctions = oldValue
|
accountInfo.value.settings.enableFunctions = oldValue
|
||||||
}
|
}
|
||||||
message.error(`点歌功能${accountInfo.value?.settings.enableFunctions.includes(FunctionTypes.SongRequest) ? '启用' : '禁用'}失败: ${data.message}`)
|
message.error(
|
||||||
|
`点歌功能${accountInfo.value?.settings.enableFunctions.includes(FunctionTypes.SongRequest) ? '启用' : '禁用'}失败: ${data.message}`,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
message.error(`点歌功能${accountInfo.value?.settings.enableFunctions.includes(FunctionTypes.SongRequest) ? '启用' : '禁用'}失败: ${err}`)
|
message.error(
|
||||||
|
`点歌功能${accountInfo.value?.settings.enableFunctions.includes(FunctionTypes.SongRequest) ? '启用' : '禁用'}失败: ${err}`,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -444,7 +473,14 @@ const columns = [
|
|||||||
NTooltip,
|
NTooltip,
|
||||||
{ trigger: 'hover' },
|
{ trigger: 'hover' },
|
||||||
{
|
{
|
||||||
trigger: () => h(NTag, { bordered: false, size: 'small' }, data.from == SongRequestFrom.Manual ? () => h(NText, { italic: true }, () => '手动添加') : () => data.user?.name),
|
trigger: () =>
|
||||||
|
h(
|
||||||
|
NTag,
|
||||||
|
{ bordered: false, size: 'small' },
|
||||||
|
data.from == SongRequestFrom.Manual
|
||||||
|
? () => h(NText, { italic: true }, () => '手动添加')
|
||||||
|
: () => data.user?.name,
|
||||||
|
),
|
||||||
default: () => (data.from == SongRequestFrom.Manual ? '就是主播自己' : data.user?.uid),
|
default: () => (data.from == SongRequestFrom.Manual ? '就是主播自己' : data.user?.uid),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -518,7 +554,15 @@ const columns = [
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return h(NTag, { type: statusType, size: 'small', style: data.status == SongRequestStatus.Singing ? 'animation: animated-border 2.5s infinite;' : '' }, () => STATUS_MAP[data.status])
|
return h(
|
||||||
|
NTag,
|
||||||
|
{
|
||||||
|
type: statusType,
|
||||||
|
size: 'small',
|
||||||
|
style: data.status == SongRequestStatus.Singing ? 'animation: animated-border 2.5s infinite;' : '',
|
||||||
|
},
|
||||||
|
() => STATUS_MAP[data.status],
|
||||||
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -705,16 +749,25 @@ onUnmounted(() => {
|
|||||||
<template>
|
<template>
|
||||||
<NAlert type="info" v-if="accountInfo">
|
<NAlert type="info" v-if="accountInfo">
|
||||||
启用点歌功能
|
启用点歌功能
|
||||||
<NSwitch :value="accountInfo?.settings.enableFunctions.includes(FunctionTypes.SongRequest)" @update:value="onUpdateFunctionEnable" />
|
<NSwitch
|
||||||
|
:value="accountInfo?.settings.enableFunctions.includes(FunctionTypes.SongRequest)"
|
||||||
|
@update:value="onUpdateFunctionEnable"
|
||||||
|
/>
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
<NText depth="3">
|
<NText depth="3">
|
||||||
如果没有部署
|
如果没有部署
|
||||||
<NButton text type="primary" tag="a" href="https://www.yuque.com/megghy/dez70g/vfvcyv3024xvaa1p" target="_blank"> VtsuruEventFetcher </NButton>
|
<NButton text type="primary" tag="a" href="https://www.yuque.com/megghy/dez70g/vfvcyv3024xvaa1p" target="_blank">
|
||||||
|
VtsuruEventFetcher
|
||||||
|
</NButton>
|
||||||
则其需要保持此页面开启才能点歌, 也不要同时开多个页面, 会导致点歌重复 !(部署了则不影响)
|
则其需要保持此页面开启才能点歌, 也不要同时开多个页面, 会导致点歌重复 !(部署了则不影响)
|
||||||
</NText>
|
</NText>
|
||||||
</NAlert>
|
</NAlert>
|
||||||
<NAlert type="warning" v-else title="你尚未注册并登录 VTsuru.live, 大部分规则设置将不可用 (因为我懒得在前段重写一遍逻辑">
|
<NAlert
|
||||||
|
type="warning"
|
||||||
|
v-else
|
||||||
|
title="你尚未注册并登录 VTsuru.live, 大部分规则设置将不可用 (因为我懒得在前段重写一遍逻辑"
|
||||||
|
>
|
||||||
<NButton tag="a" href="/manage" target="_blank" type="primary"> 前往登录或注册 </NButton>
|
<NButton tag="a" href="/manage" target="_blank" type="primary"> 前往登录或注册 </NButton>
|
||||||
</NAlert>
|
</NAlert>
|
||||||
<br />
|
<br />
|
||||||
@@ -730,7 +783,11 @@ onUnmounted(() => {
|
|||||||
</NCard>
|
</NCard>
|
||||||
<br />
|
<br />
|
||||||
<NCard>
|
<NCard>
|
||||||
<NTabs v-if="!accountInfo || accountInfo.settings.enableFunctions.includes(FunctionTypes.SongRequest)" animated display-directive="show:lazy">
|
<NTabs
|
||||||
|
v-if="!accountInfo || accountInfo.settings.enableFunctions.includes(FunctionTypes.SongRequest)"
|
||||||
|
animated
|
||||||
|
display-directive="show:lazy"
|
||||||
|
>
|
||||||
<NTabPane name="list" tab="列表">
|
<NTabPane name="list" tab="列表">
|
||||||
<NCard size="small">
|
<NCard size="small">
|
||||||
<NSpace align="center">
|
<NSpace align="center">
|
||||||
@@ -744,13 +801,20 @@ onUnmounted(() => {
|
|||||||
<template #icon>
|
<template #icon>
|
||||||
<NIcon :component="Checkmark12Regular" />
|
<NIcon :component="Checkmark12Regular" />
|
||||||
</template>
|
</template>
|
||||||
今日已演唱 | {{ songs.filter((s) => s.status != SongRequestStatus.Cancel && isSameDay(s.finishAt ?? 0, Date.now())).length }} 首
|
今日已演唱 |
|
||||||
|
{{
|
||||||
|
songs.filter((s) => s.status != SongRequestStatus.Cancel && isSameDay(s.finishAt ?? 0, Date.now()))
|
||||||
|
.length
|
||||||
|
}}
|
||||||
|
首
|
||||||
</NTag>
|
</NTag>
|
||||||
<NInputGroup>
|
<NInputGroup>
|
||||||
<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>
|
||||||
<NCheckbox v-if="configCanEdit" v-model:checked="settings.isReverse" @update:checked="updateSettings"> 倒序 </NCheckbox>
|
<NCheckbox v-if="configCanEdit" v-model:checked="settings.isReverse" @update:checked="updateSettings">
|
||||||
|
倒序
|
||||||
|
</NCheckbox>
|
||||||
<NCheckbox v-else v-model:checked="isReverse"> 倒序 </NCheckbox>
|
<NCheckbox v-else v-model:checked="isReverse"> 倒序 </NCheckbox>
|
||||||
<NPopconfirm @positive-click="deactiveAllSongs">
|
<NPopconfirm @positive-click="deactiveAllSongs">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
@@ -769,10 +833,17 @@ 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 embedded size="small" content-style="padding: 5px;" :style="`${song.status == SongRequestStatus.Singing ? 'animation: animated-border 2.5s infinite;' : ''};height: 100%;`">
|
<NCard
|
||||||
|
embedded
|
||||||
|
size="small"
|
||||||
|
content-style="padding: 5px;"
|
||||||
|
:style="`${song.status == SongRequestStatus.Singing ? 'animation: animated-border 2.5s infinite;' : ''};height: 100%;`"
|
||||||
|
>
|
||||||
<NSpace justify="space-between" align="center" style="height: 100%; margin: 0 5px 0 5px">
|
<NSpace justify="space-between" align="center" style="height: 100%; margin: 0 5px 0 5px">
|
||||||
<NSpace align="center">
|
<NSpace align="center">
|
||||||
<div :style="`border-radius: 4px; background-color: ${song.status == SongRequestStatus.Singing ? '#75c37f' : '#577fb8'}; width: 10px; height: 20px`"></div>
|
<div
|
||||||
|
:style="`border-radius: 4px; background-color: ${song.status == SongRequestStatus.Singing ? '#75c37f' : '#577fb8'}; width: 10px; height: 20px`"
|
||||||
|
></div>
|
||||||
<NText strong style="font-size: 18px">
|
<NText strong style="font-size: 18px">
|
||||||
{{ song.songName }}
|
{{ song.songName }}
|
||||||
</NText>
|
</NText>
|
||||||
@@ -791,7 +862,12 @@ onUnmounted(() => {
|
|||||||
{{ song.user?.uid }}
|
{{ song.user?.uid }}
|
||||||
</NTooltip>
|
</NTooltip>
|
||||||
</template>
|
</template>
|
||||||
<NSpace v-if="(song.from == SongRequestFrom.Danmaku || song.from == SongRequestFrom.SC) && song.user?.fans_medal_wearing_status">
|
<NSpace
|
||||||
|
v-if="
|
||||||
|
(song.from == SongRequestFrom.Danmaku || song.from == SongRequestFrom.SC) &&
|
||||||
|
song.user?.fans_medal_wearing_status
|
||||||
|
"
|
||||||
|
>
|
||||||
<NTag size="tiny" round>
|
<NTag size="tiny" round>
|
||||||
<NTag size="tiny" round :bordered="false">
|
<NTag size="tiny" round :bordered="false">
|
||||||
<NText depth="3">
|
<NText depth="3">
|
||||||
@@ -803,10 +879,21 @@ onUnmounted(() => {
|
|||||||
</span>
|
</span>
|
||||||
</NTag>
|
</NTag>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
<NTag v-if="(song.user?.guard_level ?? 0) > 0" size="small" :bordered="false" :color="{ textColor: 'white', color: GetGuardColor(song.user?.guard_level) }">
|
<NTag
|
||||||
|
v-if="(song.user?.guard_level ?? 0) > 0"
|
||||||
|
size="small"
|
||||||
|
:bordered="false"
|
||||||
|
:color="{ textColor: 'white', color: GetGuardColor(song.user?.guard_level) }"
|
||||||
|
>
|
||||||
{{ song.user?.guard_level == 1 ? '总督' : song.user?.guard_level == 2 ? '提督' : '舰长' }}
|
{{ song.user?.guard_level == 1 ? '总督' : song.user?.guard_level == 2 ? '提督' : '舰长' }}
|
||||||
</NTag>
|
</NTag>
|
||||||
<NTag v-if="song.from == SongRequestFrom.SC" size="small" :color="{ textColor: 'white', color: GetSCColor(song.scPrice ?? 0) }"> SC | {{ song.scPrice }} </NTag>
|
<NTag
|
||||||
|
v-if="song.from == SongRequestFrom.SC"
|
||||||
|
size="small"
|
||||||
|
:color="{ textColor: 'white', color: GetSCColor(song.scPrice ?? 0) }"
|
||||||
|
>
|
||||||
|
SC | {{ song.scPrice }}
|
||||||
|
</NTag>
|
||||||
<NTooltip>
|
<NTooltip>
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<NText style="font-size: small">
|
<NText style="font-size: small">
|
||||||
@@ -819,7 +906,13 @@ 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 circle type="success" style="height: 30px; width: 30px" :loading="isLrcLoading == song?.song?.key" @click="selectedSong = song.song">
|
<NButton
|
||||||
|
circle
|
||||||
|
type="success"
|
||||||
|
style="height: 30px; width: 30px"
|
||||||
|
:loading="isLrcLoading == song?.song?.key"
|
||||||
|
@click="selectedSong = song.song"
|
||||||
|
>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<NIcon :component="Play24Filled" />
|
<NIcon :component="Play24Filled" />
|
||||||
</template>
|
</template>
|
||||||
@@ -833,8 +926,17 @@ onUnmounted(() => {
|
|||||||
circle
|
circle
|
||||||
type="primary"
|
type="primary"
|
||||||
style="height: 30px; width: 30px"
|
style="height: 30px; width: 30px"
|
||||||
:disabled="songs.findIndex((s) => s.id != song.id && s.status == SongRequestStatus.Singing) > -1"
|
:disabled="
|
||||||
@click="updateSongStatus(song, song.status == SongRequestStatus.Singing ? SongRequestStatus.Waiting : SongRequestStatus.Singing)"
|
songs.findIndex((s) => s.id != song.id && s.status == SongRequestStatus.Singing) > -1
|
||||||
|
"
|
||||||
|
@click="
|
||||||
|
updateSongStatus(
|
||||||
|
song,
|
||||||
|
song.status == SongRequestStatus.Singing
|
||||||
|
? SongRequestStatus.Waiting
|
||||||
|
: SongRequestStatus.Singing,
|
||||||
|
)
|
||||||
|
"
|
||||||
: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"
|
||||||
@@ -854,7 +956,13 @@ onUnmounted(() => {
|
|||||||
</NTooltip>
|
</NTooltip>
|
||||||
<NTooltip>
|
<NTooltip>
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<NButton circle type="success" style="height: 30px; width: 30px" :loading="isLoading" @click="updateSongStatus(song, SongRequestStatus.Finish)">
|
<NButton
|
||||||
|
circle
|
||||||
|
type="success"
|
||||||
|
style="height: 30px; width: 30px"
|
||||||
|
:loading="isLoading"
|
||||||
|
@click="updateSongStatus(song, SongRequestStatus.Finish)"
|
||||||
|
>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<NIcon :component="Checkmark12Regular" />
|
<NIcon :component="Checkmark12Regular" />
|
||||||
</template>
|
</template>
|
||||||
@@ -879,7 +987,13 @@ onUnmounted(() => {
|
|||||||
</NTooltip>
|
</NTooltip>
|
||||||
<NTooltip>
|
<NTooltip>
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<NButton circle type="error" style="height: 30px; width: 30px" :loading="isLoading" @click="updateSongStatus(song, SongRequestStatus.Cancel)">
|
<NButton
|
||||||
|
circle
|
||||||
|
type="error"
|
||||||
|
style="height: 30px; width: 30px"
|
||||||
|
:loading="isLoading"
|
||||||
|
@click="updateSongStatus(song, SongRequestStatus.Cancel)"
|
||||||
|
>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<NIcon :component="Dismiss16Filled" />
|
<NIcon :component="Dismiss16Filled" />
|
||||||
</template>
|
</template>
|
||||||
@@ -949,24 +1063,71 @@ 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 v-model:checked="settings.enableOnStreaming" @update:checked="updateSettings" :disabled="!configCanEdit"> 仅在直播时才允许加入 </NCheckbox>
|
<NCheckbox
|
||||||
<NCheckbox v-model:checked="settings.allowAllDanmaku" @update:checked="updateSettings" :disabled="!configCanEdit"> 允许所有弹幕点歌 </NCheckbox>
|
v-model:checked="settings.enableOnStreaming"
|
||||||
|
@update:checked="updateSettings"
|
||||||
|
:disabled="!configCanEdit"
|
||||||
|
>
|
||||||
|
仅在直播时才允许加入
|
||||||
|
</NCheckbox>
|
||||||
|
<NCheckbox
|
||||||
|
v-model:checked="settings.allowAllDanmaku"
|
||||||
|
@update:checked="updateSettings"
|
||||||
|
:disabled="!configCanEdit"
|
||||||
|
>
|
||||||
|
允许所有弹幕点歌
|
||||||
|
</NCheckbox>
|
||||||
<template v-if="!settings.allowAllDanmaku">
|
<template v-if="!settings.allowAllDanmaku">
|
||||||
<NCheckbox v-model:checked="settings.needWearFanMedal" @update:checked="updateSettings" :disabled="!configCanEdit"> 需要拥有粉丝牌 </NCheckbox>
|
<NCheckbox
|
||||||
|
v-model:checked="settings.needWearFanMedal"
|
||||||
|
@update:checked="updateSettings"
|
||||||
|
:disabled="!configCanEdit"
|
||||||
|
>
|
||||||
|
需要拥有粉丝牌
|
||||||
|
</NCheckbox>
|
||||||
<NInputGroup v-if="settings.needWearFanMedal" style="width: 250px">
|
<NInputGroup v-if="settings.needWearFanMedal" style="width: 250px">
|
||||||
<NInputGroupLabel> 最低粉丝牌等级 </NInputGroupLabel>
|
<NInputGroupLabel> 最低粉丝牌等级 </NInputGroupLabel>
|
||||||
<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 v-if="!settings.allowAllDanmaku" v-model:checked="settings.needJianzhang" @update:checked="updateSettings" :disabled="!configCanEdit"> 只允许舰长 </NCheckbox>
|
<NCheckbox
|
||||||
<NCheckbox v-if="!settings.allowAllDanmaku" v-model:checked="settings.needTidu" @update:checked="updateSettings" :disabled="!configCanEdit"> 只允许提督 </NCheckbox>
|
v-if="!settings.allowAllDanmaku"
|
||||||
<NCheckbox v-if="!settings.allowAllDanmaku" v-model:checked="settings.needZongdu" @update:checked="updateSettings" :disabled="!configCanEdit"> 只允许总督 </NCheckbox>
|
v-model:checked="settings.needJianzhang"
|
||||||
|
@update:checked="updateSettings"
|
||||||
|
:disabled="!configCanEdit"
|
||||||
|
>
|
||||||
|
只允许舰长
|
||||||
|
</NCheckbox>
|
||||||
|
<NCheckbox
|
||||||
|
v-if="!settings.allowAllDanmaku"
|
||||||
|
v-model:checked="settings.needTidu"
|
||||||
|
@update:checked="updateSettings"
|
||||||
|
:disabled="!configCanEdit"
|
||||||
|
>
|
||||||
|
只允许提督
|
||||||
|
</NCheckbox>
|
||||||
|
<NCheckbox
|
||||||
|
v-if="!settings.allowAllDanmaku"
|
||||||
|
v-model:checked="settings.needZongdu"
|
||||||
|
@update:checked="updateSettings"
|
||||||
|
:disabled="!configCanEdit"
|
||||||
|
>
|
||||||
|
只允许总督
|
||||||
|
</NCheckbox>
|
||||||
</template>
|
</template>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
<NSpace align="center">
|
<NSpace align="center">
|
||||||
<NCheckbox v-model:checked="settings.allowSC" @update:checked="updateSettings" :disabled="!configCanEdit"> 允许通过 SuperChat 点歌 </NCheckbox>
|
<NCheckbox v-model:checked="settings.allowSC" @update:checked="updateSettings" :disabled="!configCanEdit">
|
||||||
|
允许通过 SuperChat 点歌
|
||||||
|
</NCheckbox>
|
||||||
<span v-if="settings.allowSC">
|
<span v-if="settings.allowSC">
|
||||||
<NCheckbox v-model:checked="settings.allowSC" @update:checked="updateSettings" :disabled="!configCanEdit"> SC点歌无视限制 </NCheckbox>
|
<NCheckbox
|
||||||
|
v-model:checked="settings.allowSC"
|
||||||
|
@update:checked="updateSettings"
|
||||||
|
:disabled="!configCanEdit"
|
||||||
|
>
|
||||||
|
SC点歌无视限制
|
||||||
|
</NCheckbox>
|
||||||
<NTooltip>
|
<NTooltip>
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<NIcon :component="Info24Filled" />
|
<NIcon :component="Info24Filled" />
|
||||||
@@ -981,15 +1142,31 @@ onUnmounted(() => {
|
|||||||
</NInputGroup>
|
</NInputGroup>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
<NSpace>
|
<NSpace>
|
||||||
<NCheckbox v-model:checked="settings.onlyAllowSongList" @update:checked="updateSettings" :disabled="!configCanEdit">
|
<NCheckbox
|
||||||
|
v-model:checked="settings.onlyAllowSongList"
|
||||||
|
@update:checked="updateSettings"
|
||||||
|
:disabled="!configCanEdit"
|
||||||
|
>
|
||||||
仅允许点
|
仅允许点
|
||||||
<NButton text tag="a" href="/manage/song-list" target="_blank" type="info"> 歌单 </NButton>
|
<NButton text tag="a" href="/manage/song-list" target="_blank" type="info"> 歌单 </NButton>
|
||||||
内的歌曲
|
内的歌曲
|
||||||
</NCheckbox>
|
</NCheckbox>
|
||||||
<NCheckbox v-model:checked="settings.allowFromWeb" @update:checked="updateSettings" :disabled="!configCanEdit"> 允许通过网页点歌 </NCheckbox>
|
<NCheckbox
|
||||||
|
v-model:checked="settings.allowFromWeb"
|
||||||
|
@update:checked="updateSettings"
|
||||||
|
:disabled="!configCanEdit"
|
||||||
|
>
|
||||||
|
允许通过网页点歌
|
||||||
|
</NCheckbox>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
<NDivider> 冷却 (单位: 秒) </NDivider>
|
<NDivider> 冷却 (单位: 秒) </NDivider>
|
||||||
<NCheckbox v-model:checked="settings.enableCooldown" @update:checked="updateSettings" :disabled="!configCanEdit"> 启用点歌冷却 </NCheckbox>
|
<NCheckbox
|
||||||
|
v-model:checked="settings.enableCooldown"
|
||||||
|
@update:checked="updateSettings"
|
||||||
|
:disabled="!configCanEdit"
|
||||||
|
>
|
||||||
|
启用点歌冷却
|
||||||
|
</NCheckbox>
|
||||||
<NSpace v-if="settings.enableCooldown">
|
<NSpace v-if="settings.enableCooldown">
|
||||||
<NInputGroup style="width: 250px">
|
<NInputGroup style="width: 250px">
|
||||||
<NInputGroupLabel> 普通弹幕 </NInputGroupLabel>
|
<NInputGroupLabel> 普通弹幕 </NInputGroupLabel>
|
||||||
@@ -1014,9 +1191,27 @@ onUnmounted(() => {
|
|||||||
</NSpace>
|
</NSpace>
|
||||||
<NDivider> OBS </NDivider>
|
<NDivider> OBS </NDivider>
|
||||||
<NSpace>
|
<NSpace>
|
||||||
<NCheckbox v-model:checked="settings.showRequireInfo" :disabled="!configCanEdit" @update:checked="updateSettings"> 显示底部的需求信息 </NCheckbox>
|
<NCheckbox
|
||||||
<NCheckbox v-model:checked="settings.showUserName" :disabled="!configCanEdit" @update:checked="updateSettings"> 显示点歌用户名 </NCheckbox>
|
v-model:checked="settings.showRequireInfo"
|
||||||
<NCheckbox v-model:checked="settings.showFanMadelInfo" :disabled="!configCanEdit" @update:checked="updateSettings"> 显示点歌用户粉丝牌 </NCheckbox>
|
:disabled="!configCanEdit"
|
||||||
|
@update:checked="updateSettings"
|
||||||
|
>
|
||||||
|
显示底部的需求信息
|
||||||
|
</NCheckbox>
|
||||||
|
<NCheckbox
|
||||||
|
v-model:checked="settings.showUserName"
|
||||||
|
:disabled="!configCanEdit"
|
||||||
|
@update:checked="updateSettings"
|
||||||
|
>
|
||||||
|
显示点歌用户名
|
||||||
|
</NCheckbox>
|
||||||
|
<NCheckbox
|
||||||
|
v-model:checked="settings.showFanMadelInfo"
|
||||||
|
:disabled="!configCanEdit"
|
||||||
|
@update:checked="updateSettings"
|
||||||
|
>
|
||||||
|
显示点歌用户粉丝牌
|
||||||
|
</NCheckbox>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
<NDivider> 其他 </NDivider>
|
<NDivider> 其他 </NDivider>
|
||||||
<NCheckbox v-model:checked="isWarnMessageAutoClose"> 自动关闭点歌失败时的提示消息 </NCheckbox>
|
<NCheckbox v-model:checked="isWarnMessageAutoClose"> 自动关闭点歌失败时的提示消息 </NCheckbox>
|
||||||
|
|||||||
@@ -4,12 +4,10 @@ import { useAccount } from '@/api/account'
|
|||||||
import {
|
import {
|
||||||
AddressInfo,
|
AddressInfo,
|
||||||
GoodsTypes,
|
GoodsTypes,
|
||||||
PointGoodsModel,
|
|
||||||
ResponsePointGoodModel,
|
ResponsePointGoodModel,
|
||||||
ResponsePointOrder2UserModel,
|
ResponsePointOrder2UserModel,
|
||||||
UserInfo,
|
UserInfo,
|
||||||
} from '@/api/api-models'
|
} from '@/api/api-models'
|
||||||
import { useUser } from '@/api/user'
|
|
||||||
import AddressDisplay from '@/components/manage/AddressDisplay.vue'
|
import AddressDisplay from '@/components/manage/AddressDisplay.vue'
|
||||||
import PointGoodsItem from '@/components/manage/PointGoodsItem.vue'
|
import PointGoodsItem from '@/components/manage/PointGoodsItem.vue'
|
||||||
import { POINT_API_URL } from '@/data/constants'
|
import { POINT_API_URL } from '@/data/constants'
|
||||||
@@ -18,7 +16,6 @@ import {
|
|||||||
NAlert,
|
NAlert,
|
||||||
NButton,
|
NButton,
|
||||||
NCard,
|
NCard,
|
||||||
NDataTable,
|
|
||||||
NDivider,
|
NDivider,
|
||||||
NEmpty,
|
NEmpty,
|
||||||
NFlex,
|
NFlex,
|
||||||
@@ -26,25 +23,18 @@ import {
|
|||||||
NFormItem,
|
NFormItem,
|
||||||
NGrid,
|
NGrid,
|
||||||
NGridItem,
|
NGridItem,
|
||||||
NIcon,
|
|
||||||
NInputGroup,
|
|
||||||
NInputGroupLabel,
|
|
||||||
NInputNumber,
|
NInputNumber,
|
||||||
NLayoutContent,
|
|
||||||
NModal,
|
NModal,
|
||||||
NSelect,
|
NSelect,
|
||||||
NSpace,
|
|
||||||
NSpin,
|
NSpin,
|
||||||
NTag,
|
NTag,
|
||||||
NText,
|
NText,
|
||||||
NTimeline,
|
|
||||||
NTimelineItem,
|
|
||||||
NTooltip,
|
NTooltip,
|
||||||
SelectOption,
|
SelectOption,
|
||||||
useDialog,
|
useDialog,
|
||||||
useMessage,
|
useMessage,
|
||||||
} from 'naive-ui'
|
} from 'naive-ui'
|
||||||
import { ref, computed, onMounted, h } from 'vue'
|
import { computed, h, onMounted, ref } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ResponsePointOrder2UserModel } from '@/api/api-models'
|
import { ResponsePointOrder2UserModel } from '@/api/api-models'
|
||||||
import { QueryGetAPI } from '@/api/query'
|
|
||||||
import PointOrderCard from '@/components/manage/PointOrderCard.vue'
|
import PointOrderCard from '@/components/manage/PointOrderCard.vue'
|
||||||
import { POINT_API_URL } from '@/data/constants'
|
import { POINT_API_URL } from '@/data/constants'
|
||||||
import { useAuthStore } from '@/store/useAuthStore'
|
import { useAuthStore } from '@/store/useAuthStore'
|
||||||
import { NButton, NCard, NEmpty, NList, NListItem, useMessage } from 'naive-ui'
|
import { NEmpty, useMessage } from 'naive-ui'
|
||||||
import { h, onMounted, ref } from 'vue'
|
import { onMounted, ref } from 'vue'
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
const useAuth = useAuthStore()
|
const useAuth = useAuthStore()
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { NDataTable, NLayoutContent, NSpace, useMessage } from 'naive-ui'
|
|
||||||
import { onMounted, ref } from 'vue'
|
|
||||||
import { useAuthStore } from '@/store/useAuthStore'
|
|
||||||
import { POINT_API_URL } from '@/data/constants'
|
|
||||||
import { ResponsePointHisrotyModel } from '@/api/api-models'
|
import { ResponsePointHisrotyModel } from '@/api/api-models'
|
||||||
import PointHistoryCard from '@/components/manage/PointHistoryCard.vue'
|
import PointHistoryCard from '@/components/manage/PointHistoryCard.vue'
|
||||||
|
import { POINT_API_URL } from '@/data/constants'
|
||||||
|
import { useAuthStore } from '@/store/useAuthStore'
|
||||||
|
import { useMessage } from 'naive-ui'
|
||||||
|
import { onMounted, ref } from 'vue'
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
const useAuth = useAuthStore()
|
const useAuth = useAuthStore()
|
||||||
|
|||||||
@@ -1,34 +1,33 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { UserInfo } from '@/api/api-models'
|
||||||
|
import { POINT_API_URL } from '@/data/constants'
|
||||||
|
import { useAuthStore } from '@/store/useAuthStore'
|
||||||
|
import { useRouteHash } from '@vueuse/router'
|
||||||
import {
|
import {
|
||||||
NButton,
|
NButton,
|
||||||
NCard,
|
NCard,
|
||||||
NDataTable,
|
NDataTable,
|
||||||
NListItem,
|
|
||||||
NTabPane,
|
|
||||||
NTabs,
|
|
||||||
NLayout,
|
|
||||||
NLayoutContent,
|
|
||||||
NText,
|
|
||||||
useMessage,
|
|
||||||
NLayoutHeader,
|
|
||||||
NFlex,
|
|
||||||
NDescriptions,
|
NDescriptions,
|
||||||
NDescriptionsItem,
|
NDescriptionsItem,
|
||||||
|
NDivider,
|
||||||
|
NFlex,
|
||||||
|
NLayout,
|
||||||
|
NLayoutContent,
|
||||||
|
NLayoutHeader,
|
||||||
|
NList,
|
||||||
|
NListItem,
|
||||||
NResult,
|
NResult,
|
||||||
NSpin,
|
NSpin,
|
||||||
NDivider,
|
NTabPane,
|
||||||
NTag,
|
NTabs,
|
||||||
NList,
|
NText,
|
||||||
|
useMessage,
|
||||||
} from 'naive-ui'
|
} from 'naive-ui'
|
||||||
import { computed, h, onMounted, ref } from 'vue'
|
import { computed, h, onMounted, ref } from 'vue'
|
||||||
import { useAuthStore } from '@/store/useAuthStore'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { UserInfo } from '@/api/api-models'
|
import PointOrderView from './PointOrderView.vue'
|
||||||
import { POINT_API_URL } from '@/data/constants'
|
|
||||||
import PointUserHistoryView from './PointUserHistoryView.vue'
|
import PointUserHistoryView from './PointUserHistoryView.vue'
|
||||||
import PointUserSettings from './PointUserSettings.vue'
|
import PointUserSettings from './PointUserSettings.vue'
|
||||||
import { useRouteHash } from '@vueuse/router'
|
|
||||||
import PointOrderView from './PointOrderView.vue'
|
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
|
||||||
|
|
||||||
const useAuth = useAuthStore()
|
const useAuth = useAuthStore()
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
|
|||||||
@@ -1,42 +1,35 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { AddressInfo } from '@/api/api-models'
|
import { AddressInfo } from '@/api/api-models'
|
||||||
import { QueryGetAPI } from '@/api/query'
|
import AddressDisplay from '@/components/manage/AddressDisplay.vue'
|
||||||
import { POINT_API_URL, THINGS_URL } from '@/data/constants'
|
import { 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 {
|
||||||
FormRules,
|
FormRules,
|
||||||
NButton,
|
NButton,
|
||||||
NCard,
|
NCard,
|
||||||
NCheckbox,
|
NCheckbox,
|
||||||
NCollapse,
|
NCollapse,
|
||||||
NCollapseItem,
|
NCollapseItem,
|
||||||
NDivider,
|
NDivider,
|
||||||
NFlex,
|
NFlex,
|
||||||
NForm,
|
NForm,
|
||||||
NFormItem,
|
NFormItem,
|
||||||
NInput,
|
NInput,
|
||||||
NInputNumber,
|
NInputNumber,
|
||||||
NLayoutContent,
|
NList,
|
||||||
NList,
|
NListItem,
|
||||||
NListItem,
|
NModal,
|
||||||
NModal,
|
NPopconfirm,
|
||||||
NPopconfirm,
|
NScrollbar,
|
||||||
NScrollbar,
|
NSelect,
|
||||||
NSelect,
|
NSpin,
|
||||||
NSpace,
|
NTag,
|
||||||
NSpin,
|
useMessage
|
||||||
NTag,
|
|
||||||
NText,
|
|
||||||
NTimeline,
|
|
||||||
NTimelineItem,
|
|
||||||
SelectOption,
|
|
||||||
useMessage,
|
|
||||||
} from 'naive-ui'
|
} from 'naive-ui'
|
||||||
import { computed, h, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
//@ts-ignore
|
//@ts-expect-error 导入有点问题
|
||||||
import UserAgreement from '@/document/UserAgreement.md'
|
import UserAgreement from '@/document/UserAgreement.md'
|
||||||
import AddressDisplay from '@/components/manage/AddressDisplay.vue'
|
|
||||||
|
|
||||||
type AreaData = {
|
type AreaData = {
|
||||||
[province: string]: {
|
[province: string]: {
|
||||||
@@ -203,21 +196,21 @@ function onAreaSelectChange(level: number) {
|
|||||||
const newValue = {} as AddressInfo
|
const newValue = {} as AddressInfo
|
||||||
switch (level) {
|
switch (level) {
|
||||||
case 0: {
|
case 0: {
|
||||||
// @ts-ignore
|
// @ts-expect-error 不管这个,直接赋值
|
||||||
currentAddress.value.city = undefined
|
currentAddress.value.city = undefined
|
||||||
// @ts-ignore
|
// @ts-expect-error 不管这个,直接赋值
|
||||||
currentAddress.value.district = undefined
|
currentAddress.value.district = undefined
|
||||||
// @ts-ignore
|
// @ts-expect-error 不管这个,直接赋值
|
||||||
currentAddress.value.street = undefined
|
currentAddress.value.street = undefined
|
||||||
}
|
}
|
||||||
case 1: {
|
case 1: {
|
||||||
// @ts-ignore
|
// @ts-expect-error 不管这个,直接赋值
|
||||||
currentAddress.value.district = undefined
|
currentAddress.value.district = undefined
|
||||||
// @ts-ignore
|
// @ts-expect-error 不管这个,直接赋值
|
||||||
currentAddress.value.street = undefined
|
currentAddress.value.street = undefined
|
||||||
}
|
}
|
||||||
case 2: {
|
case 2: {
|
||||||
// @ts-ignore
|
// @ts-expect-error 不管这个,直接赋值
|
||||||
currentAddress.value.street = undefined
|
currentAddress.value.street = undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +1,13 @@
|
|||||||
<!-- eslint-disable @typescript-eslint/no-explicit-any -->
|
<!-- eslint-disable @typescript-eslint/no-explicit-any -->
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, ref, watch } from 'vue'
|
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
|
||||||
import { QueryGetAPI, QueryPostAPI } from '@/api/query'
|
|
||||||
import {
|
|
||||||
NButton,
|
|
||||||
NCard,
|
|
||||||
NCheckbox,
|
|
||||||
NColorPicker,
|
|
||||||
NDivider,
|
|
||||||
NEmpty,
|
|
||||||
NFlex,
|
|
||||||
NIcon,
|
|
||||||
NInput,
|
|
||||||
NInputGroup,
|
|
||||||
NInputGroupLabel,
|
|
||||||
NInputNumber,
|
|
||||||
NRadioButton,
|
|
||||||
NRadioGroup,
|
|
||||||
NSelect,
|
|
||||||
NSpin,
|
|
||||||
NTooltip,
|
|
||||||
NDrawer,
|
|
||||||
useMessage,
|
|
||||||
NScrollbar,
|
|
||||||
NDrawerContent,
|
|
||||||
NModal,
|
|
||||||
} from 'naive-ui'
|
|
||||||
import { QAInfo, QuestionDisplayAlign, Setting_QuestionDisplay } from '@/api/api-models'
|
|
||||||
import QuestionDisplayCard from '@/views/manage/QuestionDisplayCard.vue'
|
|
||||||
import { useAccount } from '@/api/account'
|
import { useAccount } from '@/api/account'
|
||||||
|
import { QuestionDisplayAlign, Setting_QuestionDisplay } from '@/api/api-models'
|
||||||
|
import { QueryPostAPI } from '@/api/query'
|
||||||
|
import QuestionItem from '@/components/QuestionItem.vue'
|
||||||
|
import QuestionItems from '@/components/QuestionItems.vue'
|
||||||
import { QUESTION_API_URL } from '@/data/constants'
|
import { QUESTION_API_URL } from '@/data/constants'
|
||||||
|
import { useQuestionBox } from '@/store/useQuestionBox'
|
||||||
|
import QuestionDisplayCard from '@/views/manage/QuestionDisplayCard.vue'
|
||||||
import {
|
import {
|
||||||
ArrowCircleLeft12Filled,
|
ArrowCircleLeft12Filled,
|
||||||
ArrowCircleRight12Filled,
|
ArrowCircleRight12Filled,
|
||||||
@@ -39,11 +16,31 @@ import {
|
|||||||
TextAlignLeft16Filled,
|
TextAlignLeft16Filled,
|
||||||
TextAlignRight16Filled,
|
TextAlignRight16Filled,
|
||||||
} from '@vicons/fluent'
|
} from '@vicons/fluent'
|
||||||
import { useDebounceFn, useElementSize, useStorage } from '@vueuse/core'
|
|
||||||
import QuestionItems from '@/components/QuestionItems.vue'
|
|
||||||
import QuestionItem from '@/components/QuestionItem.vue'
|
|
||||||
import { useQuestionBox } from '@/store/useQuestionBox'
|
|
||||||
import { Heart, HeartOutline } from '@vicons/ionicons5'
|
import { Heart, HeartOutline } from '@vicons/ionicons5'
|
||||||
|
import { useDebounceFn, useElementSize, useStorage } 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 message = useMessage()
|
||||||
const accountInfo = useAccount()
|
const accountInfo = useAccount()
|
||||||
@@ -144,6 +141,9 @@ onMounted(() => {
|
|||||||
<NFlex :wrap="false" style="margin: 20px">
|
<NFlex :wrap="false" style="margin: 20px">
|
||||||
<NFlex style="height: calc(100vh - 40px)" :wrap="false" vertical>
|
<NFlex style="height: calc(100vh - 40px)" :wrap="false" vertical>
|
||||||
<NCard size="small" title="内容设置">
|
<NCard size="small" title="内容设置">
|
||||||
|
<template #header-extra>
|
||||||
|
<NButton @click="$router.push({ name: 'manage-questionBox' })" size="tiny" secondary> 回到控制台 </NButton>
|
||||||
|
</template>
|
||||||
<NFlex align="center">
|
<NFlex align="center">
|
||||||
<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>
|
||||||
@@ -310,7 +310,8 @@ onMounted(() => {
|
|||||||
如果选用了本地字体且使用了obs组件的话请确保运行obs的电脑上也有这个字体
|
如果选用了本地字体且使用了obs组件的话请确保运行obs的电脑上也有这个字体
|
||||||
</NTooltip>
|
</NTooltip>
|
||||||
</NFlex>
|
</NFlex>
|
||||||
<NFlex>
|
<NFlex justify="space-around" style="width: 100%">
|
||||||
|
<NFlex style="min-width: 80px">
|
||||||
字体颜色
|
字体颜色
|
||||||
<NColorPicker
|
<NColorPicker
|
||||||
:value="setting.fontColor ? '#' + setting.fontColor : undefined"
|
:value="setting.fontColor ? '#' + setting.fontColor : undefined"
|
||||||
@@ -326,7 +327,7 @@ onMounted(() => {
|
|||||||
@confirm="updateSettings"
|
@confirm="updateSettings"
|
||||||
/>
|
/>
|
||||||
</NFlex>
|
</NFlex>
|
||||||
<NFlex>
|
<NFlex style="min-width: 80px">
|
||||||
背景颜色
|
背景颜色
|
||||||
<NColorPicker
|
<NColorPicker
|
||||||
:value="setting.backgroundColor ? '#' + setting.backgroundColor : undefined"
|
:value="setting.backgroundColor ? '#' + setting.backgroundColor : undefined"
|
||||||
@@ -342,7 +343,7 @@ onMounted(() => {
|
|||||||
@confirm="updateSettings"
|
@confirm="updateSettings"
|
||||||
/>
|
/>
|
||||||
</NFlex>
|
</NFlex>
|
||||||
<NFlex>
|
<NFlex style="min-width: 80px">
|
||||||
边框颜色
|
边框颜色
|
||||||
<NColorPicker
|
<NColorPicker
|
||||||
:value="setting.borderColor ? '#' + setting.borderColor : undefined"
|
:value="setting.borderColor ? '#' + setting.borderColor : undefined"
|
||||||
@@ -359,6 +360,7 @@ onMounted(() => {
|
|||||||
/>
|
/>
|
||||||
</NFlex>
|
</NFlex>
|
||||||
</NFlex>
|
</NFlex>
|
||||||
|
</NFlex>
|
||||||
</NCard>
|
</NCard>
|
||||||
<NCard size="small" title="用户名设置">
|
<NCard size="small" title="用户名设置">
|
||||||
<NFlex>
|
<NFlex>
|
||||||
@@ -393,7 +395,7 @@ onMounted(() => {
|
|||||||
如果选用了本地字体且使用了obs组件的话请确保运行obs的电脑上也有这个字体
|
如果选用了本地字体且使用了obs组件的话请确保运行obs的电脑上也有这个字体
|
||||||
</NTooltip>
|
</NTooltip>
|
||||||
</NFlex>
|
</NFlex>
|
||||||
<NFlex>
|
<NFlex style="min-width: 80px">
|
||||||
字体颜色
|
字体颜色
|
||||||
<NColorPicker
|
<NColorPicker
|
||||||
:value="setting.nameFontColor ? '#' + setting.nameFontColor : undefined"
|
:value="setting.nameFontColor ? '#' + setting.nameFontColor : undefined"
|
||||||
@@ -428,7 +430,7 @@ onMounted(() => {
|
|||||||
preset="card"
|
preset="card"
|
||||||
v-model:show="showOBSModal"
|
v-model:show="showOBSModal"
|
||||||
closable
|
closable
|
||||||
style="max-width: 90vw"
|
style="max-width: 90vw; width: auto"
|
||||||
title="OBS组件"
|
title="OBS组件"
|
||||||
content-style="display: flex; align-items: center; justify-content: center; flex-direction: column"
|
content-style="display: flex; align-items: center; justify-content: center; flex-direction: column"
|
||||||
>
|
>
|
||||||
@@ -440,6 +442,7 @@ onMounted(() => {
|
|||||||
>
|
>
|
||||||
<QuestionDisplayCard :question="useQB.displayQuestion" :setting="setting" />
|
<QuestionDisplayCard :question="useQB.displayQuestion" :setting="setting" />
|
||||||
</div>
|
</div>
|
||||||
|
<NDivider />
|
||||||
<NInput readonly :value="'https://vtsuru.live/obs/question-display?token=' + accountInfo?.token" />
|
<NInput readonly :value="'https://vtsuru.live/obs/question-display?token=' + accountInfo?.token" />
|
||||||
</NModal>
|
</NModal>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -4,7 +4,26 @@ import { QAInfo, UserInfo } from '@/api/api-models'
|
|||||||
import { QueryGetAPI, QueryPostAPI } from '@/api/query'
|
import { QueryGetAPI, QueryPostAPI } from '@/api/query'
|
||||||
import { QUESTION_API_URL, TURNSTILE_KEY } from '@/data/constants'
|
import { QUESTION_API_URL, TURNSTILE_KEY } from '@/data/constants'
|
||||||
import GraphemeSplitter from 'grapheme-splitter'
|
import GraphemeSplitter from 'grapheme-splitter'
|
||||||
import { NAlert, NAvatar, NButton, NCard, NCheckbox, NDivider, NEmpty, NImage, NInput, NList, NListItem, NSpace, NText, NTime, NTooltip, NUpload, UploadFileInfo, useMessage } from 'naive-ui'
|
import {
|
||||||
|
NAlert,
|
||||||
|
NAvatar,
|
||||||
|
NButton,
|
||||||
|
NCard,
|
||||||
|
NCheckbox,
|
||||||
|
NDivider,
|
||||||
|
NEmpty,
|
||||||
|
NImage,
|
||||||
|
NInput,
|
||||||
|
NList,
|
||||||
|
NListItem,
|
||||||
|
NSpace,
|
||||||
|
NText,
|
||||||
|
NTime,
|
||||||
|
NTooltip,
|
||||||
|
NUpload,
|
||||||
|
UploadFileInfo,
|
||||||
|
useMessage,
|
||||||
|
} from 'naive-ui'
|
||||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||||
import VueTurnstile from 'vue-turnstile'
|
import VueTurnstile from 'vue-turnstile'
|
||||||
|
|
||||||
@@ -121,7 +140,15 @@ onUnmounted(() => {
|
|||||||
<NCard embedded>
|
<NCard embedded>
|
||||||
<NSpace vertical>
|
<NSpace vertical>
|
||||||
<NSpace align="center" justify="center">
|
<NSpace align="center" justify="center">
|
||||||
<NInput :disabled="isSelf" show-count maxlength="1000" type="textarea" :count-graphemes="countGraphemes" v-model:value="questionMessage" style="width: 300px" />
|
<NInput
|
||||||
|
:disabled="isSelf"
|
||||||
|
show-count
|
||||||
|
maxlength="1000"
|
||||||
|
type="textarea"
|
||||||
|
:count-graphemes="countGraphemes"
|
||||||
|
v-model:value="questionMessage"
|
||||||
|
style="width: 300px"
|
||||||
|
/>
|
||||||
<NUpload
|
<NUpload
|
||||||
:max="1"
|
:max="1"
|
||||||
accept=".png,.jpg,.jpeg,.gif,.svg,.webp,.ico"
|
accept=".png,.jpg,.jpeg,.gif,.svg,.webp,.ico"
|
||||||
@@ -143,10 +170,25 @@ onUnmounted(() => {
|
|||||||
</NSpace>
|
</NSpace>
|
||||||
<NDivider style="margin: 10px 0 10px 0" />
|
<NDivider style="margin: 10px 0 10px 0" />
|
||||||
<NSpace justify="center">
|
<NSpace justify="center">
|
||||||
<NButton :disabled="isSelf" type="primary" :loading="isSending || !token" @click="SendQuestion"> 发送 </NButton>
|
<NButton :disabled="isSelf" type="primary" :loading="isSending || !token" @click="SendQuestion">
|
||||||
<NButton v-if="accountInfo" :disabled="isSelf" type="info" @click="$router.push({ name: 'manage-questionBox', query: { send: '1' } })"> 我发送的 </NButton>
|
发送
|
||||||
|
</NButton>
|
||||||
|
<NButton
|
||||||
|
v-if="accountInfo"
|
||||||
|
:disabled="isSelf"
|
||||||
|
type="info"
|
||||||
|
@click="$router.push({ name: 'manage-questionBox', query: { send: '1' } })"
|
||||||
|
>
|
||||||
|
我发送的
|
||||||
|
</NButton>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
<VueTurnstile ref="turnstile" :site-key="TURNSTILE_KEY" v-model="token" theme="auto" style="text-align: center" />
|
<VueTurnstile
|
||||||
|
ref="turnstile"
|
||||||
|
:site-key="TURNSTILE_KEY"
|
||||||
|
v-model="token"
|
||||||
|
theme="auto"
|
||||||
|
style="text-align: center"
|
||||||
|
/>
|
||||||
<NAlert v-if="isSelf" type="warning"> 不能给自己提问 </NAlert>
|
<NAlert v-if="isSelf" type="warning"> 不能给自己提问 </NAlert>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
</NCard>
|
</NCard>
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<NSpin v-if="isLoading" show />
|
<NSpin v-if="isLoading" show />
|
||||||
<component v-else :is="ScheduleTemplateMap[componentType ?? ''].compoent" :bili-info="biliInfo" :user-info="userInfo" :currentData="currentData" v-bind="$attrs" />
|
<component
|
||||||
|
v-else
|
||||||
|
:is="ScheduleTemplateMap[componentType ?? ''].compoent"
|
||||||
|
:bili-info="biliInfo"
|
||||||
|
:user-info="userInfo"
|
||||||
|
:currentData="currentData"
|
||||||
|
v-bind="$attrs"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
|||||||
@@ -44,9 +44,12 @@ const settings = ref<Setting_SongRequest>({} as Setting_SongRequest)
|
|||||||
|
|
||||||
async function getSongRequestInfo() {
|
async function getSongRequestInfo() {
|
||||||
try {
|
try {
|
||||||
const data = await QueryGetAPI<{ songs: SongRequestInfo[]; setting: Setting_SongRequest }>(SONG_REQUEST_API_URL + 'get-active-and-settings', {
|
const data = await QueryGetAPI<{ songs: SongRequestInfo[]; setting: Setting_SongRequest }>(
|
||||||
|
SONG_REQUEST_API_URL + 'get-active-and-settings',
|
||||||
|
{
|
||||||
id: props.userInfo?.id,
|
id: props.userInfo?.id,
|
||||||
})
|
},
|
||||||
|
)
|
||||||
if (data.code == 200) {
|
if (data.code == 200) {
|
||||||
return data.data
|
return data.data
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,7 +76,9 @@ export const Config: TemplateConfig<ConfigType> = {
|
|||||||
<NDivider />
|
<NDivider />
|
||||||
<NSpace align="center" justify="center">
|
<NSpace align="center" justify="center">
|
||||||
<NButton type="primary" @click="navigate('https://space.bilibili.com/' + userInfo?.biliId)"> 个人主页 </NButton>
|
<NButton type="primary" @click="navigate('https://space.bilibili.com/' + userInfo?.biliId)"> 个人主页 </NButton>
|
||||||
<NButton type="primary" secondary @click="navigate('https://live.bilibili.com/' + userInfo?.biliRoomId)"> 直播间 </NButton>
|
<NButton type="primary" secondary @click="navigate('https://live.bilibili.com/' + userInfo?.biliRoomId)">
|
||||||
|
直播间
|
||||||
|
</NButton>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
|
|||||||
@@ -39,4 +39,4 @@ export const Config: TemplateConfig<ConfigType> = {
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template></template>
|
<template>1</template>
|
||||||
|
|||||||
@@ -58,7 +58,12 @@ onMounted(() => {
|
|||||||
<NDivider />
|
<NDivider />
|
||||||
<div ref="table" class="schedule-template pinky container">
|
<div ref="table" class="schedule-template pinky container">
|
||||||
<div class="schedule-template pinky day-container">
|
<div class="schedule-template pinky day-container">
|
||||||
<div class="schedule-template pinky day-item" :id="index.toString()" v-for="(item, index) in currentWeek?.days" :key="index">
|
<div
|
||||||
|
class="schedule-template pinky day-item"
|
||||||
|
:id="index.toString()"
|
||||||
|
v-for="(item, index) in currentWeek?.days"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
<div class="schedule-template pinky header">
|
<div class="schedule-template pinky header">
|
||||||
<span class="schedule-template pinky week">
|
<span class="schedule-template pinky week">
|
||||||
{{ days[index] }}
|
{{ days[index] }}
|
||||||
@@ -93,7 +98,8 @@ onMounted(() => {
|
|||||||
height: 700px;
|
height: 700px;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
background-color: #faebeb;
|
background-color: #faebeb;
|
||||||
background-image: linear-gradient(90deg, #ffffff 10%, rgba(0, 0, 0, 0) 10%), linear-gradient(#ffffff 10%, rgba(0, 0, 0, 0) 10%);
|
background-image: linear-gradient(90deg, #ffffff 10%, rgba(0, 0, 0, 0) 10%),
|
||||||
|
linear-gradient(#ffffff 10%, rgba(0, 0, 0, 0) 10%);
|
||||||
background-size: 20px 20px;
|
background-size: 20px 20px;
|
||||||
border: 3px solid #e0cbcb;
|
border: 3px solid #e0cbcb;
|
||||||
}
|
}
|
||||||
|
|||||||
46
yarn.lock
46
yarn.lock
@@ -613,6 +613,23 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@eslint/eslintrc@npm:^3.0.1":
|
||||||
|
version: 3.0.1
|
||||||
|
resolution: "@eslint/eslintrc@npm:3.0.1"
|
||||||
|
dependencies:
|
||||||
|
ajv: "npm:^6.12.4"
|
||||||
|
debug: "npm:^4.3.2"
|
||||||
|
espree: "npm:^10.0.1"
|
||||||
|
globals: "npm:^13.19.0"
|
||||||
|
ignore: "npm:^5.2.0"
|
||||||
|
import-fresh: "npm:^3.2.1"
|
||||||
|
js-yaml: "npm:^4.1.0"
|
||||||
|
minimatch: "npm:^3.1.2"
|
||||||
|
strip-json-comments: "npm:^3.1.1"
|
||||||
|
checksum: ee86b6a0246185fa0c3189f7d56c651785d98505d7c2a001c72eb3fa645e472578faf0673111fdc25b865d97c9982624f2addd70ea8af451e08ab0b78b6b56ca
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@eslint/js@npm:8.56.0":
|
"@eslint/js@npm:8.56.0":
|
||||||
version: 8.56.0
|
version: 8.56.0
|
||||||
resolution: "@eslint/js@npm:8.56.0"
|
resolution: "@eslint/js@npm:8.56.0"
|
||||||
@@ -2622,6 +2639,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"eslint-visitor-keys@npm:^4.0.0":
|
||||||
|
version: 4.0.0
|
||||||
|
resolution: "eslint-visitor-keys@npm:4.0.0"
|
||||||
|
checksum: 76619f42cf162705a1515a6868e6fc7567e185c7063a05621a8ac4c3b850d022661262c21d9f1fc1d144ecf0d5d64d70a3f43c15c3fc969a61ace0fb25698cf5
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"eslint@npm:^8.56.0":
|
"eslint@npm:^8.56.0":
|
||||||
version: 8.56.0
|
version: 8.56.0
|
||||||
resolution: "eslint@npm:8.56.0"
|
resolution: "eslint@npm:8.56.0"
|
||||||
@@ -2670,6 +2694,17 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"espree@npm:^10.0.1":
|
||||||
|
version: 10.0.1
|
||||||
|
resolution: "espree@npm:10.0.1"
|
||||||
|
dependencies:
|
||||||
|
acorn: "npm:^8.11.3"
|
||||||
|
acorn-jsx: "npm:^5.3.2"
|
||||||
|
eslint-visitor-keys: "npm:^4.0.0"
|
||||||
|
checksum: 7c0f84afa0f9db7bb899619e6364ed832ef13fe8943691757ddde9a1805ae68b826ed66803323015f707a629a5507d0d290edda2276c25131fe0ad883b8b5636
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"espree@npm:^9.3.1, espree@npm:^9.6.0, espree@npm:^9.6.1":
|
"espree@npm:^9.3.1, espree@npm:^9.6.0, espree@npm:^9.6.1":
|
||||||
version: 9.6.1
|
version: 9.6.1
|
||||||
resolution: "espree@npm:9.6.1"
|
resolution: "espree@npm:9.6.1"
|
||||||
@@ -3250,6 +3285,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"husky@npm:^9.0.11":
|
||||||
|
version: 9.0.11
|
||||||
|
resolution: "husky@npm:9.0.11"
|
||||||
|
bin:
|
||||||
|
husky: bin.mjs
|
||||||
|
checksum: 2c787dcf74a837fc9a4fea7da907509d4bd9a289f4ea10ecc9d86279e4d4542b0f5f6443a619bccae19e265f2677172cc2b86aae5c932a35a330cc227d914605
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"iconv-lite@npm:^0.6.2":
|
"iconv-lite@npm:^0.6.2":
|
||||||
version: 0.6.3
|
version: 0.6.3
|
||||||
resolution: "iconv-lite@npm:0.6.3"
|
resolution: "iconv-lite@npm:0.6.3"
|
||||||
@@ -5280,6 +5324,7 @@ __metadata:
|
|||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "vtsuru.live@workspace:."
|
resolution: "vtsuru.live@workspace:."
|
||||||
dependencies:
|
dependencies:
|
||||||
|
"@eslint/eslintrc": "npm:^3.0.1"
|
||||||
"@types/eslint": "npm:^8.56.2"
|
"@types/eslint": "npm:^8.56.2"
|
||||||
"@types/node": "npm:^20.11.19"
|
"@types/node": "npm:^20.11.19"
|
||||||
"@types/uuid": "npm:^9.0.8"
|
"@types/uuid": "npm:^9.0.8"
|
||||||
@@ -5305,6 +5350,7 @@ __metadata:
|
|||||||
file-saver: "npm:^2.0.5"
|
file-saver: "npm:^2.0.5"
|
||||||
grapheme-splitter: "npm:^1.0.4"
|
grapheme-splitter: "npm:^1.0.4"
|
||||||
html2canvas: "npm:^1.4.1"
|
html2canvas: "npm:^1.4.1"
|
||||||
|
husky: "npm:^9.0.11"
|
||||||
linqts: "npm:^1.15.0"
|
linqts: "npm:^1.15.0"
|
||||||
mitt: "npm:^3.0.1"
|
mitt: "npm:^3.0.1"
|
||||||
naive-ui: "npm:^2.37.3"
|
naive-ui: "npm:^2.37.3"
|
||||||
|
|||||||
Reference in New Issue
Block a user