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,
|
||||
"semi": false,
|
||||
"singleQuote": true
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"printWidth": 80
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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
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 以包含额外的共有属性
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -86,11 +86,6 @@ const functions = [
|
||||
desc: '绑定账号后查看粉丝 舰长 观看数 等数据的历史记录',
|
||||
icon: AnalyticsSharp,
|
||||
},
|
||||
{
|
||||
name: '绝不收米!',
|
||||
desc: '为爱发电, 所有功能都免费并提供技术支持 (当然你想赞助的话也可以捏',
|
||||
icon: MoneyOff24Filled,
|
||||
},
|
||||
{
|
||||
name: '还有更多',
|
||||
desc: '更多功能仍在开发中. 有其他合理需求或者建议, 或者有想要添加的样式? 向我提出!',
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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="请选择地址" />
|
||||
|
||||
<NButton size="small" type="info" tag="a" href="/bili-user#settings" target="_blank"> 管理收货地址 </NButton>
|
||||
</NFormItem>
|
||||
|
||||
@@ -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}`)
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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