cancel no payment

This commit is contained in:
2024-10-21 18:02:22 +08:00
parent 60b609ac26
commit c07257bb44
16 changed files with 337 additions and 227 deletions

View File

@@ -1,5 +1,7 @@
{
"tabWidth": 2,
"semi": false,
"singleQuote": true
"singleQuote": true,
"trailingComma": "none",
"printWidth": 80
}

View File

@@ -1,7 +1,10 @@
<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 { ref } from 'vue'
import { onMounted, ref } from 'vue'
const message = useMessage()
@@ -10,23 +13,78 @@ const props = defineProps<{
config: TemplateConfig<any> | undefined
}>()
const fileList = ref<UploadFileInfo[]>([])
const fileList = ref<{ [key: string]: UploadFileInfo[] }>({})
function OnFileListChange(files: UploadFileInfo[]) {
console.log(props.configData)
const isUploading = ref(false)
function OnFileListChange(key: string, files: UploadFileInfo[]) {
if (files.length == 1) {
var file = files[0]
if ((file.file?.size ?? 0) > 10 * 1024 * 1024) {
message.error('文件大小不能超过10MB')
fileList.value = []
fileList.value[key] = []
}
}
}
function onSubmit() {
console.log(props.configData)
async function onSubmit() {
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>
<template>
@@ -35,22 +93,17 @@ function getItems() {}
<NFormItem v-for="item in config.items" :key="item.name" :label="item.name">
<component v-if="item.type == 'render'" :is="item.render(configData)"></component>
<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>
<NUpload
v-else-if="item.type == 'image'"
accept=".png,.jpg,.jpeg,.gif,.svg,.webp,.ico"
list-type="image-card"
:default-upload="false"
v-model:file-list="fileList"
@update:file-list="OnFileListChange"
:max="item.imageLimit"
>
<NUpload v-else-if="item.type == 'image'" accept=".png,.jpg,.jpeg,.gif,.svg,.webp,.ico" list-type="image-card"
:default-upload="false" v-model:file-list="fileList[item.key]"
@update:file-list="file => OnFileListChange(item.key, file)" :max="item.imageLimit" im>
上传图片
</NUpload>
</NFormItem>
<NFormItem>
<NButton type="primary" @click="onSubmit">提交</NButton>
<NButton type="primary" @click="onSubmit" :loading="isUploading" >提交</NButton>
</NFormItem>
</NForm>
</template>

View File

@@ -50,7 +50,7 @@ const props = defineProps<{
songs: SongsInfo[]
canEdit?: boolean
isSelf: boolean
extraButtom?: (song: SongsInfo) => VNodeChild[]
extraButton?: (song: SongsInfo) => VNodeChild[]
}>()
watch(
() => props.songs,
@@ -380,7 +380,7 @@ function createColumns(): DataTableColumns<SongsInfo> {
}),
]
: null,
props.extraButtom?.(data),
props.extraButton?.(data),
],
)
},

23
src/data/TemplateTypes.ts Normal file
View 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
}

View File

