mirror of
https://github.com/Megghy/vtsuru.live.git
synced 2025-12-06 18:36:55 +08:00
更新项目配置,删除不必要的文件,优化依赖项,修复类型定义,添加新歌单样式
This commit is contained in:
3
default.d.ts
vendored
3
default.d.ts
vendored
@@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
import type { LoadingBarProviderInst, MessageProviderInst } from 'naive-ui'
|
import type { LoadingBarProviderInst, MessageProviderInst, ModalProviderInst } from 'naive-ui'
|
||||||
import type { useRoute } from 'vue-router'
|
import type { useRoute } from 'vue-router'
|
||||||
|
|
||||||
declare module 'vue3-aplayer' {
|
declare module 'vue3-aplayer' {
|
||||||
@@ -20,6 +20,7 @@ declare global {
|
|||||||
$message: MessageProviderInst
|
$message: MessageProviderInst
|
||||||
$loadingBar: LoadingBarProviderInst
|
$loadingBar: LoadingBarProviderInst
|
||||||
$route: ReturnType<typeof useRoute>
|
$route: ReturnType<typeof useRoute>
|
||||||
|
$modal: ModalProviderInst
|
||||||
$mitt: Emitter<MittType>
|
$mitt: Emitter<MittType>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
<link rel="preconnect" href="https://rsms.me/" />
|
<link rel="preconnect" href="https://rsms.me/" />
|
||||||
<link rel="stylesheet" href="https://rsms.me/inter/inter.css" />
|
<link rel="stylesheet" href="https://rsms.me/inter/inter.css" />
|
||||||
<script src="https://unpkg.com/peerjs@latest/dist/peerjs.min.js"></script>
|
<script async src="https://cdn.jsdelivr.net/npm/peerjs@latest/dist/peerjs.min.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -15,6 +15,8 @@
|
|||||||
"@microsoft/signalr": "^8.0.7",
|
"@microsoft/signalr": "^8.0.7",
|
||||||
"@microsoft/signalr-protocol-msgpack": "^8.0.7",
|
"@microsoft/signalr-protocol-msgpack": "^8.0.7",
|
||||||
"@mixer/postmessage-rpc": "^1.1.4",
|
"@mixer/postmessage-rpc": "^1.1.4",
|
||||||
|
"@tauri-apps/api": "^2.4.0",
|
||||||
|
"@tauri-apps/plugin-http": "^2.4.2",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.27.0",
|
"@typescript-eslint/eslint-plugin": "^8.27.0",
|
||||||
"@vicons/fluent": "^0.13.0",
|
"@vicons/fluent": "^0.13.0",
|
||||||
"@vitejs/plugin-basic-ssl": "^2.0.0",
|
"@vitejs/plugin-basic-ssl": "^2.0.0",
|
||||||
|
|||||||
38
src/App.vue
38
src/App.vue
@@ -9,6 +9,7 @@ import {
|
|||||||
NLayoutContent,
|
NLayoutContent,
|
||||||
NLoadingBarProvider,
|
NLoadingBarProvider,
|
||||||
NMessageProvider,
|
NMessageProvider,
|
||||||
|
NModalProvider,
|
||||||
NNotificationProvider,
|
NNotificationProvider,
|
||||||
NSpin,
|
NSpin,
|
||||||
zhCN,
|
zhCN,
|
||||||
@@ -16,11 +17,13 @@ import {
|
|||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import TempComponent from './components/TempComponent.vue'
|
import TempComponent from './components/TempComponent.vue'
|
||||||
import { theme } from './Utils'
|
import { isDarkMode, theme } from './Utils'
|
||||||
import OBSLayout from './views/OBSLayout.vue'
|
import OBSLayout from './views/OBSLayout.vue'
|
||||||
import OpenLiveLayout from './views/OpenLiveLayout.vue'
|
import OpenLiveLayout from './views/OpenLiveLayout.vue'
|
||||||
|
import { ThemeType } from './api/api-models';
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
const themeType = useStorage('Settings.Theme', ThemeType.Auto)
|
||||||
|
|
||||||
const layout = computed(() => {
|
const layout = computed(() => {
|
||||||
if (route.path.startsWith('/user') || route.name == 'user' || route.path.startsWith('/@')) {
|
if (route.path.startsWith('/user') || route.name == 'user' || route.path.startsWith('/@')) {
|
||||||
@@ -44,6 +47,16 @@ const layout = computed(() => {
|
|||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
watchEffect(() => {
|
||||||
|
if (isDarkMode.value) {
|
||||||
|
document.documentElement.classList.add('dark');
|
||||||
|
console.log('Added dark class to HTML'); // For debugging
|
||||||
|
} else {
|
||||||
|
document.documentElement.classList.remove('dark');
|
||||||
|
console.log('Removed dark class from HTML'); // For debugging
|
||||||
|
}
|
||||||
|
// If you dynamically apply Naive UI theme to body or provider, do it here too
|
||||||
|
});
|
||||||
|
|
||||||
const themeOverrides = {
|
const themeOverrides = {
|
||||||
common: {
|
common: {
|
||||||
@@ -53,20 +66,32 @@ const themeOverrides = {
|
|||||||
},
|
},
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (isDarkMode.value) {
|
||||||
|
document.documentElement.classList.add('dark');
|
||||||
|
console.log('Added dark class to HTML'); // For debugging
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NConfigProvider
|
<NConfigProvider
|
||||||
:theme-overrides="themeOverrides" :theme="theme" style="height: 100vh" :locale="zhCN"
|
:theme-overrides="themeOverrides"
|
||||||
|
:theme="theme"
|
||||||
|
style="height: 100vh"
|
||||||
|
:locale="zhCN"
|
||||||
:date-locale="dateZhCN"
|
:date-locale="dateZhCN"
|
||||||
>
|
>
|
||||||
<NMessageProvider>
|
<NMessageProvider>
|
||||||
<NNotificationProvider>
|
<NNotificationProvider>
|
||||||
<NDialogProvider>
|
<NDialogProvider>
|
||||||
<NLoadingBarProvider>
|
<NLoadingBarProvider>
|
||||||
|
<NModalProvider>
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<TempComponent>
|
<TempComponent>
|
||||||
<NLayoutContent>
|
<NLayoutContent>
|
||||||
|
<NElement>
|
||||||
<ViewerLayout v-if="layout == 'viewer'" />
|
<ViewerLayout v-if="layout == 'viewer'" />
|
||||||
<ManageLayout v-else-if="layout == 'manage'" />
|
<ManageLayout v-else-if="layout == 'manage'" />
|
||||||
<OpenLiveLayout v-else-if="layout == 'open-live'" />
|
<OpenLiveLayout v-else-if="layout == 'open-live'" />
|
||||||
@@ -74,12 +99,17 @@ const themeOverrides = {
|
|||||||
<template v-else>
|
<template v-else>
|
||||||
<RouterView />
|
<RouterView />
|
||||||
</template>
|
</template>
|
||||||
|
</NElement>
|
||||||
</NLayoutContent>
|
</NLayoutContent>
|
||||||
</TempComponent>
|
</TempComponent>
|
||||||
<template #fallback>
|
<template #fallback>
|
||||||
<NSpin size="large" show />
|
<NSpin
|
||||||
|
size="large"
|
||||||
|
show
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
</NModalProvider>
|
||||||
</NLoadingBarProvider>
|
</NLoadingBarProvider>
|
||||||
</NDialogProvider>
|
</NDialogProvider>
|
||||||
</NNotificationProvider>
|
</NNotificationProvider>
|
||||||
@@ -90,6 +120,8 @@ const themeOverrides = {
|
|||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
font-feature-settings: 'liga' 1, 'calt' 1;
|
font-feature-settings: 'liga' 1, 'calt' 1;
|
||||||
|
--vtsuru-header-height: 50px;
|
||||||
|
--vtsuru-content-padding: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@supports (font-variation-settings: normal) {
|
@supports (font-variation-settings: normal) {
|
||||||
|
|||||||
77
src/Utils.ts
77
src/Utils.ts
@@ -1,6 +1,9 @@
|
|||||||
import { useStorage } from '@vueuse/core'
|
import { useStorage } from '@vueuse/core'
|
||||||
import {
|
import {
|
||||||
ConfigProviderProps,
|
ConfigProviderProps,
|
||||||
|
NButton,
|
||||||
|
NIcon,
|
||||||
|
NTooltip,
|
||||||
UploadFileInfo,
|
UploadFileInfo,
|
||||||
createDiscreteApi,
|
createDiscreteApi,
|
||||||
darkTheme,
|
darkTheme,
|
||||||
@@ -8,10 +11,13 @@ import {
|
|||||||
useOsTheme,
|
useOsTheme,
|
||||||
zhCN
|
zhCN
|
||||||
} from 'naive-ui'
|
} from 'naive-ui'
|
||||||
import { ThemeType } from './api/api-models'
|
import { SongFrom, SongsInfo, ThemeType } from './api/api-models'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { VTSURU_API_URL } from './data/constants'
|
import { VTSURU_API_URL } from './data/constants'
|
||||||
import { DiscreteApiType } from 'naive-ui/es/discrete/src/interface'
|
import { DiscreteApiType } from 'naive-ui/es/discrete/src/interface'
|
||||||
|
import { SquareArrowForward24Filled } from '@vicons/fluent';
|
||||||
|
import FiveSingIcon from '@/svgs/fivesing.svg'
|
||||||
|
import NeteaseIcon from '@/svgs/netease.svg'
|
||||||
|
|
||||||
const { message } = createDiscreteApi(['message'])
|
const { message } = createDiscreteApi(['message'])
|
||||||
|
|
||||||
@@ -139,7 +145,8 @@ export async function getImageUploadModel(
|
|||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
export function getUserAvatarUrl(userId: number) {
|
export function getUserAvatarUrl(userId: number | undefined | null) {
|
||||||
|
if (!userId) return ''
|
||||||
return VTSURU_API_URL + 'user-face/' + userId
|
return VTSURU_API_URL + 'user-face/' + userId
|
||||||
}
|
}
|
||||||
export function getOUIdAvatarUrl(ouid: string) {
|
export function getOUIdAvatarUrl(ouid: string) {
|
||||||
@@ -198,3 +205,69 @@ export class GuidUtils {
|
|||||||
return buffer
|
return buffer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
export function GetPlayButton(song: SongsInfo) {
|
||||||
|
switch (song.from) {
|
||||||
|
case SongFrom.FiveSing: {
|
||||||
|
return h(NTooltip, null, {
|
||||||
|
trigger: () =>
|
||||||
|
h(
|
||||||
|
h(
|
||||||
|
NButton,
|
||||||
|
{
|
||||||
|
size: 'small',
|
||||||
|
color: '#00BBB3',
|
||||||
|
ghost: true,
|
||||||
|
onClick: () => {
|
||||||
|
window.open(`http://5sing.kugou.com/bz/${song.id}.html`)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: () => h(FiveSingIcon, { class: 'svg-icon fivesing' }),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
default: () => '在5sing打开',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
case SongFrom.Netease:
|
||||||
|
return h(NTooltip, null, {
|
||||||
|
trigger: () =>
|
||||||
|
h(
|
||||||
|
NButton,
|
||||||
|
{
|
||||||
|
size: 'small',
|
||||||
|
color: '#C20C0C',
|
||||||
|
ghost: true,
|
||||||
|
onClick: () => {
|
||||||
|
window.open(`https://music.163.com/#/song?id=${song.id}`)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: () => h(NeteaseIcon, { class: 'svg-icon netease' }),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
default: () => '在网易云打开',
|
||||||
|
})
|
||||||
|
case SongFrom.Custom:
|
||||||
|
return song.url
|
||||||
|
? h(NTooltip, null, {
|
||||||
|
trigger: () =>
|
||||||
|
h(
|
||||||
|
NButton,
|
||||||
|
{
|
||||||
|
size: 'small',
|
||||||
|
color: '#6b95bd',
|
||||||
|
ghost: true,
|
||||||
|
onClick: () => {
|
||||||
|
window.open(song.url)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: () => h(NIcon, { component: SquareArrowForward24Filled }),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
default: () => '打开链接',
|
||||||
|
})
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -183,7 +183,7 @@ export function downloadConfigDirect(name: string) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
export type ConfigStatus = 'success' | 'error' | 'notfound'
|
export type ConfigStatus = 'success' | 'error' | 'notfound'
|
||||||
export async function DownloadConfig<T>(name: string): Promise<
|
export async function DownloadConfig<T>(name: string, id?: number): Promise<
|
||||||
| {
|
| {
|
||||||
msg: undefined
|
msg: undefined
|
||||||
status: ConfigStatus
|
status: ConfigStatus
|
||||||
@@ -196,8 +196,9 @@ export async function DownloadConfig<T>(name: string): Promise<
|
|||||||
}
|
}
|
||||||
> {
|
> {
|
||||||
try {
|
try {
|
||||||
const resp = await QueryGetAPI<string>(VTSURU_API_URL + 'get-config', {
|
const resp = await QueryGetAPI<string>(VTSURU_API_URL + (id ? 'get-user-config' : 'get-config'), {
|
||||||
name: name
|
name: name,
|
||||||
|
id: id
|
||||||
})
|
})
|
||||||
if (resp.code == 200) {
|
if (resp.code == 200) {
|
||||||
console.log('已获取配置文件: ' + name)
|
console.log('已获取配置文件: ' + name)
|
||||||
|
|||||||
2
src/components.d.ts
vendored
2
src/components.d.ts
vendored
@@ -17,6 +17,8 @@ declare module 'vue' {
|
|||||||
FeedbackItem: typeof import('./components/FeedbackItem.vue')['default']
|
FeedbackItem: typeof import('./components/FeedbackItem.vue')['default']
|
||||||
LiveInfoContainer: typeof import('./components/LiveInfoContainer.vue')['default']
|
LiveInfoContainer: typeof import('./components/LiveInfoContainer.vue')['default']
|
||||||
MonacoEditorComponent: typeof import('./components/MonacoEditorComponent.vue')['default']
|
MonacoEditorComponent: typeof import('./components/MonacoEditorComponent.vue')['default']
|
||||||
|
NFormItemG: typeof import('naive-ui')['NFormItemG']
|
||||||
|
NFormItemGi: typeof import('naive-ui')['NFormItemGi']
|
||||||
NIcon: typeof import('naive-ui')['NIcon']
|
NIcon: typeof import('naive-ui')['NIcon']
|
||||||
NTag: typeof import('naive-ui')['NTag']
|
NTag: typeof import('naive-ui')['NTag']
|
||||||
PointGoodsItem: typeof import('./components/manage/PointGoodsItem.vue')['default']
|
PointGoodsItem: typeof import('./components/manage/PointGoodsItem.vue')['default']
|
||||||
|
|||||||
@@ -1,100 +1,111 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { getImageUploadModel } from '@/Utils';
|
import { getImageUploadModel } from '@/Utils';
|
||||||
import { QueryPostAPI } from '@/api/query';
|
import { QueryPostAPI } from '@/api/query';
|
||||||
import { TemplateConfig, TemplateConfigImageItem } from '@/data/VTsuruTypes'
|
import { ConfigItemDefinition, TemplateConfigImageItem } from '@/data/VTsuruTypes';
|
||||||
import { FILE_BASE_URL, VTSURU_API_URL } from '@/data/constants';
|
import { FILE_BASE_URL, VTSURU_API_URL } from '@/data/constants';
|
||||||
import { NButton, NEmpty, NForm, NFormItem, NInput, NUpload, UploadFileInfo, useMessage } from 'naive-ui'
|
import { NButton, NColorPicker, NEmpty, NForm, NFormItem, NGrid, NInput, NInputNumber, NSlider, NUpload, UploadFileInfo, useMessage } from 'naive-ui';
|
||||||
import { onMounted, ref } from 'vue'
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage();
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
configData: any
|
name?: string;
|
||||||
config: TemplateConfig<any> | undefined
|
configData: any;
|
||||||
}>()
|
config: ConfigItemDefinition[] | undefined;
|
||||||
|
}>();
|
||||||
|
|
||||||
const fileList = ref<{ [key: string]: UploadFileInfo[] }>({})
|
const fileList = ref<{ [key: string]: UploadFileInfo[]; }>({});
|
||||||
|
|
||||||
const isUploading = ref(false)
|
const isUploading = ref(false);
|
||||||
|
|
||||||
function OnFileListChange(key: string, files: UploadFileInfo[]) {
|
function OnFileListChange(key: string, files: UploadFileInfo[]) {
|
||||||
if (files.length == 1) {
|
if (files.length == 1) {
|
||||||
var file = files[0]
|
var 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');
|
||||||
fileList.value[key] = []
|
fileList.value[key] = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function onSubmit() {
|
async function onSubmit() {
|
||||||
try {
|
try {
|
||||||
isUploading.value = true
|
isUploading.value = true;
|
||||||
let images = {} as {
|
let images = {} as {
|
||||||
[key: string]: {
|
[key: string]: {
|
||||||
existImages: string[],
|
existImages: string[],
|
||||||
newImagesBase64: string[],
|
newImagesBase64: string[],
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
for (const item of props.config!.items) {
|
for (const item of props.config!) {
|
||||||
if (item.type == 'image') {
|
if (item.type == 'image') {
|
||||||
const key = (item as TemplateConfigImageItem<any>).key
|
const key = (item as TemplateConfigImageItem<any>).key;
|
||||||
images[key] = await getImageUploadModel(fileList.value[key])
|
images[key] = await getImageUploadModel(fileList.value[key]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const resp = await QueryPostAPI<any>(VTSURU_API_URL + 'set-config', {
|
const resp = await QueryPostAPI<any>(VTSURU_API_URL + 'set-config', {
|
||||||
name: props.config!.name,
|
name: props.name,
|
||||||
json: JSON.stringify(props.configData),
|
json: JSON.stringify(props.configData),
|
||||||
images: images,
|
images: images,
|
||||||
})
|
public: 'true',
|
||||||
|
});
|
||||||
if (resp.code == 200) {
|
if (resp.code == 200) {
|
||||||
message.success('已保存至服务器')
|
message.success('已保存至服务器');
|
||||||
props.config?.items.forEach(item => {
|
props.config?.forEach(item => {
|
||||||
switch (item.type) {
|
if (item.type === 'render') {
|
||||||
case 'image':
|
item.onUploaded?.(props.configData[item.key], props.configData);
|
||||||
item.onUploaded?.(resp.data[item.key], props.configData)
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
})
|
else {
|
||||||
|
item.onUploaded?.call(item, props.configData[item.key], props.configData);
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
message.error('保存失败: ' + resp.message)
|
message.error('保存失败: ' + resp.message);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
message.error('保存失败: ' + err)
|
message.error('保存失败: ' + err);
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
isUploading.value = false
|
isUploading.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getItems() { }
|
function getItems() { }
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
props.config?.items.forEach(item => {
|
props.config?.forEach(item => {
|
||||||
|
if (item.default && !props.configData[item.key]) {
|
||||||
|
props.configData[item.key] = item.default;
|
||||||
|
}
|
||||||
if (item.type == 'image') {
|
if (item.type == 'image') {
|
||||||
const configItem = props.configData[item.key]
|
const configItem = props.configData[item.key];
|
||||||
if (configItem) {
|
if (configItem) {
|
||||||
fileList.value[item.key] = configItem.map((i: string) => ({
|
fileList.value[item.key] = configItem.map((i: string) => ({
|
||||||
id: i,
|
id: i,
|
||||||
thumbnailUrl: FILE_BASE_URL + i,
|
thumbnailUrl: FILE_BASE_URL + i,
|
||||||
name: '',
|
name: '',
|
||||||
status: 'finished',
|
status: 'finished',
|
||||||
}))
|
}));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
fileList.value[item.key] = []
|
fileList.value[item.key] = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NEmpty
|
<NEmpty
|
||||||
v-if="!config"
|
v-if="!config || config.length == 0"
|
||||||
description="此模板不支持配置"
|
description="此模板不支持配置"
|
||||||
/>
|
/>
|
||||||
<NForm v-else>
|
<NForm v-else>
|
||||||
<NFormItem
|
<NGrid
|
||||||
v-for="item in config.items"
|
x-gap="10"
|
||||||
|
y-gap="10"
|
||||||
|
cols="1 600:2 1200:3 1600:4"
|
||||||
|
>
|
||||||
|
<NFormItemGi
|
||||||
|
v-for="item in config"
|
||||||
:key="item.name.toString()"
|
:key="item.name.toString()"
|
||||||
:label="item.name.toString()"
|
:label="item.name.toString()"
|
||||||
>
|
>
|
||||||
@@ -104,15 +115,32 @@ onMounted(() => {
|
|||||||
/>
|
/>
|
||||||
<template v-else-if="item.type == 'string'">
|
<template v-else-if="item.type == 'string'">
|
||||||
<NInput
|
<NInput
|
||||||
v-if="item.data"
|
|
||||||
:value="configData[item.key]"
|
:value="configData[item.key]"
|
||||||
|
:placeholder="item.placeholder"
|
||||||
@update:value="configData[item.key] = $event"
|
@update:value="configData[item.key] = $event"
|
||||||
/>
|
:type="item.inputType"
|
||||||
<NInput
|
|
||||||
v-else
|
|
||||||
v-model:value="configData[item.key]"
|
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
<NColorPicker
|
||||||
|
v-else-if="item.type == 'color'"
|
||||||
|
:value="configData[item.key]"
|
||||||
|
:show-alpha="item.showAlpha ?? false"
|
||||||
|
@update:value="configData[item.key] = $event"
|
||||||
|
/>
|
||||||
|
<NInputNumber
|
||||||
|
v-else-if="item.type == 'number'"
|
||||||
|
:value="configData[item.key]"
|
||||||
|
:min="item.min"
|
||||||
|
@update:value="configData[item.key] = $event"
|
||||||
|
/>
|
||||||
|
<NSlider
|
||||||
|
v-else-if="item.type == 'sliderNumber'"
|
||||||
|
:value="configData[item.key]"
|
||||||
|
:min="item.min"
|
||||||
|
:max="item.max"
|
||||||
|
:step="item.step"
|
||||||
|
@update:value="configData[item.key] = $event"
|
||||||
|
/>
|
||||||
<NUpload
|
<NUpload
|
||||||
v-else-if="item.type == 'image'"
|
v-else-if="item.type == 'image'"
|
||||||
v-model:file-list="fileList[item.key]"
|
v-model:file-list="fileList[item.key]"
|
||||||
@@ -125,8 +153,9 @@ onMounted(() => {
|
|||||||
>
|
>
|
||||||
上传图片
|
上传图片
|
||||||
</NUpload>
|
</NUpload>
|
||||||
</NFormItem>
|
</NFormItemGi>
|
||||||
<NFormItem>
|
</NGrid>
|
||||||
|
|
||||||
<NButton
|
<NButton
|
||||||
type="primary"
|
type="primary"
|
||||||
:loading="isUploading"
|
:loading="isUploading"
|
||||||
@@ -134,6 +163,5 @@ onMounted(() => {
|
|||||||
>
|
>
|
||||||
提交
|
提交
|
||||||
</NButton>
|
</NButton>
|
||||||
</NFormItem>
|
|
||||||
</NForm>
|
</NForm>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,18 +1,15 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { SongFrom, SongLanguage, SongRequestOption, SongsInfo } from '@/api/api-models'
|
import { SongFrom, SongRequestOption, SongsInfo } from '@/api/api-models';
|
||||||
import { QueryGetAPI, QueryPostAPI } from '@/api/query'
|
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 NeteaseIcon from '@/svgs/netease.svg'
|
|
||||||
import {
|
import {
|
||||||
Delete24Filled,
|
Delete24Filled,
|
||||||
Info24Filled,
|
Info24Filled,
|
||||||
NotepadEdit20Filled,
|
NotepadEdit20Filled,
|
||||||
Play24Filled,
|
Play24Filled
|
||||||
SquareArrowForward24Filled,
|
} from '@vicons/fluent';
|
||||||
} 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 {
|
||||||
DataTableBaseColumn,
|
DataTableBaseColumn,
|
||||||
DataTableColumns,
|
DataTableColumns,
|
||||||
@@ -42,9 +39,10 @@ import {
|
|||||||
NText,
|
NText,
|
||||||
NTooltip,
|
NTooltip,
|
||||||
useMessage,
|
useMessage,
|
||||||
} from 'naive-ui'
|
} from 'naive-ui';
|
||||||
import { VNodeChild, computed, h, onMounted, ref, watch } from 'vue'
|
import { VNodeChild, computed, h, onMounted, ref, watch } from 'vue';
|
||||||
import SongPlayer from './SongPlayer.vue'
|
import SongPlayer from './SongPlayer.vue';
|
||||||
|
import { GetPlayButton } from '@/Utils';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
songs: SongsInfo[]
|
songs: SongsInfo[]
|
||||||
@@ -393,72 +391,6 @@ function createColumns(): DataTableColumns<SongsInfo> {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
function GetPlayButton(song: SongsInfo) {
|
|
||||||
switch (song.from) {
|
|
||||||
case SongFrom.FiveSing: {
|
|
||||||
return h(NTooltip, null, {
|
|
||||||
trigger: () =>
|
|
||||||
h(
|
|
||||||
h(
|
|
||||||
NButton,
|
|
||||||
{
|
|
||||||
size: 'small',
|
|
||||||
color: '#00BBB3',
|
|
||||||
ghost: true,
|
|
||||||
onClick: () => {
|
|
||||||
window.open(`http://5sing.kugou.com/bz/${song.id}.html`)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: () => h(FiveSingIcon, { class: 'svg-icon fivesing' }),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
default: () => '在5sing打开',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
case SongFrom.Netease:
|
|
||||||
return h(NTooltip, null, {
|
|
||||||
trigger: () =>
|
|
||||||
h(
|
|
||||||
NButton,
|
|
||||||
{
|
|
||||||
size: 'small',
|
|
||||||
color: '#C20C0C',
|
|
||||||
ghost: true,
|
|
||||||
onClick: () => {
|
|
||||||
window.open(`https://music.163.com/#/song?id=${song.id}`)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: () => h(NeteaseIcon, { class: 'svg-icon netease' }),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
default: () => '在网易云打开',
|
|
||||||
})
|
|
||||||
case SongFrom.Custom:
|
|
||||||
return song.url
|
|
||||||
? h(NTooltip, null, {
|
|
||||||
trigger: () =>
|
|
||||||
h(
|
|
||||||
NButton,
|
|
||||||
{
|
|
||||||
size: 'small',
|
|
||||||
color: '#6b95bd',
|
|
||||||
ghost: true,
|
|
||||||
onClick: () => {
|
|
||||||
window.open(song.url)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: () => h(NIcon, { component: SquareArrowForward24Filled }),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
default: () => '打开链接',
|
|
||||||
})
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function renderCell(value: string | number) {
|
function renderCell(value: string | number) {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return h(NText, { depth: 3 }, { default: () => '未填写' })
|
return h(NText, { depth: 3 }, { default: () => '未填写' })
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import { useAccount } from '@/api/account';
|
import { useAccount } from '@/api/account';
|
||||||
import { useLoadingBarStore } from '@/store/useLoadingBarStore'
|
import { useLoadingBarStore } from '@/store/useLoadingBarStore'
|
||||||
import { useStorage } from '@vueuse/core';
|
import { useStorage } from '@vueuse/core';
|
||||||
import { NSpin, useLoadingBar, useMessage } from 'naive-ui'
|
import { NSpin, useLoadingBar, useMessage, useModal } from 'naive-ui'
|
||||||
import { onMounted } from 'vue'
|
import { onMounted } from 'vue'
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
|
|
||||||
@@ -14,6 +14,7 @@ onMounted(() => {
|
|||||||
window.$loadingBar = useLoadingBar()
|
window.$loadingBar = useLoadingBar()
|
||||||
window.$message = useMessage()
|
window.$message = useMessage()
|
||||||
window.$route = useRoute()
|
window.$route = useRoute()
|
||||||
|
window.$modal = useModal()
|
||||||
const providerStore = useLoadingBarStore()
|
const providerStore = useLoadingBarStore()
|
||||||
providerStore.setLoadingBar(window.$loadingBar)
|
providerStore.setLoadingBar(window.$loadingBar)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import {
|
|||||||
export interface SongListConfigType {
|
export interface SongListConfigType {
|
||||||
userInfo: UserInfo | undefined
|
userInfo: UserInfo | undefined
|
||||||
biliInfo: any | undefined
|
biliInfo: any | undefined
|
||||||
liveRequestSettings: Setting_LiveRequest
|
liveRequestSettings?: Setting_LiveRequest
|
||||||
liveRequestActive: SongRequestInfo[]
|
liveRequestActive?: SongRequestInfo[]
|
||||||
data: SongsInfo[] | undefined
|
data: SongsInfo[] | undefined
|
||||||
config?: any
|
config?: any
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,60 +1,204 @@
|
|||||||
import { VNode } from 'vue'
|
import { VNode, h } from 'vue'; // 导入 Vue 的 VNode 类型和 h 函数(用于示例)
|
||||||
|
|
||||||
|
// --- 基础和通用类型 ---
|
||||||
|
|
||||||
export type TemplateConfig<T> = {
|
|
||||||
name: string
|
|
||||||
items: (
|
|
||||||
| TemplateConfigStringItem<T>
|
|
||||||
| TemplateConfigNumberItem<T>
|
|
||||||
| TemplateConfigStringArrayItem<T>
|
|
||||||
| TemplateConfigNumberArrayItem<T>
|
|
||||||
| TemplateConfigImageItem<T>
|
|
||||||
| TemplateConfigRenderItem<T>
|
|
||||||
)[]
|
|
||||||
onConfirm?: (arg0: T) => void
|
|
||||||
}
|
|
||||||
interface TemplateConfigBase {
|
interface TemplateConfigBase {
|
||||||
name: string | VNode
|
name: string | VNode; // 名称,可以是字符串或 VNode
|
||||||
key: string //将被保存到指定key中
|
key: string; // 唯一标识符,用于数据对象的键
|
||||||
|
/**
|
||||||
|
* 可选的默认值。
|
||||||
|
* 其具体类型在更具体的项类型中被细化。
|
||||||
|
* TemplateConfigRenderItem 会使用其是否存在来进行类型推断。
|
||||||
|
*/
|
||||||
|
default?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
type CommonProps = TemplateConfigBase
|
// 大多数项类型共享的通用属性 (暂时排除 RenderItem)
|
||||||
|
// 我们使用 'unknown' 作为 T 的默认值,表示在定义单个项时
|
||||||
|
// 完整的配置对象类型是未知的。
|
||||||
|
type CommonProps<T = unknown> = TemplateConfigBase & {
|
||||||
|
// 注意:T 代表 *整个* 配置对象的数据类型。
|
||||||
|
// 由于类型推断的方式(T 依赖于完整的 items 数组),
|
||||||
|
// 在单个项定义内部的回调函数/访问器中的 'config' 参数
|
||||||
|
// 通常会是 'unknown' 类型。如果在实现中需要访问
|
||||||
|
// 完整配置对象的特定属性,你可能需要进行类型断言
|
||||||
|
// (例如:config as MyConfigType)。
|
||||||
|
};
|
||||||
|
|
||||||
|
// 数据访问器类型
|
||||||
type DataAccessor<T, V> = {
|
type DataAccessor<T, V> = {
|
||||||
get: (config: T) => V
|
get: (config: T) => V;
|
||||||
set: (config: T, value: V) => void
|
set: (config: T, value: V) => void;
|
||||||
}
|
};
|
||||||
|
|
||||||
// 扩展 CommonProps 以包含额外的共有属性
|
|
||||||
export type TemplateConfigItemWithType<T, V> = CommonProps & { data?: DataAccessor<T, V> }
|
|
||||||
|
|
||||||
export type TemplateConfigStringItem<T> = TemplateConfigItemWithType<T, string> & {
|
|
||||||
type: 'string'
|
|
||||||
}
|
|
||||||
export type TemplateConfigStringArrayItem<T> = TemplateConfigItemWithType<T, string[]> & {
|
|
||||||
type: 'stringArray'
|
|
||||||
}
|
|
||||||
export type TemplateConfigNumberItem<T> = TemplateConfigItemWithType<T, number> & {
|
|
||||||
type: 'number'
|
|
||||||
}
|
|
||||||
export type TemplateConfigNumberArrayItem<T> = TemplateConfigItemWithType<T, number[]> & {
|
|
||||||
type: 'numberArray'
|
|
||||||
}
|
|
||||||
|
|
||||||
export type TemplateConfigRenderItem<T> = TemplateConfigBase & {
|
|
||||||
type: 'render'
|
|
||||||
render: (arg0: T) => VNode
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @description 带有特定值类型 'V' 的配置项的基础类型。
|
||||||
|
* @template T - 完整配置对象的类型 (默认为 unknown)。
|
||||||
|
* @template V - 此特定配置项的值的类型。
|
||||||
|
*/
|
||||||
|
export type TemplateConfigItemWithType<T = unknown, V = unknown> = CommonProps<T> & {
|
||||||
|
type: string; // 类型判别属性
|
||||||
|
data?: DataAccessor<T, V>; // 可选的数据访问器
|
||||||
|
/**
|
||||||
|
* @description 可选的上传/更新回调函数。
|
||||||
|
* @param data - 当前项更新的数据,类型为 V。
|
||||||
|
* @param config - 整个配置数据对象,类型为 T (通常是 unknown)。
|
||||||
|
*/
|
||||||
|
onUploaded?: (data: V, config: T) => void;
|
||||||
|
/**
|
||||||
|
* 可选的默认值,约束为类型 V。
|
||||||
|
* 覆盖了 TemplateConfigBase 中的 'any' 类型。
|
||||||
|
*/
|
||||||
|
default?: V;
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Widen 工具类型 (保持不变) ---
|
||||||
|
// 递归地将类型拓宽为其基础类型。
|
||||||
|
type Widen<T> =
|
||||||
|
T extends string ? string :
|
||||||
|
T extends number ? number :
|
||||||
|
T extends boolean ? boolean :
|
||||||
|
T extends bigint ? bigint :
|
||||||
|
T extends symbol ? symbol :
|
||||||
|
T extends undefined ? undefined :
|
||||||
|
T extends null ? null :
|
||||||
|
T extends Function ? T :
|
||||||
|
T extends Date ? Date :
|
||||||
|
T extends readonly (infer U)[] ? Widen<U>[] :
|
||||||
|
T extends object ? { -readonly [K in keyof T]: Widen<T[K]> } :
|
||||||
|
T;
|
||||||
|
|
||||||
|
// --- 具体配置项类型定义 ---
|
||||||
|
// T 在所有具体类型中默认为 unknown
|
||||||
|
|
||||||
|
export type TemplateConfigStringItem<T = unknown> = TemplateConfigItemWithType<T, string> & {
|
||||||
|
type: 'string';
|
||||||
|
placeholder?: string; // 可选的占位符
|
||||||
|
inputType?: 'text' | 'password' | 'textarea'; // 可选的输入类型
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TemplateConfigStringArrayItem<T = unknown> = TemplateConfigItemWithType<T, string[]> & {
|
||||||
|
type: 'stringArray';
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TemplateConfigNumberItem<T = unknown> = TemplateConfigItemWithType<T, number> & {
|
||||||
|
type: 'number';
|
||||||
|
min?: number;
|
||||||
|
max?: number;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TemplateConfigColorItem<T = unknown> = TemplateConfigItemWithType<T, number> & {
|
||||||
|
type: 'color';
|
||||||
|
showAlpha?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TemplateConfigSliderNumberItem<T = unknown> = TemplateConfigItemWithType<T, number> & {
|
||||||
|
type: 'sliderNumber';
|
||||||
|
step?: number;
|
||||||
|
min?: number;
|
||||||
|
max?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TemplateConfigNumberArrayItem<T = unknown> = TemplateConfigItemWithType<T, number[]> & {
|
||||||
|
type: 'numberArray';
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TemplateConfigImageItem<T = unknown> = TemplateConfigItemWithType<T, string[]> & {
|
||||||
|
type: 'image';
|
||||||
|
imageLimit: number; // 图片数量限制
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 自定义渲染项的配置。使用 'this' 类型实现动态参数类型。
|
||||||
|
* @template T - 完整配置对象的类型 (默认为 unknown)。
|
||||||
|
*/
|
||||||
|
export interface TemplateConfigRenderItem<T = unknown> extends TemplateConfigBase { // 继承基础接口以获取 key, name, default 检查
|
||||||
|
type: 'render';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 渲染此项的自定义 VNode。
|
||||||
|
* @param this 当前的 TemplateConfigRenderItem 实例。
|
||||||
|
* @param config 整个配置对象 (类型为 T, 默认为 unknown)。
|
||||||
|
* 在实现内部可能需要类型断言 (例如 `config as MyConfig`)。
|
||||||
|
* @param defaultData 从此项的 'default' 属性派生的数据。
|
||||||
|
* 如果 `default` 存在,其类型为 `Widen<D>` (D 是 default 的类型),否则为 `unknown`。
|
||||||
|
* @returns 表示渲染输出的 VNode。
|
||||||
*
|
*
|
||||||
* @template T - The type of the associated data model.
|
* @importantUsage 调用此方法时,如果项定义了 `default` 属性,
|
||||||
|
* 则 **必须** 将该项的默认值 (或与其 Widen 后的类型兼容的值)
|
||||||
|
* 作为第二个参数传递。如果不存在 `default`,则传递 `undefined` 或 `null`。
|
||||||
|
* 示例: `item.render(config, item.default)`
|
||||||
*/
|
*/
|
||||||
export type TemplateConfigImageItem<T> = TemplateConfigBase & {
|
render(this: this, config: T): VNode;
|
||||||
type: 'image' // Specifies the type of configuration item as 'image'.
|
|
||||||
imageLimit: number // The maximum number of images allowed.
|
|
||||||
/**
|
/**
|
||||||
* Callback function triggered upon image upload.
|
* @description 可选的回调函数,当自定义渲染的组件发出更新信号时调用。
|
||||||
* @param {string[]} uploadedImages - The uploaded image or array of images.
|
* @param this 当前的 TemplateConfigRenderItem 实例。
|
||||||
* @param {T} config - The configuration data model.
|
* @param data 更新后的数据。如果 `default` 存在,其类型为 `Widen<D>`,否则为 `unknown`。
|
||||||
|
* @param config 整个配置对象 (类型为 T, 默认为 unknown)。
|
||||||
|
* 在实现内部可能需要类型断言 (例如 `config as MyConfig`)。
|
||||||
*/
|
*/
|
||||||
onUploaded?: (uploadedImages: string[], config: T) => void
|
onUploaded?(this: this, data: this extends { default: infer D; } ? Widen<D> : unknown, config: T): void;
|
||||||
|
|
||||||
|
// 继承自 TemplateConfigBase 的 'default?: any',这对于
|
||||||
|
// 'this extends { default: infer D }' 类型检查能正确工作至关重要。
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 联合类型和核心提取逻辑 ---
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 所有可能的配置项定义类型的联合类型。
|
||||||
|
* 使用 `<unknown>` 作为完整配置类型 T 的占位符。
|
||||||
|
*/
|
||||||
|
export type ConfigItemDefinition =
|
||||||
|
| TemplateConfigStringItem<any>
|
||||||
|
| TemplateConfigNumberItem<any>
|
||||||
|
| TemplateConfigStringArrayItem<any>
|
||||||
|
| TemplateConfigNumberArrayItem<any>
|
||||||
|
| TemplateConfigImageItem<any>
|
||||||
|
| TemplateConfigRenderItem<any> // 包含优化后的 render/onUploaded 方法
|
||||||
|
| TemplateConfigSliderNumberItem<any>
|
||||||
|
| TemplateConfigColorItem<any>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 从只读的配置项数组中提取最终的数据结构类型。
|
||||||
|
* @template Items - 通过 `defineItems([...])` 推断出的只读元组类型。
|
||||||
|
*/
|
||||||
|
export type ExtractConfigData<
|
||||||
|
Items extends readonly ConfigItemDefinition[]
|
||||||
|
> = {
|
||||||
|
// 遍历联合类型 Items[number] 中所有项的 'key' 属性
|
||||||
|
[K in Extract<Items[number], { key: string; }>['key']]:
|
||||||
|
// 找到与当前键 K 匹配的具体项定义
|
||||||
|
Extract<Items[number], { key: K; }> extends infer ItemWithKeyK
|
||||||
|
// 检查匹配到的项是否有 'default' 属性
|
||||||
|
? ItemWithKeyK extends { default: infer DefaultType; }
|
||||||
|
// 如果有,使用 default 值的 Widen 处理后的类型
|
||||||
|
? Widen<DefaultType>
|
||||||
|
// 如果没有 default,则根据 'type' 属性确定类型
|
||||||
|
: ItemWithKeyK extends { type: 'string'; } ? string
|
||||||
|
: ItemWithKeyK extends { type: 'stringArray'; } ? string[]
|
||||||
|
: ItemWithKeyK extends { type: 'number' | 'sliderNumber' | 'color'; } ? number
|
||||||
|
: ItemWithKeyK extends { type: 'numberArray'; } ? number[]
|
||||||
|
: ItemWithKeyK extends { type: 'image'; } ? string[]
|
||||||
|
// *** 优化应用:无 default 的 render 类型回退到 'unknown' ***
|
||||||
|
: ItemWithKeyK extends { type: 'render'; } ? unknown
|
||||||
|
// 其他意外情况的回退类型
|
||||||
|
: unknown
|
||||||
|
: never // 如果 K 正确派生,则不应发生
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 定义并验证配置项数组。
|
||||||
|
* 使用 `const Items` 可以在与 `as const` 结合使用时保留字面量类型和元组结构。
|
||||||
|
* @param items 一个只读的配置项定义数组。
|
||||||
|
* @returns 类型被保留的同一个只读数组。
|
||||||
|
*/
|
||||||
|
export function defineItems<
|
||||||
|
const Items extends readonly ConfigItemDefinition[] // 使用 'const' 泛型进行推断
|
||||||
|
>(items: Items): Items {
|
||||||
|
// 如果需要,可以在此处添加基本的运行时验证。
|
||||||
|
// 类型检查主要由 TypeScript 根据约束完成。
|
||||||
|
return items;
|
||||||
}
|
}
|
||||||
@@ -1,101 +1,118 @@
|
|||||||
import DefaultIndexTemplateVue from '@/views/view/indexTemplate/DefaultIndexTemplate.vue'
|
import DefaultIndexTemplateVue from '@/views/view/indexTemplate/DefaultIndexTemplate.vue';
|
||||||
import { defineAsyncComponent, ref } from 'vue'
|
import { defineAsyncComponent, ref } from 'vue';
|
||||||
|
|
||||||
const debugAPI =
|
const debugAPI =
|
||||||
import.meta.env.VITE_API == 'dev'
|
import.meta.env.VITE_API == 'dev'
|
||||||
? import.meta.env.VITE_DEBUG_DEV_API
|
? import.meta.env.VITE_DEBUG_DEV_API
|
||||||
: import.meta.env.VITE_DEBUG_RELEASE_API
|
: import.meta.env.VITE_DEBUG_RELEASE_API;
|
||||||
const releseAPI = `https://vtsuru.suki.club/`
|
const releseAPI = `https://vtsuru.suki.club/`;
|
||||||
const failoverAPI = `https://failover-api.vtsuru.suki.club/`
|
const failoverAPI = `https://failover-api.vtsuru.suki.club/`;
|
||||||
|
|
||||||
export const isBackendUsable = ref(true)
|
export const isBackendUsable = ref(true);
|
||||||
export const isDev = import.meta.env.MODE === 'development'
|
export const isDev = import.meta.env.MODE === 'development';
|
||||||
|
|
||||||
export const AVATAR_URL = 'https://workers.vrp.moe/api/bilibili/avatar/'
|
export const AVATAR_URL = 'https://workers.vrp.moe/api/bilibili/avatar/';
|
||||||
export const FILE_BASE_URL = 'https://files.vtsuru.live'
|
export const FILE_BASE_URL = 'https://files.vtsuru.live';
|
||||||
export const IMGUR_URL = FILE_BASE_URL + '/imgur/'
|
export const IMGUR_URL = FILE_BASE_URL + '/imgur/';
|
||||||
export const THINGS_URL = FILE_BASE_URL + '/things/'
|
export const THINGS_URL = FILE_BASE_URL + '/things/';
|
||||||
export const apiFail = ref(false)
|
export const apiFail = ref(false);
|
||||||
|
|
||||||
export const BASE_URL =
|
export const BASE_URL =
|
||||||
process.env.NODE_ENV === 'development'
|
process.env.NODE_ENV === 'development'
|
||||||
? debugAPI
|
? debugAPI
|
||||||
: apiFail.value
|
: apiFail.value
|
||||||
? failoverAPI
|
? failoverAPI
|
||||||
: releseAPI
|
: releseAPI;
|
||||||
export const BASE_API_URL = BASE_URL + 'api/'
|
export const BASE_API_URL = BASE_URL + 'api/';
|
||||||
export const FETCH_API = 'https://fetch.vtsuru.live/'
|
export const FETCH_API = 'https://fetch.vtsuru.live/';
|
||||||
export const BASE_HUB_URL =
|
export const BASE_HUB_URL =
|
||||||
(process.env.NODE_ENV === 'development'
|
(process.env.NODE_ENV === 'development'
|
||||||
? debugAPI
|
? debugAPI
|
||||||
: apiFail.value
|
: apiFail.value
|
||||||
? failoverAPI
|
? failoverAPI
|
||||||
: releseAPI) + 'hub/'
|
: releseAPI) + 'hub/';
|
||||||
|
|
||||||
export const TURNSTILE_KEY = '0x4AAAAAAAETUSAKbds019h0'
|
export const TURNSTILE_KEY = '0x4AAAAAAAETUSAKbds019h0';
|
||||||
|
|
||||||
export const CURRENT_HOST = `${window.location.protocol}//${window.location.host}/`
|
export const CURRENT_HOST = `${window.location.protocol}//${window.location.host}/`;
|
||||||
export const CN_HOST = 'https://vtsuru.suki.club/'
|
export const CN_HOST = 'https://vtsuru.suki.club/';
|
||||||
|
|
||||||
export const USER_API_URL = BASE_API_URL + 'user/'
|
export const USER_API_URL = BASE_API_URL + 'user/';
|
||||||
export const ACCOUNT_API_URL = BASE_API_URL + 'account/'
|
export const ACCOUNT_API_URL = BASE_API_URL + 'account/';
|
||||||
export const BILI_API_URL = BASE_API_URL + 'bili/'
|
export const BILI_API_URL = BASE_API_URL + 'bili/';
|
||||||
export const SONG_API_URL = BASE_API_URL + 'song-list/'
|
export const SONG_API_URL = BASE_API_URL + 'song-list/';
|
||||||
export const NOTIFACTION_API_URL = BASE_API_URL + 'notification/'
|
export const NOTIFACTION_API_URL = BASE_API_URL + 'notification/';
|
||||||
export const QUESTION_API_URL = BASE_API_URL + 'qa/'
|
export const QUESTION_API_URL = BASE_API_URL + 'qa/';
|
||||||
export const LOTTERY_API_URL = BASE_API_URL + 'lottery/'
|
export const LOTTERY_API_URL = BASE_API_URL + 'lottery/';
|
||||||
export const HISTORY_API_URL = BASE_API_URL + 'history/'
|
export const HISTORY_API_URL = BASE_API_URL + 'history/';
|
||||||
export const SCHEDULE_API_URL = BASE_API_URL + 'schedule/'
|
export const SCHEDULE_API_URL = BASE_API_URL + 'schedule/';
|
||||||
export const VIDEO_COLLECT_API_URL = BASE_API_URL + 'video-collect/'
|
export const VIDEO_COLLECT_API_URL = BASE_API_URL + 'video-collect/';
|
||||||
export const OPEN_LIVE_API_URL = BASE_API_URL + 'open-live/'
|
export const OPEN_LIVE_API_URL = BASE_API_URL + 'open-live/';
|
||||||
export const SONG_REQUEST_API_URL = BASE_API_URL + 'live-request/'
|
export const SONG_REQUEST_API_URL = BASE_API_URL + 'live-request/';
|
||||||
export const QUEUE_API_URL = BASE_API_URL + 'queue/'
|
export const QUEUE_API_URL = BASE_API_URL + 'queue/';
|
||||||
export const EVENT_API_URL = BASE_API_URL + 'event/'
|
export const EVENT_API_URL = BASE_API_URL + 'event/';
|
||||||
export const LIVE_API_URL = BASE_API_URL + 'live/'
|
export const LIVE_API_URL = BASE_API_URL + 'live/';
|
||||||
export const FEEDBACK_API_URL = BASE_API_URL + 'feedback/'
|
export const FEEDBACK_API_URL = BASE_API_URL + 'feedback/';
|
||||||
export const MUSIC_REQUEST_API_URL = BASE_API_URL + 'music-request/'
|
export const MUSIC_REQUEST_API_URL = BASE_API_URL + 'music-request/';
|
||||||
export const VTSURU_API_URL = BASE_API_URL + 'vtsuru/'
|
export const VTSURU_API_URL = BASE_API_URL + 'vtsuru/';
|
||||||
export const POINT_API_URL = BASE_API_URL + 'point/'
|
export const POINT_API_URL = BASE_API_URL + 'point/';
|
||||||
export const BILI_AUTH_API_URL = BASE_API_URL + 'bili-auth/'
|
export const BILI_AUTH_API_URL = BASE_API_URL + 'bili-auth/';
|
||||||
export const FORUM_API_URL = BASE_API_URL + 'forum/'
|
export const FORUM_API_URL = BASE_API_URL + 'forum/';
|
||||||
export const USER_INDEX_API_URL = BASE_API_URL + 'user-index/'
|
export const USER_INDEX_API_URL = BASE_API_URL + 'user-index/';
|
||||||
export const ANALYZE_API_URL = BASE_API_URL + 'analyze/'
|
export const ANALYZE_API_URL = BASE_API_URL + 'analyze/';
|
||||||
|
|
||||||
export const ScheduleTemplateMap = {
|
export type TemplateMapType = {
|
||||||
|
[key: string]: {
|
||||||
|
name: string;
|
||||||
|
settingName?: string;
|
||||||
|
component: any;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export const ScheduleTemplateMap: TemplateMapType = {
|
||||||
'': {
|
'': {
|
||||||
name: '默认',
|
name: '默认',
|
||||||
compoent: defineAsyncComponent(
|
//settingName: 'Template.Schedule.Default',
|
||||||
|
component: defineAsyncComponent(
|
||||||
() => import('@/views/view/scheduleTemplate/DefaultScheduleTemplate.vue')
|
() => import('@/views/view/scheduleTemplate/DefaultScheduleTemplate.vue')
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
pinky: {
|
pinky: {
|
||||||
name: '粉粉',
|
name: '粉粉',
|
||||||
compoent: defineAsyncComponent(
|
//settingName: 'Template.Schedule.Pinky',
|
||||||
|
component: defineAsyncComponent(
|
||||||
() => import('@/views/view/scheduleTemplate/PinkySchedule.vue')
|
() => import('@/views/view/scheduleTemplate/PinkySchedule.vue')
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} as { [key: string]: { name: string; compoent: any } }
|
};
|
||||||
export const SongListTemplateMap = {
|
export const SongListTemplateMap: TemplateMapType = {
|
||||||
'': {
|
'': {
|
||||||
name: '默认',
|
name: '默认',
|
||||||
compoent: defineAsyncComponent(
|
//settingName: 'Template.SongList.Default',
|
||||||
|
component: defineAsyncComponent(
|
||||||
() => import('@/views/view/songListTemplate/DefaultSongListTemplate.vue')
|
() => import('@/views/view/songListTemplate/DefaultSongListTemplate.vue')
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
simple: {
|
simple: {
|
||||||
name: '简单',
|
name: '简单',
|
||||||
compoent: defineAsyncComponent(
|
//settingName: 'Template.SongList.Simple',
|
||||||
|
component: defineAsyncComponent(
|
||||||
() => import('@/views/view/songListTemplate/SimpleSongListTemplate.vue')
|
() => import('@/views/view/songListTemplate/SimpleSongListTemplate.vue')
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
traditional: {
|
traditional: {
|
||||||
name: '传统',
|
name: '列表',
|
||||||
compoent: defineAsyncComponent(
|
settingName: 'Template.SongList.Traditional',
|
||||||
|
component: defineAsyncComponent(
|
||||||
() =>
|
() =>
|
||||||
import('@/views/view/songListTemplate/TraditionalSongListTemplate.vue')
|
import('@/views/view/songListTemplate/TraditionalSongListTemplate.vue')
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} as { [key: string]: { name: string; compoent: any } }
|
};
|
||||||
export const IndexTemplateMap = {
|
|
||||||
'': { name: '默认', compoent: DefaultIndexTemplateVue }
|
export const IndexTemplateMap: TemplateMapType = {
|
||||||
} as { [key: string]: { name: string; compoent: any } }
|
'': {
|
||||||
|
name: '默认',
|
||||||
|
//settingName: 'Template.Index.Default',
|
||||||
|
component: DefaultIndexTemplateVue
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
1
src/svgs/bilibili.svg
Normal file
1
src/svgs/bilibili.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1743395481317" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1485" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M729.32864 373.94944c-9.79456-5.94432-19.06176-6.784-19.14368-6.784l-1.06496-0.0512c-57.20064-3.8656-121.1648-5.83168-190.12608-5.83168l-13.98784 0.00512c-68.95616 0-132.92544 1.96096-190.12096 5.83168l-1.06496 0.0512c-0.08192 0-9.34912 0.83968-19.14368 6.784-15.04768 9.12896-24.27392 25.94816-27.4176 49.9712-10.07104 76.91264-4.38272 173.64992 0.18944 251.392 2.93888 49.96608 33.408 62.45888 85.04832 67.1488 10.78272 0.98816 69.08928 5.86752 159.50848 5.89312v-0.00512c90.4192-0.02048 148.72576-4.90496 159.5136-5.888 51.64032-4.68992 82.10944-17.18272 85.0432-67.1488 4.57728-77.74208 10.26048-174.47936 0.18944-251.392-3.1488-24.02816-12.37504-40.84736-27.42272-49.97632z m-390.9888 172.71808a23.64928 23.64928 0 0 1-31.68768-10.84416 23.68 23.68 0 0 1 10.84416-31.68768c2.03776-1.00352 50.69312-24.72448 110.5408-43.06432a23.68 23.68 0 1 1 13.88032 45.29152c-56.2944 17.24928-103.11168 40.07424-103.5776 40.30464z m268.89728 35.88608c-0.44032 2.23232-11.26912 54.64064-50.93888 54.64064-21.44256 0-36.10112-14.04928-44.98432-26.77248-8.69376 12.70784-22.80448 26.77248-42.65472 26.77248-35.5328 0-50.13504-48.26624-51.68128-53.77024a11.3664 11.3664 0 0 1 21.87776-6.1696c2.74944 9.6512 14.1312 37.20192 29.7984 37.20192 16.37376 0 28.89216-23.64416 31.98464-31.92832a11.37152 11.37152 0 0 1 10.6496-7.38816h0.06144c4.76672 0.03072 9.0112 3.02592 10.62912 7.50592 0.10752 0.28672 11.96544 31.81568 34.31424 31.81568 20.864 0 28.56448-35.95264 28.64128-36.32128a11.34592 11.34592 0 0 1 13.35808-8.93952 11.36128 11.36128 0 0 1 8.94464 13.35296z m110.11584-46.73536a23.68 23.68 0 0 1-31.68256 10.84416c-0.47104-0.2304-47.47264-23.1168-103.57248-40.30976a23.69024 23.69024 0 0 1-15.70816-29.58336 23.66976 23.66976 0 0 1 29.57824-15.70304c59.84768 18.33984 108.49792 42.0608 110.55104 43.06432a23.68 23.68 0 0 1 10.83392 31.68768z" fill="#F16C8D" p-id="1486"></path><path d="M849.92 51.2H174.08c-67.8656 0-122.88 55.0144-122.88 122.88v675.84c0 67.87072 55.0144 122.88 122.88 122.88h675.84c67.87072 0 122.88-55.00928 122.88-122.88V174.08c0-67.86048-55.00928-122.88-122.88-122.88z m-36.60288 627.45088c-2.62656 44.57984-21.82144 78.63296-55.51616 98.48832-25.68192 15.13472-54.17472 19.48672-81.13664 21.9392-32.45568 2.94912-92.71808 6.09792-164.66432 6.1184-71.94112-0.02048-132.20864-3.16416-164.66432-6.1184-26.96192-2.45248-55.45472-6.80448-81.13152-21.9392-33.69472-19.85536-52.8896-53.90336-55.51104-98.4832-4.70528-80.13312-10.5728-179.85536 0.19456-262.10816C221.5424 335.16544 280.99072 311.57248 311.5008 310.37952a2482.64192 2482.64192 0 0 1 81.42336-4.08576c-7.53664-8.53504-19.88096-23.3216-28.81536-38.11328-13.73696-22.73792 8.52992-41.68704 8.52992-41.68704s23.68-20.36736 44.52864 5.21216c15.69792 19.26656 38.37952 55.99744 48.61952 72.95488l53.20704-0.21504c13.2608 0 26.33216 0.07168 39.2192 0.21504 10.24-16.95744 32.9216-53.6832 48.61952-72.95488 20.84352-25.57952 44.52864-5.21216 44.52864-5.21216s22.26176 18.94912 8.5248 41.68704c-8.9344 14.79168-21.27872 29.57824-28.81536 38.11328 28.35968 0.97792 55.56224 2.33984 81.42336 4.08064 30.5152 1.19808 89.9584 24.79104 100.61312 106.17344 10.7776 82.24768 4.9152 181.96992 0.20992 262.10304z" fill="#F16C8D" p-id="1487"></path></svg>
|
||||||
|
After Width: | Height: | Size: 3.5 KiB |
1
src/svgs/douyin.svg
Normal file
1
src/svgs/douyin.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1743395530408" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2528" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512 512m-512 0a512 512 0 1 0 1024 0 512 512 0 1 0-1024 0Z" fill="#333333" p-id="2529"></path><path d="M759.296 448.512c-48.896 0-96.256-15.872-135.424-45.056v204.8c0 104.448-80.64 189.184-179.968 189.184s-179.968-84.736-179.968-189.184 80.64-189.184 179.968-189.184c9.984 0 19.712 0.768 28.928 2.56V529.92c-8.96-3.584-18.688-5.376-28.16-5.376-44.288 0-80.384 37.632-80.384 84.48s36.096 84.48 80.384 84.48 80.128-37.632 80.128-84.48V202.24h100.352c0 78.336 60.416 141.568 134.656 141.568v104.704h-0.512" fill="#FFFFFF" p-id="2530"></path></svg>
|
||||||
|
After Width: | Height: | Size: 877 B |
1
src/svgs/neteaseMusic.svg
Normal file
1
src/svgs/neteaseMusic.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1743395554102" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3724" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M849.92 51.2H174.08c-67.8656 0-122.88 55.0144-122.88 122.88v675.84c0 67.8656 55.0144 122.88 122.88 122.88h675.84c67.8656 0 122.88-55.0144 122.88-122.88V174.08c0-67.8656-55.0144-122.88-122.88-122.88z m-80.38912 605.91104c-32.82432 77.88032-90.5728 128.10752-171.7504 151.20384a284.83584 284.83584 0 0 1-94.592 10.39872c-66.60096-3.7888-124.29824-28.9536-172.90752-74.4448-47.95392-44.88192-77.85472-100.06016-88.25856-164.83328-14.15168-88.10496 7.55712-167.10656 64.96256-235.61216 31.5392-37.632 70.8864-64.75264 116.77696-82.26304 15.81568-6.03648 33.1264-0.41984 41.8048 13.39392 8.86272 14.10048 6.78912 32.25088-5.49888 43.48416-3.47136 3.1744-7.85408 5.67808-12.2368 7.46496-69.95968 28.56448-116.18304 78.86848-134.53824 152.22784-17.81248 71.17824-2.06336 136.33024 43.41248 193.73056 28.50304 35.98336 65.32608 60.14976 109.89568 72.01792 28.80512 7.66976 57.91744 8.17152 87.25504 3.6352 30.27456-4.67968 58.5472-14.63296 83.90656-31.99488 34.8928-23.88992 59.74528-55.75168 72.17664-96.26624 10.74176-34.9952 11.10528-70.31296-2.16064-105.04192-10.6752-27.94496-29.5424-49.45408-53.20704-67.18464-12.6464-9.472-26.21952-16.87552-41.69728-20.50048-0.70144-0.16384-1.41824-0.24576-2.69824-0.45056 2.11456 8.09984 4.08064 15.79008 6.10816 23.45984 4.91008 18.5088 9.92256 36.98688 14.75072 55.51616 10.48576 40.2688 0.34304 75.55584-27.392 105.62048-25.6512 27.81184-58.31168 39.9104-95.77472 36.28544-41.70752-4.0448-71.4752-26.6752-90.90048-63.32416-10.17344-19.18976-14.56128-39.87456-15.37024-61.5168-1.29024-34.4576 6.75328-66.27328 26.49088-94.70976 21.20192-30.54592 50.57024-50.14016 85.26336-62.32576 2.73408-0.95744 5.49376-1.82784 8.69888-2.8928-1.86368-7.08608-3.81952-14.0032-5.49376-20.992-2.31424-9.60512-5.28384-19.15904-6.44608-28.91776-3.44064-28.8512 5.42208-53.95968 25.35936-74.96704 16.32256-17.2032 36.12672-28.11904 59.84256-31.37536 2.61632-0.3584 5.2224-0.75776 7.82336-1.13664h10.68032c6.20544 1.00864 12.48256 1.72544 18.62144 3.07712 21.57056 4.75136 40.88832 14.30016 57.9328 28.29824 11.1872 9.18016 15.75424 21.15584 12.51328 35.34336-2.90304 12.75392-11.06432 21.34016-23.64928 25.0112-11.89376 3.47136-22.656 0.83968-32.43008-6.99392-12.38016-9.92256-26.12224-16.7936-42.59328-15.32416-13.95712 1.24416-27.31008 15.92832-26.112 29.04064 0.5376 5.86752 2.6368 11.5968 4.12672 17.36192 2.6368 10.19392 5.34016 20.36736 8.01792 30.5408 0.46592 1.75616 0.896 3.15904 3.34848 3.29216 37.51936 2.048 71.32672 14.25408 101.44768 36.8896 29.6192 22.26176 53.87264 48.96768 70.12864 82.52928 11.92448 24.63232 18.6112 50.64192 20.3776 77.94688 2.36544 36.28544-1.8944 71.7824-16.01536 105.29792z" fill="#D81E06" p-id="3725"></path><path d="M548.52096 462.01344l-8.77568-33.19808c-20.54656 6.528-38.29248 16.49664-51.58912 33.08544-17.80224 22.21568-21.35552 47.73376-15.67232 74.8544 2.94912 14.08 9.83552 26.13248 21.92384 34.54464 15.11424 10.5216 35.69664 10.06592 51.50208-0.87552 16.1536-11.1872 23.3472-29.22496 18.62656-47.72864-5.1712-20.26496-10.66496-40.45312-16.01536-60.68224z" fill="#D81E06" p-id="3726"></path></svg>
|
||||||
|
After Width: | Height: | Size: 3.3 KiB |
1
src/svgs/qqMusic.svg
Normal file
1
src/svgs/qqMusic.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1743399818369" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4772" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M42.667 516.693A469.333 469.333 0 1 0 512 52.053a469.333 469.333 0 0 0-469.333 464.64z" fill="#F8C913" p-id="4773"></path><path d="M654.933 12.373a217.173 217.173 0 0 1-105.386 49.494 409.6 409.6 0 0 0-128 39.68 184.747 184.747 0 0 0-42.667 29.866c-20.053 17.494-34.987 49.494-30.293 59.734s50.346 72.106 107.946 154.026S569.6 504.32 581.973 521.387a269.227 269.227 0 0 1 20.054 32.426c0 2.56-10.24 0-20.054-2.56A256 256 0 0 0 421.547 576a260.267 260.267 0 0 0-97.707 96.853 170.667 170.667 0 0 0-7.68 131.84 183.893 183.893 0 0 0 90.453 94.294 146.347 146.347 0 0 0 82.774 20.053 221.867 221.867 0 0 0 75.093-5.12 216.32 216.32 0 0 0 165.547-183.893c2.56-47.36-2.56-62.294-65.28-170.667-97.707-170.667-185.6-322.987-185.6-325.547l40.106-5.12a165.973 165.973 0 0 0 145.494-85.333c12.373-22.187 12.373-29.867 12.373-77.227A221.44 221.44 0 0 0 672 6.4c-2.133-8.96-4.267-6.4-17.067 5.973z" fill="#02B053" p-id="4774"></path></svg>
|
||||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -1,7 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { QueryGetAPI } from '@/api/query'
|
import { QueryGetAPI } from '@/api/query'
|
||||||
import { VTSURU_API_URL } from '@/data/constants'
|
import { VTSURU_API_URL } from '@/data/constants'
|
||||||
import vtb from '@/svgs/ic_vtuber.svg'
|
|
||||||
import {
|
import {
|
||||||
BookCoins20Filled,
|
BookCoins20Filled,
|
||||||
Info24Filled,
|
Info24Filled,
|
||||||
@@ -15,6 +14,7 @@ import { AnalyticsSharp, Calendar, Chatbox, ListCircle, MusicalNote } from '@vic
|
|||||||
import { useWindowSize } from '@vueuse/core'
|
import { useWindowSize } from '@vueuse/core'
|
||||||
import { NButton, NDivider, NEllipsis, NFlex, NGradientText, NGrid, NGridItem, NIcon, NNumberAnimation, NSpace, NText, NTooltip } from 'naive-ui'
|
import { NButton, NDivider, NEllipsis, NFlex, NGradientText, NGrid, NGridItem, NIcon, NNumberAnimation, NSpace, NText, NTooltip } from 'naive-ui'
|
||||||
import { onMounted, ref } from 'vue'
|
import { onMounted, ref } from 'vue'
|
||||||
|
import vtb from '@/svgs/ic_vtuber.svg'
|
||||||
|
|
||||||
const { width } = useWindowSize()
|
const { width } = useWindowSize()
|
||||||
|
|
||||||
|
|||||||
@@ -189,15 +189,35 @@ onMounted(async () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NLayoutContent v-if="!id" style="height: 100vh">
|
<NLayoutContent
|
||||||
<NResult status="error" title="输入的uId无效" description="再检查检查" />
|
v-if="!id"
|
||||||
|
style="height: 100vh"
|
||||||
|
>
|
||||||
|
<NResult
|
||||||
|
status="error"
|
||||||
|
title="输入的uId无效"
|
||||||
|
description="再检查检查"
|
||||||
|
/>
|
||||||
</NLayoutContent>
|
</NLayoutContent>
|
||||||
<NLayoutContent v-else-if="notfount" style="height: 100vh">
|
<NLayoutContent
|
||||||
<NResult status="error" title="未找到指定 uId 的用户" description="或者是没有进行认证" />
|
v-else-if="notfount"
|
||||||
|
style="height: 100vh"
|
||||||
|
>
|
||||||
|
<NResult
|
||||||
|
status="error"
|
||||||
|
title="未找到指定 uId 的用户"
|
||||||
|
description="或者是没有进行认证"
|
||||||
|
/>
|
||||||
</NLayoutContent>
|
</NLayoutContent>
|
||||||
<NLayout v-else style="height: 100vh">
|
<NLayout
|
||||||
|
v-else
|
||||||
|
style="height: 100vh"
|
||||||
|
>
|
||||||
<NLayoutHeader style="height: 50px; padding: 5px 15px 5px 15px">
|
<NLayoutHeader style="height: 50px; padding: 5px 15px 5px 15px">
|
||||||
<NPageHeader :subtitle="($route.meta.title as string) ?? ''" style="margin-top: 6px">
|
<NPageHeader
|
||||||
|
:subtitle="($route.meta.title as string) ?? ''"
|
||||||
|
style="margin-top: 6px"
|
||||||
|
>
|
||||||
<template #extra>
|
<template #extra>
|
||||||
<NSpace align="center">
|
<NSpace align="center">
|
||||||
<NSwitch
|
<NSwitch
|
||||||
@@ -233,8 +253,8 @@ onMounted(async () => {
|
|||||||
<NButton
|
<NButton
|
||||||
style="right: 0px; position: relative"
|
style="right: 0px; position: relative"
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="$router.push({ name: 'manage-index' })"
|
|
||||||
size="small"
|
size="small"
|
||||||
|
@click="$router.push({ name: 'manage-index' })"
|
||||||
>
|
>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<NIcon :component="WindowWrench20Filled" />
|
<NIcon :component="WindowWrench20Filled" />
|
||||||
@@ -255,13 +275,25 @@ onMounted(async () => {
|
|||||||
</NSpace>
|
</NSpace>
|
||||||
</template>
|
</template>
|
||||||
<template #title>
|
<template #title>
|
||||||
<NButton text tag="a" @click="$router.push({ name: 'index' })">
|
<NButton
|
||||||
<NText strong style="font-size: 1.5rem; text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1)"> VTSURU </NText>
|
text
|
||||||
|
tag="a"
|
||||||
|
@click="$router.push({ name: 'index' })"
|
||||||
|
>
|
||||||
|
<NText
|
||||||
|
strong
|
||||||
|
style="font-size: 1.5rem; text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1)"
|
||||||
|
>
|
||||||
|
VTSURU
|
||||||
|
</NText>
|
||||||
</NButton>
|
</NButton>
|
||||||
</template>
|
</template>
|
||||||
</NPageHeader>
|
</NPageHeader>
|
||||||
</NLayoutHeader>
|
</NLayoutHeader>
|
||||||
<NLayout has-sider style="height: calc(100vh - 50px)">
|
<NLayout
|
||||||
|
has-sider
|
||||||
|
style="height: calc(100vh - --vtsuru-header-height)"
|
||||||
|
>
|
||||||
<NLayoutSider
|
<NLayoutSider
|
||||||
ref="sider"
|
ref="sider"
|
||||||
show-trigger
|
show-trigger
|
||||||
@@ -270,11 +302,18 @@ onMounted(async () => {
|
|||||||
:collapsed-width="64"
|
:collapsed-width="64"
|
||||||
:width="180"
|
:width="180"
|
||||||
:native-scrollbar="false"
|
:native-scrollbar="false"
|
||||||
style="height: 100%"
|
style="height: calc(100vh - --vtsuru-header-height)"
|
||||||
>
|
>
|
||||||
<Transition>
|
<Transition>
|
||||||
<div v-if="userInfo?.streamerInfo" style="margin-top: 8px">
|
<div
|
||||||
<NSpace vertical justify="center" align="center">
|
v-if="userInfo?.streamerInfo"
|
||||||
|
style="margin-top: 8px"
|
||||||
|
>
|
||||||
|
<NSpace
|
||||||
|
vertical
|
||||||
|
justify="center"
|
||||||
|
align="center"
|
||||||
|
>
|
||||||
<NAvatar
|
<NAvatar
|
||||||
:src="userInfo.streamerInfo.faceUrl"
|
:src="userInfo.streamerInfo.faceUrl"
|
||||||
:img-props="{ referrerpolicy: 'no-referrer' }"
|
:img-props="{ referrerpolicy: 'no-referrer' }"
|
||||||
@@ -284,7 +323,10 @@ onMounted(async () => {
|
|||||||
boxShadow: isDarkMode ? 'rgb(195 192 192 / 35%) 0px 0px 8px' : '0 2px 3px rgba(0, 0, 0, 0.1)',
|
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>
|
||||||
{{ userInfo?.streamerInfo.name }}
|
{{ userInfo?.streamerInfo.name }}
|
||||||
</NText>
|
</NText>
|
||||||
@@ -298,13 +340,34 @@ onMounted(async () => {
|
|||||||
:collapsed-icon-size="22"
|
:collapsed-icon-size="22"
|
||||||
:options="menuOptions"
|
:options="menuOptions"
|
||||||
/>
|
/>
|
||||||
<NSpace v-if="width > 150" justify="center" align="center" vertical>
|
<NSpace
|
||||||
|
v-if="width > 150"
|
||||||
|
justify="center"
|
||||||
|
align="center"
|
||||||
|
vertical
|
||||||
|
>
|
||||||
<NText depth="3">
|
<NText depth="3">
|
||||||
有更多功能建议请
|
有更多功能建议请
|
||||||
<NButton text type="info" tag="a" href="/feedback" target="_blank"> 反馈 </NButton>
|
<NButton
|
||||||
|
text
|
||||||
|
type="info"
|
||||||
|
tag="a"
|
||||||
|
href="/feedback"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
反馈
|
||||||
|
</NButton>
|
||||||
</NText>
|
</NText>
|
||||||
<NText depth="3">
|
<NText depth="3">
|
||||||
<NButton text type="info" tag="a" href="/about" target="_blank"> 关于本站 </NButton>
|
<NButton
|
||||||
|
text
|
||||||
|
type="info"
|
||||||
|
tag="a"
|
||||||
|
href="/about"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
关于本站
|
||||||
|
</NButton>
|
||||||
</NText>
|
</NText>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
</NLayoutSider>
|
</NLayoutSider>
|
||||||
@@ -313,9 +376,16 @@ onMounted(async () => {
|
|||||||
class="viewer-page-content"
|
class="viewer-page-content"
|
||||||
:style="`box-shadow:${isDarkMode ? 'rgb(28 28 28 / 9%) 5px 5px 6px inset, rgba(139, 139, 139, 0.09) -5px -5px 6px inset' : 'inset 5px 5px 6px #8b8b8b17, inset -5px -5px 6px #8b8b8b17;'}`"
|
:style="`box-shadow:${isDarkMode ? 'rgb(28 28 28 / 9%) 5px 5px 6px inset, rgba(139, 139, 139, 0.09) -5px -5px 6px inset' : 'inset 5px 5px 6px #8b8b8b17, inset -5px -5px 6px #8b8b8b17;'}`"
|
||||||
>
|
>
|
||||||
<RouterView v-if="userInfo" v-slot="{ Component }">
|
<RouterView
|
||||||
|
v-if="userInfo"
|
||||||
|
v-slot="{ Component }"
|
||||||
|
>
|
||||||
<KeepAlive>
|
<KeepAlive>
|
||||||
<component :is="Component" :bili-info="biliUserInfo" :user-info="userInfo" />
|
<component
|
||||||
|
:is="Component"
|
||||||
|
:bili-info="biliUserInfo"
|
||||||
|
:user-info="userInfo"
|
||||||
|
/>
|
||||||
</KeepAlive>
|
</KeepAlive>
|
||||||
</RouterView>
|
</RouterView>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
@@ -326,7 +396,10 @@ onMounted(async () => {
|
|||||||
</NLayout>
|
</NLayout>
|
||||||
</NLayout>
|
</NLayout>
|
||||||
</NLayout>
|
</NLayout>
|
||||||
<NModal v-model:show="registerAndLoginModalVisiable" style="width: 500px; max-width: 90vw">
|
<NModal
|
||||||
|
v-model:show="registerAndLoginModalVisiable"
|
||||||
|
style="width: 500px; max-width: 90vw"
|
||||||
|
>
|
||||||
<div>
|
<div>
|
||||||
<RegisterAndLogin />
|
<RegisterAndLogin />
|
||||||
</div>
|
</div>
|
||||||
@@ -337,7 +410,8 @@ onMounted(async () => {
|
|||||||
.viewer-page-content{
|
.viewer-page-content{
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border-radius: 18px;
|
border-radius: 18px;
|
||||||
padding: 15px;
|
padding: var(--vtsuru-content-padding);
|
||||||
|
height: calc(100vh - var(--vtsuru-header-height));
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|||||||
@@ -1,6 +1,30 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { useAccount } from '@/api/account';
|
||||||
|
import { useWebFetcher } from '@/store/useWebFetcher';
|
||||||
|
import {fetch} from "@tauri-apps/plugin-http";
|
||||||
|
import { NFlex } from 'naive-ui';
|
||||||
|
|
||||||
|
const webfetcher = useWebFetcher();
|
||||||
|
const accountInfo = useAccount();
|
||||||
|
|
||||||
|
function initWebfetcher() {
|
||||||
|
webfetcher.Start();
|
||||||
|
webfetcher.signalRClient?.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (accountInfo.value.id) {
|
||||||
|
initWebfetcher();
|
||||||
|
console.info('WebFetcher started')
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
1
|
<NFlex v-if="!accountInfo.id">
|
||||||
|
<RegisterAndLogin />
|
||||||
|
</NFlex>
|
||||||
|
<NFlex v-else>
|
||||||
|
{{ webfetcher }}
|
||||||
|
</NFlex>
|
||||||
</template>
|
</template>
|
||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
SaveSetting,
|
SaveSetting,
|
||||||
downloadConfigDirect,
|
downloadConfigDirect,
|
||||||
useAccount,
|
useAccount,
|
||||||
} from '@/api/account'
|
} from '@/api/account';
|
||||||
import {
|
import {
|
||||||
FunctionTypes,
|
FunctionTypes,
|
||||||
ResponseUserIndexModel,
|
ResponseUserIndexModel,
|
||||||
@@ -17,19 +17,20 @@ import {
|
|||||||
SongRequestOption,
|
SongRequestOption,
|
||||||
SongsInfo,
|
SongsInfo,
|
||||||
VideoCollectVideo,
|
VideoCollectVideo,
|
||||||
} from '@/api/api-models'
|
} from '@/api/api-models';
|
||||||
import { QueryGetAPI, QueryPostAPI } from '@/api/query'
|
import { QueryGetAPI, QueryPostAPI } from '@/api/query';
|
||||||
import DynamicForm from '@/components/DynamicForm.vue'
|
import DynamicForm from '@/components/DynamicForm.vue';
|
||||||
import SimpleVideoCard from '@/components/SimpleVideoCard.vue'
|
import SimpleVideoCard from '@/components/SimpleVideoCard.vue';
|
||||||
import { TemplateConfig } from '@/data/VTsuruTypes'
|
|
||||||
import {
|
import {
|
||||||
FETCH_API,
|
FETCH_API,
|
||||||
IndexTemplateMap,
|
IndexTemplateMap,
|
||||||
ScheduleTemplateMap,
|
ScheduleTemplateMap,
|
||||||
SongListTemplateMap,
|
SongListTemplateMap,
|
||||||
|
TemplateMapType,
|
||||||
USER_INDEX_API_URL,
|
USER_INDEX_API_URL,
|
||||||
} from '@/data/constants'
|
} from '@/data/constants';
|
||||||
import { Delete24Regular } from '@vicons/fluent'
|
import { ConfigItemDefinition } from '@/data/VTsuruTypes';
|
||||||
|
import { Delete24Regular } from '@vicons/fluent';
|
||||||
import {
|
import {
|
||||||
NAlert,
|
NAlert,
|
||||||
NButton,
|
NButton,
|
||||||
@@ -55,23 +56,23 @@ import {
|
|||||||
NTooltip,
|
NTooltip,
|
||||||
SelectOption,
|
SelectOption,
|
||||||
useMessage,
|
useMessage,
|
||||||
} from 'naive-ui'
|
} from 'naive-ui';
|
||||||
import { computed, h, nextTick, onActivated, onMounted, ref, watch } from 'vue'
|
import { computed, h, nextTick, onActivated, onMounted, ref, watch } from 'vue';
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router';
|
||||||
|
|
||||||
interface TemplateDefineTypes {
|
interface TemplateDefineTypes {
|
||||||
TemplateMap: { [name: string]: { name: string; compoent: any } }
|
TemplateMap: TemplateMapType;
|
||||||
Options: SelectOption[]
|
Options: SelectOption[];
|
||||||
Data: any
|
Data: any;
|
||||||
Selected: string
|
Selected: string;
|
||||||
Config?: any | undefined
|
Config?: any | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const accountInfo = useAccount()
|
const accountInfo = useAccount();
|
||||||
const message = useMessage()
|
const message = useMessage();
|
||||||
const route = useRoute()
|
const route = useRoute();
|
||||||
|
|
||||||
const isSaving = ref(false)
|
const isSaving = ref(false);
|
||||||
|
|
||||||
const templates = ref({
|
const templates = ref({
|
||||||
index: {
|
index: {
|
||||||
@@ -218,7 +219,7 @@ const templates = ref({
|
|||||||
] as SongsInfo[],
|
] as SongsInfo[],
|
||||||
Selected: accountInfo.value?.settings.songListTemplate ?? '',
|
Selected: accountInfo.value?.settings.songListTemplate ?? '',
|
||||||
},
|
},
|
||||||
} as { [type: string]: TemplateDefineTypes })
|
} as { [type: string]: TemplateDefineTypes; });
|
||||||
|
|
||||||
const templateOptions = [
|
const templateOptions = [
|
||||||
{
|
{
|
||||||
@@ -233,302 +234,306 @@ const templateOptions = [
|
|||||||
label: '日程表',
|
label: '日程表',
|
||||||
value: 'schedule',
|
value: 'schedule',
|
||||||
},
|
},
|
||||||
] as SelectOption[]
|
] as SelectOption[];
|
||||||
const selectedOption = ref(route.query.template?.toString() ?? 'index')
|
const selectedOption = ref(route.query.template?.toString() ?? 'index');
|
||||||
const selectedTab = ref(route.query.setting?.toString() ?? 'general')
|
const selectedTab = ref(route.query.setting?.toString() ?? 'general');
|
||||||
|
|
||||||
const dynamicConfigRef = ref()
|
const dynamicConfigRef = shallowRef();
|
||||||
const selectedTemplateData = computed(() => templates.value[selectedOption.value])
|
const selectedTemplateData = computed(() => templates.value[selectedOption.value]);
|
||||||
|
const selectedTemplate = computed(() => {
|
||||||
|
return templates.value[selectedOption.value].TemplateMap[selectedTemplateData.value.Selected];
|
||||||
|
});
|
||||||
const selectedComponent = computed(
|
const selectedComponent = computed(
|
||||||
() => selectedTemplateData.value?.TemplateMap[selectedTemplateData.value.Selected].compoent,
|
() => selectedTemplate.value.component,
|
||||||
)
|
);
|
||||||
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 ConfigItemDefinition[];
|
||||||
}
|
}
|
||||||
return undefined
|
return undefined;
|
||||||
})
|
});
|
||||||
const biliUserInfo = ref()
|
const biliUserInfo = ref();
|
||||||
const settingModalVisiable = ref(false)
|
const settingModalVisiable = ref(false);
|
||||||
const showAddVideoModal = ref(false)
|
const showAddVideoModal = ref(false);
|
||||||
const showAddLinkModal = ref(false)
|
const showAddLinkModal = ref(false);
|
||||||
|
|
||||||
const indexDisplayInfo = ref<ResponseUserIndexModel>()
|
const indexDisplayInfo = ref<ResponseUserIndexModel>();
|
||||||
const addVideoUrl = ref('')
|
const addVideoUrl = ref('');
|
||||||
const isLoading = ref(false)
|
const isLoading = ref(false);
|
||||||
const addLinkName = ref('')
|
const addLinkName = ref('');
|
||||||
const addLinkUrl = ref('')
|
const addLinkUrl = ref('');
|
||||||
const linkKey = ref(0)
|
const linkKey = ref(0);
|
||||||
|
|
||||||
async function RequestBiliUserData() {
|
async function RequestBiliUserData() {
|
||||||
await fetch(FETCH_API + `https://workers.vrp.moe/api/bilibili/user-info/10021741`).then(async (respone) => {
|
await fetch(FETCH_API + `https://workers.vrp.moe/api/bilibili/user-info/10021741`).then(async (respone) => {
|
||||||
const 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 {
|
||||||
throw new Error('Bili User API Error: ' + data.message)
|
throw new Error('Bili User API Error: ' + data.message);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
async function SaveComboGroupSetting(
|
async function SaveComboGroupSetting(
|
||||||
value: (string | number)[],
|
value: (string | number)[],
|
||||||
meta: { actionType: 'check' | 'uncheck'; 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')
|
||||||
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('保存成功')
|
//message.success('保存成功')
|
||||||
} else {
|
} else {
|
||||||
message.error('修改失败')
|
message.error('修改失败');
|
||||||
if (accountInfo.value) {
|
if (accountInfo.value) {
|
||||||
accountInfo.value.settings.enableFunctions = accountInfo.value.settings.enableFunctions.filter(
|
accountInfo.value.settings.enableFunctions = accountInfo.value.settings.enableFunctions.filter(
|
||||||
(f) => f != (meta.value as FunctionTypes),
|
(f) => f != (meta.value as FunctionTypes),
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
message.error('修改失败: ' + err)
|
message.error('修改失败: ' + err);
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
isSaving.value = false
|
isSaving.value = false;
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function SaveComboSetting() {
|
async function SaveComboSetting() {
|
||||||
isSaving.value = true
|
isSaving.value = true;
|
||||||
if (accountInfo.value) {
|
if (accountInfo.value) {
|
||||||
//UpdateEnableFunction(meta.value as FunctionTypes, meta.actionType == 'check')
|
//UpdateEnableFunction(meta.value as FunctionTypes, meta.actionType == 'check')
|
||||||
await SaveAccountSettings()
|
await SaveAccountSettings()
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (data.code == 200) {
|
if (data.code == 200) {
|
||||||
message.success('已保存')
|
message.success('已保存');
|
||||||
} else {
|
} else {
|
||||||
message.error('修改失败')
|
message.error('修改失败');
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
message.error('修改失败: ' + err)
|
message.error('修改失败: ' + err);
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
isSaving.value = false
|
isSaving.value = false;
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function SaveTemplateSetting() {
|
async function SaveTemplateSetting() {
|
||||||
if (accountInfo.value) {
|
if (accountInfo.value) {
|
||||||
switch (selectedOption.value) {
|
switch (selectedOption.value) {
|
||||||
case 'index': {
|
case 'index': {
|
||||||
accountInfo.value.settings.indexTemplate = selectedTemplateData.value.Selected ?? ''
|
accountInfo.value.settings.indexTemplate = selectedTemplateData.value.Selected ?? '';
|
||||||
break
|
break;
|
||||||
}
|
}
|
||||||
case 'songlist': {
|
case 'songlist': {
|
||||||
accountInfo.value.settings.songListTemplate = selectedTemplateData.value.Selected ?? ''
|
accountInfo.value.settings.songListTemplate = selectedTemplateData.value.Selected ?? '';
|
||||||
break
|
break;
|
||||||
}
|
}
|
||||||
case 'schedule': {
|
case 'schedule': {
|
||||||
accountInfo.value.settings.scheduleTemplate = selectedTemplateData.value.Selected ?? ''
|
accountInfo.value.settings.scheduleTemplate = selectedTemplateData.value.Selected ?? '';
|
||||||
break
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await SaveComboSetting()
|
await SaveComboSetting();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function updateIndexSettings() {
|
async function updateIndexSettings() {
|
||||||
await QueryPostAPI(USER_INDEX_API_URL + 'update-setting', accountInfo.value.settings.index)
|
await QueryPostAPI(USER_INDEX_API_URL + 'update-setting', accountInfo.value.settings.index)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (data.code == 200) {
|
if (data.code == 200) {
|
||||||
message.success('已保存')
|
message.success('已保存');
|
||||||
} else {
|
} else {
|
||||||
message.error('保存失败: ' + data.message)
|
message.error('保存失败: ' + data.message);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
message.error('保存失败: ' + err)
|
message.error('保存失败: ' + err);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
async function addVideo() {
|
async function addVideo() {
|
||||||
if (!addVideoUrl.value) {
|
if (!addVideoUrl.value) {
|
||||||
message.error('请输入视频链接')
|
message.error('请输入视频链接');
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
isLoading.value = true
|
isLoading.value = true;
|
||||||
await QueryGetAPI<VideoCollectVideo>(USER_INDEX_API_URL + 'add-video', {
|
await QueryGetAPI<VideoCollectVideo>(USER_INDEX_API_URL + 'add-video', {
|
||||||
video: addVideoUrl.value,
|
video: addVideoUrl.value,
|
||||||
})
|
})
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (data.code == 200) {
|
if (data.code == 200) {
|
||||||
message.success('已添加')
|
message.success('已添加');
|
||||||
indexDisplayInfo.value?.videos.push(data.data)
|
indexDisplayInfo.value?.videos.push(data.data);
|
||||||
accountInfo.value?.settings.index.videos.push(data.data.id)
|
accountInfo.value?.settings.index.videos.push(data.data.id);
|
||||||
addVideoUrl.value = ''
|
addVideoUrl.value = '';
|
||||||
} else {
|
} else {
|
||||||
message.error('保存失败: ' + data.message)
|
message.error('保存失败: ' + data.message);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
message.error('保存失败: ' + err)
|
message.error('保存失败: ' + err);
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
isLoading.value = false
|
isLoading.value = false;
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
async function removeVideo(id: string) {
|
async function removeVideo(id: string) {
|
||||||
isLoading.value = true
|
isLoading.value = true;
|
||||||
await QueryGetAPI<VideoCollectVideo>(USER_INDEX_API_URL + 'del-video', {
|
await QueryGetAPI<VideoCollectVideo>(USER_INDEX_API_URL + 'del-video', {
|
||||||
video: id,
|
video: id,
|
||||||
})
|
})
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (data.code == 200) {
|
if (data.code == 200) {
|
||||||
message.success('已删除')
|
message.success('已删除');
|
||||||
if (indexDisplayInfo.value) {
|
if (indexDisplayInfo.value) {
|
||||||
indexDisplayInfo.value.videos = indexDisplayInfo.value?.videos.filter((v) => v.id != id)
|
indexDisplayInfo.value.videos = indexDisplayInfo.value?.videos.filter((v) => v.id != id);
|
||||||
}
|
}
|
||||||
|
|
||||||
accountInfo.value.settings.index.videos = accountInfo.value.settings.index.videos.filter((v) => v != id)
|
accountInfo.value.settings.index.videos = accountInfo.value.settings.index.videos.filter((v) => v != id);
|
||||||
} else {
|
} else {
|
||||||
message.error('删除失败: ' + data.message)
|
message.error('删除失败: ' + data.message);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
message.error('删除失败: ' + err)
|
message.error('删除失败: ' + err);
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
isLoading.value = false
|
isLoading.value = false;
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
async function addLink() {
|
async function addLink() {
|
||||||
if (!addLinkName.value || !addLinkUrl.value) {
|
if (!addLinkName.value || !addLinkUrl.value) {
|
||||||
message.error('请输入名称和链接')
|
message.error('请输入名称和链接');
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
new URL(addLinkUrl.value)
|
new URL(addLinkUrl.value);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
message.error('请输入正确的链接')
|
message.error('请输入正确的链接');
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
if (Object.keys(accountInfo.value.settings.index.links).includes(addLinkName.value)) {
|
if (Object.keys(accountInfo.value.settings.index.links).includes(addLinkName.value)) {
|
||||||
message.error(addLinkName.value + '已存在')
|
message.error(addLinkName.value + '已存在');
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
accountInfo.value.settings.index.links[addLinkName.value] = addLinkUrl.value
|
accountInfo.value.settings.index.links[addLinkName.value] = addLinkUrl.value;
|
||||||
await updateIndexSettings()
|
await updateIndexSettings();
|
||||||
addLinkName.value = ''
|
addLinkName.value = '';
|
||||||
addLinkUrl.value = ''
|
addLinkUrl.value = '';
|
||||||
location.reload()
|
location.reload();
|
||||||
}
|
}
|
||||||
async function removeLink(name: string) {
|
async function removeLink(name: string) {
|
||||||
delete accountInfo.value.settings.index.links[name]
|
delete accountInfo.value.settings.index.links[name];
|
||||||
await updateIndexSettings()
|
await updateIndexSettings();
|
||||||
|
|
||||||
location.reload()
|
location.reload();
|
||||||
}
|
}
|
||||||
async function onOpenTemplateSettings() {
|
async function onOpenTemplateSettings() {
|
||||||
settingModalVisiable.value = true
|
settingModalVisiable.value = true;
|
||||||
nextTick(async () => {
|
nextTick(async () => {
|
||||||
await getTemplateConfig()
|
await getTemplateConfig();
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
async function getTemplateConfig() {
|
async function getTemplateConfig() {
|
||||||
if (selectedTemplateConfig.value && !selectedTemplateData.value.Config) {
|
if (selectedTemplate.value && !selectedTemplateData.value.Config && selectedTemplate.value.settingName) {
|
||||||
await downloadConfigDirect(selectedTemplateConfig.value.name)
|
const name = selectedTemplate.value.settingName;
|
||||||
|
await downloadConfigDirect(name)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (data.code == 200) {
|
if (data.code == 200) {
|
||||||
message.success('已获取配置文件')
|
message.success('已获取配置文件');
|
||||||
console.log(`已获取模板配置: ${selectedTemplateConfig.value?.name}`)
|
console.log(`已获取模板配置: ${name}`);
|
||||||
selectedTemplateData.value.Config = JSON.parse(data.data)
|
selectedTemplateData.value.Config = JSON.parse(data.data);
|
||||||
} else if (data.code == 404) {
|
} else if (data.code == 404) {
|
||||||
//message.error(`未找到名为 ${name} 的配置文件`)
|
//message.error(`未找到名为 ${name} 的配置文件`)
|
||||||
console.error(`未找到名为 ${selectedTemplateConfig.value?.name} 的配置文件`)
|
console.error(`未找到名为 ${name} 的配置文件`);
|
||||||
selectedTemplateData.value.Config = dynamicConfigRef.value.DefaultConfig
|
selectedTemplateData.value.Config = dynamicConfigRef.value.DefaultConfig;
|
||||||
} else {
|
} else {
|
||||||
message.error('获取失败: ' + data.message)
|
message.error('获取失败: ' + data.message);
|
||||||
console.error('获取失败: ' + data.message)
|
console.error('获取失败: ' + data.message);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
message.error(err)
|
message.error(err);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const buttonGroup = computed(() => {
|
const buttonGroup = computed(() => {
|
||||||
return h(NSpace, () => [
|
return h(NSpace, () => [
|
||||||
h(NButton, { type: 'primary', onClick: () => SaveTemplateSetting() }, () => '设为展示模板'),
|
h(NButton, { type: 'primary', onClick: () => SaveTemplateSetting() }, () => '设为展示模板'),
|
||||||
h(NButton, { type: 'info', onClick: onOpenTemplateSettings }, () => '模板设置'),
|
h(NButton, { type: 'info', onClick: onOpenTemplateSettings, disabled: !selectedTemplate.value.settingName }, () => '模板设置'),
|
||||||
])
|
]);
|
||||||
})
|
});
|
||||||
|
|
||||||
function unblockBiliUser(id: number) {
|
function unblockBiliUser(id: number) {
|
||||||
DelBiliBlackList(id)
|
DelBiliBlackList(id)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (data.code == 200) {
|
if (data.code == 200) {
|
||||||
message.success(`[${id}] 已移除黑名单`)
|
message.success(`[${id}] 已移除黑名单`);
|
||||||
if (accountInfo.value) {
|
if (accountInfo.value) {
|
||||||
delete accountInfo.value.biliBlackList[id]
|
delete accountInfo.value.biliBlackList[id];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
message.error(data.message)
|
message.error(data.message);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
message.error(err)
|
message.error(err);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
function unblockUser(id: number) {
|
function unblockUser(id: number) {
|
||||||
DelBlackList(id)
|
DelBlackList(id)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (data.code == 200) {
|
if (data.code == 200) {
|
||||||
message.success(`[${id}] 已移除黑名单`)
|
message.success(`[${id}] 已移除黑名单`);
|
||||||
if (accountInfo.value) {
|
if (accountInfo.value) {
|
||||||
accountInfo.value.blackList = accountInfo.value.blackList.filter((u) => u.id != id)
|
accountInfo.value.blackList = accountInfo.value.blackList.filter((u) => u.id != id);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
message.error(data.message)
|
message.error(data.message);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
message.error(err)
|
message.error(err);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
async function getIndexInfo() {
|
async function getIndexInfo() {
|
||||||
try {
|
try {
|
||||||
isLoading.value = true
|
isLoading.value = true;
|
||||||
const data = await QueryGetAPI<ResponseUserIndexModel>(USER_INDEX_API_URL + 'get', { id: accountInfo.value.id })
|
const data = await QueryGetAPI<ResponseUserIndexModel>(USER_INDEX_API_URL + 'get', { id: accountInfo.value.id });
|
||||||
if (data.code == 200) {
|
if (data.code == 200) {
|
||||||
return data.data
|
return data.data;
|
||||||
} else if (data.code != 404) {
|
} else if (data.code != 404) {
|
||||||
message?.error('无法获取数据: ' + data.message)
|
message?.error('无法获取数据: ' + data.message);
|
||||||
return undefined
|
return undefined;
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
message?.error('无法获取数据: ' + err)
|
message?.error('无法获取数据: ' + err);
|
||||||
return undefined
|
return undefined;
|
||||||
} finally {
|
} finally {
|
||||||
isLoading.value = false
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function updateUserIndexSettings() {
|
async function updateUserIndexSettings() {
|
||||||
await SaveSetting('Index', accountInfo.value.settings.index)
|
await SaveSetting('Index', accountInfo.value.settings.index);
|
||||||
message.success('已保存')
|
message.success('已保存');
|
||||||
}
|
}
|
||||||
|
|
||||||
onActivated(() => {
|
onActivated(() => {
|
||||||
if (route.query.tab) {
|
if (route.query.tab) {
|
||||||
selectedTab.value = route.query.setting?.toString() ?? 'general'
|
selectedTab.value = route.query.setting?.toString() ?? 'general';
|
||||||
}
|
}
|
||||||
if (route.query.template) {
|
if (route.query.template) {
|
||||||
selectedOption.value = route.query.template.toString()
|
selectedOption.value = route.query.template.toString();
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await RequestBiliUserData()
|
await RequestBiliUserData();
|
||||||
indexDisplayInfo.value = await getIndexInfo()
|
indexDisplayInfo.value = await getIndexInfo();
|
||||||
accountInfo.value.settings.index.allowDisplayInIndex = accountInfo.value.settings.index.allowDisplayInIndex ?? true
|
accountInfo.value.settings.index.allowDisplayInIndex = accountInfo.value.settings.index.allowDisplayInIndex ?? true;
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -829,10 +834,9 @@ onMounted(async () => {
|
|||||||
v-model:show="settingModalVisiable"
|
v-model:show="settingModalVisiable"
|
||||||
preset="card"
|
preset="card"
|
||||||
closable
|
closable
|
||||||
style="width: 600px; max-width: 90vw"
|
style="width: 1200px; max-width: 90vw"
|
||||||
title="模板设置"
|
title="模板设置"
|
||||||
>
|
>
|
||||||
只是测试, 没用
|
|
||||||
<NSpin
|
<NSpin
|
||||||
v-if="!selectedTemplateData.Config"
|
v-if="!selectedTemplateData.Config"
|
||||||
show
|
show
|
||||||
@@ -840,6 +844,7 @@ onMounted(async () => {
|
|||||||
<DynamicForm
|
<DynamicForm
|
||||||
v-else
|
v-else
|
||||||
:key="selectedTemplateData.Selected"
|
:key="selectedTemplateData.Selected"
|
||||||
|
:name="selectedTemplateData.TemplateMap[selectedTemplateData.Selected].settingName"
|
||||||
:config-data="selectedTemplateData.Config"
|
:config-data="selectedTemplateData.Config"
|
||||||
:config="selectedTemplateConfig"
|
:config="selectedTemplateConfig"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<NSpin v-if="isLoading" show />
|
<NSpin
|
||||||
|
v-if="isLoading"
|
||||||
|
show
|
||||||
|
/>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<NDivider style="margin: 16px 0 16px 0" title-placement="left">
|
<NDivider
|
||||||
|
style="margin: 16px 0 16px 0"
|
||||||
|
title-placement="left"
|
||||||
|
>
|
||||||
订阅链接
|
订阅链接
|
||||||
<NTooltip>
|
<NTooltip>
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
@@ -14,15 +20,26 @@
|
|||||||
</NDivider>
|
</NDivider>
|
||||||
<NFlex align="center">
|
<NFlex align="center">
|
||||||
<NInputGroup style="max-width: 400px;">
|
<NInputGroup style="max-width: 400px;">
|
||||||
<NInput :value="`${SCHEDULE_API_URL}${userInfo?.id}.ics`" readonly />
|
<NInput
|
||||||
<NButton secondary @click="copyToClipboard(`${SCHEDULE_API_URL}${userInfo?.id}.ics`)">
|
:value="`${SCHEDULE_API_URL}${userInfo?.id}.ics`"
|
||||||
|
readonly
|
||||||
|
/>
|
||||||
|
<NButton
|
||||||
|
secondary
|
||||||
|
@click="copyToClipboard(`${SCHEDULE_API_URL}${userInfo?.id}.ics`)"
|
||||||
|
>
|
||||||
复制
|
复制
|
||||||
</NButton>
|
</NButton>
|
||||||
</NInputGroup>
|
</NInputGroup>
|
||||||
</NFlex>
|
</NFlex>
|
||||||
<NDivider />
|
<NDivider />
|
||||||
<component :is="ScheduleTemplateMap[componentType ?? ''].compoent" :bili-info="biliInfo"
|
<component
|
||||||
:user-info="userInfo" :data="currentData" v-bind="$attrs" />
|
:is="ScheduleTemplateMap[componentType ?? ''].component"
|
||||||
|
:bili-info="biliInfo"
|
||||||
|
:user-info="userInfo"
|
||||||
|
:data="currentData"
|
||||||
|
v-bind="$attrs"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -4,10 +4,10 @@
|
|||||||
show
|
show
|
||||||
/>
|
/>
|
||||||
<component
|
<component
|
||||||
:is="SongListTemplateMap[componentType ?? '']?.compoent"
|
:is="selectedTemplate?.component"
|
||||||
v-else
|
v-else
|
||||||
ref="dynamicConfigRef"
|
ref="dynamicConfigRef"
|
||||||
:config="selectedTemplateConfig?.name ? currentConfig : undefined"
|
:config="selectedTemplate?.settingName ? currentConfig : undefined"
|
||||||
:user-info="userInfo"
|
:user-info="userInfo"
|
||||||
:bili-info="biliInfo"
|
:bili-info="biliInfo"
|
||||||
:data="currentData"
|
:data="currentData"
|
||||||
@@ -16,140 +16,168 @@
|
|||||||
v-bind="$attrs"
|
v-bind="$attrs"
|
||||||
@request-song="requestSong"
|
@request-song="requestSong"
|
||||||
/>
|
/>
|
||||||
|
<NButton
|
||||||
|
v-if="selectedTemplate?.settingName && userInfo?.id == accountInfo.id"
|
||||||
|
type="info"
|
||||||
|
size="small"
|
||||||
|
style="position: absolute; right: 32px; top: 20px; z-index: 1000; border: solid 3px #dfdfdf;"
|
||||||
|
@click="showSettingModal = true"
|
||||||
|
>
|
||||||
|
自定义
|
||||||
|
</NButton>
|
||||||
|
<NModal
|
||||||
|
v-model:show="showSettingModal"
|
||||||
|
style="max-width: 90vw; width: 800px;"
|
||||||
|
preset="card"
|
||||||
|
title="设置"
|
||||||
|
>
|
||||||
|
<DynamicForm
|
||||||
|
:name="selectedTemplate?.settingName"
|
||||||
|
:config-data="currentConfig"
|
||||||
|
:config="selectedTemplateConfig"
|
||||||
|
/>
|
||||||
|
</NModal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { DownloadConfig, downloadConfigDirect, useAccount } from '@/api/account'
|
import { DownloadConfig, useAccount } from '@/api/account';
|
||||||
import { Setting_LiveRequest, SongRequestInfo, SongsInfo, UserInfo } from '@/api/api-models'
|
import { Setting_LiveRequest, SongRequestInfo, SongsInfo, UserInfo } from '@/api/api-models';
|
||||||
import { QueryGetAPI, QueryPostAPIWithParams } from '@/api/query'
|
import { QueryGetAPI, QueryPostAPIWithParams } from '@/api/query';
|
||||||
import { TemplateConfig } from '@/data/VTsuruTypes'
|
import { SONG_API_URL, SONG_REQUEST_API_URL, SongListTemplateMap } from '@/data/constants';
|
||||||
import { SONG_API_URL, SONG_REQUEST_API_URL, SongListTemplateMap, VTSURU_API_URL } from '@/data/constants'
|
import { ConfigItemDefinition } from '@/data/VTsuruTypes';
|
||||||
import { useStorage } from '@vueuse/core'
|
import { useStorage } from '@vueuse/core';
|
||||||
import { addSeconds } from 'date-fns'
|
import { addSeconds } from 'date-fns';
|
||||||
import { NSpin, useMessage } from 'naive-ui'
|
import { NButton, NModal, NSpin, useMessage } from 'naive-ui';
|
||||||
import { computed, onMounted, ref, watch, watchEffect } from 'vue'
|
import { computed, onMounted, ref, watch } from 'vue';
|
||||||
|
|
||||||
const accountInfo = useAccount()
|
const accountInfo = useAccount();
|
||||||
const nextRequestTime = useStorage('SongList.NextRequestTime', new Date())
|
const nextRequestTime = useStorage('SongList.NextRequestTime', new Date());
|
||||||
|
|
||||||
const minRequestTime = 30
|
const minRequestTime = 30;
|
||||||
|
const showSettingModal = ref(false);
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
biliInfo: any | undefined
|
biliInfo: any | undefined;
|
||||||
userInfo: UserInfo | undefined
|
userInfo: UserInfo | undefined;
|
||||||
template?: string | undefined
|
template?: string | undefined;
|
||||||
fakeData?: SongsInfo[]
|
fakeData?: SongsInfo[];
|
||||||
}>()
|
}>();
|
||||||
|
|
||||||
const componentType = computed(() => {
|
const componentType = computed(() => {
|
||||||
return props.template ?? props.userInfo?.extra?.templateTypes['songlist']?.toLowerCase()
|
return props.template ?? props.userInfo?.extra?.templateTypes['songlist']?.toLowerCase();
|
||||||
})
|
});
|
||||||
const currentData = ref<SongsInfo[]>()
|
const currentData = ref<SongsInfo[]>();
|
||||||
const dynamicConfigRef = ref()
|
const dynamicConfigRef = ref();
|
||||||
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 ConfigItemDefinition[];
|
||||||
}
|
}
|
||||||
return undefined
|
return undefined;
|
||||||
})
|
});
|
||||||
const currentConfig = ref()
|
const selectedTemplate = computed(() => {
|
||||||
|
if (componentType.value) {
|
||||||
|
return SongListTemplateMap[componentType.value];
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
const currentConfig = ref();
|
||||||
watch(
|
watch(
|
||||||
() => dynamicConfigRef,
|
() => dynamicConfigRef,
|
||||||
() => {
|
() => {
|
||||||
getConfig()
|
getConfig();
|
||||||
},
|
},
|
||||||
)
|
);
|
||||||
|
|
||||||
const isLoading = ref(true)
|
const isLoading = ref(true);
|
||||||
const message = useMessage()
|
const message = useMessage();
|
||||||
|
|
||||||
const errMessage = ref('')
|
const errMessage = ref('');
|
||||||
const songsActive = ref<SongRequestInfo[]>([])
|
const songsActive = ref<SongRequestInfo[]>([]);
|
||||||
const settings = ref<Setting_LiveRequest>({} as Setting_LiveRequest)
|
const settings = ref<Setting_LiveRequest>({} as Setting_LiveRequest);
|
||||||
|
|
||||||
async function getSongRequestInfo() {
|
async function getSongRequestInfo() {
|
||||||
try {
|
try {
|
||||||
const data = await QueryGetAPI<{ songs: SongRequestInfo[]; setting: Setting_LiveRequest }>(
|
const data = await QueryGetAPI<{ songs: SongRequestInfo[]; setting: Setting_LiveRequest; }>(
|
||||||
SONG_REQUEST_API_URL + 'get-active-and-settings',
|
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;
|
||||||
}
|
}
|
||||||
} catch (err) { }
|
} catch (err) { }
|
||||||
return {} as { songs: SongRequestInfo[]; setting: Setting_LiveRequest }
|
return {} as { songs: SongRequestInfo[]; setting: Setting_LiveRequest; };
|
||||||
}
|
}
|
||||||
async function getSongs() {
|
async function getSongs() {
|
||||||
isLoading.value = true
|
isLoading.value = true;
|
||||||
await QueryGetAPI<SongsInfo[]>(SONG_API_URL + 'get', {
|
await QueryGetAPI<SongsInfo[]>(SONG_API_URL + 'get', {
|
||||||
id: props.userInfo?.id,
|
id: props.userInfo?.id,
|
||||||
})
|
})
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (data.code == 200) {
|
if (data.code == 200) {
|
||||||
currentData.value = data.data
|
currentData.value = data.data;
|
||||||
} else {
|
} else {
|
||||||
errMessage.value = data.message
|
errMessage.value = data.message;
|
||||||
message.error('加载歌单失败: ' + data.message)
|
message.error('加载歌单失败: ' + data.message);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
message.error('加载失败: ' + err)
|
message.error('加载失败: ' + err);
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
isLoading.value = false
|
isLoading.value = false;
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
async function getConfig() {
|
async function getConfig() {
|
||||||
if(!selectedTemplateConfig.value) return
|
if (!selectedTemplateConfig.value || !selectedTemplate.value!.settingName) return;
|
||||||
isLoading.value = true
|
isLoading.value = true;
|
||||||
await DownloadConfig(selectedTemplateConfig.value!.name)
|
await DownloadConfig(selectedTemplate.value!.settingName, props.userInfo?.id)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (data.msg) {
|
if (data.msg) {
|
||||||
message.error('加载失败: ' + data.msg)
|
message.error('加载失败: ' + data.msg);
|
||||||
} else {
|
} else {
|
||||||
currentConfig.value = data.data
|
currentConfig.value = data.data;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
message.error('加载失败: ' + err)
|
message.error('加载失败: ' + err);
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
isLoading.value = false
|
isLoading.value = false;
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
async function requestSong(song: SongsInfo) {
|
async function requestSong(song: SongsInfo) {
|
||||||
if (song.options || !settings.value.allowFromWeb || (settings.value.allowFromWeb && !settings.value.allowAnonymousFromWeb)) {
|
if (song.options || !settings.value.allowFromWeb || (settings.value.allowFromWeb && !settings.value.allowAnonymousFromWeb)) {
|
||||||
navigator.clipboard.writeText(`${settings.value.orderPrefix} ${song.name}`)
|
navigator.clipboard.writeText(`${settings.value.orderPrefix} ${song.name}`);
|
||||||
if (!settings.value.allowAnonymousFromWeb) {
|
if (!settings.value.allowAnonymousFromWeb) {
|
||||||
message.warning('主播不允许匿名点歌, 需要从网页点歌的话请注册登录, 点歌弹幕已复制到剪切板')
|
message.warning('主播不允许匿名点歌, 需要从网页点歌的话请注册登录, 点歌弹幕已复制到剪切板');
|
||||||
}
|
}
|
||||||
else if (!accountInfo.value.id) {
|
else if (!accountInfo.value.id) {
|
||||||
message.warning('要从网页点歌请先登录, 点歌弹幕已复制到剪切板')
|
message.warning('要从网页点歌请先登录, 点歌弹幕已复制到剪切板');
|
||||||
} else {
|
} else {
|
||||||
message.success('复制成功')
|
message.success('复制成功');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (props.userInfo) {
|
if (props.userInfo) {
|
||||||
if (!accountInfo.value.id && nextRequestTime.value > new Date()) {
|
if (!accountInfo.value.id && nextRequestTime.value > new Date()) {
|
||||||
message.warning('距离点歌冷却还有' + (nextRequestTime.value.getTime() - new Date().getTime()) / 1000 + '秒')
|
message.warning('距离点歌冷却还有' + (nextRequestTime.value.getTime() - new Date().getTime()) / 1000 + '秒');
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const data = await QueryPostAPIWithParams(SONG_REQUEST_API_URL + 'add-from-web', {
|
const data = await QueryPostAPIWithParams(SONG_REQUEST_API_URL + 'add-from-web', {
|
||||||
target: props.userInfo?.id,
|
target: props.userInfo?.id,
|
||||||
song: song.key,
|
song: song.key,
|
||||||
})
|
});
|
||||||
|
|
||||||
if (data.code == 200) {
|
if (data.code == 200) {
|
||||||
message.success('点歌成功')
|
message.success('点歌成功');
|
||||||
nextRequestTime.value = addSeconds(new Date(), minRequestTime)
|
nextRequestTime.value = addSeconds(new Date(), minRequestTime);
|
||||||
} else {
|
} else {
|
||||||
message.error('点歌失败: ' + data.message)
|
message.error('点歌失败: ' + data.message);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
message.error('点歌失败: ' + err)
|
message.error('点歌失败: ' + err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -158,22 +186,22 @@ async function requestSong(song: SongsInfo) {
|
|||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
if (!props.fakeData) {
|
if (!props.fakeData) {
|
||||||
try {
|
try {
|
||||||
await getSongs()
|
await getSongs();
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
const r = await getSongRequestInfo()
|
const r = await getSongRequestInfo();
|
||||||
if (r) {
|
if (r) {
|
||||||
songsActive.value = r.songs
|
songsActive.value = r.songs;
|
||||||
settings.value = r.setting
|
settings.value = r.setting;
|
||||||
}
|
}
|
||||||
await getConfig()
|
await getConfig();
|
||||||
}, 300)
|
}, 300);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
message.error('加载失败: ' + err)
|
message.error('加载失败: ' + err);
|
||||||
console.error(err)
|
console.error(err);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
currentData.value = props.fakeData
|
currentData.value = props.fakeData;
|
||||||
isLoading.value = false
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ const buttons = (song: SongsInfo) => [
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
default: () =>
|
default: () =>
|
||||||
!props.liveRequestSettings.allowFromWeb || song.options
|
!props.liveRequestSettings?.allowFromWeb || song.options
|
||||||
? '点歌 | 用户不允许从网页点歌, 点击后将复制点歌内容到剪切板'
|
? '点歌 | 用户不允许从网页点歌, 点击后将复制点歌内容到剪切板'
|
||||||
: !accountInfo
|
: !accountInfo
|
||||||
? '点歌 | 你需要登录后才能点歌'
|
? '点歌 | 你需要登录后才能点歌'
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,29 +1,29 @@
|
|||||||
// vite.config.ts
|
// vite.config.ts
|
||||||
import vue from '@vitejs/plugin-vue'
|
import vue from '@vitejs/plugin-vue';
|
||||||
import vueJsx from '@vitejs/plugin-vue-jsx'
|
import vueJsx from '@vitejs/plugin-vue-jsx';
|
||||||
import path from 'path'
|
import path, { resolve } from 'path';
|
||||||
import Markdown from 'unplugin-vue-markdown/vite'
|
import Markdown from 'unplugin-vue-markdown/vite';
|
||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite';
|
||||||
import monacoEditorPluginModule from 'vite-plugin-monaco-editor'
|
import monacoEditorPluginModule from 'vite-plugin-monaco-editor';
|
||||||
|
import caddyTls from './plugins/vite-plugin-caddy';
|
||||||
|
import { VineVitePlugin } from 'vue-vine/vite';
|
||||||
|
import AutoImport from 'unplugin-auto-import/vite';
|
||||||
|
import Components from 'unplugin-vue-components/vite';
|
||||||
|
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers';
|
||||||
|
import oxlintPlugin from 'vite-plugin-oxlint';
|
||||||
import svgLoader from 'vite-svg-loader'
|
import svgLoader from 'vite-svg-loader'
|
||||||
import caddyTls from './plugins/vite-plugin-caddy'
|
|
||||||
import { VineVitePlugin } from 'vue-vine/vite'
|
|
||||||
import AutoImport from 'unplugin-auto-import/vite'
|
|
||||||
import Components from 'unplugin-vue-components/vite'
|
|
||||||
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers'
|
|
||||||
import oxlintPlugin from 'vite-plugin-oxlint'
|
|
||||||
|
|
||||||
const isObjectWithDefaultFunction = (
|
const isObjectWithDefaultFunction = (
|
||||||
module: unknown
|
module: unknown
|
||||||
): module is { default: typeof monacoEditorPluginModule } =>
|
): module is { default: typeof monacoEditorPluginModule; } =>
|
||||||
module != null &&
|
module != null &&
|
||||||
typeof module === 'object' &&
|
typeof module === 'object' &&
|
||||||
'default' in module &&
|
'default' in module &&
|
||||||
typeof module.default === 'function'
|
typeof module.default === 'function';
|
||||||
|
|
||||||
const monacoEditorPlugin = isObjectWithDefaultFunction(monacoEditorPluginModule)
|
const monacoEditorPlugin = isObjectWithDefaultFunction(monacoEditorPluginModule)
|
||||||
? monacoEditorPluginModule.default
|
? monacoEditorPluginModule.default
|
||||||
: monacoEditorPluginModule
|
: monacoEditorPluginModule;
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
@@ -34,8 +34,8 @@ export default defineConfig({
|
|||||||
compilerOptions: { isCustomElement: (tag) => tag.startsWith('yt-') }
|
compilerOptions: { isCustomElement: (tag) => tag.startsWith('yt-') }
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
svgLoader(),
|
|
||||||
vueJsx(),
|
vueJsx(),
|
||||||
|
svgLoader(),
|
||||||
Markdown({
|
Markdown({
|
||||||
/* options */
|
/* options */
|
||||||
}),
|
}),
|
||||||
@@ -66,4 +66,4 @@ export default defineConfig({
|
|||||||
include: ['@vicons/fluent', '@vicons/ionicons5', 'vue', 'vue-router']
|
include: ['@vicons/fluent', '@vicons/ionicons5', 'vue', 'vue-router']
|
||||||
},
|
},
|
||||||
build: { sourcemap: true },
|
build: { sourcemap: true },
|
||||||
})
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user