mirror of
https://github.com/Megghy/vtsuru.live.git
synced 2025-12-06 18:36:55 +08:00
cancel no payment
This commit is contained in:
@@ -1,5 +1,7 @@
|
|||||||
{
|
{
|
||||||
"tabWidth": 2,
|
"tabWidth": 2,
|
||||||
"semi": false,
|
"semi": false,
|
||||||
"singleQuote": true
|
"singleQuote": true,
|
||||||
|
"trailingComma": "none",
|
||||||
|
"printWidth": 80
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { TemplateConfig } from '@/data/VTsuruTypes'
|
import { getImageUploadModel } from '@/Utils';
|
||||||
|
import { QueryPostAPI } from '@/api/query';
|
||||||
|
import { TemplateConfig, TemplateConfigImageItem } from '@/data/VTsuruTypes'
|
||||||
|
import { FILE_BASE_URL, VTSURU_API_URL } from '@/data/constants';
|
||||||
import { NButton, NEmpty, NForm, NFormItem, NInput, NUpload, UploadFileInfo, useMessage } from 'naive-ui'
|
import { NButton, NEmpty, NForm, NFormItem, NInput, NUpload, UploadFileInfo, useMessage } from 'naive-ui'
|
||||||
import { ref } from 'vue'
|
import { onMounted, ref } from 'vue'
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
|
|
||||||
@@ -10,23 +13,78 @@ const props = defineProps<{
|
|||||||
config: TemplateConfig<any> | undefined
|
config: TemplateConfig<any> | undefined
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const fileList = ref<UploadFileInfo[]>([])
|
const fileList = ref<{ [key: string]: UploadFileInfo[] }>({})
|
||||||
|
|
||||||
function OnFileListChange(files: UploadFileInfo[]) {
|
const isUploading = ref(false)
|
||||||
console.log(props.configData)
|
|
||||||
|
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 = []
|
fileList.value[key] = []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function onSubmit() {
|
async function onSubmit() {
|
||||||
console.log(props.configData)
|
try {
|
||||||
|
isUploading.value = true
|
||||||
|
let images = {} as {
|
||||||
|
[key: string]: {
|
||||||
|
existImages: string[],
|
||||||
|
newImagesBase64: string[],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const item of props.config!.items) {
|
||||||
|
if (item.type == 'image') {
|
||||||
|
const key = (item as TemplateConfigImageItem<any>).key
|
||||||
|
images[key] = await getImageUploadModel(fileList.value[key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const resp = await QueryPostAPI<any>(VTSURU_API_URL + 'set-config', {
|
||||||
|
name: props.config!.name,
|
||||||
|
json: JSON.stringify(props.configData),
|
||||||
|
images: images,
|
||||||
|
})
|
||||||
|
if (resp.code == 200) {
|
||||||
|
message.success('已保存至服务器')
|
||||||
|
props.config?.items.forEach(item => {
|
||||||
|
switch (item.type) {
|
||||||
|
case 'image':
|
||||||
|
item.onUploaded?.(resp.data[item.key], props.configData)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
message.error('保存失败: ' + resp.message)
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
message.error('保存失败: ' + err)
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
isUploading.value = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getItems() {}
|
function getItems() { }
|
||||||
|
onMounted(() => {
|
||||||
|
props.config?.items.forEach(item => {
|
||||||
|
if (item.type == 'image') {
|
||||||
|
const configItem = props.configData[item.key]
|
||||||
|
if (configItem) {
|
||||||
|
fileList.value[item.key] = configItem.map((i: string) => ({
|
||||||
|
id: i,
|
||||||
|
thumbnailUrl: FILE_BASE_URL + i,
|
||||||
|
name: '',
|
||||||
|
status: 'finished',
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
fileList.value[item.key] = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -35,22 +93,17 @@ function getItems() {}
|
|||||||
<NFormItem v-for="item in config.items" :key="item.name" :label="item.name">
|
<NFormItem v-for="item in config.items" :key="item.name" :label="item.name">
|
||||||
<component v-if="item.type == 'render'" :is="item.render(configData)"></component>
|
<component v-if="item.type == 'render'" :is="item.render(configData)"></component>
|
||||||
<template v-else-if="item.type == 'string'">
|
<template v-else-if="item.type == 'string'">
|
||||||
<NInput :value="item.data.get(configData)" @update:value="item.data.set(configData, $event)" />
|
<NInput v-if="item.data" :value="configData[item.key]" @update:value="configData[item.key] = $event" />
|
||||||
|
<NInput v-else v-model:value="configData[item.key]" />
|
||||||
</template>
|
</template>
|
||||||
<NUpload
|
<NUpload v-else-if="item.type == 'image'" accept=".png,.jpg,.jpeg,.gif,.svg,.webp,.ico" list-type="image-card"
|
||||||
v-else-if="item.type == 'image'"
|
:default-upload="false" v-model:file-list="fileList[item.key]"
|
||||||
accept=".png,.jpg,.jpeg,.gif,.svg,.webp,.ico"
|
@update:file-list="file => OnFileListChange(item.key, file)" :max="item.imageLimit" im>
|
||||||
list-type="image-card"
|
|
||||||
:default-upload="false"
|
|
||||||
v-model:file-list="fileList"
|
|
||||||
@update:file-list="OnFileListChange"
|
|
||||||
:max="item.imageLimit"
|
|
||||||
>
|
|
||||||
上传图片
|
上传图片
|
||||||
</NUpload>
|
</NUpload>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
<NFormItem>
|
<NFormItem>
|
||||||
<NButton type="primary" @click="onSubmit">提交</NButton>
|
<NButton type="primary" @click="onSubmit" :loading="isUploading" >提交</NButton>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
</NForm>
|
</NForm>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ const props = defineProps<{
|
|||||||
songs: SongsInfo[]
|
songs: SongsInfo[]
|
||||||
canEdit?: boolean
|
canEdit?: boolean
|
||||||
isSelf: boolean
|
isSelf: boolean
|
||||||
extraButtom?: (song: SongsInfo) => VNodeChild[]
|
extraButton?: (song: SongsInfo) => VNodeChild[]
|
||||||
}>()
|
}>()
|
||||||
watch(
|
watch(
|
||||||
() => props.songs,
|
() => props.songs,
|
||||||
@@ -380,7 +380,7 @@ function createColumns(): DataTableColumns<SongsInfo> {
|
|||||||
}),
|
}),
|
||||||
]
|
]
|
||||||
: null,
|
: null,
|
||||||
props.extraButtom?.(data),
|
props.extraButton?.(data),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|||||||
23
src/data/TemplateTypes.ts
Normal file
23
src/data/TemplateTypes.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import {
|
||||||
|
ScheduleWeekInfo,
|
||||||
|
Setting_LiveRequest,
|
||||||
|
SongRequestInfo,
|
||||||
|
SongsInfo,
|
||||||
|
UserInfo
|
||||||
|
} from '@/api/api-models'
|
||||||
|
|
||||||
|
export interface SongListConfigType {
|
||||||
|
userInfo: UserInfo | undefined
|
||||||
|
biliInfo: any | undefined
|
||||||
|
songRequestSettings: Setting_LiveRequest
|
||||||
|
songRequestActive: SongRequestInfo[]
|
||||||
|
data: SongsInfo[] | undefined
|
||||||
|
}
|
||||||
|
export interface SongListConfigTypeWithConfig<T> extends SongListConfigType {
|
||||||
|
config?: T
|
||||||
|
}
|
||||||
|
export interface ScheduleConfigType {
|
||||||
|
userInfo: UserInfo | undefined
|
||||||
|
biliInfo: any | undefined
|
||||||
|
data: ScheduleWeekInfo[] | undefined
|
||||||
|
}
|
||||||
@@ -23,10 +23,11 @@ type DataAccessor<T, V> = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 扩展 CommonProps 以包含额外的共有属性
|
// 扩展 CommonProps 以包含额外的共有属性
|
||||||
export type TemplateConfigItemWithType<T, V> = CommonProps & { data: DataAccessor<T, V> }
|
export type TemplateConfigItemWithType<T, V> = CommonProps & { data?: DataAccessor<T, V> }
|
||||||
|
|
||||||
export type TemplateConfigStringItem<T> = TemplateConfigItemWithType<T, string> & {
|
export type TemplateConfigStringItem<T> = TemplateConfigItemWithType<T, string> & {
|
||||||
type: 'string'
|
type: 'string'
|
||||||
|
key: string //将被保存到指定key中
|
||||||
}
|
}
|
||||||
export type TemplateConfigStringArrayItem<T> = TemplateConfigItemWithType<T, string[]> & {
|
export type TemplateConfigStringArrayItem<T> = TemplateConfigItemWithType<T, string[]> & {
|
||||||
type: 'stringArray'
|
type: 'stringArray'
|
||||||
@@ -42,8 +43,19 @@ export type TemplateConfigRenderItem<T> = TemplateConfigBase & {
|
|||||||
type: 'render'
|
type: 'render'
|
||||||
render: (arg0: T) => VNode
|
render: (arg0: T) => VNode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @template T - The type of the associated data model.
|
||||||
|
*/
|
||||||
export type TemplateConfigImageItem<T> = TemplateConfigBase & {
|
export type TemplateConfigImageItem<T> = TemplateConfigBase & {
|
||||||
type: 'image'
|
type: 'image' // Specifies the type of configuration item as 'image'.
|
||||||
imageLimit: number
|
imageLimit: number // The maximum number of images allowed.
|
||||||
onUploaded: (arg0: string | string[], arg1: T) => void
|
key: string //图片将被保存到指定key中, 类型为字符串数组
|
||||||
|
/**
|
||||||
|
* Callback function triggered upon image upload.
|
||||||
|
* @param {string[]} uploadedImages - The uploaded image or array of images.
|
||||||
|
* @param {T} config - The configuration data model.
|
||||||
|
*/
|
||||||
|
onUploaded?: (uploadedImages: string[], config: T) => void
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,6 +70,10 @@ export const SongListTemplateMap = {
|
|||||||
name: '简单',
|
name: '简单',
|
||||||
compoent: defineAsyncComponent(() => import('@/views/view/songListTemplate/SimpleSongListTemplate.vue')),
|
compoent: defineAsyncComponent(() => import('@/views/view/songListTemplate/SimpleSongListTemplate.vue')),
|
||||||
},
|
},
|
||||||
|
traditional: {
|
||||||
|
name: '传统',
|
||||||
|
compoent: defineAsyncComponent(() => import('@/views/view/songListTemplate/TraditionalSongListTemplate.vue')),
|
||||||
|
},
|
||||||
} as { [key: string]: { name: string; compoent: any } }
|
} as { [key: string]: { name: string; compoent: any } }
|
||||||
export const IndexTemplateMap = {
|
export const IndexTemplateMap = {
|
||||||
'': { name: '默认', compoent: DefaultIndexTemplateVue },
|
'': { name: '默认', compoent: DefaultIndexTemplateVue },
|
||||||
|
|||||||
@@ -86,11 +86,6 @@ const functions = [
|
|||||||
desc: '绑定账号后查看粉丝 舰长 观看数 等数据的历史记录',
|
desc: '绑定账号后查看粉丝 舰长 观看数 等数据的历史记录',
|
||||||
icon: AnalyticsSharp,
|
icon: AnalyticsSharp,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: '绝不收米!',
|
|
||||||
desc: '为爱发电, 所有功能都免费并提供技术支持 (当然你想赞助的话也可以捏',
|
|
||||||
icon: MoneyOff24Filled,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: '还有更多',
|
name: '还有更多',
|
||||||
desc: '更多功能仍在开发中. 有其他合理需求或者建议, 或者有想要添加的样式? 向我提出!',
|
desc: '更多功能仍在开发中. 有其他合理需求或者建议, 或者有想要添加的样式? 向我提出!',
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ import {
|
|||||||
SelectOption,
|
SelectOption,
|
||||||
useMessage,
|
useMessage,
|
||||||
} from 'naive-ui'
|
} from 'naive-ui'
|
||||||
import { computed, h, nextTick, onActivated, onMounted, ref } 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 {
|
||||||
@@ -247,7 +247,6 @@ const selectedTemplateConfig = computed(() => {
|
|||||||
}
|
}
|
||||||
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)
|
||||||
@@ -547,19 +546,15 @@ onMounted(async () => {
|
|||||||
<NCheckbox v-model:checked="accountInfo.settings.sendEmail.recieveQA" @update:checked="SaveComboSetting">
|
<NCheckbox v-model:checked="accountInfo.settings.sendEmail.recieveQA" @update:checked="SaveComboSetting">
|
||||||
收到新提问时发送邮件
|
收到新提问时发送邮件
|
||||||
</NCheckbox>
|
</NCheckbox>
|
||||||
<NCheckbox
|
<NCheckbox v-model:checked="accountInfo.settings.sendEmail.recieveQAReply"
|
||||||
v-model:checked="accountInfo.settings.sendEmail.recieveQAReply"
|
@update:checked="SaveComboSetting">
|
||||||
@update:checked="SaveComboSetting"
|
|
||||||
>
|
|
||||||
提问收到回复时发送邮件
|
提问收到回复时发送邮件
|
||||||
</NCheckbox>
|
</NCheckbox>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
<NDivider> 提问箱 </NDivider>
|
<NDivider> 提问箱 </NDivider>
|
||||||
<NSpace>
|
<NSpace>
|
||||||
<NCheckbox
|
<NCheckbox v-model:checked="accountInfo.settings.questionBox.allowUnregistedUser"
|
||||||
v-model:checked="accountInfo.settings.questionBox.allowUnregistedUser"
|
@update:checked="SaveComboSetting">
|
||||||
@update:checked="SaveComboSetting"
|
|
||||||
>
|
|
||||||
允许未注册用户提问
|
允许未注册用户提问
|
||||||
</NCheckbox>
|
</NCheckbox>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
@@ -649,28 +644,22 @@ onMounted(async () => {
|
|||||||
<br />
|
<br />
|
||||||
<NSpace vertical>
|
<NSpace vertical>
|
||||||
<NSpace align="center">
|
<NSpace align="center">
|
||||||
页面 <NSelect :options="templateOptions" v-model:value="selectedOption" style="width: 150px" />
|
页面
|
||||||
|
<NSelect :options="templateOptions" v-model:value="selectedOption" style="width: 150px" />
|
||||||
</NSpace>
|
</NSpace>
|
||||||
<NDivider style="margin: 5px 0 5px 0" title-placement="left"> 模板 </NDivider>
|
<NDivider style="margin: 5px 0 5px 0" title-placement="left"> 模板 </NDivider>
|
||||||
<div>
|
<div>
|
||||||
<NSpace>
|
<NSpace>
|
||||||
<NSelect
|
<NSelect style="width: 150px" :options="selectedTemplateData.Options"
|
||||||
style="width: 150px"
|
v-model:value="selectedTemplateData.Selected" />
|
||||||
:options="selectedTemplateData.Options"
|
|
||||||
v-model:value="selectedTemplateData.Selected"
|
|
||||||
/>
|
|
||||||
<component :is="buttonGroup" />
|
<component :is="buttonGroup" />
|
||||||
</NSpace>
|
</NSpace>
|
||||||
<NDivider />
|
<NDivider />
|
||||||
<Transition name="fade" mode="out-in">
|
<Transition name="fade" mode="out-in">
|
||||||
<div v-if="selectedComponent" :key="selectedTemplateData.Selected">
|
<div v-if="selectedComponent" :key="selectedTemplateData.Selected">
|
||||||
<component
|
<component ref="dynamicConfigRef" @vue:mounted="getTemplateConfig" :is="selectedComponent"
|
||||||
ref="dynamicConfigRef"
|
:user-info="accountInfo" :bili-info="biliUserInfo" :current-data="selectedTemplateData.Data"
|
||||||
:is="selectedComponent"
|
:config="selectedTemplateData.Config" />
|
||||||
:user-info="accountInfo"
|
|
||||||
:bili-info="biliUserInfo"
|
|
||||||
:current-data="selectedTemplateData.Data"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</Transition>
|
</Transition>
|
||||||
</div>
|
</div>
|
||||||
@@ -679,29 +668,13 @@ onMounted(async () => {
|
|||||||
</NTabs>
|
</NTabs>
|
||||||
</NSpin>
|
</NSpin>
|
||||||
</NCard>
|
</NCard>
|
||||||
<NModal
|
<NModal preset="card" v-model:show="settingModalVisiable" closable style="width: 600px; max-width: 90vw" title="模板设置">
|
||||||
preset="card"
|
|
||||||
v-model:show="settingModalVisiable"
|
|
||||||
closable
|
|
||||||
style="width: 600px; max-width: 90vw"
|
|
||||||
title="模板设置"
|
|
||||||
>
|
|
||||||
只是测试, 没用
|
只是测试, 没用
|
||||||
<NSpin v-if="!selectedTemplateData.Config" show />
|
<NSpin v-if="!selectedTemplateData.Config" show />
|
||||||
<DynamicForm
|
<DynamicForm v-else :key="selectedTemplateData.Selected" :configData="selectedTemplateData.Config"
|
||||||
v-else
|
:config="selectedTemplateConfig" />
|
||||||
:key="selectedTemplateData.Selected"
|
|
||||||
:configData="selectedTemplateData.Config"
|
|
||||||
:config="selectedTemplateConfig"
|
|
||||||
/>
|
|
||||||
</NModal>
|
</NModal>
|
||||||
<NModal
|
<NModal preset="card" v-model:show="showAddVideoModal" closable style="width: 600px; max-width: 90vw" title="添加视频">
|
||||||
preset="card"
|
|
||||||
v-model:show="showAddVideoModal"
|
|
||||||
closable
|
|
||||||
style="width: 600px; max-width: 90vw"
|
|
||||||
title="添加视频"
|
|
||||||
>
|
|
||||||
<NInput v-model:value="addVideoUrl" placeholder="请输入视频链接" />
|
<NInput v-model:value="addVideoUrl" placeholder="请输入视频链接" />
|
||||||
<NDivider />
|
<NDivider />
|
||||||
<NButton type="primary" @click="addVideo" :loading="isLoading"> 添加视频 </NButton>
|
<NButton type="primary" @click="addVideo" :loading="isLoading"> 添加视频 </NButton>
|
||||||
|
|||||||
@@ -2,35 +2,36 @@
|
|||||||
import { NavigateToNewTab } from '@/Utils'
|
import { NavigateToNewTab } from '@/Utils'
|
||||||
import { useAccount } from '@/api/account'
|
import { useAccount } from '@/api/account'
|
||||||
import {
|
import {
|
||||||
AddressInfo,
|
AddressInfo,
|
||||||
GoodsTypes,
|
GoodsTypes,
|
||||||
ResponsePointGoodModel,
|
ResponsePointGoodModel,
|
||||||
ResponsePointOrder2UserModel,
|
ResponsePointOrder2UserModel,
|
||||||
UserInfo,
|
UserInfo,
|
||||||
} from '@/api/api-models'
|
} from '@/api/api-models'
|
||||||
import AddressDisplay from '@/components/manage/AddressDisplay.vue'
|
import AddressDisplay from '@/components/manage/AddressDisplay.vue'
|
||||||
import PointGoodsItem from '@/components/manage/PointGoodsItem.vue'
|
import PointGoodsItem from '@/components/manage/PointGoodsItem.vue'
|
||||||
import { POINT_API_URL } from '@/data/constants'
|
import { POINT_API_URL } from '@/data/constants'
|
||||||
import { useAuthStore } from '@/store/useAuthStore'
|
import { useAuthStore } from '@/store/useAuthStore'
|
||||||
import {
|
import {
|
||||||
NAlert,
|
NAlert,
|
||||||
NButton,
|
NButton,
|
||||||
NCard,
|
NCard,
|
||||||
NDivider,
|
NCheckbox,
|
||||||
NEmpty,
|
NDivider,
|
||||||
NFlex,
|
NEmpty,
|
||||||
NForm,
|
NFlex,
|
||||||
NFormItem,
|
NForm,
|
||||||
NInputNumber,
|
NFormItem,
|
||||||
NModal,
|
NInputNumber,
|
||||||
NSelect,
|
NModal,
|
||||||
NSpin,
|
NSelect,
|
||||||
NTag,
|
NSpin,
|
||||||
NText,
|
NTag,
|
||||||
NTooltip,
|
NText,
|
||||||
SelectOption,
|
NTooltip,
|
||||||
useDialog,
|
SelectOption,
|
||||||
useMessage
|
useDialog,
|
||||||
|
useMessage
|
||||||
} from 'naive-ui'
|
} from 'naive-ui'
|
||||||
import { computed, h, onMounted, ref } from 'vue'
|
import { computed, h, onMounted, ref } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
@@ -65,11 +66,12 @@ const tags = computed(() => {
|
|||||||
})
|
})
|
||||||
const selectedTag = ref<string>()
|
const selectedTag = ref<string>()
|
||||||
const selectedItems = computed(() => {
|
const selectedItems = computed(() => {
|
||||||
return selectedTag.value
|
return goods.value.filter((item) => selectedTag.value ? item.tags.includes(selectedTag.value) : true).filter((item) => !onlyCanBuy.value || getTooltip(item) == '开始兑换').filter((item) => !ignoreGuard.value || item.allowGuardLevel == 0)
|
||||||
? goods.value.filter((g) => g.tags.includes(selectedTag.value))
|
|
||||||
: goods.value
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const onlyCanBuy = ref(false)
|
||||||
|
const ignoreGuard = ref(false)
|
||||||
|
|
||||||
const addressOptions = computed(() => {
|
const addressOptions = computed(() => {
|
||||||
if (!biliAuth.value.id) return []
|
if (!biliAuth.value.id) return []
|
||||||
return (
|
return (
|
||||||
@@ -86,7 +88,7 @@ const canBuy = computed(() => {
|
|||||||
if (!biliAuth.value.id) return false
|
if (!biliAuth.value.id) return false
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
function getTooltip(goods: ResponsePointGoodModel) {
|
function getTooltip(goods: ResponsePointGoodModel): '开始兑换' | '当前积分不足' | '请先进行账号认证' | '库存不足' {
|
||||||
if ((currentPoint.value ?? 0) < goods.price) {
|
if ((currentPoint.value ?? 0) < goods.price) {
|
||||||
return '当前积分不足'
|
return '当前积分不足'
|
||||||
} else if (!biliAuth.value.id) return '请先进行账号认证'
|
} else if (!biliAuth.value.id) return '请先进行账号认证'
|
||||||
@@ -215,34 +217,29 @@ onMounted(async () => {
|
|||||||
<NText> 你在 {{ userInfo.extra?.streamerInfo?.name ?? userInfo.name }} 的直播间的积分为 {{ currentPoint }} </NText>
|
<NText> 你在 {{ userInfo.extra?.streamerInfo?.name ?? userInfo.name }} 的直播间的积分为 {{ currentPoint }} </NText>
|
||||||
</NCard>
|
</NCard>
|
||||||
<NDivider />
|
<NDivider />
|
||||||
<NCard size="small" title="标签">
|
<NCard v-if="tags.length > 0" size="small" title="标签筛选">
|
||||||
<NFlex align="center" justify="center">
|
<NFlex align="center" justify="center">
|
||||||
<NButton v-for="tag in tags"
|
<NButton v-for="tag in tags" :type="tag == selectedTag ? 'success' : 'default'"
|
||||||
:type="tag == selectedTag ? 'success' : 'default'"
|
@click="selectedTag = selectedTag == tag ? undefined : tag" :borderd="false" style="margin: 4px" size="small">
|
||||||
@click="selectedTag = selectedTag == tag ? undefined : tag"
|
|
||||||
:borderd="false"
|
|
||||||
style="margin: 4px"
|
|
||||||
size="small">
|
|
||||||
{{ tag }}
|
{{ tag }}
|
||||||
</NButton>
|
</NButton>
|
||||||
</NFlex>
|
</NFlex>
|
||||||
|
<NDivider />
|
||||||
|
<NCheckbox v-model:checked="onlyCanBuy"> 只显示可兑换的礼物 </NCheckbox>
|
||||||
|
<NCheckbox v-model:checked="ignoreGuard"> 忽略需要舰长的礼物 </NCheckbox>
|
||||||
</NCard>
|
</NCard>
|
||||||
<NDivider />
|
<NDivider />
|
||||||
<NSpin :show="isLoading">
|
<NSpin :show="isLoading">
|
||||||
<NEmpty v-if="selectedItems.length == 0"> 暂无礼物 </NEmpty>
|
<NEmpty v-if="selectedItems.length == 0"> 暂无礼物 </NEmpty>
|
||||||
<NFlex justify="center">
|
<NFlex justify="center">
|
||||||
<PointGoodsItem v-for="item in selectedItems" :key="item.id" :goods="item" content-style="max-width: 300px;height: 365px">
|
<PointGoodsItem v-for="item in selectedItems" :key="item.id" :goods="item"
|
||||||
|
content-style="max-width: 300px;height: 365px">
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<NFlex justify="space-between" align="center">
|
<NFlex justify="space-between" align="center">
|
||||||
<NTooltip>
|
<NTooltip>
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<NButton
|
<NButton :disabled="getTooltip(item) != '开始兑换'" size="small" type="primary" @click="onBuyClick(item)">兑换
|
||||||
:disabled="getTooltip(item) != '开始兑换'"
|
</NButton>
|
||||||
size="small"
|
|
||||||
type="primary"
|
|
||||||
@click="onBuyClick(item)"
|
|
||||||
>兑换</NButton
|
|
||||||
>
|
|
||||||
</template>
|
</template>
|
||||||
{{ getTooltip(item) }}
|
{{ getTooltip(item) }}
|
||||||
</NTooltip>
|
</NTooltip>
|
||||||
@@ -262,13 +259,8 @@ onMounted(async () => {
|
|||||||
</PointGoodsItem>
|
</PointGoodsItem>
|
||||||
</NFlex>
|
</NFlex>
|
||||||
</NSpin>
|
</NSpin>
|
||||||
<NModal
|
<NModal v-model:show="showBuyModal" v-if="currentGoods" preset="card" title="确认兑换"
|
||||||
v-model:show="showBuyModal"
|
style="width: 500px; max-width: 90vw; height: auto">
|
||||||
v-if="currentGoods"
|
|
||||||
preset="card"
|
|
||||||
title="确认兑换"
|
|
||||||
style="width: 500px; max-width: 90vw; height: auto"
|
|
||||||
>
|
|
||||||
<template #header>
|
<template #header>
|
||||||
<NFlex align="baseline">
|
<NFlex align="baseline">
|
||||||
<NTag :type="currentGoods.type == GoodsTypes.Physical ? 'info' : 'default'" :bordered="false">
|
<NTag :type="currentGoods.type == GoodsTypes.Physical ? 'info' : 'default'" :bordered="false">
|
||||||
@@ -281,32 +273,16 @@ onMounted(async () => {
|
|||||||
<template v-if="currentGoods.type == GoodsTypes.Physical">
|
<template v-if="currentGoods.type == GoodsTypes.Physical">
|
||||||
<NDivider> 选项 </NDivider>
|
<NDivider> 选项 </NDivider>
|
||||||
<NForm>
|
<NForm>
|
||||||
<NFormItem label="兑换数量" required
|
<NFormItem label="兑换数量" required>
|
||||||
><NInputNumber
|
<NInputNumber v-model:value="buyCount" :min="1" :max="currentGoods.maxBuyCount ?? 100000"
|
||||||
v-model:value="buyCount"
|
style="max-width: 120px" step="1" :precision="0" />
|
||||||
:min="1"
|
|
||||||
:max="currentGoods.maxBuyCount ?? 100000"
|
|
||||||
style="max-width: 120px"
|
|
||||||
step="1"
|
|
||||||
:precision="0"
|
|
||||||
/>
|
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
<NFormItem
|
<NFormItem v-if="
|
||||||
v-if="
|
|
||||||
currentGoods.type == GoodsTypes.Physical &&
|
currentGoods.type == GoodsTypes.Physical &&
|
||||||
(currentGoods.collectUrl == null || currentGoods.collectUrl == undefined)
|
(currentGoods.collectUrl == null || currentGoods.collectUrl == undefined)
|
||||||
"
|
" label="收货地址" required>
|
||||||
label="收货地址"
|
<NSelect v-model:show="showAddressSelect" :value="selectedAddress?.id" :options="addressOptions"
|
||||||
required
|
:render-label="renderLabel" :render-option="renderOption" placeholder="请选择地址" />
|
||||||
>
|
|
||||||
<NSelect
|
|
||||||
v-model:show="showAddressSelect"
|
|
||||||
:value="selectedAddress?.id"
|
|
||||||
:options="addressOptions"
|
|
||||||
:render-label="renderLabel"
|
|
||||||
:render-option="renderOption"
|
|
||||||
placeholder="请选择地址"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<NButton size="small" type="info" tag="a" href="/bili-user#settings" target="_blank"> 管理收货地址 </NButton>
|
<NButton size="small" type="info" tag="a" href="/bili-user#settings" target="_blank"> 管理收货地址 </NButton>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
|
|||||||
@@ -1,25 +1,19 @@
|
|||||||
<template>
|
<template>
|
||||||
<NSpin v-if="isLoading" show />
|
<NSpin v-if="isLoading" show />
|
||||||
<component
|
<component v-else ref="dynamicConfigRef" :config="currentConfig"
|
||||||
v-else
|
:is="SongListTemplateMap[componentType ?? '']?.compoent" :user-info="userInfo" :bili-info="biliInfo"
|
||||||
:is="SongListTemplateMap[componentType ?? '']?.compoent"
|
:currentData="currentData" :live-request-settings="settings" :live-request-active="songsActive"
|
||||||
:user-info="userInfo"
|
@request-song="requestSong" v-bind="$attrs" />
|
||||||
:bili-info="biliInfo"
|
|
||||||
:currentData="currentData"
|
|
||||||
:live-request-settings="settings"
|
|
||||||
:live-request-active="songsActive"
|
|
||||||
@request-song="requestSong"
|
|
||||||
v-bind="$attrs"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useAccount } from '@/api/account'
|
import { DownloadConfig, downloadConfigDirect, 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 { SONG_API_URL, SONG_REQUEST_API_URL, SongListTemplateMap } from '@/data/constants'
|
import { TemplateConfig } from '@/data/VTsuruTypes'
|
||||||
|
import { SONG_API_URL, SONG_REQUEST_API_URL, SongListTemplateMap, VTSURU_API_URL } from '@/data/constants'
|
||||||
import { NSpin, useMessage } from 'naive-ui'
|
import { NSpin, useMessage } from 'naive-ui'
|
||||||
import { computed, onMounted, ref } from 'vue'
|
import { computed, onMounted, ref, watch, watchEffect } from 'vue'
|
||||||
|
|
||||||
const accountInfo = useAccount()
|
const accountInfo = useAccount()
|
||||||
|
|
||||||
@@ -35,6 +29,21 @@ 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 selectedTemplateConfig = computed(() => {
|
||||||
|
if (dynamicConfigRef.value?.Config) {
|
||||||
|
return dynamicConfigRef.value?.Config as TemplateConfig<any>
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
})
|
||||||
|
const currentConfig = ref()
|
||||||
|
watch(
|
||||||
|
() => dynamicConfigRef,
|
||||||
|
() => {
|
||||||
|
getConfig()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
const isLoading = ref(true)
|
const isLoading = ref(true)
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
|
|
||||||
@@ -53,7 +62,7 @@ async function getSongRequestInfo() {
|
|||||||
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() {
|
||||||
@@ -76,6 +85,23 @@ async function getSongs() {
|
|||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
async function getConfig() {
|
||||||
|
isLoading.value = true
|
||||||
|
await DownloadConfig(selectedTemplateConfig.value!.name)
|
||||||
|
.then((data) => {
|
||||||
|
if (data.msg) {
|
||||||
|
message.error('加载失败: ' + data.msg)
|
||||||
|
} else {
|
||||||
|
currentConfig.value = data.data
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
message.error('加载失败')
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
isLoading.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
async function requestSong(song: SongsInfo) {
|
async function requestSong(song: SongsInfo) {
|
||||||
if (song.options || !settings.value.allowFromWeb) {
|
if (song.options || !settings.value.allowFromWeb) {
|
||||||
navigator.clipboard.writeText(`${settings.value.orderPrefix} ${song.name}`)
|
navigator.clipboard.writeText(`${settings.value.orderPrefix} ${song.name}`)
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ function navigate(url: string) {
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export type ConfigType = {
|
export type ConfigType = {
|
||||||
cover: string
|
test: string
|
||||||
}
|
}
|
||||||
export const DefaultConfig = {} as ConfigType
|
export const DefaultConfig = {} as ConfigType
|
||||||
export const Config: TemplateConfig<ConfigType> = {
|
export const Config: TemplateConfig<ConfigType> = {
|
||||||
@@ -57,19 +57,12 @@ export const Config: TemplateConfig<ConfigType> = {
|
|||||||
name: '封面',
|
name: '封面',
|
||||||
type: 'image',
|
type: 'image',
|
||||||
imageLimit: 1,
|
imageLimit: 1,
|
||||||
onUploaded: (url, config) => {
|
key: 'cover',
|
||||||
config.cover = url instanceof String ? (url as string) : url[0]
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'test',
|
name: '测试',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
data: {
|
key: 'test',
|
||||||
get: (d) => d.cover,
|
|
||||||
set: (d, v) => {
|
|
||||||
d.cover = v
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,16 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useAccount } from '@/api/account'
|
import { useAccount } from '@/api/account';
|
||||||
import { ScheduleWeekInfo, UserInfo } from '@/api/api-models'
|
import ScheduleList from '@/components/ScheduleList.vue';
|
||||||
import ScheduleList from '@/components/ScheduleList.vue'
|
import { ScheduleConfigType } from '@/data/TemplateTypes';
|
||||||
import { NDivider } from 'naive-ui'
|
import { NDivider } from 'naive-ui';
|
||||||
|
|
||||||
const accountInfo = useAccount()
|
const accountInfo = useAccount()
|
||||||
|
|
||||||
defineProps<{
|
defineProps<ScheduleConfigType>()
|
||||||
userInfo: UserInfo | undefined
|
|
||||||
biliInfo: any | undefined
|
|
||||||
currentData: ScheduleWeekInfo[] | undefined
|
|
||||||
}>()
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NDivider style="margin-top: 10px" />
|
<NDivider style="margin-top: 10px" />
|
||||||
<ScheduleList v-if="currentData" :schedules="currentData ?? []" :is-self="false" v-bind="$attrs" />
|
<ScheduleList v-if="data" :schedules="data ?? []" :is-self="false" v-bind="$attrs" />
|
||||||
<NDivider />
|
<NDivider />
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ScheduleWeekInfo, UserInfo } from '@/api/api-models'
|
|
||||||
import SaveCompoent from '@/components/SaveCompoent.vue'
|
import SaveCompoent from '@/components/SaveCompoent.vue'
|
||||||
|
import { ScheduleConfigType } from '@/data/TemplateTypes'
|
||||||
import { getWeek, getYear } from 'date-fns'
|
import { getWeek, getYear } from 'date-fns'
|
||||||
import { NDivider, NSelect, NSpace } from 'naive-ui'
|
import { NDivider, NSelect, NSpace } from 'naive-ui'
|
||||||
import { computed, onMounted, ref } from 'vue'
|
import { computed, onMounted, ref } from 'vue'
|
||||||
@@ -8,17 +8,13 @@ import { computed, onMounted, ref } from 'vue'
|
|||||||
const days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
|
const days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
|
||||||
const table = ref()
|
const table = ref()
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<ScheduleConfigType>()
|
||||||
userInfo: UserInfo | undefined
|
|
||||||
biliInfo: any | undefined
|
|
||||||
currentData: ScheduleWeekInfo[] | undefined
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const currentWeek = computed(() => {
|
const currentWeek = computed(() => {
|
||||||
if (props.currentData?.length == 1) {
|
if (props.data?.length == 1) {
|
||||||
return props.currentData[0]
|
return props.data[0]
|
||||||
}
|
}
|
||||||
return props.currentData?.find((item) => {
|
return props.data?.find((item) => {
|
||||||
if (selectedDate.value) {
|
if (selectedDate.value) {
|
||||||
return item.year + '-' + item.week === selectedDate.value
|
return item.year + '-' + item.week === selectedDate.value
|
||||||
}
|
}
|
||||||
@@ -26,7 +22,7 @@ const currentWeek = computed(() => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
const options = computed(() => {
|
const options = computed(() => {
|
||||||
return props.currentData?.map((item) => {
|
return props.data?.map((item) => {
|
||||||
return {
|
return {
|
||||||
label: item.year + '年' + item.week + '周',
|
label: item.year + '年' + item.week + '周',
|
||||||
value: item.year + '-' + item.week,
|
value: item.year + '-' + item.week,
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useAccount } from '@/api/account'
|
import { useAccount } from '@/api/account'
|
||||||
import { Setting_LiveRequest, SongRequestInfo, SongsInfo, UserInfo } from '@/api/api-models'
|
import { SongsInfo } from '@/api/api-models'
|
||||||
import SongList from '@/components/SongList.vue'
|
import SongList from '@/components/SongList.vue'
|
||||||
|
import { SongListConfigType } from '@/data/TemplateTypes'
|
||||||
import LiveRequestOBS from '@/views/obs/LiveRequestOBS.vue'
|
import LiveRequestOBS from '@/views/obs/LiveRequestOBS.vue'
|
||||||
import { CloudAdd20Filled } from '@vicons/fluent'
|
import { CloudAdd20Filled } from '@vicons/fluent'
|
||||||
import { NButton, NCard, NCollapse, NCollapseItem, NDivider, NIcon, NTooltip, useMessage } from 'naive-ui'
|
import { NButton, NCard, NCollapse, NCollapseItem, NDivider, NIcon, NTooltip, useMessage } from 'naive-ui'
|
||||||
@@ -10,19 +11,13 @@ import { h, ref } from 'vue'
|
|||||||
const accountInfo = useAccount()
|
const accountInfo = useAccount()
|
||||||
|
|
||||||
//所有模板都应该有这些
|
//所有模板都应该有这些
|
||||||
const props = defineProps<{
|
const props = defineProps<SongListConfigType>()
|
||||||
userInfo: UserInfo | undefined
|
|
||||||
biliInfo: any | undefined
|
|
||||||
songRequestSettings: Setting_LiveRequest
|
|
||||||
songRequestActive: SongRequestInfo[]
|
|
||||||
currentData?: SongsInfo[] | undefined
|
|
||||||
}>()
|
|
||||||
const emits = defineEmits(['requestSong'])
|
const emits = defineEmits(['requestSong'])
|
||||||
|
|
||||||
const isLoading = ref('')
|
const isLoading = ref('')
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
|
|
||||||
const buttoms = (song: SongsInfo) => [
|
const buttons = (song: SongsInfo) => [
|
||||||
accountInfo.value?.id != props.userInfo?.id
|
accountInfo.value?.id != props.userInfo?.id
|
||||||
? h(
|
? h(
|
||||||
NTooltip,
|
NTooltip,
|
||||||
@@ -62,10 +57,10 @@ const buttoms = (song: SongsInfo) => [
|
|||||||
<template>
|
<template>
|
||||||
<NDivider style="margin-top: 10px" />
|
<NDivider style="margin-top: 10px" />
|
||||||
<SongList
|
<SongList
|
||||||
v-if="currentData"
|
v-if="data"
|
||||||
:songs="currentData ?? []"
|
:songs="data ?? []"
|
||||||
:is-self="accountInfo?.id == userInfo?.id"
|
:is-self="accountInfo?.id == userInfo?.id"
|
||||||
:extra-buttom="buttoms"
|
:extraButton="buttons"
|
||||||
v-bind="$attrs"
|
v-bind="$attrs"
|
||||||
/>
|
/>
|
||||||
<NCollapse v-if="userInfo?.canRequestSong">
|
<NCollapse v-if="userInfo?.canRequestSong">
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { GetGuardColor } from '@/Utils'
|
import { GetGuardColor } from '@/Utils'
|
||||||
import { useAccount } from '@/api/account'
|
import { useAccount } from '@/api/account'
|
||||||
import { FunctionTypes, Setting_LiveRequest, SongRequestInfo, SongsInfo, UserInfo } from '@/api/api-models'
|
import { FunctionTypes, SongsInfo } from '@/api/api-models'
|
||||||
import SongPlayer from '@/components/SongPlayer.vue'
|
import SongPlayer from '@/components/SongPlayer.vue'
|
||||||
|
import { SongListConfigType } from '@/data/TemplateTypes'
|
||||||
import LiveRequestOBS from '@/views/obs/LiveRequestOBS.vue'
|
import LiveRequestOBS from '@/views/obs/LiveRequestOBS.vue'
|
||||||
import { CloudAdd20Filled, Play24Filled } from '@vicons/fluent'
|
import { CloudAdd20Filled, Play24Filled } from '@vicons/fluent'
|
||||||
import { useWindowSize } from '@vueuse/core'
|
import { useWindowSize } from '@vueuse/core'
|
||||||
@@ -28,13 +29,7 @@ import {
|
|||||||
} from 'naive-ui'
|
} from 'naive-ui'
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<SongListConfigType>()
|
||||||
userInfo: UserInfo | undefined
|
|
||||||
biliInfo: any | undefined
|
|
||||||
songRequestSettings: Setting_LiveRequest
|
|
||||||
songRequestActive: SongRequestInfo[]
|
|
||||||
currentData: SongsInfo[] | undefined
|
|
||||||
}>()
|
|
||||||
const emits = defineEmits(['requestSong'])
|
const emits = defineEmits(['requestSong'])
|
||||||
const windowSize = useWindowSize()
|
const windowSize = useWindowSize()
|
||||||
const container = ref()
|
const container = ref()
|
||||||
@@ -51,10 +46,10 @@ const isLrcLoading = ref('')
|
|||||||
const isLoading = ref('')
|
const isLoading = ref('')
|
||||||
|
|
||||||
const tags = computed(() => {
|
const tags = computed(() => {
|
||||||
if (props.currentData) {
|
if (props.data) {
|
||||||
return [
|
return [
|
||||||
...new Set(
|
...new Set(
|
||||||
props.currentData
|
props.data
|
||||||
.map((item) => {
|
.map((item) => {
|
||||||
return item.tags ?? []
|
return item.tags ?? []
|
||||||
})
|
})
|
||||||
@@ -65,10 +60,10 @@ const tags = computed(() => {
|
|||||||
return []
|
return []
|
||||||
})
|
})
|
||||||
const authors = computed(() => {
|
const authors = computed(() => {
|
||||||
if (props.currentData) {
|
if (props.data) {
|
||||||
return [
|
return [
|
||||||
...new Set(
|
...new Set(
|
||||||
props.currentData
|
props.data
|
||||||
.map((item) => {
|
.map((item) => {
|
||||||
return item.author ?? []
|
return item.author ?? []
|
||||||
})
|
})
|
||||||
@@ -79,8 +74,8 @@ const authors = computed(() => {
|
|||||||
return []
|
return []
|
||||||
})
|
})
|
||||||
const songs = computed(() => {
|
const songs = computed(() => {
|
||||||
if (props.currentData) {
|
if (props.data) {
|
||||||
return props.currentData
|
return props.data
|
||||||
.filter((item) => {
|
.filter((item) => {
|
||||||
return (
|
return (
|
||||||
(!selectedTag.value || item.tags?.includes(selectedTag.value)) &&
|
(!selectedTag.value || item.tags?.includes(selectedTag.value)) &&
|
||||||
@@ -99,8 +94,8 @@ const onScroll = throttle((e: Event) => {
|
|||||||
}
|
}
|
||||||
}, 100)
|
}, 100)
|
||||||
function loadMore() {
|
function loadMore() {
|
||||||
if (props.currentData) {
|
if (props.data) {
|
||||||
index.value += props.currentData.length > 20 + index.value ? 20 : props.currentData.length - index.value
|
index.value += props.data.length > 20 + index.value ? 20 : props.data.length - index.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -151,7 +146,7 @@ function loadMore() {
|
|||||||
/>
|
/>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
</NCard>
|
</NCard>
|
||||||
<NEmpty v-if="!currentData || songs?.length == 0" description="暂无曲目" style="max-width: 0 auto" />
|
<NEmpty v-if="!data || songs?.length == 0" description="暂无曲目" style="max-width: 0 auto" />
|
||||||
<NScrollbar
|
<NScrollbar
|
||||||
v-else
|
v-else
|
||||||
ref="container"
|
ref="container"
|
||||||
@@ -301,7 +296,7 @@ function loadMore() {
|
|||||||
</NGrid>
|
</NGrid>
|
||||||
<NDivider />
|
<NDivider />
|
||||||
<NSpace justify="center">
|
<NSpace justify="center">
|
||||||
<NButton v-if="currentData.length > index" @click="loadMore"> 加载更多 </NButton>
|
<NButton v-if="data.length > index" @click="loadMore"> 加载更多 </NButton>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
</NScrollbar>
|
</NScrollbar>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { Setting_LiveRequest, SongRequestInfo, SongsInfo, UserInfo } from '@/api/api-models';
|
||||||
|
import { SongListConfigType, SongListConfigTypeWithConfig } from '@/data/TemplateTypes';
|
||||||
|
import { TemplateConfig } from '@/data/VTsuruTypes';
|
||||||
|
import { FILE_BASE_URL } from '@/data/constants';
|
||||||
|
|
||||||
|
const props = defineProps<SongListConfigTypeWithConfig<ConfigType>>()
|
||||||
|
defineExpose({ Config, DefaultConfig })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export type ConfigType = {
|
||||||
|
background: string[],
|
||||||
|
notice: string,
|
||||||
|
}
|
||||||
|
export const DefaultConfig = {} as ConfigType
|
||||||
|
export const Config: TemplateConfig<ConfigType> = {
|
||||||
|
name: 'Template.SongList.Traditional',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
name: '背景',
|
||||||
|
type: 'image',
|
||||||
|
imageLimit: 1,
|
||||||
|
key: 'background',
|
||||||
|
onUploaded: (url, config) => {
|
||||||
|
config.background = url
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '公告',
|
||||||
|
type: 'string',
|
||||||
|
key: 'notice',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div :style="{
|
||||||
|
backgroundImage: `${props.config?.background ? 'url(' + FILE_BASE_URL + props.config?.background[0] + ')' : ''}`,
|
||||||
|
height: '100%', 'max-width': '100%',
|
||||||
|
minHeight: '400px',
|
||||||
|
backgroundSize: 'cover',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
position: 'relative',
|
||||||
|
}">
|
||||||
|
<div style="{
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 400px;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}">
|
||||||
|
<div style="{
|
||||||
|
border-radius: 20px;
|
||||||
|
border: 3px solid var(--pinky-border-color-dark);
|
||||||
|
height: 50px;
|
||||||
|
width: 400px;
|
||||||
|
}">
|
||||||
|
<div v-for="song in props.data" :key="song.id">
|
||||||
|
{{song.name}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
Reference in New Issue
Block a user