media card icon

This commit is contained in:
jxxghp
2024-04-27 18:16:05 +08:00
parent 2cf95c6706
commit fce10b6dca
3 changed files with 86 additions and 154 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -8,6 +8,9 @@ import { doneNProgress, startNProgress } from '@/api/nprogress'
import type { MediaInfo, NotExistMediaInfo, Subscribe, TmdbSeason } from '@/api/types' import type { MediaInfo, NotExistMediaInfo, Subscribe, TmdbSeason } from '@/api/types'
import router from '@/router' import router from '@/router'
import noImage from '@images/no-image.jpeg' import noImage from '@images/no-image.jpeg'
import tmdbImage from '@images/logos/tmdb.png'
import doubanImage from '@images/logos/douban-black.png'
import bangumiImage from '@images/logos/bangumi.png'
// 输入参数 // 输入参数
const props = defineProps({ const props = defineProps({
@@ -52,31 +55,35 @@ const seasonInfos = ref<TmdbSeason[]>([])
// 选中的订阅季 // 选中的订阅季
const seasonsSelected = ref<TmdbSeason[]>([]) const seasonsSelected = ref<TmdbSeason[]>([])
// 来源角标字典
const sourceIconDict = {
themoviedb: tmdbImage,
douban: doubanImage,
bangumi: bangumiImage,
}
// 获得mediaid // 获得mediaid
function getMediaId() { function getMediaId() {
return props.media?.tmdb_id return props.media?.tmdb_id
? `tmdb:${props.media?.tmdb_id}` ? `tmdb:${props.media?.tmdb_id}`
: props.media?.douban_id : props.media?.douban_id
? `douban:${props.media?.douban_id}` ? `douban:${props.media?.douban_id}`
: `bangumi:${props.media?.bangumi_id}` : `bangumi:${props.media?.bangumi_id}`
} }
// 订阅弹窗选择的多季 // 订阅弹窗选择的多季
function subscribeSeasons() { function subscribeSeasons() {
subscribeSeasonDialog.value = false subscribeSeasonDialog.value = false
seasonsSelected.value.forEach((season) => { seasonsSelected.value.forEach(season => {
addSubscribe(season.season_number) addSubscribe(season.season_number)
}) })
} }
// 角标颜色 // 角标颜色
function getChipColor(type: string) { function getChipColor(type: string) {
if (type === '电影') if (type === '电影') return 'border-blue-500 bg-blue-600'
return 'border-blue-500 bg-blue-600' else if (type === '电视剧') return ' bg-indigo-500 border-indigo-600'
else if (type === '电视剧') else return 'border-purple-600 bg-purple-600'
return ' bg-indigo-500 border-indigo-600'
else
return 'border-purple-600 bg-purple-600'
} }
// 添加订阅处理 // 添加订阅处理
@@ -93,26 +100,22 @@ async function handleAddSubscribe() {
// 检查各季的缺失状态 // 检查各季的缺失状态
await checkSeasonsNotExists() await checkSeasonsNotExists()
if (!tmdbFlag.value) if (!tmdbFlag.value) return
return
if (seasonInfos.value.length === 1) { if (seasonInfos.value.length === 1) {
// 添加订阅 // 添加订阅
addSubscribe(1) addSubscribe(1)
} } else {
else {
// 弹出季选择列表,支持多选 // 弹出季选择列表,支持多选
seasonsSelected.value = [] seasonsSelected.value = []
subscribeSeasonDialog.value = true subscribeSeasonDialog.value = true
} }
} } else if (props.media?.type === '电视剧') {
else if (props.media?.type === '电视剧') {
// 豆瓣电视剧,只会有一季 // 豆瓣电视剧,只会有一季
const season = props.media?.season ?? 1 const season = props.media?.season ?? 1
// 添加订阅 // 添加订阅
addSubscribe(season) addSubscribe(season)
} } else {
else {
// 电影 // 电影
addSubscribe() addSubscribe()
} }
@@ -147,13 +150,7 @@ async function addSubscribe(season = 0) {
} }
// 提示 // 提示
showSubscribeAddToast( showSubscribeAddToast(result.success, props.media?.title ?? '', season, result.message, best_version)
result.success,
props.media?.title ?? '',
season,
result.message,
best_version,
)
// 弹出订阅编辑弹窗 // 弹出订阅编辑弹窗
if (result.success && seasonsSelected.value.length <= 1) { if (result.success && seasonsSelected.value.length <= 1) {
@@ -163,8 +160,7 @@ async function addSubscribe(season = 0) {
subscribeEditDialog.value = true subscribeEditDialog.value = true
} }
} }
} } catch (error) {
catch (error) {
console.error(error) console.error(error)
} finally { } finally {
doneNProgress() doneNProgress()
@@ -172,22 +168,14 @@ async function addSubscribe(season = 0) {
} }
// 弹出添加订阅提示 // 弹出添加订阅提示
function showSubscribeAddToast(result: boolean, function showSubscribeAddToast(result: boolean, title: string, season: number, message: string, best_version: number) {
title: string, if (season) title = `${title} ${formatSeason(season.toString())}`
season: number,
message: string,
best_version: number) {
if (season)
title = `${title} ${formatSeason(season.toString())}`
let subname = '订阅' let subname = '订阅'
if (best_version > 0) if (best_version > 0) subname = '洗版订阅'
subname = '洗版订阅'
if (result && seasonsSelected.value.length > 1) if (result && seasonsSelected.value.length > 1) $toast.success(`${title} 添加${subname}成功!`)
$toast.success(`${title} 添加${subname}成功`) else if (!result) $toast.error(`${title} 添加${subname}失败:${message}`)
else if (!result)
$toast.error(`${title} 添加${subname}失败:${message}`)
} }
// 调用API取消订阅 // 调用API取消订阅
@@ -197,24 +185,19 @@ async function removeSubscribe() {
try { try {
const mediaid = getMediaId() const mediaid = getMediaId()
const result: { [key: string]: any } = await api.delete( const result: { [key: string]: any } = await api.delete(`subscribe/media/${mediaid}`, {
`subscribe/media/${mediaid}`, params: {
{ season: props.media?.season,
params: {
season: props.media?.season,
},
}, },
) })
if (result.success) { if (result.success) {
isSubscribed.value = false isSubscribed.value = false
$toast.success(`${props.media?.title} 已取消订阅!`) $toast.success(`${props.media?.title} 已取消订阅!`)
} } else {
else {
$toast.error(`${props.media?.title} 取消订阅失败:${result.message}`) $toast.error(`${props.media?.title} 取消订阅失败:${result.message}`)
} }
} } catch (error) {
catch (error) {
console.error(error) console.error(error)
} }
doneNProgress() doneNProgress()
@@ -224,10 +207,8 @@ async function removeSubscribe() {
async function handleCheckSubscribe() { async function handleCheckSubscribe() {
try { try {
const result = await checkSubscribe(props.media?.season) const result = await checkSubscribe(props.media?.season)
if (result) if (result) isSubscribed.value = true
isSubscribed.value = true } catch (error) {
}
catch (error) {
console.error(error) console.error(error)
} }
} }
@@ -245,10 +226,8 @@ async function handleCheckExists() {
}, },
}) })
if (result.success) if (result.success) isExists.value = true
isExists.value = true } catch (error) {
}
catch (error) {
console.error(error) console.error(error)
} }
} }
@@ -266,8 +245,7 @@ async function checkSubscribe(season = 0) {
}) })
return result.id || null return result.id || null
} } catch (error) {
catch (error) {
console.error(error) console.error(error)
} }
@@ -281,19 +259,16 @@ async function checkSeasonsNotExists() {
try { try {
const result: NotExistMediaInfo[] = await api.post('mediaserver/notexists', props.media) const result: NotExistMediaInfo[] = await api.post('mediaserver/notexists', props.media)
if (result) { if (result) {
result.forEach((item) => { result.forEach(item => {
// 0-已入库 1-部分缺失 2-全部缺失 // 0-已入库 1-部分缺失 2-全部缺失
let state = 0 let state = 0
if (item.episodes.length === 0) if (item.episodes.length === 0) state = 2
state = 2 else if (item.episodes.length < item.total_episode) state = 1
else if (item.episodes.length < item.total_episode)
state = 1
seasonsNotExisted.value[item.season] = state seasonsNotExisted.value[item.season] = state
}) })
} }
} } catch (error) {
catch (error) {
$toast.error(`${props.media?.title}无法识别TMDB媒体信息`) $toast.error(`${props.media?.title}无法识别TMDB媒体信息`)
tmdbFlag.value = false tmdbFlag.value = false
} }
@@ -306,8 +281,7 @@ async function checkSeasonsNotExists() {
async function getMediaSeasons() { async function getMediaSeasons() {
try { try {
seasonInfos.value = await api.get(`tmdb/seasons/${props.media?.tmdb_id}`) seasonInfos.value = await api.get(`tmdb/seasons/${props.media?.tmdb_id}`)
} } catch (error) {
catch (error) {
console.error(error) console.error(error)
} }
} }
@@ -316,17 +290,13 @@ async function getMediaSeasons() {
async function queryDefaultSubscribeConfig() { async function queryDefaultSubscribeConfig() {
try { try {
let subscribe_config_url = '' let subscribe_config_url = ''
if (props.media?.type === '电影') if (props.media?.type === '电影') subscribe_config_url = 'system/setting/DefaultMovieSubscribeConfig'
subscribe_config_url = 'system/setting/DefaultMovieSubscribeConfig' else subscribe_config_url = 'system/setting/DefaultTvSubscribeConfig'
else
subscribe_config_url = 'system/setting/DefaultTvSubscribeConfig'
const result: { [key: string]: any } = await api.get(subscribe_config_url) const result: { [key: string]: any } = await api.get(subscribe_config_url)
if (result.data?.value) if (result.data?.value) return result.data.value.show_edit_dialog
return result.data.value.show_edit_dialog } catch (error) {
}
catch (error) {
console.log(error) console.log(error)
} }
return false return false
@@ -334,38 +304,28 @@ async function queryDefaultSubscribeConfig() {
// 爱心订阅按钮响应 // 爱心订阅按钮响应
function handleSubscribe() { function handleSubscribe() {
if (isSubscribed.value) if (isSubscribed.value) removeSubscribe()
removeSubscribe() else handleAddSubscribe()
else
handleAddSubscribe()
} }
// 计算存在状态的颜色 // 计算存在状态的颜色
function getExistColor(season: number) { function getExistColor(season: number) {
const state = seasonsNotExisted.value[season] const state = seasonsNotExisted.value[season]
if (!state) if (!state) return 'success'
return 'success'
if (state === 1) if (state === 1) return 'warning'
return 'warning' else if (state === 2) return 'error'
else if (state === 2) else return 'success'
return 'error'
else
return 'success'
} }
// 计算存在状态的文本 // 计算存在状态的文本
function getExistText(season: number) { function getExistText(season: number) {
const state = seasonsNotExisted.value[season] const state = seasonsNotExisted.value[season]
if (!state) if (!state) return '已入库'
return '已入库'
if (state === 1) if (state === 1) return '部分缺失'
return '部分缺失' else if (state === 2) return '缺失'
else if (state === 2) else return '已入库'
return '缺失'
else
return '已入库'
} }
// 打开详情页 // 打开详情页
@@ -402,8 +362,7 @@ onBeforeMount(() => {
// 计算图片地址 // 计算图片地址
const getImgUrl: Ref<string> = computed(() => { const getImgUrl: Ref<string> = computed(() => {
if (imageLoadError.value) if (imageLoadError.value) return noImage
return noImage
const url = props.media?.poster_path?.replace('original', 'w500') ?? noImage const url = props.media?.poster_path?.replace('original', 'w500') ?? noImage
// 如果地址中包含douban则使用中转代理 // 如果地址中包含douban则使用中转代理
if (url.includes('doubanio.com')) if (url.includes('doubanio.com'))
@@ -414,22 +373,19 @@ const getImgUrl: Ref<string> = computed(() => {
// 拼装季图片地址 // 拼装季图片地址
function getSeasonPoster(posterPath: string) { function getSeasonPoster(posterPath: string) {
if (!posterPath) if (!posterPath) return ''
return ''
return `https://image.tmdb.org/t/p/w500${posterPath}` return `https://image.tmdb.org/t/p/w500${posterPath}`
} }
// 将yyyy-mm-dd转换为yyyy年mm月dd日 // 将yyyy-mm-dd转换为yyyy年mm月dd日
function formatAirDate(airDate: string) { function formatAirDate(airDate: string) {
if (!airDate) if (!airDate) return ''
return ''
const date = new Date(airDate.replaceAll(/-/g, '/')) const date = new Date(airDate.replaceAll(/-/g, '/'))
return `${date.getFullYear()}${date.getMonth() + 1}${date.getDate()}` return `${date.getFullYear()}${date.getMonth() + 1}${date.getDate()}`
} }
// 从yyyy-mm-dd中提取年份 // 从yyyy-mm-dd中提取年份
function getYear(airDate: string) { function getYear(airDate: string) {
if (!airDate) if (!airDate) return ''
return ''
const date = new Date(airDate.replaceAll(/-/g, '/')) const date = new Date(airDate.replaceAll(/-/g, '/'))
return date.getFullYear() return date.getFullYear()
} }
@@ -499,43 +455,30 @@ function getYear(airDate: string) {
{{ props.media?.overview }} {{ props.media?.overview }}
</p> </p>
<div class="flex align-center justify-between"> <div class="flex align-center justify-between">
<IconBtn <IconBtn icon="mdi-magnify" color="white" @click.stop="handleSearch" />
icon="mdi-magnify" <IconBtn icon="mdi-heart" :color="isSubscribed ? 'error' : 'white'" @click.stop="handleSubscribe" />
color="white"
@click.stop="handleSearch"
/>
<IconBtn
icon="mdi-heart"
:color="isSubscribed ? 'error' : 'white'"
@click.stop="handleSubscribe"
/>
</div> </div>
</VCardText> </VCardText>
<VAvatar
size="24"
density="compact"
class="absolute bottom-1 right-1"
tile
v-if="!hover.isHovering && isImageLoaded"
>
<VImg cover :src="sourceIconDict[props.media?.source]" class="shadow-lg" />
</VAvatar>
</VCard> </VCard>
</template> </template>
</VHover> </VHover>
<!-- 订阅季弹窗 --> <!-- 订阅季弹窗 -->
<VBottomSheet <VBottomSheet v-if="subscribeSeasonDialog" v-model="subscribeSeasonDialog" inset scrollable>
v-if="subscribeSeasonDialog"
v-model="subscribeSeasonDialog"
inset
scrollable
>
<VCard class="rounded-t"> <VCard class="rounded-t">
<DialogCloseBtn @click="subscribeSeasonDialog = false" /> <DialogCloseBtn @click="subscribeSeasonDialog = false" />
<VCardTitle class="pe-10"> <VCardTitle class="pe-10"> 订阅 - {{ props.media?.title }} </VCardTitle>
订阅 - {{ props.media?.title }}
</VCardTitle>
<VCardText> <VCardText>
<VList <VList v-model:selected="seasonsSelected" lines="three" select-strategy="classic">
v-model:selected="seasonsSelected" <VListItem v-for="(item, i) in seasonInfos" :key="i" :value="item">
lines="three"
select-strategy="classic"
>
<VListItem
v-for="(item, i) in seasonInfos" :key="i"
:value="item"
>
<template #prepend> <template #prepend>
<VImg <VImg
height="90" height="90"
@@ -552,16 +495,9 @@ function getYear(airDate: string) {
</template> </template>
</VImg> </VImg>
</template> </template>
<VListItemTitle> <VListItemTitle> {{ item.season_number }} </VListItemTitle>
{{ item.season_number }}
</VListItemTitle>
<VListItemSubtitle class="mt-1 me-2"> <VListItemSubtitle class="mt-1 me-2">
<VChip <VChip v-if="item.vote_average" color="primary" size="small" class="mb-1">
v-if="item.vote_average"
color="primary"
size="small"
class="mb-1"
>
<VIcon icon="mdi-star" /> {{ item.vote_average }} <VIcon icon="mdi-star" /> {{ item.vote_average }}
</VChip> </VChip>
{{ getYear(item.air_date || '') }} {{ item.episode_count }} {{ getYear(item.air_date || '') }} {{ item.episode_count }}
@@ -570,12 +506,7 @@ function getYear(airDate: string) {
{{ media?.title }} {{ item.season_number }} 季于 {{ formatAirDate(item.air_date || '') }} 首播 {{ media?.title }} {{ item.season_number }} 季于 {{ formatAirDate(item.air_date || '') }} 首播
</VListItemSubtitle> </VListItemSubtitle>
<VListItemSubtitle> <VListItemSubtitle>
<VChip <VChip v-if="seasonsNotExisted" class="mt-2" size="small" :color="getExistColor(item.season_number || 0)">
v-if="seasonsNotExisted"
class="mt-2"
size="small"
:color="getExistColor(item.season_number || 0)"
>
{{ getExistText(item.season_number || 0) }} {{ getExistText(item.season_number || 0) }}
</VChip> </VChip>
</VListItemSubtitle> </VListItemSubtitle>
@@ -588,11 +519,7 @@ function getYear(airDate: string) {
</VList> </VList>
</VCardText> </VCardText>
<div class="my-2 text-center"> <div class="my-2 text-center">
<VBtn <VBtn :disabled="seasonsSelected.length === 0" width="30%" @click="subscribeSeasons">
:disabled="seasonsSelected.length === 0"
width="30%"
@click="subscribeSeasons"
>
{{ seasonsSelected.length === 0 ? '请选择订阅季' : '提交订阅' }} {{ seasonsSelected.length === 0 ? '请选择订阅季' : '提交订阅' }}
</VBtn> </VBtn>
</div> </div>
@@ -605,7 +532,12 @@ function getYear(airDate: string) {
:subid="subscribeId" :subid="subscribeId"
@close="subscribeEditDialog = false" @close="subscribeEditDialog = false"
@save="subscribeEditDialog = false" @save="subscribeEditDialog = false"
@remove="() => { subscribeEditDialog = false; handleCheckSubscribe(); }" @remove="
() => {
subscribeEditDialog = false
handleCheckSubscribe()
}
"
/> />
</template> </template>