mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-07-09 14:31:35 +08:00
feat: 添加 MediaInfoDialog 组件并更新相关引用
This commit is contained in:
@@ -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 }}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
125
src/components/dialog/ForkSubscribeDialog.vue
Normal file
125
src/components/dialog/ForkSubscribeDialog.vue
Normal 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>
|
||||||
22
src/components/dialog/MediaInfoDialog.vue
Normal file
22
src/components/dialog/MediaInfoDialog.vue
Normal 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>
|
||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user