feat: 添加 MediaInfoDialog 组件并更新相关引用

This commit is contained in:
jxxghp
2024-12-29 14:22:27 +08:00
parent a604d3223a
commit 3b2d5e45bb
5 changed files with 245 additions and 117 deletions

View File

@@ -4,7 +4,7 @@ import type { Context } from '@/api/types'
import { isNullOrEmptyObject } from '@/@core/utils' import { isNullOrEmptyObject } from '@/@core/utils'
// 输入参数 // 输入参数
const props = defineProps({ defineProps({
context: Object as PropType<Context>, context: Object as PropType<Context>,
}) })
@@ -45,8 +45,7 @@ function openTmdbPage(type: string, tmdbId: number) {
</template> </template>
</VImg> </VImg>
</div> </div>
<div class="flex-grow">
<div>
<VCardItem class="pb-1"> <VCardItem class="pb-1">
<VCardTitle class="text-center text-md-left"> <VCardTitle class="text-center text-md-left">
{{ context?.media_info?.title || context?.meta_info?.name }} {{ context?.media_info?.title || context?.meta_info?.name }}

View File

@@ -1,24 +1,15 @@
<script lang="ts" setup> <script lang="ts" setup>
import { formatDateDifference } from '@/@core/utils/formatters' import { formatDateDifference } from '@/@core/utils/formatters'
import api from '@/api'
import { doneNProgress, startNProgress } from '@/api/nprogress'
import type { SubscribeShare } from '@/api/types' import type { SubscribeShare } from '@/api/types'
import router from '@/router' import router from '@/router'
import { useToast } from 'vue-toast-notification'
import { useConfirm } from 'vuetify-use-dialog'
import SubscribeEditDialog from '../dialog/SubscribeEditDialog.vue' import SubscribeEditDialog from '../dialog/SubscribeEditDialog.vue'
import ForkSubscribeDialog from '../dialog/ForkSubscribeDialog.vue'
// 输入参数 // 输入参数
const props = defineProps({ const props = defineProps({
media: Object as PropType<SubscribeShare>, media: Object as PropType<SubscribeShare>,
}) })
// 提示框
const $toast = useToast()
// 确认框
const createConfirm = useConfirm()
// 从 provide 中获取全局设置 // 从 provide 中获取全局设置
const globalSettings: any = inject('globalSettings') const globalSettings: any = inject('globalSettings')
@@ -28,6 +19,9 @@ const imageLoaded = ref(false)
// 订阅编辑弹窗 // 订阅编辑弹窗
const subscribeEditDialog = ref(false) const subscribeEditDialog = ref(false)
// 复用订阅弹窗
const forkSubscribeDialog = ref(false)
// 订阅ID // 订阅ID
const subscribeId = ref<number>() const subscribeId = ref<number>()
@@ -69,109 +63,99 @@ async function viewMediaDetail() {
} }
// 复用订阅 // 复用订阅
async function forkSubscribe() { function showForkSubscribe() {
// 开始处理 forkSubscribeDialog.value = true
startNProgress() }
try {
// 确认
const isConfirmed = await createConfirm({
title: '确认',
content: `是否确认添加来自 ${props.media?.share_user} 分享的订阅:${props.media?.share_title}`,
})
if (!isConfirmed) return
// 请求API // 完成复用订阅
const result: { [key: string]: any } = await api.post('subscribe/fork', props.media) function finishForkSubscribe(subid: number) {
subscribeId.value = subid
// 订阅状态 subscribeEditDialog.value = true
if (result.success) {
$toast.success(`${props.media?.share_title} 添加订阅成功!`)
// 弹出订阅编辑弹窗
subscribeId.value = result.data.id
subscribeEditDialog.value = true
} else {
$toast.error(`${props.media?.share_title} 添加订阅失败:${result.message}`)
}
} catch (error) {
console.error(error)
} finally {
doneNProgress()
}
} }
</script> </script>
<template> <template>
<VHover> <div>
<template #default="hover"> <VHover>
<VCard <template #default="hover">
v-bind="hover.props" <VCard
:key="props.media?.id" v-bind="hover.props"
class="flex flex-col rounded-lg" :key="props.media?.id"
:class="{ class="flex flex-col rounded-lg"
'transition transform-cpu duration-300 scale-105 shadow-lg': hover.isHovering, :class="{
}" 'transition transform-cpu duration-300 scale-105 shadow-lg': hover.isHovering,
min-height="170" }"
@click="forkSubscribe" min-height="170"
> @click="showForkSubscribe"
<template #image> >
<VImg :src="backdropUrl || posterUrl" aspect-ratio="3/2" cover @load="imageLoadHandler" position="top"> <template #image>
<template #placeholder> <VImg :src="backdropUrl || posterUrl" aspect-ratio="3/2" cover @load="imageLoadHandler" position="top">
<div class="w-full h-full"> <template #placeholder>
<VSkeletonLoader class="object-cover aspect-w-3 aspect-h-2" /> <div class="w-full h-full">
<VSkeletonLoader class="object-cover aspect-w-3 aspect-h-2" />
</div>
</template>
<div class="absolute inset-0 subscribe-card-background"></div>
</VImg>
</template>
<div>
<VCardText class="flex items-center pb-1">
<div class="h-auto w-12 flex-shrink-0 overflow-hidden rounded-md shadow-lg" v-if="imageLoaded">
<VImg :src="posterUrl" aspect-ratio="2/3" cover @click.stop="viewMediaDetail">
<template #placeholder>
<div class="w-full h-full">
<VSkeletonLoader class="object-cover aspect-w-2 aspect-h-3" />
</div>
</template>
</VImg>
</div> </div>
</template> <div class="flex flex-col justify-center pl-2 xl:pl-4">
<div class="absolute inset-0 subscribe-card-background"></div> <div class="mr-2 min-w-0 text-lg font-bold text-white line-clamp-2 overflow-hidden text-ellipsis ...">
</VImg> {{ props.media?.share_title }}
</template> </div>
<div> <div class="text-sm font-medium text-gray-200 sm:pt-1 line-clamp-3 overflow-hidden text-ellipsis ...">
<VCardText class="flex items-center pb-1"> {{ props.media?.share_comment }}
<div class="h-auto w-12 flex-shrink-0 overflow-hidden rounded-md shadow-lg" v-if="imageLoaded"> </div>
<VImg :src="posterUrl" aspect-ratio="2/3" cover @click.stop="viewMediaDetail">
<template #placeholder>
<div class="w-full h-full">
<VSkeletonLoader class="object-cover aspect-w-2 aspect-h-3" />
</div>
</template>
</VImg>
</div>
<div class="flex flex-col justify-center pl-2 xl:pl-4">
<div class="mr-2 min-w-0 text-lg font-bold text-white line-clamp-2 overflow-hidden text-ellipsis ...">
{{ props.media?.share_title }}
</div> </div>
<div class="text-sm font-medium text-gray-200 sm:pt-1 line-clamp-3 overflow-hidden text-ellipsis ..."> </VCardText>
{{ props.media?.share_comment }} <VCardText class="flex justify-space-between align-center flex-wrap">
<div class="flex align-center">
<IconBtn v-bind="props" icon="mdi-account" color="white" class="me-1" />
<div class="text-subtitle-2 me-4 text-white">
{{ props.media?.share_user }}
</div>
<IconBtn v-if="props.media?.count" icon="mdi-fire" color="error" class="me-1" />
<span v-if="props.media?.count" class="text-subtitle-2 me-4 text-white">
{{ props.media?.count.toLocaleString() }}
</span>
</div> </div>
</div> </VCardText>
</VCardText> <VCardText class="absolute right-0 bottom-0 d-flex align-center p-2 text-gray-300">
<VCardText class="flex justify-space-between align-center flex-wrap"> <VIcon icon="mdi-calcdar" class="me-1" />
<div class="flex align-center"> {{ dateText }}
<IconBtn v-bind="props" icon="mdi-account" color="white" class="me-1" /> </VCardText>
<div class="text-subtitle-2 me-4 text-white"> </div>
{{ props.media?.share_user }} </VCard>
</div> </template>
<IconBtn v-if="props.media?.count" icon="mdi-fire" color="error" class="me-1" /> </VHover>
<span v-if="props.media?.count" class="text-subtitle-2 me-4 text-white"> <!-- 订阅编辑弹窗 -->
{{ props.media?.count.toLocaleString() }} <SubscribeEditDialog
</span> v-if="subscribeEditDialog"
</div> v-model="subscribeEditDialog"
</VCardText> :subid="subscribeId"
<VCardText class="absolute right-0 bottom-0 d-flex align-center p-2 text-gray-300"> @close="subscribeEditDialog = false"
<VIcon icon="mdi-calcdar" class="me-1" /> @save="subscribeEditDialog = false"
{{ dateText }} @remove="subscribeEditDialog = false"
</VCardText> />
</div> <!-- 复用订阅弹窗 -->
</VCard> <ForkSubscribeDialog
</template> v-if="forkSubscribeDialog"
</VHover> v-model="forkSubscribeDialog"
<!-- 订阅编辑弹窗 --> :media="props.media"
<SubscribeEditDialog @close="forkSubscribeDialog = false"
v-if="subscribeEditDialog" @done="finishForkSubscribe"
v-model="subscribeEditDialog" />
:subid="subscribeId" </div>
@close="subscribeEditDialog = false"
@save="subscribeEditDialog = false"
@remove="subscribeEditDialog = false"
/>
</template> </template>
<style lang="scss"> <style lang="scss">
.subscribe-card-background { .subscribe-card-background {

View File

@@ -0,0 +1,125 @@
<script setup lang="ts">
import api from '@/api'
import { doneNProgress, startNProgress } from '@/api/nprogress'
import { SubscribeShare } from '@/api/types'
import router from '@/router'
import { useToast } from 'vue-toast-notification'
import { VBtn } from 'vuetify/lib/components/index.mjs'
// 输入参数
const props = defineProps({
media: Object as PropType<SubscribeShare>,
})
// 定义事件
const emit = defineEmits(['close', 'done'])
// 从 provide 中获取全局设置
const globalSettings: any = inject('globalSettings')
// 提示框
const $toast = useToast()
// 计算海报图片地址
const posterUrl = computed(() => {
const url = props.media?.poster
// 使用图片缓存
if (globalSettings.GLOBAL_IMAGE_CACHE && url)
return `${import.meta.env.VITE_API_BASE_URL}system/cache/image?url=${encodeURIComponent(url)}`
return url
})
// 查看媒体详情
async function viewMediaDetail() {
router.push({
path: '/media',
query: {
mediaid: `${props.media?.tmdbid ? `tmdb:${props.media?.tmdbid}` : `douban:${props.media?.doubanid}`}`,
type: props.media?.type,
},
})
}
// 复用订阅
async function doFork() {
// 开始处理
startNProgress()
try {
// 请求API
const result: { [key: string]: any } = await api.post('subscribe/fork', props.media)
// 订阅状态
if (result.success) {
$toast.success(`${props.media?.share_title} 添加订阅成功!`)
// 完成
emit('done', result.data.id)
} else {
$toast.error(`${props.media?.share_title} 添加订阅失败:${result.message}`)
}
} catch (error) {
console.error(error)
} finally {
doneNProgress()
}
}
</script>
<template>
<VDialog max-width="40rem">
<VCard>
<DialogCloseBtn @click="emit('close')" />
<VCardText>
<VCol>
<div class="d-flex justify-space-between flex-wrap flex-md-nowrap flex-column flex-md-row">
<div class="ma-auto">
<VImg
width="10rem"
aspect-ratio="2/3"
class="object-cover aspect-w-2 aspect-h-3 rounded-lg ring-1 ring-gray-500"
:src="posterUrl"
@click="viewMediaDetail"
cover
>
<template #placeholder>
<div class="w-full h-full">
<VSkeletonLoader class="object-cover aspect-w-2 aspect-h-3" />
</div>
</template>
</VImg>
</div>
<div class="flex-grow">
<VCardItem>
<VCardTitle>
{{ props.media?.share_title }}
</VCardTitle>
<VCardSubtitle
class="break-words whitespace-break-spaces line-clamp-2 overflow-hidden text-ellipsis ..."
>
{{ props.media?.share_comment }}
</VCardSubtitle>
<VList lines="one">
<VListItem class="ps-0">
<VListItemTitle>
<span class="font-weight-medium">分享人</span>
<span class="text-body-1"> {{ media?.share_user }}</span>
</VListItemTitle>
</VListItem>
<VListItem class="ps-0" v-if="media?.custom_words">
<VListItemTitle
class="break-words whitespace-break-spaces line-clamp-3 overflow-hidden text-ellipsis ..."
>
<span class="font-weight-medium">识别词</span>
<span class="text-body-1"> {{ media?.custom_words }}</span>
</VListItemTitle>
</VListItem>
</VList>
<div>
<VBtn color="primary" @click="doFork" prepend-icon="mdi-heart">添加到我的订阅</VBtn>
</div>
</VCardItem>
</div>
</div>
</VCol>
</VCardText>
</VCard>
</VDialog>
</template>

View File

@@ -0,0 +1,22 @@
<script setup lang="ts">
import { Context } from '@/api/types'
import MediaInfoCard from '../cards/MediaInfoCard.vue'
// 输入参数
defineProps({
context: Object as PropType<Context>,
})
// 定义事件
const emit = defineEmits(['close'])
</script>
<template>
<VDialog max-width="50rem">
<VCard>
<DialogCloseBtn @click="emit('close')" />
<VCardItem>
<MediaInfoCard :context="context" />
</VCardItem>
</VCard>
</VDialog>
</template>

View File

@@ -7,9 +7,9 @@ import ReorganizeDialog from '../dialog/ReorganizeDialog.vue'
import { formatBytes } from '@core/utils/formatters' import { formatBytes } from '@core/utils/formatters'
import type { Context, EndPoints, FileItem } from '@/api/types' import type { Context, EndPoints, FileItem } from '@/api/types'
import api from '@/api' import api from '@/api'
import MediaInfoCard from '@/components/cards/MediaInfoCard.vue'
import ProgressDialog from '../dialog/ProgressDialog.vue' import ProgressDialog from '../dialog/ProgressDialog.vue'
import { useDisplay } from 'vuetify' import { useDisplay } from 'vuetify'
import MediaInfoDialog from '../dialog/MediaInfoDialog.vue'
// 显示器宽度 // 显示器宽度
const display = useDisplay() const display = useDisplay()
@@ -735,14 +735,12 @@ onMounted(() => {
<!-- 进度框 --> <!-- 进度框 -->
<ProgressDialog v-if="progressDialog" v-model="progressDialog" :text="progressText" :value="progressValue" /> <ProgressDialog v-if="progressDialog" v-model="progressDialog" :text="progressText" :value="progressValue" />
<!-- 识别结果对话框 --> <!-- 识别结果对话框 -->
<VDialog v-if="nameTestDialog" v-model="nameTestDialog" width="50rem"> <MediaInfoDialog
<VCard> v-if="nameTestDialog"
<DialogCloseBtn @click="nameTestDialog = false" /> v-model="nameTestDialog"
<VCardItem> :context="nameTestResult"
<MediaInfoCard :context="nameTestResult" /> @close="nameTestDialog = false"
</VCardItem> />
</VCard>
</VDialog>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>