Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c05f5779e | ||
|
|
9af200f89e | ||
|
|
7e221cfd46 | ||
|
|
640882d178 | ||
|
|
3a1436abef | ||
|
|
d431f0490d | ||
|
|
4c2a6c92a6 | ||
|
|
086c230e9e | ||
|
|
27e2ff50f2 | ||
|
|
3134e5596b | ||
|
|
315274abf9 | ||
|
|
52bbf65fa8 | ||
|
|
9c018ec63b | ||
|
|
bd7e457cdb | ||
|
|
36a0f8515b | ||
|
|
cac10a337d | ||
|
|
edb53cc58f | ||
|
|
1dceeecdad | ||
|
|
f8071ada0b | ||
|
|
21bc8edbd8 | ||
|
|
2a8aeb5041 | ||
|
|
1a7760cf6d | ||
|
|
aee4eed5ac | ||
|
|
87215fb590 | ||
|
|
5409126187 | ||
|
|
9840782ce5 | ||
|
|
d18f42cd6f | ||
|
|
9372e98459 | ||
|
|
9400f4660d | ||
|
|
f0d66b8fba | ||
|
|
78abe72815 | ||
|
|
1ce75916ef | ||
|
|
46959d4baa | ||
|
|
b24cc44493 | ||
|
|
46f6c29e1d | ||
|
|
5ad75b8420 | ||
|
|
2030459f20 | ||
|
|
2855bf812b | ||
|
|
69989893d9 | ||
|
|
ffc61f4a31 | ||
|
|
dd051f28d2 | ||
|
|
a3d2def72b | ||
|
|
e8552b4385 | ||
|
|
d73e4853a8 | ||
|
|
7f991da183 | ||
|
|
046d96a012 | ||
|
|
9ee6ca43e3 |
7
.github/workflows/build.yml
vendored
@@ -27,6 +27,13 @@ jobs:
|
|||||||
node-version: '18'
|
node-version: '18'
|
||||||
cache: 'yarn'
|
cache: 'yarn'
|
||||||
|
|
||||||
|
- name: Download Icons
|
||||||
|
run: |
|
||||||
|
pwd
|
||||||
|
curl -sL "https://github.com/jxxghp/MoviePilot-Plugins/archive/refs/heads/main.zip" | busybox unzip -d /tmp -
|
||||||
|
mv /tmp/MoviePilot-Plugins-main/icons public/plugin_icon
|
||||||
|
rm -rf /tmp/MoviePilot-Plugins-main
|
||||||
|
|
||||||
- name: Build frontend
|
- name: Build frontend
|
||||||
id: build_frontend
|
id: build_frontend
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
1
.gitignore
vendored
@@ -32,3 +32,4 @@ dist-ssr
|
|||||||
|
|
||||||
# iconify dist files
|
# iconify dist files
|
||||||
src/@iconify/*.js
|
src/@iconify/*.js
|
||||||
|
public/plugin_icon/**
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "moviepilot",
|
"name": "moviepilot",
|
||||||
"version": "1.3.4-2",
|
"version": "1.4.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"bin": "dist/service.js",
|
"bin": "dist/service.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 154 KiB |
|
Before Width: | Height: | Size: 93 KiB |
|
Before Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 7.0 KiB |
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 186 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 81 KiB |
@@ -55,7 +55,7 @@ export function formatFileSize(bytes: number) {
|
|||||||
if (bytes < 0)
|
if (bytes < 0)
|
||||||
throw new Error('字节数不能为负数。')
|
throw new Error('字节数不能为负数。')
|
||||||
|
|
||||||
const units = ['B', 'K', 'M', 'G', 'T']
|
const units = ['B', 'KB', 'MB', 'GB', 'TB']
|
||||||
let size = bytes
|
let size = bytes
|
||||||
let unitIndex = 0
|
let unitIndex = 0
|
||||||
|
|
||||||
|
|||||||
@@ -337,7 +337,7 @@ export interface TmdbEpisode {
|
|||||||
guest_stars: Object[]
|
guest_stars: Object[]
|
||||||
}
|
}
|
||||||
|
|
||||||
// TMDB人特信息
|
// TMDB人物信息
|
||||||
export interface TmdbPerson {
|
export interface TmdbPerson {
|
||||||
// ID
|
// ID
|
||||||
id?: number
|
id?: number
|
||||||
@@ -388,6 +388,34 @@ export interface TmdbPerson {
|
|||||||
biography?: string
|
biography?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 豆瓣人物信息
|
||||||
|
export interface DoubanPerson {
|
||||||
|
// ID
|
||||||
|
id?: string
|
||||||
|
|
||||||
|
// 名称
|
||||||
|
name?: string
|
||||||
|
|
||||||
|
// 角色
|
||||||
|
roles?: string[]
|
||||||
|
|
||||||
|
// 简介
|
||||||
|
title?: string
|
||||||
|
|
||||||
|
// 详情页面
|
||||||
|
url?: string
|
||||||
|
|
||||||
|
// 饰演
|
||||||
|
character?: string
|
||||||
|
|
||||||
|
// 图片 large/normal
|
||||||
|
avatar?: { [key: string]: string }
|
||||||
|
|
||||||
|
// 别名
|
||||||
|
latin_name?: string
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// 站点
|
// 站点
|
||||||
export interface Site {
|
export interface Site {
|
||||||
|
|
||||||
@@ -541,6 +569,15 @@ export interface Plugin {
|
|||||||
|
|
||||||
// 是否有详情页面
|
// 是否有详情页面
|
||||||
has_page?: boolean
|
has_page?: boolean
|
||||||
|
|
||||||
|
// 是否有新版本
|
||||||
|
has_update?: boolean
|
||||||
|
|
||||||
|
// 是否本地插件
|
||||||
|
is_local?: boolean
|
||||||
|
|
||||||
|
// 插件仓库地址
|
||||||
|
repo_url?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
// 种子信息
|
// 种子信息
|
||||||
@@ -625,6 +662,9 @@ export interface MetaInfo {
|
|||||||
// 原字符串
|
// 原字符串
|
||||||
org_string?: string
|
org_string?: string
|
||||||
|
|
||||||
|
// 原标题(未经识别词转换)
|
||||||
|
title?: string
|
||||||
|
|
||||||
// 副标题
|
// 副标题
|
||||||
subtitle?: string
|
subtitle?: string
|
||||||
|
|
||||||
@@ -726,6 +766,9 @@ export interface MetaInfo {
|
|||||||
|
|
||||||
// 资源类型+特效
|
// 资源类型+特效
|
||||||
edition: string
|
edition: string
|
||||||
|
|
||||||
|
// 应用的自定义识别词
|
||||||
|
apply_words: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
// 上下文信息
|
// 上下文信息
|
||||||
|
|||||||
88
src/components/cards/DoubanPersonCard.vue
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import personIcon from '@images/misc/person-icon.png'
|
||||||
|
import type { DoubanPerson } from '@/api/types'
|
||||||
|
|
||||||
|
const personProps = defineProps({
|
||||||
|
person: Object as PropType<DoubanPerson>,
|
||||||
|
width: String,
|
||||||
|
height: String,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 当前人物
|
||||||
|
const personInfo = ref(personProps.person)
|
||||||
|
|
||||||
|
// 人物图片是否加载
|
||||||
|
const isImageLoaded = ref(false)
|
||||||
|
|
||||||
|
// 人物图片地址
|
||||||
|
function getPersonImage() {
|
||||||
|
if (!personInfo.value?.avatar)
|
||||||
|
return personIcon
|
||||||
|
return personInfo.value?.avatar?.large
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打开人物详情
|
||||||
|
function goPersonDetail() {
|
||||||
|
if (!personInfo.value?.id)
|
||||||
|
return
|
||||||
|
window.open(`https://movie.douban.com/celebrity/${personInfo.value?.id}/`, '_blank')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<VHover v-bind="personProps">
|
||||||
|
<template #default="hover">
|
||||||
|
<VCard
|
||||||
|
v-bind="hover.props"
|
||||||
|
:height="personProps.height"
|
||||||
|
:width="personProps.width"
|
||||||
|
class="rounded-lg"
|
||||||
|
:class="{
|
||||||
|
'transition transform-cpu duration-300 scale-105': hover.isHovering,
|
||||||
|
}"
|
||||||
|
@click.stop="goPersonDetail"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="person-card relative transform-gpu cursor-pointer rounded shadow ring-1 transition duration-150 ease-in-out scale-100 ring-gray-700"
|
||||||
|
>
|
||||||
|
<div style="padding-bottom: 150%;">
|
||||||
|
<div class="absolute inset-0 flex h-full w-full flex-col items-center p-2">
|
||||||
|
<div class="relative mt-2 mb-4 flex h-1/2 w-full justify-center">
|
||||||
|
<VAvatar
|
||||||
|
size="120"
|
||||||
|
:class="{
|
||||||
|
'ring-1 ring-gray-700': isImageLoaded,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<VImg
|
||||||
|
v-img
|
||||||
|
:src="getPersonImage()"
|
||||||
|
cover
|
||||||
|
@load="isImageLoaded = true"
|
||||||
|
/>
|
||||||
|
</VAvatar>
|
||||||
|
</div>
|
||||||
|
<div class="w-full truncate text-center font-bold">
|
||||||
|
{{ personInfo?.name }}
|
||||||
|
</div>
|
||||||
|
<div class="overflow-hidden whitespace-normal text-center text-sm" style=" display: -webkit-box; overflow: hidden; -webkit-box-orient: vertical;-webkit-line-clamp: 2;">
|
||||||
|
{{ personInfo?.character }}
|
||||||
|
</div>
|
||||||
|
<div class="absolute bottom-0 left-0 right-0 h-12 rounded-b" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</VCard>
|
||||||
|
</template>
|
||||||
|
</VHover>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.person-card {
|
||||||
|
background-image: linear-gradient(45deg, rgb(var(--v-theme-background)), rgb(var(--v-theme-surface)) 60%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.person-card:hover {
|
||||||
|
background-image: linear-gradient(45deg, rgb(var(--v-theme-background)), rgb(var(--v-custom-background)) 60%);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -17,7 +17,7 @@ function getPercentage() {
|
|||||||
|
|
||||||
// 速度
|
// 速度
|
||||||
function getSpeedText() {
|
function getSpeedText() {
|
||||||
return `↑ ${props.info?.upspeed}/s ↓ ${props.info?.dlspeed}/s`
|
return `↑ ${props.info?.upspeed}/s ↓ ${props.info?.dlspeed}/s ${props.info?.left_time}`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 下载状态
|
// 下载状态
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ const isSubscribed = ref(false)
|
|||||||
// 本地存在状态
|
// 本地存在状态
|
||||||
const isExists = ref(false)
|
const isExists = ref(false)
|
||||||
|
|
||||||
// 各季缺失状态:0-已存在 1-部分缺失 2-全部缺失,没有数据也是已存在
|
// 各季缺失状态:0-已入库 1-部分缺失 2-全部缺失,没有数据也是已入库
|
||||||
const seasonsNotExisted = ref<{ [key: number]: number }>({})
|
const seasonsNotExisted = ref<{ [key: number]: number }>({})
|
||||||
|
|
||||||
// 订阅季弹窗
|
// 订阅季弹窗
|
||||||
@@ -44,7 +44,7 @@ const subscribeSeasonDialog = ref(false)
|
|||||||
const subscribeEditDialog = ref(false)
|
const subscribeEditDialog = ref(false)
|
||||||
|
|
||||||
// 订阅ID
|
// 订阅ID
|
||||||
const subscribeId = ref(0)
|
const subscribeId = ref<number>()
|
||||||
|
|
||||||
// 季详情
|
// 季详情
|
||||||
const seasonInfos = ref<TmdbSeason[]>([])
|
const seasonInfos = ref<TmdbSeason[]>([])
|
||||||
@@ -220,7 +220,7 @@ async function handleCheckSubscribe() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询当前媒体是否已存在
|
// 查询当前媒体是否已入库
|
||||||
async function handleCheckExists() {
|
async function handleCheckExists() {
|
||||||
try {
|
try {
|
||||||
const result: { [key: string]: any } = await api.get('media/exists', {
|
const result: { [key: string]: any } = await api.get('media/exists', {
|
||||||
@@ -251,6 +251,7 @@ async function checkSubscribe(season = 0) {
|
|||||||
const result: Subscribe = await api.get(`subscribe/media/${mediaid}`, {
|
const result: Subscribe = await api.get(`subscribe/media/${mediaid}`, {
|
||||||
params: {
|
params: {
|
||||||
season,
|
season,
|
||||||
|
title: props.media?.title,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -271,7 +272,7 @@ async function checkSeasonsNotExists() {
|
|||||||
const result: NotExistMediaInfo[] = await api.post('download/notexists', props.media)
|
const result: NotExistMediaInfo[] = await api.post('download/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
|
||||||
@@ -327,14 +328,14 @@ function getExistColor(season: number) {
|
|||||||
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)
|
else if (state === 2)
|
||||||
return '缺失'
|
return '缺失'
|
||||||
else
|
else
|
||||||
return '已存在'
|
return '已入库'
|
||||||
}
|
}
|
||||||
|
|
||||||
// 打开详情页
|
// 打开详情页
|
||||||
|
|||||||
@@ -143,5 +143,33 @@ function openTmdbPage(type: string, tmdbId: number) {
|
|||||||
识别失败,无法识别到有效信息!
|
识别失败,无法识别到有效信息!
|
||||||
</VAlert>
|
</VAlert>
|
||||||
</VCol>
|
</VCol>
|
||||||
|
<VExpansionPanels
|
||||||
|
v-show="context?.meta_info?.title !== context?.meta_info.org_string"
|
||||||
|
>
|
||||||
|
<VExpansionPanel>
|
||||||
|
<VExpansionPanelTitle>
|
||||||
|
识别词应用详情
|
||||||
|
</VExpansionPanelTitle>
|
||||||
|
<VExpansionPanelText>
|
||||||
|
<VChip
|
||||||
|
variant="elevated"
|
||||||
|
class="me-1 mb-1 break-all"
|
||||||
|
color="primary"
|
||||||
|
>
|
||||||
|
{{ context?.meta_info.org_string }}
|
||||||
|
</VChip>
|
||||||
|
<VChip
|
||||||
|
v-for="(val, key) in context?.meta_info.apply_words"
|
||||||
|
:key="key"
|
||||||
|
:val="val"
|
||||||
|
variant="outlined"
|
||||||
|
color="info"
|
||||||
|
class="me-1 mb-1 break-all"
|
||||||
|
>
|
||||||
|
{{ val }}
|
||||||
|
</VChip>
|
||||||
|
</VExpansionPanelText>
|
||||||
|
</VExpansionPanel>
|
||||||
|
</VExpansionPanels>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -16,16 +16,35 @@ const emit = defineEmits(['install'])
|
|||||||
// 提示框
|
// 提示框
|
||||||
const $toast = useToast()
|
const $toast = useToast()
|
||||||
|
|
||||||
|
// 进度框
|
||||||
|
const progressDialog = ref(false)
|
||||||
|
|
||||||
|
// 进度框文本
|
||||||
|
const progressText = ref('正在安装插件...')
|
||||||
|
|
||||||
// 图片是否加载完成
|
// 图片是否加载完成
|
||||||
const isImageLoaded = ref(false)
|
const isImageLoaded = ref(false)
|
||||||
|
|
||||||
// 安装插件
|
// 安装插件
|
||||||
async function installPlugin() {
|
async function installPlugin() {
|
||||||
try {
|
try {
|
||||||
|
// 显示等待提示框
|
||||||
|
progressDialog.value = true
|
||||||
|
progressText.value = `正在安装 ${props.plugin?.plugin_name} ${props?.plugin?.plugin_version} 插件...`
|
||||||
|
|
||||||
const result: { [key: string]: any } = await api.get(
|
const result: { [key: string]: any } = await api.get(
|
||||||
`plugin/install/${props.plugin?.id}`,
|
`plugin/install/${props.plugin?.id}`,
|
||||||
|
{
|
||||||
|
params: {
|
||||||
|
repo_url: props.plugin?.repo_url,
|
||||||
|
force: props.plugin?.has_update,
|
||||||
|
},
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 隐藏等待提示框
|
||||||
|
progressDialog.value = false
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
$toast.success(`插件 ${props.plugin?.plugin_name} 安装成功!`)
|
$toast.success(`插件 ${props.plugin?.plugin_name} 安装成功!`)
|
||||||
|
|
||||||
@@ -40,6 +59,13 @@ async function installPlugin() {
|
|||||||
console.error(error)
|
console.error(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 计算图标路径
|
||||||
|
const iconPath = computed(() => {
|
||||||
|
return props.plugin?.plugin_icon?.startsWith('http')
|
||||||
|
? props.plugin?.plugin_icon
|
||||||
|
: `/plugin_icon/${props.plugin?.plugin_icon}`
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -52,12 +78,21 @@ async function installPlugin() {
|
|||||||
class="relative pa-4 text-center card-cover-blurred"
|
class="relative pa-4 text-center card-cover-blurred"
|
||||||
:style="{ background: `${props.plugin?.plugin_color}` }"
|
:style="{ background: `${props.plugin?.plugin_color}` }"
|
||||||
>
|
>
|
||||||
|
<div
|
||||||
|
v-if="props.plugin?.has_update"
|
||||||
|
class="me-n3 absolute top-0 right-5"
|
||||||
|
>
|
||||||
|
<VIcon
|
||||||
|
icon="mdi-new-box"
|
||||||
|
class="text-white"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<VAvatar
|
<VAvatar
|
||||||
size="8rem"
|
size="8rem"
|
||||||
:class="{ shadow: isImageLoaded }"
|
:class="{ shadow: isImageLoaded }"
|
||||||
>
|
>
|
||||||
<VImg
|
<VImg
|
||||||
:src="`/plugin_icon/${props.plugin?.plugin_icon}`"
|
:src="iconPath"
|
||||||
aspect-ratio="4/3"
|
aspect-ratio="4/3"
|
||||||
cover
|
cover
|
||||||
@load="isImageLoaded = true"
|
@load="isImageLoaded = true"
|
||||||
@@ -76,9 +111,29 @@ async function installPlugin() {
|
|||||||
@click.stop
|
@click.stop
|
||||||
>
|
>
|
||||||
{{ props.plugin?.plugin_author }}
|
{{ props.plugin?.plugin_author }}
|
||||||
</a>
|
</a><br>
|
||||||
|
版本:{{ props.plugin?.plugin_version }}
|
||||||
</VCardText>
|
</VCardText>
|
||||||
</VCard>
|
</VCard>
|
||||||
|
<!-- 安装插件进度框 -->
|
||||||
|
<VDialog
|
||||||
|
v-model="progressDialog"
|
||||||
|
:scrim="false"
|
||||||
|
width="25rem"
|
||||||
|
>
|
||||||
|
<VCard
|
||||||
|
color="primary"
|
||||||
|
>
|
||||||
|
<VCardText class="text-center">
|
||||||
|
{{ progressText }}
|
||||||
|
<VProgressLinear
|
||||||
|
indeterminate
|
||||||
|
color="white"
|
||||||
|
class="mb-0 mt-1"
|
||||||
|
/>
|
||||||
|
</VCardText>
|
||||||
|
</VCard>
|
||||||
|
</VDialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@@ -136,6 +136,13 @@ async function showPluginConfig() {
|
|||||||
pluginConfigDialog.value = true
|
pluginConfigDialog.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 计算图标路径
|
||||||
|
const iconPath = computed(() => {
|
||||||
|
return props.plugin?.plugin_icon?.startsWith('http')
|
||||||
|
? props.plugin?.plugin_icon
|
||||||
|
: `/plugin_icon/${props.plugin?.plugin_icon}`
|
||||||
|
})
|
||||||
|
|
||||||
// 弹出菜单
|
// 弹出菜单
|
||||||
const dropdownItems = ref([
|
const dropdownItems = ref([
|
||||||
{
|
{
|
||||||
@@ -216,7 +223,7 @@ const dropdownItems = ref([
|
|||||||
:class="{ shadow: isImageLoaded }"
|
:class="{ shadow: isImageLoaded }"
|
||||||
>
|
>
|
||||||
<VImg
|
<VImg
|
||||||
:src="`/plugin_icon/${props.plugin?.plugin_icon}`"
|
:src="iconPath"
|
||||||
aspect-ratio="4/3"
|
aspect-ratio="4/3"
|
||||||
cover
|
cover
|
||||||
:class="{ shadow: isImageLoaded }"
|
:class="{ shadow: isImageLoaded }"
|
||||||
@@ -226,7 +233,7 @@ const dropdownItems = ref([
|
|||||||
<VCardItem class="py-2">
|
<VCardItem class="py-2">
|
||||||
<VCardTitle class="flex items-center flex-row">
|
<VCardTitle class="flex items-center flex-row">
|
||||||
<VBadge v-if="props.plugin?.state" dot inline color="success" class="me-1 mb-1" />
|
<VBadge v-if="props.plugin?.state" dot inline color="success" class="me-1 mb-1" />
|
||||||
{{ props.plugin?.plugin_name }}
|
{{ props.plugin?.plugin_name }}<span class="text-sm ms-2 mt-1 text-gray-500">{{ props.plugin?.plugin_version }}</span>
|
||||||
</VCardTitle>
|
</VCardTitle>
|
||||||
</VCardItem>
|
</VCardItem>
|
||||||
<VCardText>
|
<VCardText>
|
||||||
|
|||||||
@@ -33,9 +33,6 @@ const testButtonText = ref('测试')
|
|||||||
// 测试按钮可用性
|
// 测试按钮可用性
|
||||||
const testButtonDisable = ref(false)
|
const testButtonDisable = ref(false)
|
||||||
|
|
||||||
// 更新按钮文字
|
|
||||||
const updateButtonText = ref('更新')
|
|
||||||
|
|
||||||
// 更新按钮可用性
|
// 更新按钮可用性
|
||||||
const updateButtonDisable = ref(false)
|
const updateButtonDisable = ref(false)
|
||||||
|
|
||||||
@@ -48,6 +45,12 @@ const siteEditDialog = ref(false)
|
|||||||
// 资源浏览弹窗
|
// 资源浏览弹窗
|
||||||
const resourceDialog = ref(false)
|
const resourceDialog = ref(false)
|
||||||
|
|
||||||
|
// 进度条
|
||||||
|
const progressDialog = ref(false)
|
||||||
|
|
||||||
|
// 进度文本
|
||||||
|
const progressText = ref('请稍候 ...')
|
||||||
|
|
||||||
// 资源浏览表头
|
// 资源浏览表头
|
||||||
const resourceHeaders = [
|
const resourceHeaders = [
|
||||||
{ title: '标题', key: 'title', sortable: false },
|
{ title: '标题', key: 'title', sortable: false },
|
||||||
@@ -138,9 +141,11 @@ async function updateSiteCookie() {
|
|||||||
|
|
||||||
// 更新按钮状态
|
// 更新按钮状态
|
||||||
siteCookieDialog.value = false
|
siteCookieDialog.value = false
|
||||||
updateButtonText.value = '更新中 ...'
|
|
||||||
updateButtonDisable.value = true
|
updateButtonDisable.value = true
|
||||||
|
|
||||||
|
progressDialog.value = true
|
||||||
|
progressText.value = `正在更新 ${cardProps.site?.name} Cookie & UA ...`
|
||||||
|
|
||||||
const result: { [key: string]: any } = await api.get(
|
const result: { [key: string]: any } = await api.get(
|
||||||
`site/cookie/${cardProps.site?.id}`,
|
`site/cookie/${cardProps.site?.id}`,
|
||||||
{
|
{
|
||||||
@@ -156,7 +161,7 @@ async function updateSiteCookie() {
|
|||||||
else
|
else
|
||||||
$toast.error(`${cardProps.site?.name} 更新失败:${result.message}`)
|
$toast.error(`${cardProps.site?.name} 更新失败:${result.message}`)
|
||||||
|
|
||||||
updateButtonText.value = '更新'
|
progressDialog.value = false
|
||||||
updateButtonDisable.value = false
|
updateButtonDisable.value = false
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
@@ -299,7 +304,7 @@ onMounted(() => {
|
|||||||
<template #prepend>
|
<template #prepend>
|
||||||
<VIcon icon="mdi-refresh" />
|
<VIcon icon="mdi-refresh" />
|
||||||
</template>
|
</template>
|
||||||
{{ updateButtonText }}
|
更新
|
||||||
</VBtn>
|
</VBtn>
|
||||||
<VBtn
|
<VBtn
|
||||||
:disabled="testButtonDisable"
|
:disabled="testButtonDisable"
|
||||||
@@ -488,6 +493,24 @@ onMounted(() => {
|
|||||||
</VCardText>
|
</VCardText>
|
||||||
</VCard>
|
</VCard>
|
||||||
</VDialog>
|
</VDialog>
|
||||||
|
<VDialog
|
||||||
|
v-model="progressDialog"
|
||||||
|
:scrim="false"
|
||||||
|
width="25rem"
|
||||||
|
>
|
||||||
|
<VCard
|
||||||
|
color="primary"
|
||||||
|
>
|
||||||
|
<VCardText class="text-center">
|
||||||
|
{{ progressText }}
|
||||||
|
<VProgressLinear
|
||||||
|
indeterminate
|
||||||
|
color="white"
|
||||||
|
class="mb-0 mt-1"
|
||||||
|
/>
|
||||||
|
</VCardText>
|
||||||
|
</VCard>
|
||||||
|
</VDialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|||||||
@@ -47,7 +47,6 @@ const $toast = useToast()
|
|||||||
// 调用API修改订阅
|
// 调用API修改订阅
|
||||||
async function updateSubscribeInfo() {
|
async function updateSubscribeInfo() {
|
||||||
try {
|
try {
|
||||||
subscribeForm.value.best_version = subscribeForm.value.best_version ? 1 : 0
|
|
||||||
const result: { [key: string]: any } = await api.put('subscribe/', subscribeForm.value)
|
const result: { [key: string]: any } = await api.put('subscribe/', subscribeForm.value)
|
||||||
// 提示
|
// 提示
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
@@ -122,11 +121,6 @@ async function removeSubscribe() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
watchEffect(() => {
|
|
||||||
if (props.subid)
|
|
||||||
getSubscribeInfo()
|
|
||||||
})
|
|
||||||
|
|
||||||
// 质量选择框数据
|
// 质量选择框数据
|
||||||
const qualityOptions = ref([
|
const qualityOptions = ref([
|
||||||
{
|
{
|
||||||
@@ -211,10 +205,11 @@ const effectOptions = ref([
|
|||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
// 初始化
|
watchEffect(() => {
|
||||||
onMounted(async () => {
|
if (props.subid) {
|
||||||
// 加载订阅站点列表
|
getSiteList()
|
||||||
getSiteList()
|
getSubscribeInfo()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -328,7 +323,10 @@ onMounted(async () => {
|
|||||||
</VCol>
|
</VCol>
|
||||||
</VRow>
|
</VRow>
|
||||||
<VRow>
|
<VRow>
|
||||||
<VCol cols="12">
|
<VCol
|
||||||
|
cols="12"
|
||||||
|
md="4"
|
||||||
|
>
|
||||||
<VSwitch
|
<VSwitch
|
||||||
v-model="subscribeForm.best_version"
|
v-model="subscribeForm.best_version"
|
||||||
label="洗版"
|
label="洗版"
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ const route = useRoute()
|
|||||||
// 标题
|
// 标题
|
||||||
const title = route.query?.title?.toString()
|
const title = route.query?.title?.toString()
|
||||||
|
|
||||||
|
// 类型
|
||||||
|
const type = route.query?.type?.toString()
|
||||||
|
|
||||||
// 计算API路径
|
// 计算API路径
|
||||||
function getApiPath(paths: string[] | string) {
|
function getApiPath(paths: string[] | string) {
|
||||||
if (Array.isArray(paths))
|
if (Array.isArray(paths))
|
||||||
@@ -34,6 +37,7 @@ function getApiPath(paths: string[] | string) {
|
|||||||
<PersonCardListView
|
<PersonCardListView
|
||||||
:apipath="getApiPath(props.paths || '')"
|
:apipath="getApiPath(props.paths || '')"
|
||||||
:params="route.query"
|
:params="route.query"
|
||||||
|
:type="type"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ const isImageLoaded = ref(false)
|
|||||||
// 获取背景图片
|
// 获取背景图片
|
||||||
async function fetchBackgroundImage() {
|
async function fetchBackgroundImage() {
|
||||||
api
|
api
|
||||||
.get('/login/tmdb')
|
.get('/login/wallpaper')
|
||||||
.then((response: any) => {
|
.then((response: any) => {
|
||||||
backgroundImageUrl.value = response.message
|
backgroundImageUrl.value = response.message
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -28,6 +28,18 @@ import MediaCardSlideView from '@/views/discover/MediaCardSlideView.vue'
|
|||||||
title="热门电视剧"
|
title="热门电视剧"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<MediaCardSlideView
|
||||||
|
apipath="douban/movie_hot"
|
||||||
|
linkurl="/browse/douban/movie_hot?title=热门电影"
|
||||||
|
title="热门电影"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<MediaCardSlideView
|
||||||
|
apipath="douban/tv_hot"
|
||||||
|
linkurl="/browse/douban/tv_hot?title=热门电视剧"
|
||||||
|
title="热门电视剧"
|
||||||
|
/>
|
||||||
|
|
||||||
<MediaCardSlideView
|
<MediaCardSlideView
|
||||||
apipath="douban/tv_animation"
|
apipath="douban/tv_animation"
|
||||||
linkurl="/browse/douban/tv_animation?title=热门动漫"
|
linkurl="/browse/douban/tv_animation?title=热门动漫"
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import NoDataFound from '@/components/NoDataFound.vue'
|
||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import type { Context } from '@/api/types'
|
import type { Context } from '@/api/types'
|
||||||
import store from '@/store'
|
import store from '@/store'
|
||||||
|
|||||||
@@ -6,6 +6,10 @@ import { hexToRgb } from '@layouts/utils'
|
|||||||
|
|
||||||
const vuetifyTheme = useTheme()
|
const vuetifyTheme = useTheme()
|
||||||
|
|
||||||
|
// 从Vuex Store中获取信息
|
||||||
|
const store = useStore()
|
||||||
|
const superUser = store.state.auth.superUser
|
||||||
|
|
||||||
const options = controlledComputed(
|
const options = controlledComputed(
|
||||||
() => vuetifyTheme.name.value,
|
() => vuetifyTheme.name.value,
|
||||||
() => {
|
() => {
|
||||||
@@ -129,6 +133,7 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<VBtn
|
<VBtn
|
||||||
|
v-if="superUser"
|
||||||
block
|
block
|
||||||
to="/history"
|
to="/history"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ async function fetchData({ done }: { done: any }) {
|
|||||||
v-if="dataList.length === 0 && isRefreshed"
|
v-if="dataList.length === 0 && isRefreshed"
|
||||||
error-code="404"
|
error-code="404"
|
||||||
error-title="没有数据"
|
error-title="没有数据"
|
||||||
error-description="无法获取到TMDB媒体信息。"
|
error-description="无法获取到媒体信息。"
|
||||||
/>
|
/>
|
||||||
</VInfiniteScroll>
|
</VInfiniteScroll>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -37,14 +37,14 @@ const isRefreshed = ref(false)
|
|||||||
// 存储每一季的集信息
|
// 存储每一季的集信息
|
||||||
const seasonEpisodesInfo = ref({} as { [key: number]: TmdbEpisode[] })
|
const seasonEpisodesInfo = ref({} as { [key: number]: TmdbEpisode[] })
|
||||||
|
|
||||||
// 各季缺失状态:0-已存在 1-部分缺失 2-全部缺失,没有数据也是已存在
|
// 各季缺失状态:0-已入库 1-部分缺失 2-全部缺失,没有数据也是已入库
|
||||||
const seasonsNotExisted = ref<{ [key: number]: number }>({})
|
const seasonsNotExisted = ref<{ [key: number]: number }>({})
|
||||||
|
|
||||||
// 各季的订阅状态
|
// 各季的订阅状态
|
||||||
const seasonsSubscribed = ref<{ [key: number]: boolean }>({})
|
const seasonsSubscribed = ref<{ [key: number]: boolean }>({})
|
||||||
|
|
||||||
// 订阅编号
|
// 订阅编号
|
||||||
const subscribeId = ref(0)
|
const subscribeId = ref<number>()
|
||||||
|
|
||||||
// 调用API查询详情
|
// 调用API查询详情
|
||||||
async function getMediaDetail() {
|
async function getMediaDetail() {
|
||||||
@@ -85,7 +85,7 @@ async function loadSeasonEpisodes(season: number) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询当前媒体是否已存在
|
// 查询当前媒体是否已入库
|
||||||
async function checkMovieExists() {
|
async function checkMovieExists() {
|
||||||
try {
|
try {
|
||||||
const result: { [key: string]: any } = await api.get('media/exists', {
|
const result: { [key: string]: any } = await api.get('media/exists', {
|
||||||
@@ -109,11 +109,12 @@ async function checkMovieExists() {
|
|||||||
// 查询当前媒体是否已订阅
|
// 查询当前媒体是否已订阅
|
||||||
async function checkSubscribe(season = 0) {
|
async function checkSubscribe(season = 0) {
|
||||||
try {
|
try {
|
||||||
const mediaid = `tmdb:${mediaDetail.value.tmdb_id}`
|
const mediaid = mediaDetail.value.tmdb_id ? `tmdb:${mediaDetail.value.tmdb_id}` : `douban:${mediaDetail.value.douban_id}`
|
||||||
|
|
||||||
const result: Subscribe = await api.get(`subscribe/media/${mediaid}`, {
|
const result: Subscribe = await api.get(`subscribe/media/${mediaid}`, {
|
||||||
params: {
|
params: {
|
||||||
season,
|
season,
|
||||||
|
title: mediaDetail.value.title,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -138,7 +139,7 @@ async function checkSeasonsNotExists() {
|
|||||||
isExists.value = true
|
isExists.value = true
|
||||||
|
|
||||||
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
|
||||||
@@ -358,14 +359,14 @@ function getExistColor(season: number) {
|
|||||||
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)
|
else if (state === 2)
|
||||||
return '缺失'
|
return '缺失'
|
||||||
else
|
else
|
||||||
return '已存在'
|
return '已入库'
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算订阅图标
|
// 计算订阅图标
|
||||||
@@ -391,10 +392,11 @@ function joinArray(arr: string[]) {
|
|||||||
|
|
||||||
// 开始搜索
|
// 开始搜索
|
||||||
function handleSearch(area: string) {
|
function handleSearch(area: string) {
|
||||||
|
const keyword = mediaDetail.value.tmdb_id ? `tmdb:${mediaDetail.value.tmdb_id}` : `douban:${mediaDetail.value.douban_id}`
|
||||||
router.push({
|
router.push({
|
||||||
path: '/resource',
|
path: '/resource',
|
||||||
query: {
|
query: {
|
||||||
keyword: `tmdb:${mediaDetail.value.tmdb_id}`,
|
keyword,
|
||||||
type: mediaDetail.value.type,
|
type: mediaDetail.value.type,
|
||||||
area,
|
area,
|
||||||
},
|
},
|
||||||
@@ -418,9 +420,9 @@ onBeforeMount(() => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="mediaDetail.tmdb_id || mediaDetail.douban_id" class="max-w-8xl mx-auto px-4">
|
<div v-if="mediaDetail.tmdb_id || mediaDetail.douban_id" class="max-w-8xl mx-auto px-4">
|
||||||
<template v-if="mediaDetail.backdrop_path">
|
<template v-if="mediaDetail.backdrop_path || mediaDetail.poster_path">
|
||||||
<div class="vue-media-back absolute left-0 top-0 w-full h-96">
|
<div class="vue-media-back absolute left-0 top-0 w-full h-96">
|
||||||
<VImg class="h-96" :src="mediaDetail.backdrop_path" cover />
|
<VImg class="h-96" :src="mediaDetail.backdrop_path || mediaDetail.poster_path" cover />
|
||||||
</div>
|
</div>
|
||||||
<div class="vue-media-back absolute left-0 top-0 w-full h-96" />
|
<div class="vue-media-back absolute left-0 top-0 w-full h-96" />
|
||||||
</template>
|
</template>
|
||||||
@@ -456,7 +458,7 @@ onBeforeMount(() => {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="media-actions">
|
<div class="media-actions">
|
||||||
<VBtn v-if="mediaDetail.tmdb_id" variant="tonal" color="info">
|
<VBtn v-if="mediaDetail.tmdb_id || mediaDetail.douban_id" variant="tonal" color="info">
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<VIcon icon="mdi-magnify" />
|
<VIcon icon="mdi-magnify" />
|
||||||
</template>
|
</template>
|
||||||
@@ -482,7 +484,7 @@ onBeforeMount(() => {
|
|||||||
</VList>
|
</VList>
|
||||||
</VMenu>
|
</VMenu>
|
||||||
</VBtn>
|
</VBtn>
|
||||||
<VBtn v-if="mediaDetail.type === '电影'" class="ms-2" :color="getSubscribeColor" variant="tonal" @click="handleSubscribe(0)">
|
<VBtn v-if="mediaDetail.type === '电影' || mediaDetail.douban_id" class="ms-2" :color="getSubscribeColor" variant="tonal" @click="handleSubscribe(0)">
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<VIcon :icon="getSubscribeIcon" />
|
<VIcon :icon="getSubscribeIcon" />
|
||||||
</template>
|
</template>
|
||||||
@@ -510,10 +512,6 @@ onBeforeMount(() => {
|
|||||||
<span>{{ joinArray(director.roles) }}</span>
|
<span>{{ joinArray(director.roles) }}</span>
|
||||||
<a class="crew-name" :href="`${director.url}`" target="_blank">{{ director.name }}</a>
|
<a class="crew-name" :href="`${director.url}`" target="_blank">{{ director.name }}</a>
|
||||||
</li>
|
</li>
|
||||||
<li v-for="director in mediaDetail.actors" :key="director.id">
|
|
||||||
<span>{{ joinArray(director.roles) }}</span>
|
|
||||||
<a class="crew-name" :href="`${director.url}`" target="_blank">{{ director.name }}</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
<div class="mt-6">
|
<div class="mt-6">
|
||||||
<a v-if="mediaDetail.tmdb_id" class="mb-2 mr-2 inline-flex last:mr-0" :href="getTheMovieDbLink()" target="_blank">
|
<a v-if="mediaDetail.tmdb_id" class="mb-2 mr-2 inline-flex last:mr-0" :href="getTheMovieDbLink()" target="_blank">
|
||||||
@@ -665,12 +663,56 @@ onBeforeMount(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-else-if="mediaDetail.douban_id" class="media-overview-right">
|
||||||
|
<div class="media-facts">
|
||||||
|
<div v-if="mediaDetail.vote_average" class="media-ratings">
|
||||||
|
<VRating
|
||||||
|
v-model="mediaDetail.vote_average"
|
||||||
|
density="compact"
|
||||||
|
length="10"
|
||||||
|
class="ma-2"
|
||||||
|
readonly
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-if="mediaDetail.douban_id" class="media-fact">
|
||||||
|
<span>豆瓣ID</span>
|
||||||
|
<span class="media-fact-value">{{ mediaDetail.douban_id }}</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="mediaDetail.original_title" class="media-fact">
|
||||||
|
<span>原始标题</span>
|
||||||
|
<span class="media-fact-value">{{ mediaDetail.original_title }}</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="mediaDetail.release_date" class="media-fact">
|
||||||
|
<span>上映日期</span>
|
||||||
|
<span class="media-fact-value">
|
||||||
|
{{ mediaDetail.release_date }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="mediaDetail.production_countries" class="media-fact border-b-0">
|
||||||
|
<span>出品国家</span>
|
||||||
|
<span class="media-fact-value">
|
||||||
|
<span v-for="country in getProductionCountries" :key="country" class="flex items-center justify-end text-end">
|
||||||
|
{{ country }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="mediaDetail.tmdb_id">
|
<div v-if="mediaDetail.tmdb_id">
|
||||||
<PersonCardSlideView
|
<PersonCardSlideView
|
||||||
:apipath="`tmdb/credits/${mediaDetail.tmdb_id}/${mediaProps.type}`"
|
:apipath="`tmdb/credits/${mediaDetail.tmdb_id}/${mediaProps.type}`"
|
||||||
:linkurl="`/credits/tmdb/credits/${mediaDetail.tmdb_id}/${mediaProps.type}?title=演员阵容`"
|
:linkurl="`/credits/tmdb/credits/${mediaDetail.tmdb_id}/${mediaProps.type}?title=演员阵容&type=tmdb`"
|
||||||
title="演员阵容"
|
title="演员阵容"
|
||||||
|
type="tmdb"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="mediaDetail.douban_id">
|
||||||
|
<PersonCardSlideView
|
||||||
|
:apipath="`douban/credits/${mediaDetail.douban_id}/${mediaProps.type}`"
|
||||||
|
:linkurl="`/credits/douban/credits/${mediaDetail.douban_id}/${mediaProps.type}?title=演员阵容&type=douban`"
|
||||||
|
title="演员阵容"
|
||||||
|
type="douban"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="mediaDetail.tmdb_id">
|
<div v-if="mediaDetail.tmdb_id">
|
||||||
@@ -680,6 +722,13 @@ onBeforeMount(() => {
|
|||||||
title="推荐"
|
title="推荐"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-else-if="mediaDetail.douban_id">
|
||||||
|
<MediaCardSlideView
|
||||||
|
:apipath="`douban/recommend/${mediaDetail.douban_id}/${mediaProps.type}`"
|
||||||
|
:linkurl="`/browse/douban/recommend/${mediaDetail.douban_id}/${mediaProps.type}?title=推荐`"
|
||||||
|
title="推荐"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div v-if="mediaDetail.tmdb_id">
|
<div v-if="mediaDetail.tmdb_id">
|
||||||
<MediaCardSlideView
|
<MediaCardSlideView
|
||||||
:apipath="`tmdb/similar/${mediaDetail.tmdb_id}/${mediaProps.type}`"
|
:apipath="`tmdb/similar/${mediaDetail.tmdb_id}/${mediaProps.type}`"
|
||||||
@@ -693,7 +742,7 @@ onBeforeMount(() => {
|
|||||||
v-if="!mediaDetail.tmdb_id && !mediaDetail.douban_id && isRefreshed"
|
v-if="!mediaDetail.tmdb_id && !mediaDetail.douban_id && isRefreshed"
|
||||||
error-code="500"
|
error-code="500"
|
||||||
error-title="出错啦!"
|
error-title="出错啦!"
|
||||||
error-description="未识别到TMDB媒体信息。"
|
error-description="未识别到媒体信息。"
|
||||||
/>
|
/>
|
||||||
<!-- 订阅编辑弹窗 -->
|
<!-- 订阅编辑弹窗 -->
|
||||||
<SubscribeEditForm
|
<SubscribeEditForm
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import type { TmdbPerson } from '@/api/types'
|
import DoubanPersonCard from '@/components/cards/DoubanPersonCard.vue'
|
||||||
import PersonCard from '@/components/cards/PersonCard.vue'
|
import TmdbPersonCard from '@/components/cards/TmdbPersonCard.vue'
|
||||||
import NoDataFound from '@/components/NoDataFound.vue'
|
import NoDataFound from '@/components/NoDataFound.vue'
|
||||||
|
|
||||||
// 输入参数
|
// 输入参数
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
apipath: String,
|
apipath: String,
|
||||||
params: Object as PropType<{ [key: string]: any }>,
|
params: Object as PropType<{ [key: string]: any }>,
|
||||||
|
type: String,
|
||||||
})
|
})
|
||||||
|
|
||||||
// 判断是否有滚动条
|
// 判断是否有滚动条
|
||||||
@@ -29,8 +30,8 @@ const loading = ref(false)
|
|||||||
const isRefreshed = ref(false)
|
const isRefreshed = ref(false)
|
||||||
|
|
||||||
// 数据列表
|
// 数据列表
|
||||||
const dataList = ref<TmdbPerson[]>([])
|
const dataList = ref<any>([])
|
||||||
const currData = ref<TmdbPerson[]>([])
|
const currData = ref<any>([])
|
||||||
|
|
||||||
// 获取列表数据
|
// 获取列表数据
|
||||||
async function fetchData({ done }: { done: any }) {
|
async function fetchData({ done }: { done: any }) {
|
||||||
@@ -135,11 +136,22 @@ async function fetchData({ done }: { done: any }) {
|
|||||||
>
|
>
|
||||||
<template #loading />
|
<template #loading />
|
||||||
<div
|
<div
|
||||||
v-if="dataList.length > 0"
|
v-if="dataList.length > 0 && props.type === 'tmdb'"
|
||||||
class="grid gap-4 grid-media-card mx-3"
|
class="grid gap-4 grid-media-card mx-3"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
>
|
>
|
||||||
<PersonCard
|
<TmdbPersonCard
|
||||||
|
v-for="data in dataList"
|
||||||
|
:key="data.id"
|
||||||
|
:person="data"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="dataList.length > 0 && props.type === 'douban'"
|
||||||
|
class="grid gap-4 grid-media-card mx-3"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<DoubanPersonCard
|
||||||
v-for="data in dataList"
|
v-for="data in dataList"
|
||||||
:key="data.id"
|
:key="data.id"
|
||||||
:person="data"
|
:person="data"
|
||||||
@@ -149,7 +161,7 @@ async function fetchData({ done }: { done: any }) {
|
|||||||
v-if="dataList.length === 0 && isRefreshed"
|
v-if="dataList.length === 0 && isRefreshed"
|
||||||
error-code="404"
|
error-code="404"
|
||||||
error-title="没有数据"
|
error-title="没有数据"
|
||||||
error-description="无法获取到TMDB媒体信息。"
|
error-description="无法获取到媒体信息。"
|
||||||
/>
|
/>
|
||||||
</VInfiniteScroll>
|
</VInfiniteScroll>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,21 +1,22 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import PersionCard from '@/components/cards/PersonCard.vue'
|
import TmdbPersonCard from '@/components/cards/TmdbPersonCard.vue'
|
||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import type { TmdbPerson } from '@/api/types'
|
|
||||||
import SlideView from '@/components/slide/SlideView.vue'
|
import SlideView from '@/components/slide/SlideView.vue'
|
||||||
|
import DoubanPersonCard from '@/components/cards/DoubanPersonCard.vue'
|
||||||
|
|
||||||
// 输入参数
|
// 输入参数
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
apipath: String,
|
apipath: String,
|
||||||
linkurl: String,
|
linkurl: String,
|
||||||
title: String,
|
title: String,
|
||||||
|
type: String,
|
||||||
})
|
})
|
||||||
|
|
||||||
// 组件加载完成
|
// 组件加载完成
|
||||||
const componentLoaded = ref(false)
|
const componentLoaded = ref(false)
|
||||||
|
|
||||||
// 数据列表
|
// 数据列表
|
||||||
const dataList = ref<TmdbPerson[]>([])
|
const dataList = ref<any>([])
|
||||||
|
|
||||||
// 获取订阅列表数据
|
// 获取订阅列表数据
|
||||||
async function fetchData() {
|
async function fetchData() {
|
||||||
@@ -46,7 +47,14 @@ onMounted(fetchData)
|
|||||||
v-for="data in dataList"
|
v-for="data in dataList"
|
||||||
:key="data.id"
|
:key="data.id"
|
||||||
>
|
>
|
||||||
<PersionCard
|
<TmdbPersonCard
|
||||||
|
v-if="props.type === 'tmdb'"
|
||||||
|
:person="data"
|
||||||
|
height="15rem"
|
||||||
|
width="10rem"
|
||||||
|
/>
|
||||||
|
<DoubanPersonCard
|
||||||
|
v-if="props.type === 'douban'"
|
||||||
:person="data"
|
:person="data"
|
||||||
height="15rem"
|
height="15rem"
|
||||||
width="10rem"
|
width="10rem"
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ watchEffect(() => {
|
|||||||
// group data
|
// group data
|
||||||
const key = `${torrent_info.title}_${torrent_info.size}`
|
const key = `${torrent_info.title}_${torrent_info.size}`
|
||||||
if (groupMap.has(key)) {
|
if (groupMap.has(key)) {
|
||||||
// 已存在相同标题和大小的分组,将当前上下文信息添加到分组中
|
// 已入库相同标题和大小的分组,将当前上下文信息添加到分组中
|
||||||
const group = groupMap.get(key)
|
const group = groupMap.get(key)
|
||||||
group?.push(item)
|
group?.push(item)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,9 +19,9 @@ const getInstalledPluginList = computed(() => {
|
|||||||
return dataList.value.filter(item => item.installed)
|
return dataList.value.filter(item => item.installed)
|
||||||
})
|
})
|
||||||
|
|
||||||
// 获取未安装的插件列表
|
// 获取未安装或者有更新的插件列表
|
||||||
const getUninstalledPluginList = computed(() => {
|
const getUninstalledPluginList = computed(() => {
|
||||||
return dataList.value.filter(item => !item.installed)
|
return dataList.value.filter(item => !item.installed || item.has_update)
|
||||||
})
|
})
|
||||||
|
|
||||||
// 关闭插件市场窗口
|
// 关闭插件市场窗口
|
||||||
@@ -84,13 +84,14 @@ onBeforeMount(fetchData)
|
|||||||
<VDialog
|
<VDialog
|
||||||
v-model="PluginAppDialog"
|
v-model="PluginAppDialog"
|
||||||
fullscreen
|
fullscreen
|
||||||
|
scrollable
|
||||||
:scrim="false"
|
:scrim="false"
|
||||||
transition="dialog-bottom-transition"
|
transition="dialog-bottom-transition"
|
||||||
>
|
>
|
||||||
<!-- Dialog Activator -->
|
<!-- Dialog Activator -->
|
||||||
<template #activator="{ props }">
|
<template #activator="{ props }">
|
||||||
<VBtn
|
<VBtn
|
||||||
icon="mdi-plus"
|
icon="mdi-store-plus"
|
||||||
v-bind="props"
|
v-bind="props"
|
||||||
size="x-large"
|
size="x-large"
|
||||||
class="fixed right-5 bottom-5"
|
class="fixed right-5 bottom-5"
|
||||||
@@ -119,7 +120,7 @@ onBeforeMount(fetchData)
|
|||||||
</VToolbarItems>
|
</VToolbarItems>
|
||||||
</VToolbar>
|
</VToolbar>
|
||||||
</div>
|
</div>
|
||||||
<div class="pa-4">
|
<VCardText>
|
||||||
<div class="grid gap-4 grid-plugin-card">
|
<div class="grid gap-4 grid-plugin-card">
|
||||||
<PluginAppCard
|
<PluginAppCard
|
||||||
v-for="data in getUninstalledPluginList"
|
v-for="data in getUninstalledPluginList"
|
||||||
@@ -134,7 +135,7 @@ onBeforeMount(fetchData)
|
|||||||
error-title="没有未安装插件"
|
error-title="没有未安装插件"
|
||||||
error-description="所有可用插件均已安装。"
|
error-description="所有可用插件均已安装。"
|
||||||
/>
|
/>
|
||||||
</div>
|
</VCardText>
|
||||||
</VCard>
|
</VCard>
|
||||||
</VDialog>
|
</VDialog>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ const loading = ref(false)
|
|||||||
const totalItems = ref(0)
|
const totalItems = ref(0)
|
||||||
|
|
||||||
// 每页条数
|
// 每页条数
|
||||||
const itemsPerPage = ref(25)
|
const itemsPerPage = ref(50)
|
||||||
|
|
||||||
// 当前页码
|
// 当前页码
|
||||||
const currentPage = ref(1)
|
const currentPage = ref(1)
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ onMounted(() => {
|
|||||||
<div>
|
<div>
|
||||||
<div class="max-w-6xl py-4 sm:grid sm:grid-cols-3 sm:gap-4">
|
<div class="max-w-6xl py-4 sm:grid sm:grid-cols-3 sm:gap-4">
|
||||||
<dt class="block text-sm font-bold">
|
<dt class="block text-sm font-bold">
|
||||||
当前版本
|
软件版本
|
||||||
</dt>
|
</dt>
|
||||||
<dd class="flex text-sm sm:col-span-2 sm:mt-0">
|
<dd class="flex text-sm sm:col-span-2 sm:mt-0">
|
||||||
<span class="flex-grow flex flex-row items-center truncate">
|
<span class="flex-grow flex flex-row items-center truncate">
|
||||||
@@ -98,6 +98,30 @@ onMounted(() => {
|
|||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="max-w-6xl py-4 sm:grid sm:grid-cols-3 sm:gap-4">
|
||||||
|
<dt class="block text-sm font-bold">
|
||||||
|
认证资源版本
|
||||||
|
</dt>
|
||||||
|
<dd class="flex text-sm sm:col-span-2 sm:mt-0">
|
||||||
|
<span class="flex-grow flex flex-row items-center truncate">
|
||||||
|
<code class="truncate">{{ systemEnv.AUTH_VERSION }}</code>
|
||||||
|
</span>
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="max-w-6xl py-4 sm:grid sm:grid-cols-3 sm:gap-4">
|
||||||
|
<dt class="block text-sm font-bold">
|
||||||
|
站点资源版本
|
||||||
|
</dt>
|
||||||
|
<dd class="flex text-sm sm:col-span-2 sm:mt-0">
|
||||||
|
<span class="flex-grow flex flex-row items-center truncate">
|
||||||
|
<code class="truncate">{{ systemEnv.INDEXER_VERSION }}</code>
|
||||||
|
</span>
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="max-w-6xl py-4 sm:grid sm:grid-cols-3 sm:gap-4">
|
<div class="max-w-6xl py-4 sm:grid sm:grid-cols-3 sm:gap-4">
|
||||||
<dt class="block text-sm font-bold">
|
<dt class="block text-sm font-bold">
|
||||||
|
|||||||
@@ -147,6 +147,10 @@ async function deactivateUser(user: User) {
|
|||||||
|
|
||||||
// 新增用户
|
// 新增用户
|
||||||
async function addUser() {
|
async function addUser() {
|
||||||
|
if (!userForm.name || !userForm.password || !userForm.email) {
|
||||||
|
$toast.error('请填写完整信息!')
|
||||||
|
return
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const result: { [key: string]: any } = await api.post('user', userForm)
|
const result: { [key: string]: any } = await api.post('user', userForm)
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
@@ -447,6 +451,7 @@ onMounted(() => {
|
|||||||
>
|
>
|
||||||
<VTextField
|
<VTextField
|
||||||
v-model="userForm.email"
|
v-model="userForm.email"
|
||||||
|
:rules="[requiredValidator]"
|
||||||
label="邮箱"
|
label="邮箱"
|
||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
|
|||||||
@@ -166,13 +166,26 @@ onMounted(() => {
|
|||||||
<VTextarea
|
<VTextarea
|
||||||
v-model="customIdentifiers"
|
v-model="customIdentifiers"
|
||||||
auto-grow
|
auto-grow
|
||||||
placeholder="支持正则表达式,特殊字符需要\转义,一行为一组,支持以下几种配置格式:
|
placeholder="支持正则表达式,特殊字符需要\转义,一行为一组"
|
||||||
屏蔽词
|
|
||||||
被替换词 => 替换词
|
|
||||||
前定位词 <> 后定位词 >> 集偏移量(EP)
|
|
||||||
被替换词 => 替换词 && 前定位词 <> 后定位词 >> 集偏移量(EP)"
|
|
||||||
/>
|
/>
|
||||||
</VCardItem>
|
</VCardItem>
|
||||||
|
<VCardItem>
|
||||||
|
<VAlert
|
||||||
|
type="info"
|
||||||
|
variant="tonal"
|
||||||
|
title="支持的配置格式:"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
v-html="`
|
||||||
|
屏蔽词<br>
|
||||||
|
被替换词 => 替换词<br>
|
||||||
|
前定位词 <> 后定位词 >> 集偏移量(EP)<br>
|
||||||
|
被替换词 => 替换词 && 前定位词 <> 后定位词 >> 集偏移量(EP)<br>
|
||||||
|
其中替换词支持格式:{[tmdbid/doubanid=xxx;type=movie/tv;s=xxx;e=xxx]} 直接指定TMDBID/豆瓣ID识别,其中s、e为季数和集数(可选)<br>
|
||||||
|
`"
|
||||||
|
/>
|
||||||
|
</VAlert>
|
||||||
|
</VCardItem>
|
||||||
<VCardItem>
|
<VCardItem>
|
||||||
<VBtn
|
<VBtn
|
||||||
type="submit"
|
type="submit"
|
||||||
|
|||||||