mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-05-10 17:42:50 +08:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7cce57496d | ||
|
|
e54e851f61 | ||
|
|
17020cf62d | ||
|
|
0c7be28eaa | ||
|
|
0d5a183f2e | ||
|
|
c222594bea | ||
|
|
3df8bdfbf2 | ||
|
|
5722547d93 | ||
|
|
dea5ebd95d | ||
|
|
048e41c1ca | ||
|
|
5078036c51 | ||
|
|
e7a128bf0d |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "moviepilot",
|
||||
"version": "1.1.9-1",
|
||||
"version": "1.2.0-1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite --host",
|
||||
|
||||
@@ -17,17 +17,18 @@
|
||||
// ℹ️ This mixin is inspired from vuetify for adding hover styles via before pseudo element
|
||||
@mixin before-pseudo() {
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
background: currentcolor;
|
||||
block-size: 100%;
|
||||
border-radius: inherit;
|
||||
content: "";
|
||||
inline-size: 100%;
|
||||
inset: 0;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
@media (hover) {
|
||||
&::before {
|
||||
position: absolute;
|
||||
background: currentcolor;
|
||||
block-size: 100%;
|
||||
border-radius: inherit;
|
||||
content: "";
|
||||
inline-size: 100%;
|
||||
inset: 0;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,17 +8,18 @@
|
||||
// ℹ️ This mixin is inspired from vuetify for adding hover styles via before pseudo element
|
||||
@mixin before-pseudo() {
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
border-radius: inherit;
|
||||
background: currentcolor;
|
||||
block-size: 100%;
|
||||
content: "";
|
||||
inline-size: 100%;
|
||||
inset: 0;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
@media (hover) {
|
||||
&::before {
|
||||
position: absolute;
|
||||
border-radius: inherit;
|
||||
background: currentcolor;
|
||||
block-size: 100%;
|
||||
content: "";
|
||||
inline-size: 100%;
|
||||
inset: 0;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ export default defineComponent({
|
||||
<style scoped>
|
||||
* {
|
||||
backface-visibility: hidden;
|
||||
perspective: 1000px;
|
||||
perspective: 62.5rem;
|
||||
transform: translateZ(0);
|
||||
will-change: block-size;
|
||||
}
|
||||
|
||||
@@ -840,55 +840,6 @@ export interface Setting {
|
||||
DOWNLOAD_PATH: string
|
||||
}
|
||||
|
||||
// 自定义订阅
|
||||
export interface Rss {
|
||||
id?: number
|
||||
// 名称
|
||||
name?: string
|
||||
// RSS地址
|
||||
url?: string
|
||||
// 类型
|
||||
type?: string
|
||||
// 标题
|
||||
title?: string
|
||||
// 年份
|
||||
year?: string
|
||||
// TMDBID
|
||||
tmdbid?: number
|
||||
// 季号
|
||||
season?: number
|
||||
// 海报
|
||||
poster?: string
|
||||
// 背景图
|
||||
backdrop?: string
|
||||
// 评分
|
||||
vote?: number
|
||||
// 简介
|
||||
description?: string
|
||||
// 总集数
|
||||
total_episode?: number
|
||||
// 包含
|
||||
include?: string
|
||||
// 排除
|
||||
exclude?: string
|
||||
// 洗版
|
||||
best_version?: number
|
||||
// 是否使用代理服务器
|
||||
proxy?: number
|
||||
// 是否使用过滤规则
|
||||
filter?: boolean
|
||||
// 保存路径
|
||||
save_path?: string
|
||||
// 已处理数量
|
||||
processed?: number
|
||||
// 附加信息
|
||||
note?: string
|
||||
// 最后更新时间
|
||||
last_update?: string
|
||||
// 状态 0-停用,1-启用
|
||||
state?: number
|
||||
}
|
||||
|
||||
// 文件浏览接口
|
||||
export interface EndPoints {
|
||||
list: any
|
||||
|
||||
@@ -53,7 +53,7 @@ async function installPlugin() {
|
||||
:style="{ background: `${props.plugin?.plugin_color}` }"
|
||||
>
|
||||
<VAvatar
|
||||
size="128"
|
||||
size="8rem"
|
||||
:class="{ shadow: isImageLoaded }"
|
||||
>
|
||||
<VImg
|
||||
|
||||
@@ -212,7 +212,7 @@ const dropdownItems = ref([
|
||||
</IconBtn>
|
||||
</div>
|
||||
<VAvatar
|
||||
size="128"
|
||||
size="8rem"
|
||||
:class="{ shadow: isImageLoaded }"
|
||||
>
|
||||
<VImg
|
||||
@@ -236,7 +236,7 @@ const dropdownItems = ref([
|
||||
<!-- 插件配置页面 -->
|
||||
<VDialog
|
||||
v-model="pluginConfigDialog"
|
||||
max-width="800"
|
||||
max-width="50rem"
|
||||
scrollable
|
||||
persistent
|
||||
>
|
||||
@@ -265,7 +265,7 @@ const dropdownItems = ref([
|
||||
<!-- 插件详情页面 -->
|
||||
<VDialog
|
||||
v-model="pluginInfoDialog"
|
||||
max-width="1000"
|
||||
max-width="62.5rem"
|
||||
scrollable
|
||||
persistent
|
||||
>
|
||||
|
||||
@@ -1,594 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { useToast } from 'vue-toast-notification'
|
||||
import { calculateTimeDifference } from '@/@core/utils'
|
||||
import { formatFileSize, formatSeason } from '@/@core/utils/formatters'
|
||||
import api from '@/api'
|
||||
import type { Rss, Site, TorrentInfo } from '@/api/types'
|
||||
|
||||
// 输入参数
|
||||
const props = defineProps({
|
||||
media: Object as PropType<Rss>,
|
||||
})
|
||||
|
||||
// 定义触发的自定义事件
|
||||
const emit = defineEmits(['remove', 'save'])
|
||||
|
||||
// 提示框
|
||||
const $toast = useToast()
|
||||
|
||||
// 图片是否加载完成
|
||||
const imageLoaded = ref(false)
|
||||
|
||||
// 订阅弹窗
|
||||
const rssInfoDialog = ref(false)
|
||||
|
||||
// RSS预览窗口
|
||||
const rssPreviewDialog = ref(false)
|
||||
|
||||
// 加载状态
|
||||
const previewLoading = ref(false)
|
||||
|
||||
// 总条数
|
||||
const previewTotalItems = ref(0)
|
||||
|
||||
// 每页条数
|
||||
const previewItemsPerPage = ref(25)
|
||||
|
||||
// 预览表头
|
||||
const previewHeaders = [
|
||||
{ title: '标题', key: 'title', sortable: true },
|
||||
{ title: '时间', key: 'pubdate', sortable: true },
|
||||
{ title: '大小', key: 'size', sortable: true },
|
||||
{ title: '', key: 'actions', sortable: false },
|
||||
]
|
||||
|
||||
// 预览数据
|
||||
const previewDataList = ref<TorrentInfo[]>([])
|
||||
|
||||
// 站点名称
|
||||
const siteName = ref('')
|
||||
|
||||
// 订阅编辑表单
|
||||
const rssForm = reactive<any>(props.media ?? {})
|
||||
|
||||
// 类型转换
|
||||
rssForm.best_version = rssForm.best_version === 1
|
||||
rssForm.proxy = rssForm.proxy === 1
|
||||
rssForm.filter = rssForm.filter === 1
|
||||
|
||||
// 上一次更新时间
|
||||
const lastUpdateText = ref(
|
||||
`${
|
||||
props.media?.last_update
|
||||
? `${calculateTimeDifference(props.media?.last_update || '')}前`
|
||||
: ''
|
||||
}`,
|
||||
)
|
||||
|
||||
// 图片加载完成响应
|
||||
function imageLoadHandler() {
|
||||
imageLoaded.value = true
|
||||
}
|
||||
|
||||
// 根据 type 返回不同的图标
|
||||
function getIcon() {
|
||||
if (props.media?.type === '电影')
|
||||
return 'mdi-movie'
|
||||
else if (props.media?.type === '电视剧')
|
||||
return 'mdi-television-classic'
|
||||
else
|
||||
return 'mdi-help-circle'
|
||||
}
|
||||
|
||||
// 计算文本颜色
|
||||
function getTextColor() {
|
||||
return imageLoaded.value ? 'white' : ''
|
||||
}
|
||||
|
||||
// 计算文本类
|
||||
function getTextClass() {
|
||||
return imageLoaded.value ? 'text-white' : ''
|
||||
}
|
||||
|
||||
// 删除订阅
|
||||
async function removerRss() {
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.delete(
|
||||
`rss/${props.media?.id}`,
|
||||
)
|
||||
|
||||
if (result.success) {
|
||||
// 通知父组件刷新
|
||||
emit('remove')
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
|
||||
// 调用API修改订阅
|
||||
async function updateRssInfo() {
|
||||
rssInfoDialog.value = false
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.put('rss', rssForm)
|
||||
|
||||
// 提示
|
||||
if (result.success) {
|
||||
$toast.success(`${props.media?.name} 更新成功!`)
|
||||
// 通知父组件刷新
|
||||
emit('remove')
|
||||
}
|
||||
else { $toast.error(`${props.media?.name} 更新失败:${result.message}!`) }
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
|
||||
// 查询站点名称
|
||||
async function querySiteName() {
|
||||
try {
|
||||
const result: Site = await api.get(
|
||||
`site/domain/${props.media?.url?.split('/')[2]}`,
|
||||
)
|
||||
|
||||
if (result)
|
||||
siteName.value = result.name
|
||||
}
|
||||
catch (e) {
|
||||
// 截取URL中的主域名作为站点名称
|
||||
siteName.value = props.media?.url?.split('/')[2] ?? '未知'
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
|
||||
// 预览按钮响应
|
||||
async function handleRssPreview() {
|
||||
rssPreviewDialog.value = true
|
||||
previewLoading.value = true
|
||||
await previewRss()
|
||||
previewLoading.value = false
|
||||
}
|
||||
|
||||
// 预览站点RSS
|
||||
async function previewRss() {
|
||||
try {
|
||||
const result: TorrentInfo[] = await api.get(
|
||||
`rss/preview/${props.media?.id}`,
|
||||
)
|
||||
previewDataList.value = result
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
|
||||
// 编辑订阅响应
|
||||
async function editRssDialog() {
|
||||
rssInfoDialog.value = true
|
||||
}
|
||||
|
||||
// 刷新按钮响应
|
||||
async function refreshRss() {
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.get(
|
||||
`rss/refresh/${props.media?.id}`,
|
||||
)
|
||||
|
||||
if (result.success)
|
||||
$toast.success(`${props.media?.name} 已提交刷新任务!`)
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
|
||||
// 生成1到50季的下拉框选项
|
||||
const seasonItems = ref(
|
||||
Array.from({ length: 50 }, (_, i) => i + 1).map(item => ({
|
||||
title: `第 ${item} 季`,
|
||||
value: item,
|
||||
})),
|
||||
)
|
||||
|
||||
// 打开种子详情页面
|
||||
function openTorrentDetail(page_url: string) {
|
||||
window.open(page_url, '_blank')
|
||||
}
|
||||
|
||||
// 下载种子文件
|
||||
async function downloadTorrentFile(enclosure: string) {
|
||||
window.open(enclosure, '_blank')
|
||||
}
|
||||
|
||||
// 弹出菜单
|
||||
const dropdownItems = ref([
|
||||
{
|
||||
title: '编辑',
|
||||
value: 1,
|
||||
props: {
|
||||
prependIcon: 'mdi-file-edit-outline',
|
||||
click: editRssDialog,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '预览',
|
||||
value: 2,
|
||||
props: {
|
||||
prependIcon: 'mdi-eye-outline',
|
||||
click: handleRssPreview,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '刷新',
|
||||
value: 3,
|
||||
props: {
|
||||
prependIcon: 'mdi-refresh',
|
||||
click: refreshRss,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '取消订阅',
|
||||
value: 4,
|
||||
props: {
|
||||
prependIcon: 'mdi-trash-can-outline',
|
||||
color: 'error',
|
||||
click: removerRss,
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
onMounted(() => {
|
||||
querySiteName()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VCard
|
||||
:key="props.media?.id"
|
||||
:class="`${rssForm.best_version ? 'outline-dashed outline-1' : ''}`"
|
||||
@click="editRssDialog"
|
||||
>
|
||||
<template #image>
|
||||
<VImg
|
||||
:src="props.media?.backdrop || props.media?.poster"
|
||||
aspect-ratio="2/3"
|
||||
cover
|
||||
class="brightness-50"
|
||||
@load="imageLoadHandler"
|
||||
/>
|
||||
</template>
|
||||
<VCardItem>
|
||||
<template #prepend>
|
||||
<VIcon
|
||||
size="1.9rem"
|
||||
:color="getTextColor()"
|
||||
:icon="getIcon()"
|
||||
/>
|
||||
</template>
|
||||
<VCardTitle :class="getTextClass()">
|
||||
{{ props.media?.name }}
|
||||
{{ formatSeason(props.media?.season ? props.media?.season.toString() : "") }}
|
||||
</VCardTitle>
|
||||
<template #append>
|
||||
<div class="me-n3">
|
||||
<IconBtn>
|
||||
<VIcon
|
||||
icon="mdi-dots-vertical"
|
||||
:color="getTextColor()"
|
||||
/>
|
||||
<VMenu
|
||||
activator="parent"
|
||||
close-on-content-click
|
||||
>
|
||||
<VList>
|
||||
<VListItem
|
||||
v-for="(item, i) in dropdownItems"
|
||||
:key="i"
|
||||
variant="plain"
|
||||
:base-color="item.props.color"
|
||||
@click="item.props.click"
|
||||
>
|
||||
<template #prepend>
|
||||
<VIcon :icon="item.props.prependIcon" />
|
||||
</template>
|
||||
<VListItemTitle v-text="item.title" />
|
||||
</VListItem>
|
||||
</VList>
|
||||
</VMenu>
|
||||
</IconBtn>
|
||||
</div>
|
||||
</template>
|
||||
</VCardItem>
|
||||
|
||||
<VCardText>
|
||||
<p
|
||||
class="clamp-text mb-0"
|
||||
:class="getTextClass()"
|
||||
>
|
||||
{{ props.media?.description }}
|
||||
</p>
|
||||
</VCardText>
|
||||
|
||||
<VCardText class="d-flex justify-space-between align-center flex-wrap">
|
||||
<div class="d-flex align-center">
|
||||
<IconBtn
|
||||
icon="mdi-star"
|
||||
:color="getTextColor()"
|
||||
class="me-1"
|
||||
/>
|
||||
<span
|
||||
class="text-subtitle-2 me-4"
|
||||
:class="getTextClass()"
|
||||
>{{
|
||||
props.media?.vote
|
||||
}}</span>
|
||||
<IconBtn
|
||||
v-bind="props"
|
||||
icon="mdi-progress-clock"
|
||||
:color="getTextColor()"
|
||||
class="me-1"
|
||||
/>
|
||||
<span
|
||||
class="text-subtitle-2 me-4"
|
||||
:class="getTextClass()"
|
||||
>{{ props.media?.processed || 0 }}</span>
|
||||
<IconBtn
|
||||
v-if="siteName"
|
||||
icon="mdi-web"
|
||||
:color="getTextColor()"
|
||||
class="me-1"
|
||||
/>
|
||||
<span
|
||||
v-if="siteName"
|
||||
class="text-subtitle-2 me-4"
|
||||
:class="getTextClass()"
|
||||
>
|
||||
{{ siteName }}
|
||||
</span>
|
||||
</div>
|
||||
</VCardText>
|
||||
<VCardText
|
||||
v-if="lastUpdateText"
|
||||
class="absolute right-0 bottom-0 d-flex align-center p-2 text-gray-300"
|
||||
>
|
||||
<VIcon
|
||||
icon="mdi-download"
|
||||
class="me-1"
|
||||
/> {{ lastUpdateText }}
|
||||
</VCardText>
|
||||
</VCard>
|
||||
<!-- 订阅编辑弹窗 -->
|
||||
<VDialog
|
||||
v-model="rssInfoDialog"
|
||||
max-width="800"
|
||||
persistent
|
||||
scrollable
|
||||
>
|
||||
<!-- Dialog Content -->
|
||||
<VCard :title="`订阅 - ${props.media?.name}`">
|
||||
<DialogCloseBtn @click="rssInfoDialog = false" />
|
||||
<VCardText class="pt-2">
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<VRow>
|
||||
<VCol
|
||||
cols="12"
|
||||
>
|
||||
<VTextField
|
||||
v-model="rssForm.url"
|
||||
label="RSS地址"
|
||||
placeholder="https://example.com/rss"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<VSelect
|
||||
v-model="rssForm.type"
|
||||
label="类型"
|
||||
:items="[{ title: '电影', value: '电影' }, { title: '电视剧', value: '电视剧' }]"
|
||||
readonly
|
||||
/>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<VTextField
|
||||
v-model="rssForm.title"
|
||||
label="标题"
|
||||
readonly
|
||||
/>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<VTextField
|
||||
v-model="rssForm.year"
|
||||
label="年份"
|
||||
readonly
|
||||
/>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<VSelect
|
||||
v-show="rssForm.type === '电视剧'"
|
||||
v-model="rssForm.season"
|
||||
label="季"
|
||||
:items="seasonItems"
|
||||
readonly
|
||||
/>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<VTextField
|
||||
v-model="rssForm.include"
|
||||
label="包含"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<VTextField
|
||||
v-model="rssForm.exclude"
|
||||
label="排除"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<VTextField
|
||||
v-model="rssForm.save_path"
|
||||
label="保存路径"
|
||||
placeholder="留空自动"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<VSelect
|
||||
v-model="rssForm.state"
|
||||
label="状态"
|
||||
:items="[{
|
||||
title: '启用',
|
||||
value: 1,
|
||||
}, {
|
||||
title: '停用',
|
||||
value: 0,
|
||||
}]"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
<VRow>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="4"
|
||||
>
|
||||
<VSwitch
|
||||
v-model="rssForm.best_version"
|
||||
label="洗版"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="4"
|
||||
>
|
||||
<VSwitch
|
||||
v-model="rssForm.proxy"
|
||||
label="代理服务器"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="4"
|
||||
>
|
||||
<VSwitch
|
||||
v-model="rssForm.filter"
|
||||
label="过滤规则"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
|
||||
<VCardActions>
|
||||
<VBtn @click="rssInfoDialog = false">
|
||||
取消
|
||||
</VBtn>
|
||||
<VSpacer />
|
||||
<VBtn @click="updateRssInfo">
|
||||
确定
|
||||
</VBtn>
|
||||
</VCardActions>
|
||||
</VCard>
|
||||
</VDialog>
|
||||
<!-- RSS预览窗口 -->
|
||||
<VDialog
|
||||
v-model="rssPreviewDialog"
|
||||
max-width="1280"
|
||||
scrollable
|
||||
>
|
||||
<!-- Dialog Content -->
|
||||
<VCard title="RSS预览">
|
||||
<DialogCloseBtn @click="rssPreviewDialog = false" />
|
||||
<VCardText class="pt-2">
|
||||
<VDataTable
|
||||
v-model:items-per-page="previewItemsPerPage"
|
||||
:headers="previewHeaders"
|
||||
:items="previewDataList"
|
||||
:items-length="previewTotalItems"
|
||||
:loading="previewLoading"
|
||||
density="compact"
|
||||
item-value="title"
|
||||
return-object
|
||||
fixed-header
|
||||
items-per-page-text="每页条数"
|
||||
page-text="{0}-{1} 共 {2} 条"
|
||||
>
|
||||
<template #item.title="{ item }">
|
||||
<div class="text-high-emphasis">
|
||||
{{ item.raw.title }}
|
||||
</div>
|
||||
</template>
|
||||
<template #item.size="{ item }">
|
||||
<div class="text-nowrap whitespace-nowrap">
|
||||
{{ formatFileSize(item.raw.size) }}
|
||||
</div>
|
||||
</template>
|
||||
<template #item.pubdate="{ item }">
|
||||
<div class="text-sm">
|
||||
{{ item.raw.pubdate }}
|
||||
</div>
|
||||
</template>
|
||||
<template #item.actions="{ item }">
|
||||
<div class="me-n3">
|
||||
<IconBtn>
|
||||
<VIcon
|
||||
icon="mdi-dots-vertical"
|
||||
/>
|
||||
<VMenu
|
||||
activator="parent"
|
||||
close-on-content-click
|
||||
>
|
||||
<VList>
|
||||
<VListItem
|
||||
variant="plain"
|
||||
@click="openTorrentDetail(item.raw.page_url)"
|
||||
>
|
||||
<template #prepend>
|
||||
<VIcon icon="mdi-information" />
|
||||
</template>
|
||||
<VListItemTitle>查看详情</VListItemTitle>
|
||||
</VListItem>
|
||||
<VListItem
|
||||
variant="plain"
|
||||
@click="downloadTorrentFile(item.raw.enclosure)"
|
||||
>
|
||||
<template #prepend>
|
||||
<VIcon icon="mdi-download" />
|
||||
</template>
|
||||
<VListItemTitle>下载种子</VListItemTitle>
|
||||
</VListItem>
|
||||
</VList>
|
||||
</VMenu>
|
||||
</IconBtn>
|
||||
</div>
|
||||
</template>
|
||||
<template #no-data>
|
||||
没有数据
|
||||
</template>
|
||||
</VDataTable>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VDialog>
|
||||
</template>
|
||||
@@ -72,9 +72,6 @@ const resourceTotalItems = ref(0)
|
||||
// 每页条数
|
||||
const resourceItemsPerPage = ref(25)
|
||||
|
||||
// 当前页码
|
||||
const resourceCurrentPage = ref(0)
|
||||
|
||||
// 用户名密码表单
|
||||
const userPwForm = ref({
|
||||
username: '',
|
||||
@@ -431,7 +428,7 @@ onMounted(() => {
|
||||
<!-- 站点编辑弹窗 -->
|
||||
<VDialog
|
||||
v-model="siteInfoDialog"
|
||||
max-width="1000"
|
||||
max-width="50rem"
|
||||
persistent
|
||||
scrollable
|
||||
>
|
||||
@@ -474,6 +471,12 @@ onMounted(() => {
|
||||
</VCol>
|
||||
</VRow>
|
||||
<VRow>
|
||||
<VCol cols="12">
|
||||
<VTextField
|
||||
v-model="siteForm.rss"
|
||||
label="RSS地址"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
<VTextarea
|
||||
v-model="siteForm.cookie"
|
||||
@@ -556,7 +559,7 @@ onMounted(() => {
|
||||
<!-- 站点资源弹窗 -->
|
||||
<VDialog
|
||||
v-model="resourceDialog"
|
||||
max-width="1280"
|
||||
max-width="80rem"
|
||||
scrollable
|
||||
>
|
||||
<!-- Dialog Content -->
|
||||
|
||||
@@ -325,7 +325,7 @@ const dropdownItems = ref([
|
||||
<!-- 订阅编辑弹窗 -->
|
||||
<VDialog
|
||||
v-model="subscribeInfoDialog"
|
||||
max-width="1000"
|
||||
max-width="50rem"
|
||||
persistent
|
||||
scrollable
|
||||
>
|
||||
|
||||
@@ -2,10 +2,17 @@
|
||||
import api from '@/api'
|
||||
import type { MediaInfo } from '@/api/types'
|
||||
|
||||
interface TmdbItem {
|
||||
title: string
|
||||
overview: string
|
||||
tmdbid: number
|
||||
poster: string
|
||||
}
|
||||
|
||||
// update:modelValue 事件
|
||||
const emit = defineEmits(['update:modelValue', 'close'])
|
||||
|
||||
const items = ref<{}[]>([])
|
||||
const items = ref<TmdbItem[]>([])
|
||||
|
||||
// 搜索词
|
||||
const keyword = ref('')
|
||||
@@ -14,12 +21,19 @@ const keyword = ref('')
|
||||
const loading = ref(false)
|
||||
|
||||
// 选中条目
|
||||
function selectMedia(item: MediaInfo) {
|
||||
function selectMedia(item: TmdbItem) {
|
||||
console.log(item)
|
||||
emit('update:modelValue', item.tmdb_id)
|
||||
emit('update:modelValue', item.tmdbid)
|
||||
emit('close')
|
||||
}
|
||||
|
||||
// TMDB图片转换为w500大小
|
||||
function getW500Image(url = '') {
|
||||
if (!url)
|
||||
return ''
|
||||
return url.replace('original', 'w500')
|
||||
}
|
||||
|
||||
// 搜索词条
|
||||
async function searchMedias() {
|
||||
if (!keyword)
|
||||
@@ -41,13 +55,11 @@ async function searchMedias() {
|
||||
|
||||
// 赋值
|
||||
for (const item of result) {
|
||||
if (items.value.length > 0)
|
||||
items.value.push({ type: 'divider', inset: true })
|
||||
items.value.push({
|
||||
prependAvatar: item.poster_path,
|
||||
tmdbid: item.tmdb_id || 0,
|
||||
poster: getW500Image(item.poster_path),
|
||||
title: `${item.title}(${item.year})`,
|
||||
subtitle: `<span class="text-primary">${item.type}</span> ${item.overview}`,
|
||||
onClick: () => selectMedia(item),
|
||||
overview: `<span class="text-primary">${item.type}</span> ${item.overview}`,
|
||||
})
|
||||
}
|
||||
loading.value = false
|
||||
@@ -81,12 +93,35 @@ async function searchMedias() {
|
||||
|
||||
<VList
|
||||
v-if="items.length > 0"
|
||||
:items="items"
|
||||
item-props
|
||||
lines="three"
|
||||
>
|
||||
<template #subtitle="{ subtitle }">
|
||||
<div v-html="subtitle" />
|
||||
<template v-for="(item, i) in items" :key="i">
|
||||
<VListItem
|
||||
density="compact"
|
||||
@click="selectMedia(item)"
|
||||
>
|
||||
<template #prepend>
|
||||
<VImg
|
||||
height="75"
|
||||
width="50"
|
||||
:src="item.poster"
|
||||
aspect-ratio="2/3"
|
||||
class="object-cover rounded shadow ring-gray-500 me-3"
|
||||
cover
|
||||
>
|
||||
<template #placeholder>
|
||||
<div class="w-full h-full">
|
||||
<VSkeletonLoader class="object-cover aspect-w-2 aspect-h-3" />
|
||||
</div>
|
||||
</template>
|
||||
</VImg>
|
||||
</template>
|
||||
<VListItemTitle>
|
||||
{{ item.title }}
|
||||
</VListItemTitle>
|
||||
<VListItemSubtitle v-html="item.overview" />
|
||||
</VListItem>
|
||||
<VDivider v-if="i < items.length - 1" class="mt-1" inset />
|
||||
</template>
|
||||
</VList>
|
||||
</VCard>
|
||||
|
||||
@@ -613,7 +613,7 @@ onMounted(() => {
|
||||
<!-- 文件整理弹窗 -->
|
||||
<VDialog
|
||||
v-model="transferPopper"
|
||||
max-width="800"
|
||||
max-width="50rem"
|
||||
scrollable
|
||||
>
|
||||
<template #activator="{ props }">
|
||||
@@ -744,7 +744,7 @@ onMounted(() => {
|
||||
<vDialog
|
||||
v-model="progressDialog"
|
||||
:scrim="false"
|
||||
width="400"
|
||||
width="25rem"
|
||||
>
|
||||
<vCard
|
||||
color="primary"
|
||||
@@ -763,7 +763,7 @@ onMounted(() => {
|
||||
<!-- 识别结果对话框 -->
|
||||
<vDialog
|
||||
v-model="nameTestDialog"
|
||||
width="800"
|
||||
width="50rem"
|
||||
>
|
||||
<vCard>
|
||||
<DialogCloseBtn @click="nameTestDialog = false" />
|
||||
@@ -775,7 +775,7 @@ onMounted(() => {
|
||||
<!-- TMDB ID搜索框 -->
|
||||
<vDialog
|
||||
v-model="tmdbSelectorDialog"
|
||||
width="600"
|
||||
width="40rem"
|
||||
scrollable
|
||||
>
|
||||
<TmdbSelectorCard
|
||||
|
||||
@@ -106,14 +106,6 @@ const superUser = store.state.auth.superUser
|
||||
to: '/subscribe-tv',
|
||||
}"
|
||||
/>
|
||||
<VerticalNavLink
|
||||
v-if="superUser"
|
||||
:item="{
|
||||
title: '自定义',
|
||||
icon: 'mdi-rss',
|
||||
to: '/subscribe-rss',
|
||||
}"
|
||||
/>
|
||||
<VerticalNavLink
|
||||
:item="{
|
||||
title: '日历',
|
||||
|
||||
@@ -172,7 +172,7 @@ const avatar = store.state.auth.avatar
|
||||
<!-- 重启进度框 -->
|
||||
<vDialog
|
||||
v-model="progressDialog"
|
||||
width="400"
|
||||
width="25rem"
|
||||
>
|
||||
<vCard
|
||||
color="primary"
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import RssListView from '@/views/subscribe/RssListView.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<RssListView />
|
||||
</div>
|
||||
</template>
|
||||
@@ -48,13 +48,6 @@ const router = createRouter({
|
||||
requiresAuth: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'subscribe-rss',
|
||||
component: () => import('../pages/subscribe-rss.vue'),
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'calendar',
|
||||
component: () => import('../pages/calendar.vue'),
|
||||
|
||||
@@ -446,7 +446,7 @@ const dropdownItems = ref([
|
||||
<vDialog
|
||||
v-model="progressDialog"
|
||||
:scrim="false"
|
||||
width="400"
|
||||
width="25rem"
|
||||
>
|
||||
<vCard
|
||||
color="primary"
|
||||
|
||||
@@ -408,7 +408,7 @@ onMounted(() => {
|
||||
<!-- 站点编辑弹窗 -->
|
||||
<VDialog
|
||||
v-model="addUserDialog"
|
||||
max-width="800"
|
||||
max-width="50rem"
|
||||
persistent
|
||||
>
|
||||
<!-- Dialog Content -->
|
||||
|
||||
@@ -129,10 +129,11 @@ onMounted(() => {
|
||||
<VTextarea
|
||||
v-model="customIdentifiers"
|
||||
auto-grow
|
||||
placeholder="支持正则表达式,特殊字符需要\转义,一行为一组,支持三种配置格式:
|
||||
placeholder="支持正则表达式,特殊字符需要\转义,一行为一组,支持以下几种配置格式:
|
||||
屏蔽词
|
||||
被替换词 => 替换词
|
||||
前定位词 <> 后定位词 >> 偏移量(EP)"
|
||||
前定位词 <> 后定位词 >> 集偏移量(EP)
|
||||
被替换词 => 替换词 && 前定位词 <> 后定位词 >> 集偏移量(EP)"
|
||||
/>
|
||||
</VCardItem>
|
||||
<VCardItem>
|
||||
|
||||
@@ -135,7 +135,7 @@ onBeforeMount(fetchData)
|
||||
<!-- Dialog Content -->
|
||||
<VDialog
|
||||
v-model="siteAddDialog"
|
||||
max-width="800"
|
||||
max-width="50rem"
|
||||
persistent
|
||||
scrollable
|
||||
>
|
||||
@@ -149,6 +149,7 @@ onBeforeMount(fetchData)
|
||||
/>
|
||||
</template>
|
||||
<VCard title="新增站点">
|
||||
<DialogCloseBtn @click="siteAddDialog = false" />
|
||||
<VCardText class="pt-2">
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<VRow>
|
||||
@@ -185,6 +186,12 @@ onBeforeMount(fetchData)
|
||||
</VCol>
|
||||
</VRow>
|
||||
<VRow>
|
||||
<VCol cols="12">
|
||||
<VTextField
|
||||
v-model="siteForm.rss"
|
||||
label="RSS地址"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
<VTextarea
|
||||
v-model="siteForm.cookie"
|
||||
|
||||
@@ -79,17 +79,7 @@ async function getSubscribes() {
|
||||
subscribes.map(async sub => eventsHander(sub)),
|
||||
)
|
||||
|
||||
// 自定义订阅
|
||||
const rsses: Rss[] = await api.get('rss')
|
||||
|
||||
const rssEvents = await Promise.all(
|
||||
rsses.map(async rss => eventsHander(rss)),
|
||||
)
|
||||
|
||||
// 合并事件
|
||||
const events = [...subEvents, ...rssEvents]
|
||||
|
||||
calendarOptions.value.events = events.flat().filter(event => event.start) as EventSourceInput
|
||||
calendarOptions.value.events = subEvents.flat().filter(event => event.start) as EventSourceInput
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
|
||||
@@ -1,336 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import PullRefresh from 'pull-refresh-vue3'
|
||||
import { useToast } from 'vue-toast-notification'
|
||||
import api from '@/api'
|
||||
import type { Rss } from '@/api/types'
|
||||
import NoDataFound from '@/components/NoDataFound.vue'
|
||||
import RssCard from '@/components/cards/RssCard.vue'
|
||||
import { numberValidator, requiredValidator } from '@/@validators'
|
||||
import { doneNProgress, startNProgress } from '@/api/nprogress'
|
||||
|
||||
// 提示框
|
||||
const $toast = useToast()
|
||||
|
||||
// 是否刷新过
|
||||
const isRefreshed = ref(false)
|
||||
|
||||
// 新增按钮文本
|
||||
const addBtnText = ref('新增订阅')
|
||||
// 新增按钮状态
|
||||
const addBtnState = ref(false)
|
||||
|
||||
// 新增自定义订阅对话框
|
||||
const rssAddDialog = ref(false)
|
||||
|
||||
// 新增订阅表单
|
||||
const rssForm = reactive({
|
||||
// RSS地址
|
||||
url: '',
|
||||
// 类型
|
||||
type: '电影',
|
||||
// 标题
|
||||
title: '',
|
||||
// 年份
|
||||
year: '',
|
||||
// 季号
|
||||
season: 1,
|
||||
// 包含
|
||||
include: '',
|
||||
// 排除
|
||||
exclude: '',
|
||||
// 洗版
|
||||
best_version: false,
|
||||
// 是否使用代理服务器
|
||||
proxy: false,
|
||||
// 是否使用过滤规则
|
||||
filter: true,
|
||||
// 保存路径
|
||||
save_path: '',
|
||||
// 状态 0-停用,1-启用
|
||||
state: 1,
|
||||
|
||||
})
|
||||
|
||||
// 数据列表
|
||||
const dataList = ref<Rss[]>([])
|
||||
|
||||
// 获取订阅列表数据
|
||||
async function fetchData() {
|
||||
try {
|
||||
dataList.value = await api.get('rss')
|
||||
isRefreshed.value = true
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 调用API 新增自定义订阅
|
||||
async function addRss() {
|
||||
if (!rssForm.url || !rssForm.title)
|
||||
return
|
||||
|
||||
startNProgress()
|
||||
|
||||
addBtnText.value = '新增中...'
|
||||
addBtnState.value = true
|
||||
|
||||
if (rssForm.type === '电影')
|
||||
rssForm.season = 0
|
||||
|
||||
try {
|
||||
const result: { [key: string]: string } = await api.post('rss', rssForm)
|
||||
if (result.success) {
|
||||
$toast.success('新增自定义订阅成功')
|
||||
|
||||
// 刷新数据
|
||||
fetchData()
|
||||
}
|
||||
else { $toast.error(`新增自定义订阅失败:${result.message}`) }
|
||||
rssAddDialog.value = false
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
|
||||
doneNProgress()
|
||||
|
||||
addBtnText.value = '新增订阅'
|
||||
addBtnState.value = false
|
||||
}
|
||||
|
||||
// 生成1到50季的下拉框选项
|
||||
const seasonItems = ref(
|
||||
Array.from({ length: 50 }, (_, i) => i + 1).map(item => ({
|
||||
title: `第 ${item} 季`,
|
||||
value: item,
|
||||
})),
|
||||
)
|
||||
|
||||
// 加载时获取数据
|
||||
onBeforeMount(fetchData)
|
||||
|
||||
// 刷新状态
|
||||
const loading = ref(false)
|
||||
|
||||
// 下拉刷新
|
||||
function onRefresh() {
|
||||
loading.value = true
|
||||
fetchData()
|
||||
loading.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="!isRefreshed"
|
||||
class="mt-12 w-full text-center text-gray-500 text-sm flex flex-col items-center"
|
||||
>
|
||||
<VProgressCircular
|
||||
v-if="!isRefreshed"
|
||||
size="48"
|
||||
indeterminate
|
||||
color="primary"
|
||||
/>
|
||||
</div>
|
||||
<PullRefresh
|
||||
v-model="loading"
|
||||
@refresh="onRefresh"
|
||||
>
|
||||
<div
|
||||
v-if="dataList.length > 0"
|
||||
class="grid gap-3 grid-rss-card p-1"
|
||||
>
|
||||
<RssCard
|
||||
v-for="data in dataList"
|
||||
:key="data.id"
|
||||
:media="data"
|
||||
@remove="fetchData"
|
||||
@save="fetchData"
|
||||
/>
|
||||
</div>
|
||||
<NoDataFound
|
||||
v-if="dataList.length === 0 && isRefreshed"
|
||||
error-code="404"
|
||||
error-title="没有自定义订阅"
|
||||
error-description="点击右下角按钮新增订阅。"
|
||||
/>
|
||||
</PullRefresh>
|
||||
|
||||
<!-- 新增订阅 -->
|
||||
<VDialog
|
||||
v-model="rssAddDialog"
|
||||
max-width="800"
|
||||
persistent
|
||||
scrollable
|
||||
>
|
||||
<!-- Dialog Activator -->
|
||||
<template #activator="{ props }">
|
||||
<VBtn
|
||||
icon="mdi-plus"
|
||||
v-bind="props"
|
||||
size="x-large"
|
||||
class="fixed right-5 bottom-5"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- Dialog Content -->
|
||||
<VCard title="新增自定义订阅">
|
||||
<DialogCloseBtn @click="rssAddDialog = false" />
|
||||
<VCardText class="pt-2">
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<VRow>
|
||||
<VCol
|
||||
cols="12"
|
||||
>
|
||||
<VTextField
|
||||
v-model="rssForm.url"
|
||||
label="RSS地址"
|
||||
placeholder="https://example.com/rss"
|
||||
:rules="[requiredValidator]"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<VSelect
|
||||
v-model="rssForm.type"
|
||||
label="类型"
|
||||
:items="[{ title: '电影', value: '电影' }, { title: '电视剧', value: '电视剧' }]"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<VTextField
|
||||
v-model="rssForm.title"
|
||||
label="标题"
|
||||
:rules="[requiredValidator]"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<VTextField
|
||||
v-model="rssForm.year"
|
||||
label="年份"
|
||||
:rules="[numberValidator]"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol
|
||||
v-if="rssForm.type === '电视剧'"
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<VSelect
|
||||
v-model="rssForm.season"
|
||||
label="季"
|
||||
:items="seasonItems"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<VTextField
|
||||
v-model="rssForm.include"
|
||||
label="包含"
|
||||
placeholder="支持正则表达式"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<VTextField
|
||||
v-model="rssForm.exclude"
|
||||
label="排除"
|
||||
placeholder="支持正则表达式"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<VTextField
|
||||
v-model="rssForm.save_path"
|
||||
label="保存路径"
|
||||
placeholder="留空自动"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<VSelect
|
||||
v-model="rssForm.state"
|
||||
label="状态"
|
||||
:items="[{
|
||||
title: '启用',
|
||||
value: 1,
|
||||
}, {
|
||||
title: '停用',
|
||||
value: 0,
|
||||
}]"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
<VRow>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="4"
|
||||
>
|
||||
<VSwitch
|
||||
v-model="rssForm.best_version"
|
||||
label="洗版"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="4"
|
||||
>
|
||||
<VSwitch
|
||||
v-model="rssForm.proxy"
|
||||
label="代理服务器"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="4"
|
||||
>
|
||||
<VSwitch
|
||||
v-model="rssForm.filter"
|
||||
label="过滤规则"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
<VCardActions>
|
||||
<VBtn
|
||||
@click="rssAddDialog = false"
|
||||
>
|
||||
取消
|
||||
</VBtn>
|
||||
<VSpacer />
|
||||
<VBtn
|
||||
color="primary"
|
||||
:disabled="addBtnState"
|
||||
@click="addRss"
|
||||
>
|
||||
{{ addBtnText }}
|
||||
</VBtn>
|
||||
</VCardActions>
|
||||
</VCard>
|
||||
</VDialog>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.grid-rss-card {
|
||||
grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
|
||||
padding-block-end: 1rem;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user