This commit is contained in:
jxxghp
2024-06-15 21:06:56 +08:00
parent 73c54992e2
commit c66d7cafa6
7 changed files with 72 additions and 95 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "moviepilot", "name": "moviepilot",
"version": "1.9.6-beta", "version": "1.9.6",
"private": true, "private": true,
"bin": "dist/service.js", "bin": "dist/service.js",
"scripts": { "scripts": {
+25 -48
View File
@@ -35,36 +35,28 @@ function imageErrorHandler() {
// 默认图片 // 默认图片
function getDefaultImage() { function getDefaultImage() {
if (props.media?.server === 'plex') if (props.media?.server === 'plex') return plex
return plex else if (props.media?.server === 'emby') return emby
else if (props.media?.server === 'emby') else if (props.media?.server === 'jellyfin') return jellyfin
return emby else return plex
else if (props.media?.server === 'jellyfin')
return jellyfin
else
return plex
} }
// 跳转播放 // 跳转播放
function goPlay() { function goPlay() {
if (props.media?.link) if (props.media?.link) window.open(props.media?.link, '_blank')
window.open(props.media?.link, '_blank')
} }
// 生成图片代理路径 // 生成图片代理路径
function getImgUrl(url: string) { function getImgUrl(url: string) {
if (!url) if (!url) return getDefaultImage()
return getDefaultImage() else return `${import.meta.env.VITE_API_BASE_URL}system/img/0?imgurl=${encodeURIComponent(url)}`
else
return `${import.meta.env.VITE_API_BASE_URL}system/img/0?imgurl=${encodeURIComponent(url)}`
} }
// 根据多张图片生成媒体库封面 // 根据多张图片生成媒体库封面
async function drawImages(imageList: string[]) { async function drawImages(imageList: string[]) {
// 图片 // 图片
const IMAGES = imageList const IMAGES = imageList
if (IMAGES.length === 0) if (IMAGES.length === 0) return getDefaultImage()
return getDefaultImage()
// 为所有图片添加system/img前缀 // 为所有图片添加system/img前缀
for (let i = 0; i < IMAGES.length; i++) for (let i = 0; i < IMAGES.length; i++)
@@ -72,8 +64,7 @@ async function drawImages(imageList: string[]) {
// canvas // canvas
const canvas = canvasRef.value const canvas = canvasRef.value
if (!canvas) if (!canvas) return getDefaultImage()
return getDefaultImage()
// 画布参数 // 画布参数
const POSTER_WIDTH = (canvas.width - 32) / 4 const POSTER_WIDTH = (canvas.width - 32) / 4
@@ -85,8 +76,7 @@ async function drawImages(imageList: string[]) {
// 获取画布上下文 // 获取画布上下文
const ctx = canvas.getContext('2d') const ctx = canvas.getContext('2d')
if (!ctx) if (!ctx) return getDefaultImage()
return getDefaultImage()
// 设置背景色为黑色 // 设置背景色为黑色
ctx.fillStyle = '#000000' ctx.fillStyle = '#000000'
@@ -94,16 +84,14 @@ async function drawImages(imageList: string[]) {
// 绘制图片 // 绘制图片
async function drawImageWithReflection(imgSrc: string, index: number) { async function drawImageWithReflection(imgSrc: string, index: number) {
if (!canvas) if (!canvas) return
return
if (!ctx) if (!ctx) return
return
const img = new Image() const img = new Image()
img.setAttribute('crossorigin', 'anonymous') img.setAttribute('crossorigin', 'anonymous')
img.src = imgSrc img.src = imgSrc
await new Promise(resolve => img.onload = resolve) await new Promise(resolve => (img.onload = resolve))
const x = MARGIN_WIDTH * index + POSTER_WIDTH * (index - 1) const x = MARGIN_WIDTH * index + POSTER_WIDTH * (index - 1)
const y = MARGIN_HEIGHT const y = MARGIN_HEIGHT
@@ -125,12 +113,7 @@ async function drawImages(imageList: string[]) {
REFLECTION_HEIGHT, REFLECTION_HEIGHT,
) )
const gradient = ctx.createLinearGradient( const gradient = ctx.createLinearGradient(0, REFLECTION_SHOW_HEIGHT - REFLECTION_HEIGHT, 0, REFLECTION_HEIGHT)
0,
REFLECTION_SHOW_HEIGHT - REFLECTION_HEIGHT,
0,
REFLECTION_HEIGHT,
)
gradient.addColorStop(0, 'rgba(0, 0, 0, 1)') gradient.addColorStop(0, 'rgba(0, 0, 0, 1)')
gradient.addColorStop(1, 'rgba(0, 0, 0, 0.3)') gradient.addColorStop(1, 'rgba(0, 0, 0, 0.3)')
@@ -142,8 +125,7 @@ async function drawImages(imageList: string[]) {
// 绘制多张图片 // 绘制多张图片
const loopCount = Math.min(4, IMAGES.length) const loopCount = Math.min(4, IMAGES.length)
for (let i = 0; i < loopCount; i++) for (let i = 0; i < loopCount; i++) await drawImageWithReflection(IMAGES[i], i + 1)
await drawImageWithReflection(IMAGES[i], i + 1)
// 转换为图片地址 // 转换为图片地址
return canvas.toDataURL('image/png') return canvas.toDataURL('image/png')
@@ -152,17 +134,12 @@ async function drawImages(imageList: string[]) {
onMounted(async () => { onMounted(async () => {
if (props.media?.image_list && props.media?.image_list.length > 0) if (props.media?.image_list && props.media?.image_list.length > 0)
imgUrl.value = await drawImages(props.media?.image_list || []) imgUrl.value = await drawImages(props.media?.image_list || [])
else else imgUrl.value = getImgUrl(props.media?.image || '')
imgUrl.value = getImgUrl(props.media?.image || '')
}) })
</script> </script>
<template> <template>
<VHover <VHover v-bind="props" :height="props.height" :width="props.width">
v-bind="props"
:height="props.height"
:width="props.width"
>
<template #default="hover"> <template #default="hover">
<VCard <VCard
v-bind="hover.props" v-bind="hover.props"
@@ -175,13 +152,7 @@ onMounted(async () => {
> >
<template #image> <template #image>
<canvas ref="canvasRef" class="w-full h-full hidden" /> <canvas ref="canvasRef" class="w-full h-full hidden" />
<VImg <VImg :src="imgUrl" aspect-ratio="2/3" cover @load="imageLoadHandler" @error="imageErrorHandler">
:src="imgUrl"
aspect-ratio="2/3"
cover
@load="imageLoadHandler"
@error="imageErrorHandler"
>
<template #placeholder> <template #placeholder>
<div class="w-full h-full"> <div class="w-full h-full">
<VSkeletonLoader class="object-cover aspect-w-3 aspect-h-2" /> <VSkeletonLoader class="object-cover aspect-w-3 aspect-h-2" />
@@ -190,7 +161,7 @@ onMounted(async () => {
<VCardText <VCardText
class="w-full flex flex-col flex-wrap justify-end align-center text-white absolute bottom-0 cursor-pointer pa-2" class="w-full flex flex-col flex-wrap justify-end align-center text-white absolute bottom-0 cursor-pointer pa-2"
> >
<h1 class="mb-1 text-white font-bold line-clamp-2 overflow-hidden text-ellipsis ..."> <h1 class="mb-1 text-white text-shadow font-bold line-clamp-2 overflow-hidden text-ellipsis ...">
{{ props.media?.name }} {{ props.media?.name }}
</h1> </h1>
</VCardText> </VCardText>
@@ -200,3 +171,9 @@ onMounted(async () => {
</template> </template>
</VHover> </VHover>
</template> </template>
<style lang="scss">
.text-shadow {
text-shadow: 1px 1px #777;
}
</style>
+20 -14
View File
@@ -180,6 +180,7 @@ watch(
class="flex flex-col rounded-lg" class="flex flex-col rounded-lg"
:class="{ :class="{
'outline-dashed outline-1': props.media?.best_version && imageLoaded, 'outline-dashed outline-1': props.media?.best_version && imageLoaded,
'transition transform-cpu duration-300 scale-105 shadow-lg': hover.isHovering,
}" }"
@click="editSubscribeDialog" @click="editSubscribeDialog"
> >
@@ -223,26 +224,29 @@ watch(
</template> </template>
<div v-if="imageLoaded"> <div v-if="imageLoaded">
<VCardText class="flex items-center"> <VCardText class="flex items-center">
<div <div class="h-auto w-12 flex-shrink-0 overflow-hidden rounded-md shadow-lg">
class="h-auto w-12 flex-shrink-0 overflow-hidden rounded-md shadow-lg" <VImg :src="props.media?.poster" aspect-ratio="2/3" cover @click.stop="viewMediaDetail">
:class="{ 'transition transform-cpu duration-300 scale-105': hover.isHovering }" <template #placeholder>
> <div class="w-full h-full">
<VImg :src="props.media?.poster" aspect-ratio="2/3" cover @click.stop="viewMediaDetail" /> <VSkeletonLoader class="object-cover aspect-w-2 aspect-h-3" />
</div>
</template>
</VImg>
</div> </div>
<div class="flex flex-col justify-center overflow-hidden pl-2 xl:pl-4"> <div class="flex flex-col justify-center overflow-hidden pl-2 xl:pl-4">
<div class="text-sm font-medium text-white sm:pt-1">{{ props.media?.year }}</div> <div class="text-sm font-medium text-white sm:pt-1">{{ props.media?.year }}</div>
<div class="mr-2 min-w-0 text-lg font-bold text-white xl:text-xl"> <div class="mr-2 min-w-0 text-lg font-bold text-white">
{{ props.media?.name }} {{ props.media?.name }}
{{ formatSeason(props.media?.season ? props.media?.season.toString() : '') }} {{ formatSeason(props.media?.season ? props.media?.season.toString() : '') }}
</div> </div>
</div> </div>
</VCardText> </VCardText>
<VCardText class="flex justify-space-between align-center flex-wrap"> <VCardText class="flex justify-space-between align-center flex-wrap">
<div class="d-flex align-center"> <div class="flex align-center">
<IconBtn <IconBtn
v-if="props.media?.total_episode" v-if="props.media?.total_episode"
v-bind="props" v-bind="props"
icon="mdi-progress-clock" icon="mdi-progress-download"
color="white" color="white"
class="me-1" class="me-1"
/> />
@@ -260,12 +264,14 @@ watch(
<VIcon icon="mdi-download" class="me-1" /> <VIcon icon="mdi-download" class="me-1" />
{{ lastUpdateText }} {{ lastUpdateText }}
</VCardText> </VCardText>
<VProgressLinear <div class="w-full absolute bottom-0">
v-if="getPercentage() > 0" <VProgressLinear
:model-value="getPercentage()" v-if="getPercentage() > 0"
bg-color="success" :model-value="getPercentage()"
color="success" bg-color="success"
/> color="success"
/>
</div>
</div> </div>
</VCard> </VCard>
</template> </template>
+15 -16
View File
@@ -27,12 +27,10 @@ function slideNext(next: boolean) {
if (run_to_left_px >= slideview_content.value.scrollWidth - slideview_content.value.clientWidth) if (run_to_left_px >= slideview_content.value.scrollWidth - slideview_content.value.clientWidth)
run_to_left_px = slideview_content.value.scrollWidth - slideview_content.value.clientWidth run_to_left_px = slideview_content.value.scrollWidth - slideview_content.value.clientWidth
// console.log(`最多显示: ${card_max} 当前起点: ${card_current} 目标起点: ${card_index} 卡片宽度: ${card_width}`) // console.log(`最多显示: ${card_max} 当前起点: ${card_current} 目标起点: ${card_index} 卡片宽度: ${card_width}`)
} } else {
else {
const card_index = card_current - card_max const card_index = card_current - card_max
run_to_left_px = card_index * card_width run_to_left_px = card_index * card_width
if (run_to_left_px <= 0) if (run_to_left_px <= 0) run_to_left_px = 0
run_to_left_px = 0
// console.log(`最多显示: ${card_max} 当前起点: ${card_current} 目标起点: ${card_index} 卡片宽度: ${card_width}`) // console.log(`最多显示: ${card_max} 当前起点: ${card_current} 目标起点: ${card_index} 卡片宽度: ${card_width}`)
} }
slideview_content.value.scrollTo({ slideview_content.value.scrollTo({
@@ -46,7 +44,7 @@ function slideNext(next: boolean) {
function countMaxNumber() { function countMaxNumber() {
slide_card_length = slideview_content.value.children.length slide_card_length = slideview_content.value.children.length
card_width = slideview_content.value.firstElementChild.getBoundingClientRect().width card_width = slideview_content.value.firstElementChild.getBoundingClientRect().width
slide_gap_px = (slideview_content.value.scrollWidth / slide_card_length) - card_width slide_gap_px = slideview_content.value.scrollWidth / slide_card_length - card_width
card_width += slide_gap_px card_width += slide_gap_px
card_max = Math.trunc(slideview_content.value.clientWidth / card_width) card_max = Math.trunc(slideview_content.value.clientWidth / card_width)
countDisabled() countDisabled()
@@ -55,16 +53,18 @@ function countMaxNumber() {
// 修改分页切换按钮状态 // 修改分页切换按钮状态
function countDisabled() { function countDisabled() {
slideview_scrollLeft.value = slideview_content.value.scrollLeft slideview_scrollLeft.value = slideview_content.value.scrollLeft
card_current = slideview_content.value.scrollLeft === 0 ? 0 : Math.trunc((slideview_content.value.scrollLeft + card_width / 2) / card_width) card_current =
if (slide_card_length * card_width <= slideview_content.value.clientWidth) slideview_content.value.scrollLeft === 0
disabled.value = 3 ? 0
else if (slideview_content.value.scrollLeft === 0) : Math.trunc((slideview_content.value.scrollLeft + card_width / 2) / card_width)
disabled.value = 0 if (slide_card_length * card_width <= slideview_content.value.clientWidth) disabled.value = 3
else if (slideview_content.value.scrollLeft >= slideview_content.value.scrollWidth - slideview_content.value.clientWidth - 2) else if (slideview_content.value.scrollLeft === 0) disabled.value = 0
else if (
slideview_content.value.scrollLeft >=
slideview_content.value.scrollWidth - slideview_content.value.clientWidth - 2
)
disabled.value = 2 disabled.value = 2
else disabled.value = 1
else
disabled.value = 1
} }
// 组件加载完成 // 组件加载完成
@@ -123,8 +123,7 @@ onActivated(() => {
<style lang="scss" scoped> <style lang="scss" scoped>
.slideview_content { .slideview_content {
-ms-overflow-style: none !important; -ms-overflow-style: none !important;
overflow-x: scroll !important; overflow: scroll hidden !important;
overflow-y: hidden !important;
overscroll-behavior-x: contain !important; overscroll-behavior-x: contain !important;
scrollbar-width: none !important; scrollbar-width: none !important;
} }
+5 -14
View File
@@ -1,22 +1,13 @@
<script lang="ts" setup> <script lang="ts" setup>
// 输入参数 // 输入参数
const props = inject('rankingPropsKey') const props: any = inject('rankingPropsKey')
</script> </script>
<template> <template>
<div <div class="ms-1">
class="ms-1" <RouterLink :to="props?.linkurl ? props?.linkurl : ''" class="slider-title">
> <span>{{ props?.title }}</span>
<RouterLink <VIcon icon="mdi-arrow-right-circle-outline" class="ms-1" />
:to="props.linkurl ? props.linkurl : ''"
class="slider-title"
>
<span>{{ props.title }}</span>
<VIcon
icon="mdi-arrow-right-circle-outline"
class="ms-1"
/>
</RouterLink> </RouterLink>
</div> </div>
</template> </template>
+1 -1
View File
@@ -31,7 +31,7 @@ function getApiPath(paths: string[] | string) {
<div v-if="title" class="mt-3 md:flex md:items-center md:justify-between"> <div v-if="title" class="mt-3 md:flex md:items-center md:justify-between">
<div class="min-w-0 flex-1 mx-0"> <div class="min-w-0 flex-1 mx-0">
<h2 <h2
class="mb-4 truncate text-2xl font-bold leading-7 text-gray-100 sm:overflow-visible sm:text-4xl sm:leading-9 md:mb-0" class="mb-4 ms-3 truncate text-2xl font-bold leading-7 text-gray-100 sm:overflow-visible sm:text-4xl sm:leading-9 md:mb-0"
data-testid="page-header" data-testid="page-header"
> >
<span class="text-moviepilot">{{ title }}</span> <span class="text-moviepilot">{{ title }}</span>
+5 -1
View File
@@ -16,6 +16,9 @@ const progressDialog = ref(false)
// 加载中 // 加载中
const loading = ref(false) const loading = ref(false)
// 已加载过
const isLoaded = ref(false)
// 日历属性 // 日历属性
const calendarOptions: Ref<CalendarOptions> = ref({ const calendarOptions: Ref<CalendarOptions> = ref({
height: 'auto', height: 'auto',
@@ -103,7 +106,7 @@ async function eventsHander(subscribe: Subscribe) {
// 调用API查询所有订阅 // 调用API查询所有订阅
async function getSubscribes() { async function getSubscribes() {
progressDialog.value = true if (!isLoaded.value) progressDialog.value = true
try { try {
// 订阅 // 订阅
loading.value = true loading.value = true
@@ -111,6 +114,7 @@ async function getSubscribes() {
loading.value = false loading.value = false
const subEvents = await Promise.all(subscribes.map(async sub => eventsHander(sub))) const subEvents = await Promise.all(subscribes.map(async sub => eventsHander(sub)))
calendarOptions.value.events = subEvents.flat().filter(event => event.start) as EventSourceInput calendarOptions.value.events = subEvents.flat().filter(event => event.start) as EventSourceInput
isLoaded.value = true
} catch (error) { } catch (error) {
console.error(error) console.error(error)
} }