add more custom item in index

This commit is contained in:
2024-04-14 16:54:57 +08:00
parent cbd2748c71
commit 9a44b5e89c
5 changed files with 329 additions and 6 deletions

View File

@@ -104,6 +104,7 @@ export interface UserSetting {
queue: Setting_Queue queue: Setting_Queue
point: Setting_Point point: Setting_Point
questionDisplay: Setting_QuestionDisplay questionDisplay: Setting_QuestionDisplay
index: Setting_Index
enableFunctions: FunctionTypes[] enableFunctions: FunctionTypes[]
@@ -111,6 +112,13 @@ export interface UserSetting {
songListTemplate: string | null songListTemplate: string | null
scheduleTemplate: string | null scheduleTemplate: string | null
} }
export interface Setting_Index {
videos: string[]
notification: string
links: {
[key: string]: string
}
}
export interface Setting_LiveRequest { export interface Setting_LiveRequest {
orderPrefix: string orderPrefix: string
sortType?: QueueSortType sortType?: QueueSortType
@@ -474,7 +482,7 @@ export enum SongRequestFrom {
Danmaku, Danmaku,
SC, SC,
Web, Web,
Gift Gift,
} }
export enum QueueFrom { export enum QueueFrom {
Manual, Manual,
@@ -716,3 +724,11 @@ export enum PointFrom {
Manual, Manual,
Use, Use,
} }
export interface ResponseUserIndexModel{
notification: string
videos: VideoCollectVideo[]
links: {
[key: string]: string
}
}

View File

@@ -0,0 +1,34 @@
<script setup lang="ts">
import { VideoCollectVideo } from '@/api/api-models'
import { NCard, NEllipsis, NImage, NText } from 'naive-ui'
const props = defineProps<{
video: VideoCollectVideo
width?: number
}>()
</script>
<template>
<NCard size="small" hoverable :style="`max-width: ${width ?? 300}px;`">
<template #cover>
<NImage :src="video.cover" :img-props="{ referrerpolicy: 'no-referrer' }" />
</template>
<template #header>
<a :href="`https://bilibili.com/video/${video.id}`" target="_blank">
<NText>
<NEllipsis>{{ video.title }}</NEllipsis>
</NText>
</a>
</template>
<NText depth="3" style="white-space: pre-line">
<NEllipsis>
{{ video.description }}
<template #tooltip>
<div style="white-space: pre-line; max-width: 300px">
{{ video.description }}
</div>
</template>
</NEllipsis>
</NText>
</NCard>
</template>

View File

@@ -48,6 +48,7 @@ export const VTSURU_API_URL = { toString: () => `${BASE_API_URL}vtsuru/` }
export const POINT_API_URL = { toString: () => `${BASE_API_URL}point/` } export const POINT_API_URL = { toString: () => `${BASE_API_URL}point/` }
export const BILI_AUTH_API_URL = { toString: () => `${BASE_API_URL}bili-auth/` } export const BILI_AUTH_API_URL = { toString: () => `${BASE_API_URL}bili-auth/` }
export const FORUM_API_URL = { toString: () => `${BASE_API_URL}forum/` } export const FORUM_API_URL = { toString: () => `${BASE_API_URL}forum/` }
export const USER_INDEX_API_URL = { toString: () => `${BASE_API_URL}user-index/` }
export const ScheduleTemplateMap = { export const ScheduleTemplateMap = {
'': { '': {

View File

@@ -6,10 +6,28 @@ import {
downloadConfigDirect, downloadConfigDirect,
useAccount, useAccount,
} from '@/api/account' } from '@/api/account'
import { FunctionTypes, ScheduleWeekInfo, SongFrom, SongLanguage, SongRequestOption, SongsInfo } from '@/api/api-models' import {
FunctionTypes,
ResponseUserIndexModel,
ScheduleWeekInfo,
SongFrom,
SongLanguage,
SongRequestOption,
SongsInfo,
VideoCollectVideo,
} from '@/api/api-models'
import { QueryGetAPI, QueryPostAPI } from '@/api/query'
import DynamicForm from '@/components/DynamicForm.vue' import DynamicForm from '@/components/DynamicForm.vue'
import SimpleVideoCard from '@/components/SimpleVideoCard.vue'
import { TemplateConfig } from '@/data/VTsuruTypes' import { TemplateConfig } from '@/data/VTsuruTypes'
import { FETCH_API, IndexTemplateMap, ScheduleTemplateMap, SongListTemplateMap } from '@/data/constants' import {
FETCH_API,
IndexTemplateMap,
ScheduleTemplateMap,
SongListTemplateMap,
USER_INDEX_API_URL,
} from '@/data/constants'
import { Delete24Regular } from '@vicons/fluent'
import { import {
NAlert, NAlert,
NButton, NButton,
@@ -18,15 +36,21 @@ import {
NCheckboxGroup, NCheckboxGroup,
NDivider, NDivider,
NEmpty, NEmpty,
NFlex,
NIcon,
NInput,
NList, NList,
NListItem, NListItem,
NModal, NModal,
NPopconfirm,
NSelect, NSelect,
NSpace, NSpace,
NSpin, NSpin,
NTabPane, NTabPane,
NTabs, NTabs,
NTag,
NText, NText,
NTooltip,
SelectOption, SelectOption,
useMessage, useMessage,
} from 'naive-ui' } from 'naive-ui'
@@ -225,6 +249,15 @@ const selectedTemplateConfig = computed(() => {
const biliUserInfo = ref() const biliUserInfo = ref()
const settingModalVisiable = ref(false) const settingModalVisiable = ref(false)
const showAddVideoModal = ref(false)
const showAddLinkModal = ref(false)
const indexDisplayInfo = ref<ResponseUserIndexModel>()
const addVideoUrl = ref('')
const isLoading = ref(false)
const addLinkName = ref('')
const addLinkUrl = ref('')
const linkKey = ref(0)
async function RequestBiliUserData() { async function RequestBiliUserData() {
await fetch(FETCH_API + `https://account.bilibili.com/api/member/getCardByMid?mid=10021741`).then(async (respone) => { await fetch(FETCH_API + `https://account.bilibili.com/api/member/getCardByMid?mid=10021741`).then(async (respone) => {
@@ -303,6 +336,96 @@ async function SaveTemplateSetting() {
await SaveComboSetting() await SaveComboSetting()
} }
} }
async function updateIndexSettings() {
await QueryPostAPI(USER_INDEX_API_URL + 'update-setting', accountInfo.value.settings.index)
.then((data) => {
if (data.code == 200) {
message.success('已保存')
} else {
message.error('保存失败: ' + data.message)
}
})
.catch((err) => {
message.error('保存失败: ' + err)
})
}
async function addVideo() {
if (!addVideoUrl.value) {
message.error('请输入视频链接')
return
}
isLoading.value = true
await QueryGetAPI<VideoCollectVideo>(USER_INDEX_API_URL + 'add-video', {
video: addVideoUrl.value,
})
.then((data) => {
if (data.code == 200) {
message.success('已添加')
indexDisplayInfo.value?.videos.push(data.data)
accountInfo.value?.settings.index.videos.push(data.data.id)
addVideoUrl.value = ''
} else {
message.error('保存失败: ' + data.message)
}
})
.catch((err) => {
message.error('保存失败: ' + err)
})
.finally(() => {
isLoading.value = false
})
}
async function removeVideo(id: string) {
isLoading.value = true
await QueryGetAPI<VideoCollectVideo>(USER_INDEX_API_URL + 'del-video', {
video: id,
})
.then((data) => {
if (data.code == 200) {
message.success('已删除')
if (indexDisplayInfo.value) {
indexDisplayInfo.value.videos = indexDisplayInfo.value?.videos.filter((v) => v.id != id)
}
accountInfo.value.settings.index.videos = accountInfo.value.settings.index.videos.filter((v) => v != id)
} else {
message.error('删除失败: ' + data.message)
}
})
.catch((err) => {
message.error('删除失败: ' + err)
})
.finally(() => {
isLoading.value = false
})
}
async function addLink() {
if (!addLinkName.value || !addLinkUrl.value) {
message.error('请输入名称和链接')
return
}
try {
new URL(addLinkUrl.value)
} catch (e) {
message.error('请输入正确的链接')
return
}
if (Object.keys(accountInfo.value.settings.index.links).includes(addLinkName.value)) {
message.error(addLinkName.value + '已存在')
return
}
accountInfo.value.settings.index.links[addLinkName.value] = addLinkUrl.value
await updateIndexSettings()
addLinkName.value = ''
addLinkUrl.value = ''
location.reload()
}
async function removeLink(name: string) {
delete accountInfo.value.settings.index.links[name]
await updateIndexSettings()
location.reload()
}
async function onOpenTemplateSettings() { async function onOpenTemplateSettings() {
settingModalVisiable.value = true settingModalVisiable.value = true
nextTick(async () => { nextTick(async () => {
@@ -354,6 +477,23 @@ function unblockUser(id: number) {
message.error(err) message.error(err)
}) })
} }
async function getIndexInfo() {
try {
isLoading.value = true
const data = await QueryGetAPI<ResponseUserIndexModel>(USER_INDEX_API_URL + 'get', { id: accountInfo.value.id })
if (data.code == 200) {
return data.data
} else if (data.code != 404) {
message?.error('无法获取数据: ' + data.message)
return undefined
}
} catch (err) {
message?.error('无法获取数据: ' + err)
return undefined
} finally {
isLoading.value = false
}
}
onActivated(() => { onActivated(() => {
if (route.query.tab) { if (route.query.tab) {
@@ -365,6 +505,10 @@ onActivated(() => {
}) })
onMounted(async () => { onMounted(async () => {
await RequestBiliUserData() await RequestBiliUserData()
indexDisplayInfo.value = await getIndexInfo()
if (route.query.tab) {
message.info('已切换到指定面板, 在页面下方')
}
}) })
</script> </script>
@@ -403,6 +547,57 @@ onMounted(async () => {
</NCheckbox> </NCheckbox>
</NSpace> </NSpace>
</NTabPane> </NTabPane>
<NTabPane tab="主页" name="index">
<NDivider> 通知 </NDivider>
<NInput v-model:value="accountInfo.settings.index.notification" type="textarea" />
<br /><br />
<NButton type="primary" @click="updateIndexSettings"> 保存 </NButton>
<NDivider> 展示视频 </NDivider>
<NButton type="primary" @click="showAddVideoModal = true"> 添加视频 </NButton>
<br /><br />
<NEmpty v-if="accountInfo.settings.index.videos.length == 0" />
<NFlex v-else>
<NCard v-for="item in indexDisplayInfo?.videos ?? []" :key="item.id" style="width: 300px">
<SimpleVideoCard :video="item" />
<template #footer>
<NButton type="warning" @click="removeVideo(item.id)"> 删除 </NButton>
</template>
</NCard>
</NFlex>
<NDivider> 其他链接 </NDivider>
<NButton type="primary" @click="showAddLinkModal = true"> 添加链接 </NButton>
<br /><br />
<NEmpty v-if="Object.entries(indexDisplayInfo?.links ?? {}).length == 0" />
<NFlex v-else :key="linkKey">
<NFlex v-for="item in Object.entries(indexDisplayInfo?.links ?? {})" :key="item[0]" align="center">
<NTooltip>
<template #trigger>
<NTag :bordered="false" size="small" type="info">
{{ item[0] }}
</NTag>
</template>
{{ item[1] }}
</NTooltip>
<NPopconfirm @positive-click="removeLink(item[0])">
<template #trigger>
<NButton type="error" text>
<template #icon>
<NIcon :component="Delete24Regular" />
</template>
</NButton>
</template>
确定要删除这个链接吗?
</NPopconfirm>
</NFlex>
</NFlex>
<NModal v-model:show="showAddLinkModal" :show-icon="false" preset="dialog" title="添加链接">
<NFlex vertical>
<NInput v-model:value="addLinkName" placeholder="链接名称" />
<NInput v-model:value="addLinkUrl" placeholder="链接地址" />
<NButton type="primary" @click="addLink"> 添加 </NButton>
</NFlex>
</NModal>
</NTabPane>
<NTabPane tab="黑名单" name="blacklist"> <NTabPane tab="黑名单" name="blacklist">
<NList v-if="accountInfo.biliBlackList && Object.keys(accountInfo.biliBlackList).length > 0"> <NList v-if="accountInfo.biliBlackList && Object.keys(accountInfo.biliBlackList).length > 0">
<NListItem v-for="item in Object.entries(accountInfo.biliBlackList)" :key="item[0]"> <NListItem v-for="item in Object.entries(accountInfo.biliBlackList)" :key="item[0]">
@@ -470,4 +665,15 @@ onMounted(async () => {
:config="selectedTemplateConfig" :config="selectedTemplateConfig"
/> />
</NModal> </NModal>
<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>
</NModal>
</template> </template>

View File

@@ -1,9 +1,15 @@
<script lang="ts" setup> <script lang="ts" setup>
import { isDarkMode } from '@/Utils' import { isDarkMode } from '@/Utils'
import { UserInfo } from '@/api/api-models' import { useAccount } from '@/api/account'
import { ResponseUserIndexModel, UserInfo } from '@/api/api-models'
import { QueryGetAPI } from '@/api/query'
import SimpleVideoCard from '@/components/SimpleVideoCard.vue'
import { TemplateConfig } from '@/data/VTsuruTypes' import { TemplateConfig } from '@/data/VTsuruTypes'
import { NAvatar, NButton, NDivider, NSpace, NText } from 'naive-ui' import { USER_INDEX_API_URL } from '@/data/constants'
import { NAlert, NAvatar, NButton, NCard, NDivider, NFlex, NSpace, NText, useMessage } from 'naive-ui'
import { ref } from 'vue'
defineExpose({ Config, DefaultConfig })
const width = window.innerWidth const width = window.innerWidth
const props = defineProps<{ const props = defineProps<{
@@ -11,10 +17,32 @@ const props = defineProps<{
biliInfo: any | undefined biliInfo: any | undefined
currentData?: any currentData?: any
}>() }>()
const isLoading = ref(true)
const message = useMessage()
const accountInfo = useAccount()
const indexInfo = ref<ResponseUserIndexModel>((await getIndexInfo()) || ({} as ResponseUserIndexModel))
async function getIndexInfo() {
try {
isLoading.value = true
const data = await QueryGetAPI<ResponseUserIndexModel>(USER_INDEX_API_URL + 'get', { id: props.userInfo?.name })
if (data.code == 200) {
return data.data
} else if (data.code != 404) {
message?.error('无法获取数据: ' + data.message)
return undefined
}
} catch (err) {
message?.error('无法获取数据: ' + err)
return undefined
} finally {
isLoading.value = false
}
}
function navigate(url: string) { function navigate(url: string) {
window.open(url, '_blank') window.open(url, '_blank')
} }
defineExpose({ Config, DefaultConfig })
</script> </script>
<script lang="ts"> <script lang="ts">
@@ -50,6 +78,19 @@ export const Config: TemplateConfig<ConfigType> = {
<template> <template>
<NDivider /> <NDivider />
<template v-if="userInfo?.biliId"> <template v-if="userInfo?.biliId">
<template v-if="userInfo?.id == accountInfo?.id">
<NButton type="primary" @click="$router.push({ name: 'manage-index', query: { tab: 'index' } })">
自定义个人主页
</NButton>
<NDivider />
</template>
<template v-if="indexInfo?.notification">
<NCard size="small" content-style="text-align: center">
{{ indexInfo?.notification }}
</NCard>
<br />
</template>
<NSpace justify="center" align="center" vertical> <NSpace justify="center" align="center" vertical>
<NAvatar <NAvatar
v-if="biliInfo" v-if="biliInfo"
@@ -79,7 +120,31 @@ export const Config: TemplateConfig<ConfigType> = {
<NButton type="primary" secondary @click="navigate('https://live.bilibili.com/' + userInfo?.biliRoomId)"> <NButton type="primary" secondary @click="navigate('https://live.bilibili.com/' + userInfo?.biliRoomId)">
直播间 直播间
</NButton> </NButton>
<temlate v-if="Object.keys(indexInfo.links || {}).length > 0">
<NFlex align="center">
<NDivider vertical />
<NButton
type="info"
secondary
tag="a"
:href="link[1]"
target="_blank"
v-for="link in Object.entries(indexInfo.links || {})"
:key="link[0] + link[1]"
>
{{ link[0] }}
</NButton>
</NFlex>
</temlate>
</NSpace> </NSpace>
<template v-if="indexInfo.videos?.length || 0 > 0">
<NDivider>
<NText>相关视频</NText>
</NDivider>
<NFlex justify="center">
<SimpleVideoCard v-for="video in indexInfo.videos" :video="video" :key="video.id" />
</NFlex>
</template>
</template> </template>
<template v-else> <template v-else>
<NSpace justify="center" align="center"> <NSpace justify="center" align="center">
@@ -88,3 +153,4 @@ export const Config: TemplateConfig<ConfigType> = {
</NSpace> </NSpace>
</template> </template>
</template> </template>
import { QueryGetAPI } from '@/api/query' import { USER_INDEX_API_URL } from '@/data/constants'