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

View File

@@ -35,36 +35,28 @@ function imageErrorHandler() {
// 默认图片
function getDefaultImage() {
if (props.media?.server === 'plex')
return plex
else if (props.media?.server === 'emby')
return emby
else if (props.media?.server === 'jellyfin')
return jellyfin
else
return plex
if (props.media?.server === 'plex') return plex
else if (props.media?.server === 'emby') return emby
else if (props.media?.server === 'jellyfin') return jellyfin
else return plex
}
// 跳转播放
function goPlay() {
if (props.media?.link)
window.open(props.media?.link, '_blank')
if (props.media?.link) window.open(props.media?.link, '_blank')
}
// 生成图片代理路径
function getImgUrl(url: string) {
if (!url)
return getDefaultImage()
else
return `${import.meta.env.VITE_API_BASE_URL}system/img/0?imgurl=${encodeURIComponent(url)}`
if (!url) return getDefaultImage()
else return `${import.meta.env.VITE_API_BASE_URL}system/img/0?imgurl=${encodeURIComponent(url)}`
}
// 根据多张图片生成媒体库封面
async function drawImages(imageList: string[]) {
// 图片
const IMAGES = imageList
if (IMAGES.length === 0)
return getDefaultImage()
if (IMAGES.length === 0) return getDefaultImage()
// 为所有图片添加system/img前缀
for (let i = 0; i < IMAGES.length; i++)
@@ -72,8 +64,7 @@ async function drawImages(imageList: string[]) {
// canvas
const canvas = canvasRef.value
if (!canvas)
return getDefaultImage()
if (!canvas) return getDefaultImage()
// 画布参数
const POSTER_WIDTH = (canvas.width - 32) / 4
@@ -85,8 +76,7 @@ async function drawImages(imageList: string[]) {
// 获取画布上下文
const ctx = canvas.getContext('2d')
if (!ctx)
return getDefaultImage()
if (!ctx) return getDefaultImage()
// 设置背景色为黑色
ctx.fillStyle = '#000000'
@@ -94,16 +84,14 @@ async function drawImages(imageList: string[]) {
// 绘制图片
async function drawImageWithReflection(imgSrc: string, index: number) {
if (!canvas)
return
if (!canvas) return
if (!ctx)
return
if (!ctx) return
const img = new Image()
img.setAttribute('crossorigin', 'anonymous')
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 y = MARGIN_HEIGHT
@@ -125,12 +113,7 @@ async function drawImages(imageList: string[]) {
REFLECTION_HEIGHT,
)
const gradient = ctx.createLinearGradient(
0,
REFLECTION_SHOW_HEIGHT - REFLECTION_HEIGHT,
0,
REFLECTION_HEIGHT,
)
const gradient = ctx.createLinearGradient(0, REFLECTION_SHOW_HEIGHT - REFLECTION_HEIGHT, 0, REFLECTION_HEIGHT)
gradient.addColorStop(0, 'rgba(0, 0, 0, 1)')
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)
for (let i = 0; i < loopCount; i++)
await drawImageWithReflection(IMAGES[i], i + 1)
for (let i = 0; i < loopCount; i++) await drawImageWithReflection(IMAGES[i], i + 1)
// 转换为图片地址
return canvas.toDataURL('image/png')
@@ -152,17 +134,12 @@ async function drawImages(imageList: string[]) {
onMounted(async () => {
if (props.media?.image_list && props.media?.image_list.length > 0)
imgUrl.value = await drawImages(props.media?.image_list || [])
else
imgUrl.value = getImgUrl(props.media?.image || '')
else imgUrl.value = getImgUrl(props.media?.image || '')
})
</script>
<template>
<VHover
v-bind="props"
:height="props.height"
:width="props.width"
>
<VHover v-bind="props" :height="props.height" :width="props.width">
<template #default="hover">
<VCard
v-bind="hover.props"
@@ -175,13 +152,7 @@ onMounted(async () => {
>
<template #image>
<canvas ref="canvasRef" class="w-full h-full hidden" />
<VImg
:src="imgUrl"
aspect-ratio="2/3"
cover
@load="imageLoadHandler"
@error="imageErrorHandler"
>
<VImg :src="imgUrl" aspect-ratio="2/3" cover @load="imageLoadHandler" @error="imageErrorHandler">
<template #placeholder>
<div class="w-full h-full">
<VSkeletonLoader class="object-cover aspect-w-3 aspect-h-2" />
@@ -190,7 +161,7 @@ onMounted(async () => {
<VCardText
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 }}
</h1>
</VCardText>
@@ -200,3 +171,9 @@ onMounted(async () => {
</template>
</VHover>
</template>
<style lang="scss">
.text-shadow {
text-shadow: 1px 1px #777;
}
</style>

View File

@@ -180,6 +180,7 @@ watch(
class="flex flex-col rounded-lg"
:class="{
'outline-dashed outline-1': props.media?.best_version && imageLoaded,
'transition transform-cpu duration-300 scale-105 shadow-lg': hover.isHovering,
}"
@click="editSubscribeDialog"
>
@@ -223,26 +224,29 @@ watch(
</template>
<div v-if="imageLoaded">
<VCardText class="flex items-center">
<div
class="h-auto w-12 flex-shrink-0 overflow-hidden rounded-md shadow-lg"
:class="{ 'transition transform-cpu duration-300 scale-105': hover.isHovering }"
>
<VImg :src="props.media?.poster" aspect-ratio="2/3" cover @click.stop="viewMediaDetail" />
<div 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">
<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 overflow-hidden pl-2 xl:pl-4">
<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 }}
{{ formatSeason(props.media?.season ? props.media?.season.toString() : '') }}
</div>
</div>
</VCardText>
<VCardText class="flex justify-space-between align-center flex-wrap">
<div class="d-flex align-center">
<div class="flex align-center">
<IconBtn
v-if="props.media?.total_episode"
v-bind="props"
icon="mdi-progress-clock"
icon="mdi-progress-download"
color="white"
class="me-1"
/>
@@ -260,12 +264,14 @@ watch(
<VIcon icon="mdi-download" class="me-1" />
{{ lastUpdateText }}
</VCardText>
<VProgressLinear
v-if="getPercentage() > 0"
:model-value="getPercentage()"
bg-color="success"
color="success"
/>
<div class="w-full absolute bottom-0">
<VProgressLinear
v-if="getPercentage() > 0"
:model-value="getPercentage()"
bg-color="success"
color="success"
/>
</div>
</div>
</VCard>
</template>

View File

@@ -27,12 +27,10 @@ function slideNext(next: boolean) {
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
// console.log(`最多显示: ${card_max} 当前起点: ${card_current} 目标起点: ${card_index} 卡片宽度: ${card_width}`)
}
else {
} else {
const card_index = card_current - card_max
run_to_left_px = card_index * card_width
if (run_to_left_px <= 0)
run_to_left_px = 0
if (run_to_left_px <= 0) run_to_left_px = 0
// console.log(`最多显示: ${card_max} 当前起点: ${card_current} 目标起点: ${card_index} 卡片宽度: ${card_width}`)
}
slideview_content.value.scrollTo({
@@ -46,7 +44,7 @@ function slideNext(next: boolean) {
function countMaxNumber() {
slide_card_length = slideview_content.value.children.length
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_max = Math.trunc(slideview_content.value.clientWidth / card_width)
countDisabled()
@@ -55,16 +53,18 @@ function countMaxNumber() {
// 修改分页切换按钮状态
function countDisabled() {
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)
if (slide_card_length * card_width <= slideview_content.value.clientWidth)
disabled.value = 3
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)
card_current =
slideview_content.value.scrollLeft === 0
? 0
: Math.trunc((slideview_content.value.scrollLeft + card_width / 2) / card_width)
if (slide_card_length * card_width <= slideview_content.value.clientWidth) disabled.value = 3
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
else
disabled.value = 1
else disabled.value = 1
}
// 组件加载完成
@@ -123,8 +123,7 @@ onActivated(() => {
<style lang="scss" scoped>
.slideview_content {
-ms-overflow-style: none !important;
overflow-x: scroll !important;
overflow-y: hidden !important;
overflow: scroll hidden !important;
overscroll-behavior-x: contain !important;
scrollbar-width: none !important;
}

View File

@@ -1,22 +1,13 @@
<script lang="ts" setup>
// 输入参数
const props = inject('rankingPropsKey')
const props: any = inject('rankingPropsKey')
</script>
<template>
<div
class="ms-1"
>
<RouterLink
:to="props.linkurl ? props.linkurl : ''"
class="slider-title"
>
<span>{{ props.title }}</span>
<VIcon
icon="mdi-arrow-right-circle-outline"
class="ms-1"
/>
<div class="ms-1">
<RouterLink :to="props?.linkurl ? props?.linkurl : ''" class="slider-title">
<span>{{ props?.title }}</span>
<VIcon icon="mdi-arrow-right-circle-outline" class="ms-1" />
</RouterLink>
</div>
</template>

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 class="min-w-0 flex-1 mx-0">
<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"
>
<span class="text-moviepilot">{{ title }}</span>

View File

@@ -16,6 +16,9 @@ const progressDialog = ref(false)
// 加载中
const loading = ref(false)
// 已加载过
const isLoaded = ref(false)
// 日历属性
const calendarOptions: Ref<CalendarOptions> = ref({
height: 'auto',
@@ -103,7 +106,7 @@ async function eventsHander(subscribe: Subscribe) {
// 调用API查询所有订阅
async function getSubscribes() {
progressDialog.value = true
if (!isLoaded.value) progressDialog.value = true
try {
// 订阅
loading.value = true
@@ -111,6 +114,7 @@ async function getSubscribes() {
loading.value = false
const subEvents = await Promise.all(subscribes.map(async sub => eventsHander(sub)))
calendarOptions.value.events = subEvents.flat().filter(event => event.start) as EventSourceInput
isLoaded.value = true
} catch (error) {
console.error(error)
}