feat:订阅历史

This commit is contained in:
jxxghp
2024-04-18 21:00:35 +08:00
parent 2d7880351b
commit 62bb6de80d
12 changed files with 403 additions and 347 deletions

View File

@@ -0,0 +1,197 @@
<script lang="ts" setup>
import api from '@/api';
import { Subscribe } from '@/api/types';
import { formatDateDifference } from '@core/utils/formatters'
// 输入参数
const props = defineProps({
type: String,
})
// 定义触发的自定义事件
const emit = defineEmits(['close'])
// 订阅历史列表
const historyList = ref<Subscribe[]>([])
// 当前加载数据
const currData = ref<Subscribe[]>([])
// 当前页
const currentPage = ref(1)
// 每页数量
const pageSize = ref(30)
// 是否加载中
const loading = ref(false)
// 是否加载完成
const isRefreshed = ref(false)
// 调用API查询列表
async function loadHistory({ done }: { done: any }) {
// 如果正在加载中,直接返回
if (loading.value) {
done('ok')
return
}
// 设置加载中
loading.value = true
// 调用API查询列表
try {
currData.value = await api.get(`subscribe/history/${props.type}`, {
params: {
page: currentPage.value,
count: pageSize.value,
},
})
// 标计为已请求完成
isRefreshed.value = true
if (currData.value.length === 0) {
// 如果没有数据,跳出
done('ok')
return
}
// 合并数据
historyList.value = [...historyList.value, ...currData.value]
// 页码+1
currentPage.value++
// 取消加载中
loading.value = false
// 返回加载成功
done('ok')
} catch (e) {
console.error(e)
// 返回加载失败
done('error')
}
}
// 重新订阅
function reSubscribe(item: Subscribe) {
}
// 删除记录
function deleteHistory(item: Subscribe) {
}
// 弹出菜单
const dropdownItems = ref([
{
title: '重新订阅',
value: 1,
color: '',
props: {
prependIcon: 'mdi-redo',
click: reSubscribe,
},
},
{
title: '删除',
value: 2,
color: 'error',
props: {
prependIcon: 'mdi-delete',
click: deleteHistory,
},
}
])
</script>
<template>
<VCard
class="mx-auto"
width="100%"
:title = "props.type + '订阅历史'"
>
<DialogCloseBtn @click="() => { emit('close') }" />
<div
v-if="!isRefreshed"
class="mt-12 w-full text-center text-gray-500 text-sm flex flex-col items-center"
>
<VProgressCircular
size="48"
indeterminate
color="primary"
/>
</div>
<VList
lines="two"
>
<VInfiniteScroll
mode="intersect"
side="end"
:items="historyList"
class="overflow-hidden"
@load="loadHistory"
>
<template #loading />
<template v-for="(item, i) in historyList" :key="i">
<VListItem>
<template #prepend>
<VImg
height="75"
width="50"
:src="item.poster"
aspect-ratio="2/3"
class="object-cover rounded shadow ring-gray-500 me-3"
cover
>
<template #placeholder>
<div class="w-full h-full">
<VSkeletonLoader class="object-cover aspect-w-2 aspect-h-3" />
</div>
</template>
</VImg>
</template>
<VListItemTitle v-if="item.type == '电视剧'">
{{ item.name }} <span class="text-sm"> {{ item.season }} </span>
</VListItemTitle>
<VListItemTitle v-else>
{{ item.name }}
</VListItemTitle>
<VListItemSubtitle class="mt-2">{{ formatDateDifference(item.date) }}</VListItemSubtitle>
<VListItemSubtitle class="mt-2">{{ item.description }}</VListItemSubtitle>
<template #append>
<div class="me-n3">
<IconBtn>
<VIcon
icon="mdi-dots-vertical"
/>
<VMenu
activator="parent"
close-on-content-click
>
<VList>
<VListItem
v-for="(item, i) in dropdownItems"
:key="i"
variant="plain"
:base-color="item.color"
@click="item.props.click"
>
<template #prepend>
<VIcon :icon="item.props.prependIcon" />
</template>
<VListItemTitle v-text="item.title" />
</VListItem>
</VList>
</VMenu>
</IconBtn>
</div>
</template>
</VListItem>
</template>
</VInfiniteScroll>
</VList>
<VCardText v-if="historyList.length == 0 && isRefreshed" class="text-center">
没有数据
</VCardText>
</VCard>
</template>

View File

@@ -0,0 +1,137 @@
<script lang="ts" setup>
import api from '@/api'
import type { MediaInfo } from '@/api/types'
interface TmdbItem {
title: string
overview: string
tmdbid: number
poster: string
}
// update:modelValue 事件
const emit = defineEmits(['update:modelValue', 'close'])
const items = ref<TmdbItem[]>([])
// 搜索词
const keyword = ref('')
// 加载中
const loading = ref(false)
// ref
const tmdbKeyword = ref<HTMLElement | null>(null)
// 选中条目
function selectMedia(item: TmdbItem) {
emit('update:modelValue', item.tmdbid)
emit('close')
}
// TMDB图片转换为w500大小
function getW500Image(url = '') {
if (!url)
return ''
return url.replace('original', 'w500')
}
// 搜索词条
async function searchMedias() {
if (!keyword)
return
// 调用API搜索词条
try {
loading.value = true
const result: MediaInfo[] = await api.get('media/search', {
params: {
title: keyword.value,
page: 1,
count: 20,
},
})
// 清空
items.value = []
// 赋值
for (const item of result) {
items.value.push({
tmdbid: item.tmdb_id || 0,
poster: getW500Image(item.poster_path),
title: `${item.title}${item.year}`,
overview: `<span class="text-primary">${item.type}</span> ${item.overview}`,
})
}
loading.value = false
}
catch (e) {
console.error(e)
}
}
// 加载时聚焦搜索框
onMounted(() => {
// 500ms后聚焦
setTimeout(() => {
tmdbKeyword.value?.focus()
}, 500)
})
</script>
<template>
<VCard
class="mx-auto"
width="100%"
>
<VToolbar flat class="p-0">
<VTextField
ref="tmdbKeyword"
v-model="keyword"
label="输入名称搜索"
single-line
placeholder="电影或电视剧名称"
variant="solo"
prepend-inner-icon="mdi-magnify"
flat
class="mx-1"
:loading="loading"
@click:append-inner="searchMedias"
@keydown.enter="searchMedias"
/>
</VToolbar>
<DialogCloseBtn @click="() => { emit('close') }" />
<VList
v-if="items.length > 0"
lines="three"
>
<template v-for="(item, i) in items" :key="i">
<VListItem
@click="selectMedia(item)"
>
<template #prepend>
<VImg
height="75"
width="50"
:src="item.poster"
aspect-ratio="2/3"
class="object-cover rounded shadow ring-gray-500 me-3"
cover
>
<template #placeholder>
<div class="w-full h-full">
<VSkeletonLoader class="object-cover aspect-w-2 aspect-h-3" />
</div>
</template>
</VImg>
</template>
<VListItemTitle>
{{ item.title }}
</VListItemTitle>
<VListItemSubtitle class="mt-2" v-html="item.overview" />
</VListItem>
</template>
</VList>
</VCard>
</template>