@@ -23,10 +23,11 @@ type DataAccessor<T, V> = {
}
// 扩展 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> & {
type: 'string'
key: string //将被保存到指定key中
}
export type TemplateConfigStringArrayItem<T> = TemplateConfigItemWithType<T, string[]> & {
type: 'stringArray'
@@ -42,8 +43,19 @@ export type TemplateConfigRenderItem<T> = TemplateConfigBase & {
type: 'render'
render: (arg0: T) => VNode
}
/**
*
* @template T - The type of the associated data model.
*/
export type TemplateConfigImageItem<T> = TemplateConfigBase & {
type: 'image'
imageLimit: number
onUploaded: (arg0: string | string[], arg1: T) => void
type: 'image' // Specifies the type of configuration item as 'image'.
imageLimit: number // The maximum number of images allowed.
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
}

View File

@@ -70,6 +70,10 @@ export const SongListTemplateMap = {
name: '简单',
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 } }
export const IndexTemplateMap = {
'': { name: '默认', compoent: DefaultIndexTemplateVue },

View File

@@ -86,11 +86,6 @@ const functions = [
desc: '绑定账号后查看粉丝 舰长 观看数 等数据的历史记录',
icon: AnalyticsSharp,
},
{
name: '绝不收米!',
desc: '为爱发电, 所有功能都免费并提供技术支持 (当然你想赞助的话也可以捏',
icon: MoneyOff24Filled,
},
{
name: '还有更多',
desc: '更多功能仍在开发中. 有其他合理需求或者建议, 或者有想要添加的样式? 向我提出!',

View File

@@ -55,7 +55,7 @@ import {
SelectOption,
useMessage,
} 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'
interface TemplateDefineTypes {
@@ -247,7 +247,6 @@ const selectedTemplateConfig = computed(() => {
}
return undefined
})
const biliUserInfo = ref()
const settingModalVisiable = ref(false)
const showAddVideoModal = ref(false)
@@ -547,19 +546,15 @@ onMounted(async () => {
<NCheckbox v-model:checked="accountInfo.settings.sendEmail.recieveQA" @update:checked="SaveComboSetting">
收到新提问时发送邮件
</NCheckbox>
<NCheckbox
v-model:checked="accountInfo.settings.sendEmail.recieveQAReply"
@update:checked="SaveComboSetting"
>
<NCheckbox v-model:checked="accountInfo.settings.sendEmail.recieveQAReply"
@update:checked="SaveComboSetting">
提问收到回复时发送邮件
</NCheckbox>
</NSpace>
<NDivider> 提问箱 </NDivider>
<NSpace>
<NCheckbox
v-model:checked="accountInfo.settings.questionBox.allowUnregistedUser"
@update:checked="SaveComboSetting"
>
<NCheckbox v-model:checked="accountInfo.settings.questionBox.allowUnregistedUser"
@update:checked="SaveComboSetting">
允许未注册用户提问
</NCheckbox>
</NSpace>
@@ -649,28 +644,22 @@ onMounted(async () => {
<br />
<NSpace vertical>
<NSpace align="center">
页面 <NSelect :options="templateOptions" v-model:value="selectedOption" style="width: 150px" />
页面
<NSelect :options="templateOptions" v-model:value="selectedOption" style="width: 150px" />
</NSpace>
<NDivider style="margin: 5px 0 5px 0" title-placement="left"> 模板 </NDivider>
<div>
<NSpace>
<NSelect
style="width: 150px"
:options="selectedTemplateData.Options"
v-model:value="selectedTemplateData.Selected"
/>
<NSelect style="width: 150px" :options="selectedTemplateData.Options"
v-model:value="selectedTemplateData.Selected" />
<component :is="buttonGroup" />
</NSpace>
<NDivider />
<Transition name="fade" mode="out-in">
<div v-if="selectedComponent" :key="selectedTemplateData.Selected">
<component
ref="dynamicConfigRef"
:is="selectedComponent"
:user-info="accountInfo"
:bili-info="biliUserInfo"
:current-data="selectedTemplateData.Data"
/>
<component ref="dynamicConfigRef" @vue:mounted="getTemplateConfig" :is="selectedComponent"
:user-info="accountInfo" :bili-info="biliUserInfo" :current-data="selectedTemplateData.Data"
:config="selectedTemplateData.Config" />
</div>
</Transition>
</div>
@@ -679,29 +668,13 @@ onMounted(async () => {
</NTabs>
</NSpin>
</NCard>
<NModal
preset="card"
v-model:show="settingModalVisiable"
closable
style="width: 600px; max-width: 90vw"
title="模板设置"
>
<NModal preset="card" v-model:show="settingModalVisiable" closable style="width: 600px; max-width: 90vw" title="模板设置">
只是测试, 没用
<NSpin v-if="!selectedTemplateData.Config" show />
<DynamicForm
v-else
:key="selectedTemplateData.Selected"
:configData="selectedTemplateData.Config"
:config="selectedTemplateConfig"
/>
<DynamicForm v-else :key="selectedTemplateData.Selected" :configData="selectedTemplateData.Config"
:config="selectedTemplateConfig" />
</NModal>
<NModal
preset="card"
v-model:show="showAddVideoModal"
closable
style="width: 600px; max-width: 90vw"
title="添加视频"
>
<NModal preset="card" v-model:show="showAddVideoModal" closable style="width: 600px; max-width: 90vw" title="添加视频">
<NInput v-model:value="addVideoUrl" placeholder="请输入视频链接" />
<NDivider />
<NButton type="primary" @click="addVideo" :loading="isLoading"> 添加视频 </NButton>

View File

@@ -2,35 +2,36 @@
import { NavigateToNewTab } from '@/Utils'
import { useAccount } from '@/api/account'
import {
AddressInfo,
GoodsTypes,
ResponsePointGoodModel,
ResponsePointOrder2UserModel,
UserInfo,
AddressInfo,
GoodsTypes,
ResponsePointGoodModel,
ResponsePointOrder2UserModel,
UserInfo,
} from '@/api/api-models'
import AddressDisplay from '@/components/manage/AddressDisplay.vue'
import PointGoodsItem from '@/components/manage/PointGoodsItem.vue'
import { POINT_API_URL } from '@/data/constants'
import { useAuthStore } from '@/store/useAuthStore'
import {
NAlert,
NButton,
NCard,
NDivider,
NEmpty,
NFlex,
NForm,
NFormItem,
NInputNumber,
NModal,
NSelect,
NSpin,
NTag,
NText,
NTooltip,
SelectOption,
useDialog,
useMessage
NAlert,
NButton,
NCard,
NCheckbox,
NDivider,
NEmpty,
NFlex,
NForm,
NFormItem,
NInputNumber,
NModal,
NSelect,
NSpin,
NTag,
NText,
NTooltip,
SelectOption,
useDialog,
useMessage
} from 'naive-ui'
import { computed, h, onMounted, ref } from 'vue'
import { useRouter } from 'vue-router'
@@ -65,11 +66,12 @@ const tags = computed(() => {
})
const selectedTag = ref<string>()
const selectedItems = computed(() => {
return selectedTag.value
? goods.value.filter((g) => g.tags.includes(selectedTag.value))
: goods.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)
})
const onlyCanBuy = ref(false)
const ignoreGuard = ref(false)
const addressOptions = computed(() => {
if (!biliAuth.value.id) return []
return (
@@ -86,7 +88,7 @@ const canBuy = computed(() => {
if (!biliAuth.value.id) return false
return true
})
function getTooltip(goods: ResponsePointGoodModel) {
function getTooltip(goods: ResponsePointGoodModel): '开始兑换' | '当前积分不足' | '请先进行账号认证' | '库存不足' {
if ((currentPoint.value ?? 0) < goods.price) {
return '当前积分不足'
} else if (!biliAuth.value.id) return '请先进行账号认证'
@@ -215,34 +217,29 @@ onMounted(async () => {
<NText> 你在 {{ userInfo.extra?.streamerInfo?.name ?? userInfo.name }} 的直播间的积分为 {{ currentPoint }} </NText>
</NCard>
<NDivider />
<NCard size="small" title="标签">
<NCard v-if="tags.length > 0" size="small" title="标签筛选">
<NFlex align="center" justify="center">
<NButton v-for="tag in tags"
:type="tag == selectedTag ? 'success' : 'default'"
@click="selectedTag = selectedTag == tag ? undefined : tag"
:borderd="false"
style="margin: 4px"
size="small">
<NButton v-for="tag in tags" :type="tag == selectedTag ? 'success' : 'default'"
@click="selectedTag = selectedTag == tag ? undefined : tag" :borderd="false" style="margin: 4px" size="small">
{{ tag }}
</NButton>
</NFlex>
<NDivider />
<NCheckbox v-model:checked="onlyCanBuy"> 只显示可兑换的礼物 </NCheckbox>
<NCheckbox v-model:checked="ignoreGuard"> 忽略需要舰长的礼物 </NCheckbox>
</NCard>
<NDivider />
<NSpin :show="isLoading">
<NEmpty v-if="selectedItems.length == 0"> 暂无礼物 </NEmpty>
<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>
<NFlex justify="space-between" align="center">
<NTooltip>
<template #trigger>
<NButton
:disabled="getTooltip(item) != '开始兑换'"
size="small"
type="primary"
@click="onBuyClick(item)"
>兑换</NButton
>
<NButton :disabled="getTooltip(item) != '开始兑换'" size="small" type="primary" @click="onBuyClick(item)">兑换
</NButton>
</template>
{{ getTooltip(item) }}
</NTooltip>
@@ -262,13 +259,8 @@ onMounted(async () => {
</PointGoodsItem>
</NFlex>
</NSpin>
<NModal
v-model:show="showBuyModal"
v-if="currentGoods"
preset="card"
title="确认兑换"
style="width: 500px; max-width: 90vw; height: auto"
>
<NModal v-model:show="showBuyModal" v-if="currentGoods" preset="card" title="确认兑换"
style="width: 500px; max-width: 90vw; height: auto">
<template #header>
<NFlex align="baseline">
<NTag :type="currentGoods.type == GoodsTypes.Physical ? 'info' : 'default'" :bordered="false">
@@ -281,32 +273,16 @@ onMounted(async () => {
<template v-if="currentGoods.type == GoodsTypes.Physical">
<NDivider> 选项 </NDivider>
<NForm>
<NFormItem label="兑换数量" required
><NInputNumber
v-model:value="buyCount"
:min="1"
:max="currentGoods.maxBuyCount ?? 100000"
style="max-width: 120px"
step="1"
:precision="0"
/>
<NFormItem label="兑换数量" required>
<NInputNumber v-model:value="buyCount" :min="1" :max="currentGoods.maxBuyCount ?? 100000"
style="max-width: 120px" step="1" :precision="0" />
</NFormItem>
<NFormItem
v-if="
<NFormItem v-if="
currentGoods.type == GoodsTypes.Physical &&
(currentGoods.collectUrl == null || currentGoods.collectUrl == undefined)
"
label="收货地址"
required
>
<NSelect
v-model:show="showAddressSelect"
:value="selectedAddress?.id"
:options="addressOptions"
:render-label="renderLabel"
:render-option="renderOption"
placeholder="请选择地址"
/>
" label="收货地址" required>
<NSelect v-model:show="showAddressSelect" :value="selectedAddress?.id" :options="addressOptions"
:render-label="renderLabel" :render-option="renderOption" placeholder="请选择地址" />
&nbsp;
<NButton size="small" type="info" tag="a" href="/bili-user#settings" target="_blank"> 管理收货地址 </NButton>
</NFormItem>

View File

@@ -1,25 +1,19 @@
<template>
<NSpin v-if="isLoading" show />
<component
v-else
:is="SongListTemplateMap[componentType ?? '']?.compoent"
:user-info="userInfo"
:bili-info="biliInfo"
:currentData="currentData"
:live-request-settings="settings"
:live-request-active="songsActive"
@request-song="requestSong"
v-bind="$attrs"
/>
<component v-else ref="dynamicConfigRef" :config="currentConfig"
:is="SongListTemplateMap[componentType ?? '']?.compoent" :user-info="userInfo" :bili-info="biliInfo"
:currentData="currentData" :live-request-settings="settings" :live-request-active="songsActive"
@request-song="requestSong" v-bind="$attrs" />
</template>
<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 { 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 { computed, onMounted, ref } from 'vue'
import { computed, onMounted, ref, watch, watchEffect } from 'vue'
const accountInfo = useAccount()
@@ -35,6 +29,21 @@ const componentType = computed(() => {
return props.template ?? props.userInfo?.extra?.templateTypes['songlist']?.toLowerCase()
})
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 message = useMessage()
@@ -53,7 +62,7 @@ async function getSongRequestInfo() {
if (data.code == 200) {
return data.data
}
} catch (err) {}
} catch (err) { }
return {} as { songs: SongRequestInfo[]; setting: Setting_LiveRequest }
}
async function getSongs() {
@@ -76,6 +85,23 @@ async function getSongs() {
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) {
if (song.options || !settings.value.allowFromWeb) {
navigator.clipboard.writeText(`${settings.value.orderPrefix} ${song.name}`)

View File

@@ -47,7 +47,7 @@ function navigate(url: string) {
<script lang="ts">
export type ConfigType = {
cover: string
test: string
}
export const DefaultConfig = {} as ConfigType
export const Config: TemplateConfig<ConfigType> = {
@@ -57,19 +57,12 @@ export const Config: TemplateConfig<ConfigType> = {
name: '封面',
type: 'image',
imageLimit: 1,
onUploaded: (url, config) => {
config.cover = url instanceof String ? (url as string) : url[0]
},
key: 'cover',
},
{
name: 'test',
name: '测试',
type: 'string',
data: {
get: (d) => d.cover,
set: (d, v) => {
d.cover = v
},
},
key: 'test',
},
],
}

View File

@@ -1,20 +1,16 @@
<script setup lang="ts">
import { useAccount } from '@/api/account'
import { ScheduleWeekInfo, UserInfo } from '@/api/api-models'
import ScheduleList from '@/components/ScheduleList.vue'
import { NDivider } from 'naive-ui'
import { useAccount } from '@/api/account';
import ScheduleList from '@/components/ScheduleList.vue';
import { ScheduleConfigType } from '@/data/TemplateTypes';
import { NDivider } from 'naive-ui';
const accountInfo = useAccount()
defineProps<{
userInfo: UserInfo | undefined
biliInfo: any | undefined
currentData: ScheduleWeekInfo[] | undefined
}>()
defineProps<ScheduleConfigType>()
</script>
<template>
<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 />
</template>

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import { ScheduleWeekInfo, UserInfo } from '@/api/api-models'
import SaveCompoent from '@/components/SaveCompoent.vue'
import { ScheduleConfigType } from '@/data/TemplateTypes'
import { getWeek, getYear } from 'date-fns'
import { NDivider, NSelect, NSpace } from 'naive-ui'
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 table = ref()
const props = defineProps<{
userInfo: UserInfo | undefined
biliInfo: any | undefined
currentData: ScheduleWeekInfo[] | undefined
}>()
const props = defineProps<ScheduleConfigType>()
const currentWeek = computed(() => {
if (props.currentData?.length == 1) {
return props.currentData[0]
if (props.data?.length == 1) {
return props.data[0]
}
return props.currentData?.find((item) => {
return props.data?.find((item) => {
if (selectedDate.value) {
return item.year + '-' + item.week === selectedDate.value
}
@@ -26,7 +22,7 @@ const currentWeek = computed(() => {
})
})
const options = computed(() => {
return props.currentData?.map((item) => {
return props.data?.map((item) => {
return {
label: item.year + '年' + item.week + '周',
value: item.year + '-' + item.week,

View File

@@ -1,7 +1,8 @@
<script setup lang="ts">
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 { SongListConfigType } from '@/data/TemplateTypes'
import LiveRequestOBS from '@/views/obs/LiveRequestOBS.vue'
import { CloudAdd20Filled } from '@vicons/fluent'
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 props = defineProps<{
userInfo: UserInfo | undefined
biliInfo: any | undefined
songRequestSettings: Setting_LiveRequest
songRequestActive: SongRequestInfo[]
currentData?: SongsInfo[] | undefined
}>()
const props = defineProps<SongListConfigType>()
const emits = defineEmits(['requestSong'])
const isLoading = ref('')
const message = useMessage()
const buttoms = (song: SongsInfo) => [
const buttons = (song: SongsInfo) => [
accountInfo.value?.id != props.userInfo?.id
? h(
NTooltip,
@@ -62,10 +57,10 @@ const buttoms = (song: SongsInfo) => [
<template>
<NDivider style="margin-top: 10px" />
<SongList
v-if="currentData"
:songs="currentData ?? []"
v-if="data"
:songs="data ?? []"
:is-self="accountInfo?.id == userInfo?.id"
:extra-buttom="buttoms"
:extraButton="buttons"
v-bind="$attrs"
/>
<NCollapse v-if="userInfo?.canRequestSong">

View File

@@ -1,8 +1,9 @@
<script setup lang="ts">
import { GetGuardColor } from '@/Utils'
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 { SongListConfigType } from '@/data/TemplateTypes'
import LiveRequestOBS from '@/views/obs/LiveRequestOBS.vue'
import { CloudAdd20Filled, Play24Filled } from '@vicons/fluent'
import { useWindowSize } from '@vueuse/core'
@@ -28,13 +29,7 @@ import {
} from 'naive-ui'
import { computed, ref } from 'vue'
const props = defineProps<{
userInfo: UserInfo | undefined
biliInfo: any | undefined
songRequestSettings: Setting_LiveRequest
songRequestActive: SongRequestInfo[]
currentData: SongsInfo[] | undefined
}>()
const props = defineProps<SongListConfigType>()
const emits = defineEmits(['requestSong'])
const windowSize = useWindowSize()
const container = ref()
@@ -51,10 +46,10 @@ const isLrcLoading = ref('')
const isLoading = ref('')
const tags = computed(() => {
if (props.currentData) {
if (props.data) {
return [
...new Set(
props.currentData
props.data
.map((item) => {
return item.tags ?? []
})
@@ -65,10 +60,10 @@ const tags = computed(() => {
return []
})
const authors = computed(() => {
if (props.currentData) {
if (props.data) {
return [
...new Set(
props.currentData
props.data
.map((item) => {
return item.author ?? []
})
@@ -79,8 +74,8 @@ const authors = computed(() => {
return []
})
const songs = computed(() => {
if (props.currentData) {
return props.currentData
if (props.data) {
return props.data
.filter((item) => {
return (
(!selectedTag.value || item.tags?.includes(selectedTag.value)) &&
@@ -99,8 +94,8 @@ const onScroll = throttle((e: Event) => {
}
}, 100)
function loadMore() {
if (props.currentData) {
index.value += props.currentData.length > 20 + index.value ? 20 : props.currentData.length - index.value
if (props.data) {
index.value += props.data.length > 20 + index.value ? 20 : props.data.length - index.value
}
}
</script>
@@ -151,7 +146,7 @@ function loadMore() {
/>
</NSpace>
</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
v-else
ref="container"
@@ -301,7 +296,7 @@ function loadMore() {
</NGrid>
<NDivider />
<NSpace justify="center">
<NButton v-if="currentData.length > index" @click="loadMore"> 加载更多 </NButton>
<NButton v-if="data.length > index" @click="loadMore"> 加载更多 </NButton>
</NSpace>
</NScrollbar>
</div>

View File

@@ -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>