mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-05-20 07:40:12 +08:00
Merge branch 'main' into develop-20240417-用户配置
This commit is contained in:
1
components.d.ts
vendored
1
components.d.ts
vendored
@@ -10,6 +10,7 @@ declare module 'vue' {
|
||||
DialogCloseBtn: typeof import('./src/@core/components/DialogCloseBtn.vue')['default']
|
||||
ErrorHeader: typeof import('./src/@core/components/ErrorHeader.vue')['default']
|
||||
ExistIcon: typeof import('./src/@core/components/ExistIcon.vue')['default']
|
||||
LoadingBanner: typeof import('./src/@core/components/LoadingBanner.vue')['default']
|
||||
MoreBtn: typeof import('./src/@core/components/MoreBtn.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "moviepilot",
|
||||
"version": "1.8.2",
|
||||
"version": "1.8.2-2",
|
||||
"private": true,
|
||||
"bin": "dist/service.js",
|
||||
"scripts": {
|
||||
|
||||
28
src/@core/components/LoadingBanner.vue
Normal file
28
src/@core/components/LoadingBanner.vue
Normal file
@@ -0,0 +1,28 @@
|
||||
<script lang="ts" setup>
|
||||
// 定义输入参数
|
||||
const props = defineProps({
|
||||
progress: Number,
|
||||
text: String
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="w-full text-center text-gray-500 text-sm flex flex-col items-center"
|
||||
>
|
||||
<VProgressCircular
|
||||
v-if="!props.text"
|
||||
size="48"
|
||||
indeterminate
|
||||
color="primary"
|
||||
/>
|
||||
<VProgressCircular
|
||||
v-if="props.progress"
|
||||
class="mb-3"
|
||||
color="primary"
|
||||
:model-value="props.progress"
|
||||
size="64"
|
||||
/>
|
||||
<span>{{ props.text }}</span>
|
||||
</div>
|
||||
</template>
|
||||
@@ -2,13 +2,8 @@
|
||||
import { useToast } from 'vue-toast-notification'
|
||||
import { useTheme } from 'vuetify'
|
||||
import api from '@/api'
|
||||
|
||||
import store from './store'
|
||||
|
||||
import { fixArrayAt } from '@/@core/utils/compatibility'
|
||||
|
||||
// 修复低版本Safari等浏览器数组不支持at函数的问题
|
||||
fixArrayAt()
|
||||
|
||||
// 提示框
|
||||
const $toast = useToast()
|
||||
|
||||
@@ -88,6 +88,9 @@ export interface Subscribe {
|
||||
|
||||
// 保存目录
|
||||
save_path: string
|
||||
|
||||
// 时间
|
||||
date: string
|
||||
}
|
||||
|
||||
// 历史记录
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
<script lang="ts" setup>
|
||||
import type { Axios } from 'axios'
|
||||
import axios from 'axios'
|
||||
import List from './filebrowser/List.vue'
|
||||
|
||||
import Toolbar from './filebrowser/Toolbar.vue'
|
||||
import FileList from './filebrowser/FileList.vue'
|
||||
import FileToolbar from './filebrowser/FileToolbar.vue'
|
||||
import type { EndPoints } from '@/api/types'
|
||||
|
||||
// 输入参数
|
||||
@@ -100,7 +99,7 @@ onMounted(() => {
|
||||
<template>
|
||||
<VCard class="mx-auto" :loading="loading > 0 || !path">
|
||||
<div v-if="path">
|
||||
<Toolbar
|
||||
<FileToolbar
|
||||
:path="path"
|
||||
:storages="storagesArray"
|
||||
:storage="activeStorage"
|
||||
@@ -111,7 +110,7 @@ onMounted(() => {
|
||||
@foldercreated="refreshPending = true"
|
||||
@sortchanged="sortChanged"
|
||||
/>
|
||||
<List
|
||||
<FileList
|
||||
:path="path"
|
||||
:storage="activeStorage"
|
||||
:icons="fileIcons"
|
||||
|
||||
@@ -25,7 +25,7 @@ function goPlay() {
|
||||
// 计算图片地址
|
||||
const getImgUrl = computed(() => {
|
||||
const image = props.media?.image || ''
|
||||
return `${import.meta.env.VITE_API_BASE_URL}system/img/0/${encodeURIComponent(image).replace(/%2F/g, '/')}`
|
||||
return `${import.meta.env.VITE_API_BASE_URL}system/img/0?imgurl=${encodeURIComponent(image)}`
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { kFormatter } from '@core/utils/formatters'
|
||||
|
||||
interface Props {
|
||||
title: string
|
||||
color?: string
|
||||
icon: string
|
||||
stats: number
|
||||
change: number
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
color: 'primary',
|
||||
})
|
||||
|
||||
const isPositive = controlledComputed(() => props.change, () => Math.sign(props.change) === 1)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VCard>
|
||||
<VCardText class="d-flex align-center">
|
||||
<VAvatar
|
||||
size="44"
|
||||
rounded
|
||||
:color="props.color"
|
||||
variant="tonal"
|
||||
class="me-4"
|
||||
>
|
||||
<VIcon
|
||||
:icon="props.icon"
|
||||
size="30"
|
||||
/>
|
||||
</VAvatar>
|
||||
|
||||
<div>
|
||||
<span class="text-caption">{{ props.title }}</span>
|
||||
<div class="d-flex align-center flex-wrap">
|
||||
<span class="text-h6 font-weight-semibold">{{ kFormatter(props.stats) }}</span>
|
||||
<div
|
||||
v-if="props.change"
|
||||
:class="`${isPositive ? 'text-success' : 'text-error'} mt-1`"
|
||||
>
|
||||
<VIcon :icon="isPositive ? 'mdi-chevron-up' : 'mdi-chevron-down'" />
|
||||
<span class="text-caption font-weight-semibold">{{ Math.abs(props.change) }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</template>
|
||||
@@ -1,56 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
interface Props {
|
||||
title: string
|
||||
color?: string
|
||||
icon: string
|
||||
stats: string
|
||||
change: number
|
||||
subtitle: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
color: 'primary',
|
||||
})
|
||||
|
||||
const isPositive = controlledComputed(() => props.change, () => Math.sign(props.change) === 1)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VCard>
|
||||
<VCardText class="d-flex align-center">
|
||||
<VAvatar
|
||||
v-if="props.icon"
|
||||
size="38"
|
||||
:color="props.color"
|
||||
>
|
||||
<VIcon
|
||||
:icon="props.icon"
|
||||
size="24"
|
||||
/>
|
||||
</VAvatar>
|
||||
|
||||
<VSpacer />
|
||||
|
||||
<MoreBtn class="me-n3 mt-n1" />
|
||||
</VCardText>
|
||||
|
||||
<VCardText>
|
||||
<h6 class="text-sm font-weight-semibold mb-2">
|
||||
{{ props.title }}
|
||||
</h6>
|
||||
<div
|
||||
v-if="props.change"
|
||||
class="d-flex align-center mb-2"
|
||||
>
|
||||
<span class="font-weight-semibold text-h5 me-2">{{ props.stats }}</span>
|
||||
<span
|
||||
:class="isPositive ? 'text-success' : 'text-error'"
|
||||
class="text-caption"
|
||||
>
|
||||
{{ isPositive ? `+${props.change}` : props.change }}%
|
||||
</span>
|
||||
</div>
|
||||
<span class="text-caption">{{ props.subtitle }}</span>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</template>
|
||||
@@ -1,65 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
interface Props {
|
||||
title: string
|
||||
subtitle: string
|
||||
stats: string
|
||||
change: number
|
||||
image: string
|
||||
color?: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
color: 'primary',
|
||||
})
|
||||
|
||||
const isPositive = controlledComputed(() => props.change, () => Math.sign(props.change) === 1)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VCard class="overflow-visible">
|
||||
<div class="d-flex position-relative">
|
||||
<VCardText>
|
||||
<h6 class="text-base font-weight-semibold mb-4">
|
||||
{{ props.title }}
|
||||
</h6>
|
||||
<div class="d-flex align-center flex-wrap mb-4">
|
||||
<h5 class="text-h5 font-weight-semibold me-2">
|
||||
{{ props.stats }}
|
||||
</h5>
|
||||
<span
|
||||
class="text-caption"
|
||||
:class="isPositive ? 'text-success' : 'text-error'"
|
||||
>
|
||||
{{ isPositive ? `+${props.change}` : props.change }}%
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<VChip
|
||||
v-if="props.subtitle"
|
||||
size="small"
|
||||
:color="props.color"
|
||||
>
|
||||
{{ props.subtitle }}
|
||||
</VChip>
|
||||
</VCardText>
|
||||
|
||||
<VSpacer />
|
||||
|
||||
<div class="illustrator-img">
|
||||
<VImg
|
||||
v-if="props.image"
|
||||
:src="props.image"
|
||||
:width="110"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</VCard>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.illustrator-img {
|
||||
position: absolute;
|
||||
inset-block-end: 0;
|
||||
inset-inline-end: 5%;
|
||||
}
|
||||
</style>
|
||||
@@ -56,7 +56,7 @@ function getImgUrl(url: string) {
|
||||
if (!url)
|
||||
return getDefaultImage()
|
||||
else
|
||||
return `${import.meta.env.VITE_API_BASE_URL}system/img/0/${encodeURIComponent(url).replace(/%2F/g, '/')}`
|
||||
return `${import.meta.env.VITE_API_BASE_URL}system/img/0?imgurl=${encodeURIComponent(url)}`
|
||||
}
|
||||
|
||||
// 根据多张图片生成媒体库封面
|
||||
@@ -68,7 +68,7 @@ async function drawImages(imageList: string[]) {
|
||||
|
||||
// 为所有图片添加system/img前缀
|
||||
for (let i = 0; i < IMAGES.length; i++)
|
||||
IMAGES[i] = `${import.meta.env.VITE_API_BASE_URL}system/img/0/${encodeURIComponent(IMAGES[i]).replace(/%2F/g, '/')}`
|
||||
IMAGES[i] = `${import.meta.env.VITE_API_BASE_URL}system/img/0?imgurl=${encodeURIComponent(IMAGES[i])}`
|
||||
|
||||
// canvas
|
||||
const canvas = canvasRef.value
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
import type { PropType, Ref } from 'vue'
|
||||
import { useToast } from 'vue-toast-notification'
|
||||
import SubscribeEditForm from '../form/SubscribeEditForm.vue'
|
||||
import SubscribeEditDialog from '../dialog/SubscribeEditDialog.vue'
|
||||
import { formatSeason } from '@/@core/utils/formatters'
|
||||
import api from '@/api'
|
||||
import { doneNProgress, startNProgress } from '@/api/nprogress'
|
||||
@@ -157,7 +157,7 @@ async function addSubscribe(season = 0) {
|
||||
|
||||
// 弹出订阅编辑弹窗
|
||||
if (result.success && seasonsSelected.value.length <= 1) {
|
||||
const show_edit_dialog = await querySubscribeRules()
|
||||
const show_edit_dialog = await queryDefaultSubscribeConfig()
|
||||
if (show_edit_dialog) {
|
||||
subscribeId.value = result.data.id
|
||||
subscribeEditDialog.value = true
|
||||
@@ -313,11 +313,16 @@ async function getMediaSeasons() {
|
||||
}
|
||||
|
||||
// 查询订阅弹窗规则
|
||||
async function querySubscribeRules() {
|
||||
async function queryDefaultSubscribeConfig() {
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.get(
|
||||
'system/setting/DefaultFilterRules',
|
||||
)
|
||||
let subscribe_config_url = ''
|
||||
if (props.media?.type === '电影')
|
||||
subscribe_config_url = 'system/setting/DefaultMovieSubscribeConfig'
|
||||
else
|
||||
subscribe_config_url = 'system/setting/DefaultTvSubscribeConfig'
|
||||
|
||||
const result: { [key: string]: any } = await api.get(subscribe_config_url)
|
||||
|
||||
if (result.data?.value)
|
||||
return result.data.value.show_edit_dialog
|
||||
}
|
||||
@@ -364,14 +369,16 @@ function getExistText(season: number) {
|
||||
}
|
||||
|
||||
// 打开详情页
|
||||
function goMediaDetail() {
|
||||
router.push({
|
||||
path: '/media',
|
||||
query: {
|
||||
mediaid: getMediaId(),
|
||||
type: props.media?.type,
|
||||
},
|
||||
})
|
||||
function goMediaDetail(isHovering = false) {
|
||||
if (isHovering) {
|
||||
router.push({
|
||||
path: '/media',
|
||||
query: {
|
||||
mediaid: getMediaId(),
|
||||
type: props.media?.type,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 开始搜索
|
||||
@@ -400,7 +407,7 @@ const getImgUrl: Ref<string> = computed(() => {
|
||||
const url = props.media?.poster_path?.replace('original', 'w500') ?? noImage
|
||||
// 如果地址中包含douban则使用中转代理
|
||||
if (url.includes('doubanio.com'))
|
||||
return `${import.meta.env.VITE_API_BASE_URL}douban/img/${encodeURIComponent(url)}`
|
||||
return `${import.meta.env.VITE_API_BASE_URL}douban/img?imgurl=${encodeURIComponent(url)}`
|
||||
|
||||
return url
|
||||
})
|
||||
@@ -440,7 +447,7 @@ function getYear(airDate: string) {
|
||||
'transition transform-cpu duration-300 scale-105 shadow-lg': hover.isHovering,
|
||||
'ring-1': isImageLoaded,
|
||||
}"
|
||||
@click.stop="goMediaDetail"
|
||||
@click.stop="goMediaDetail(hover.isHovering)"
|
||||
>
|
||||
<VImg
|
||||
aspect-ratio="2/3"
|
||||
@@ -592,7 +599,7 @@ function getYear(airDate: string) {
|
||||
</VCard>
|
||||
</VBottomSheet>
|
||||
<!-- 订阅编辑弹窗 -->
|
||||
<SubscribeEditForm
|
||||
<SubscribeEditDialog
|
||||
v-if="subscribeEditDialog"
|
||||
v-model="subscribeEditDialog"
|
||||
:subid="subscribeId"
|
||||
|
||||
@@ -91,7 +91,7 @@ const iconPath: Ref<string> = computed(() => {
|
||||
return noImage
|
||||
// 如果是网络图片则使用代理后返回
|
||||
if (props.plugin?.plugin_icon?.startsWith('http'))
|
||||
return `${import.meta.env.VITE_API_BASE_URL}system/img/1/${encodeURIComponent(props.plugin?.plugin_icon).replace(/%2F/g, '/')}`
|
||||
return `${import.meta.env.VITE_API_BASE_URL}system/img/1?imgurl=${encodeURIComponent(props.plugin?.plugin_icon)}`
|
||||
|
||||
return `./plugin_icon/${props.plugin?.plugin_icon}`
|
||||
})
|
||||
|
||||
@@ -223,7 +223,7 @@ const iconPath: Ref<string> = computed(() => {
|
||||
return noImage
|
||||
// 如果是网络图片则使用代理后返回
|
||||
if (props.plugin?.plugin_icon?.startsWith('http'))
|
||||
return `${import.meta.env.VITE_API_BASE_URL}system/img/1/${encodeURIComponent(props.plugin?.plugin_icon).replace(/%2F/g, '/')}`
|
||||
return `${import.meta.env.VITE_API_BASE_URL}system/img/1?imgurl=${encodeURIComponent(props.plugin?.plugin_icon)}`
|
||||
|
||||
return `./plugin_icon/${props.plugin?.plugin_icon}`
|
||||
})
|
||||
|
||||
@@ -31,12 +31,12 @@ const getImgUrl = computed(() => {
|
||||
if (imageLoadError.value)
|
||||
return noImage
|
||||
const image = props.media?.image || ''
|
||||
return `${import.meta.env.VITE_API_BASE_URL}system/img/0/${encodeURIComponent(image).replace(/%2F/g, '/')}`
|
||||
return `${import.meta.env.VITE_API_BASE_URL}system/img/0?imgurl=${encodeURIComponent(image)}`
|
||||
})
|
||||
|
||||
// 跳转播放
|
||||
function goPlay() {
|
||||
if (props.media?.link)
|
||||
function goPlay(isHovering = false) {
|
||||
if (props.media?.link && isHovering)
|
||||
window.open(props.media?.link, '_blank')
|
||||
}
|
||||
</script>
|
||||
@@ -53,7 +53,7 @@ function goPlay() {
|
||||
'transition transform-cpu duration-300 scale-105 shadow-lg': hover.isHovering,
|
||||
'ring-1': isImageLoaded,
|
||||
}"
|
||||
@click.stop="goPlay"
|
||||
@click.stop="goPlay(hover.isHovering)"
|
||||
>
|
||||
<VImg
|
||||
aspect-ratio="2/3"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
import type { PropType } from 'vue'
|
||||
import { useToast } from 'vue-toast-notification'
|
||||
import SiteAddEditForm from '../form/SiteAddEditForm.vue'
|
||||
import SiteAddEditDialog from '../dialog/SiteAddEditDialog.vue'
|
||||
import SiteTorrentTable from '../table/SiteTorrentTable.vue'
|
||||
import { requiredValidator } from '@/@validators'
|
||||
import api from '@/api'
|
||||
@@ -166,13 +166,19 @@ const statColor = computed(() => {
|
||||
}
|
||||
else if (siteStats.value?.lst_state == 0){
|
||||
if (!siteStats.value?.seconds)
|
||||
return 'success'
|
||||
return 'secondary'
|
||||
if (siteStats.value?.seconds >= 5)
|
||||
return 'warning'
|
||||
return 'success'
|
||||
}
|
||||
})
|
||||
|
||||
// 监听resourceDialog,如果为false则重新查询站点使用统计
|
||||
watch(resourceDialog, (value) => {
|
||||
if (!value)
|
||||
getSiteStats()
|
||||
})
|
||||
|
||||
// 装载时查询站点图标
|
||||
onMounted(() => {
|
||||
getSiteIcon()
|
||||
@@ -360,7 +366,7 @@ onMounted(() => {
|
||||
</VCardActions>
|
||||
</VCard>
|
||||
</VDialog>
|
||||
<SiteAddEditForm
|
||||
<SiteAddEditDialog
|
||||
v-if="siteEditDialog"
|
||||
v-model="siteEditDialog"
|
||||
:siteid="cardProps.site?.id"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang='ts' setup>
|
||||
import { useToast } from 'vue-toast-notification'
|
||||
import SubscribeEditForm from '../form/SubscribeEditForm.vue'
|
||||
import SubscribeEditDialog from '../dialog/SubscribeEditDialog.vue'
|
||||
import { calculateTimeDifference } from '@/@core/utils'
|
||||
import { formatSeason } from '@/@core/utils/formatters'
|
||||
import api from '@/api'
|
||||
@@ -284,7 +284,7 @@ const dropdownItems = ref([
|
||||
/>
|
||||
</VCard>
|
||||
<!-- 订阅编辑弹窗 -->
|
||||
<SubscribeEditForm
|
||||
<SubscribeEditDialog
|
||||
v-if="subscribeEditDialog"
|
||||
v-model="subscribeEditDialog"
|
||||
:subid="props.media?.id"
|
||||
|
||||
45
src/components/dialog/ImportCodeDialog.vue
Normal file
45
src/components/dialog/ImportCodeDialog.vue
Normal file
@@ -0,0 +1,45 @@
|
||||
<script lang="ts" setup>
|
||||
// 输入参数
|
||||
const props = defineProps({
|
||||
title: String,
|
||||
})
|
||||
|
||||
// 定义事件
|
||||
const emit = defineEmits(['update:modelValue', 'close'])
|
||||
|
||||
// 代码
|
||||
const codeString = ref('')
|
||||
|
||||
// 导入
|
||||
function handleImport() {
|
||||
emit('update:modelValue', codeString.value)
|
||||
emit('close')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VDialog
|
||||
width="40rem"
|
||||
scrollable
|
||||
max-height="85vh"
|
||||
>
|
||||
<VCard
|
||||
:title="props.title"
|
||||
class="rounded-t"
|
||||
>
|
||||
<DialogCloseBtn @click="emit('close')" />
|
||||
<VCardText class="pt-2">
|
||||
<VTextarea v-model="codeString" />
|
||||
</VCardText>
|
||||
<VCardActions>
|
||||
<VSpacer />
|
||||
<VBtn
|
||||
variant="tonal"
|
||||
@click="handleImport"
|
||||
>
|
||||
导入
|
||||
</VBtn>
|
||||
</VCardActions>
|
||||
</VCard>
|
||||
</VDialog>
|
||||
</template>
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
import { useToast } from 'vue-toast-notification'
|
||||
import TmdbSelectorCard from '../cards/TmdbSelectorCard.vue'
|
||||
import TmdbSelector from '../misc/TmdbSelector.vue'
|
||||
import store from '@/store'
|
||||
import api from '@/api'
|
||||
import { numberValidator } from '@/@validators'
|
||||
@@ -306,7 +306,7 @@ async function transfer() {
|
||||
scrollable
|
||||
max-height="85vh"
|
||||
>
|
||||
<TmdbSelectorCard
|
||||
<TmdbSelector
|
||||
v-model="transferForm.tmdbid"
|
||||
@close="tmdbSelectorDialog = false"
|
||||
/>
|
||||
@@ -43,6 +43,8 @@ const subscribeForm = ref<Subscribe>({
|
||||
username: '',
|
||||
current_priority: 0,
|
||||
save_path: '',
|
||||
date: '',
|
||||
show_edit_dialog: false
|
||||
})
|
||||
|
||||
// 提示框
|
||||
@@ -270,7 +272,7 @@ onMounted(() => {
|
||||
max-width="60rem"
|
||||
>
|
||||
<VCard
|
||||
:title="`${props.default ? `设置${props.type}默认订阅规则` : `编辑订阅 - ${subscribeForm.name} ${subscribeForm.season ? `第 ${subscribeForm.season} 季` : ''}`}`"
|
||||
:title="`${props.default ? `${props.type}默认订阅规则` : `编辑订阅 - ${subscribeForm.name} ${subscribeForm.season ? `第 ${subscribeForm.season} 季` : ''}`}`"
|
||||
class="rounded-t"
|
||||
>
|
||||
<VCardText class="pt-2">
|
||||
@@ -412,6 +414,16 @@ onMounted(() => {
|
||||
hint="开启后将使用 ImdbID 搜索资源,搜索结果更精确,但不是所有站点都支持"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol v-if="props.default"
|
||||
cols="12"
|
||||
md="4"
|
||||
>
|
||||
<VSwitch
|
||||
v-model="subscribeForm.show_edit_dialog"
|
||||
label="订阅时编辑更多规则"
|
||||
hint="开启后将在添加订阅后弹出编辑订阅的对话框,方便用户编辑订阅规则"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
239
src/components/dialog/SubscribeHistoryDialog.vue
Normal file
239
src/components/dialog/SubscribeHistoryDialog.vue
Normal file
@@ -0,0 +1,239 @@
|
||||
<script lang="ts" setup>
|
||||
import api from '@/api';
|
||||
import { Subscribe } from '@/api/types';
|
||||
import { formatDateDifference } from '@core/utils/formatters'
|
||||
|
||||
// 输入参数
|
||||
const props = defineProps({
|
||||
type: String,
|
||||
})
|
||||
|
||||
// 定义触发的自定义事件
|
||||
const emit = defineEmits(['close', 'save'])
|
||||
|
||||
// 订阅历史列表
|
||||
const historyList = ref<Subscribe[]>([])
|
||||
|
||||
// 当前加载数据
|
||||
const currData = ref<Subscribe[]>([])
|
||||
|
||||
// 当前页
|
||||
const currentPage = ref(1)
|
||||
|
||||
// 每页数量
|
||||
const pageSize = ref(30)
|
||||
|
||||
// 是否加载中
|
||||
const loading = ref(false)
|
||||
|
||||
// 是否加载完成
|
||||
const isRefreshed = ref(false)
|
||||
|
||||
// 进度框
|
||||
const progressDialog = ref(false)
|
||||
|
||||
// 进度文字
|
||||
const progressText = ref('正在重新订阅...')
|
||||
|
||||
// 调用API查询列表
|
||||
async function loadHistory({ done }: { done: any }) {
|
||||
// 如果正在加载中,直接返回
|
||||
if (loading.value) {
|
||||
done('ok')
|
||||
return
|
||||
}
|
||||
|
||||
// 调用API查询列表
|
||||
try {
|
||||
// 设置加载中
|
||||
loading.value = true
|
||||
currData.value = await api.get(`subscribe/history/${props.type}`, {
|
||||
params: {
|
||||
page: currentPage.value,
|
||||
count: pageSize.value,
|
||||
},
|
||||
})
|
||||
// 标计为已请求完成
|
||||
isRefreshed.value = true
|
||||
if (currData.value.length === 0) {
|
||||
// 如果没有数据,跳出
|
||||
done('empty')
|
||||
} else {
|
||||
// 合并数据
|
||||
historyList.value = [...historyList.value, ...currData.value]
|
||||
// 页码+1
|
||||
currentPage.value++
|
||||
// 返回加载成功
|
||||
done('ok')
|
||||
}
|
||||
// 取消加载中
|
||||
loading.value = false
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
// 返回加载失败
|
||||
done('error')
|
||||
}
|
||||
}
|
||||
|
||||
// 重新订阅
|
||||
async function reSubscribe(item: Subscribe) {
|
||||
if (item.type === '电影')
|
||||
progressText.value = `正在重新订阅 ${item.name} ...`
|
||||
else
|
||||
progressText.value = `正在重新订阅 ${item.name} 第 ${item.season} 季 ...`
|
||||
progressDialog.value = true
|
||||
try {
|
||||
const result: {[key: string]: any} = await api.post('subscribe', item)
|
||||
if (result.success){
|
||||
emit('save')
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
progressDialog.value = false
|
||||
}
|
||||
|
||||
// 删除记录
|
||||
async function deleteHistory(item: Subscribe) {
|
||||
try {
|
||||
const result: {[key: string]: any} = await api.delete(`subscribe/history/${item.id}`)
|
||||
if (result.success){
|
||||
historyList.value = historyList.value.filter((i) => i.id !== item.id)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
// 弹出菜单
|
||||
const dropdownItems = ref([
|
||||
{
|
||||
title: '重新订阅',
|
||||
value: 1,
|
||||
color: '',
|
||||
props: {
|
||||
prependIcon: 'mdi-redo',
|
||||
click: reSubscribe,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '删除',
|
||||
value: 2,
|
||||
color: 'error',
|
||||
props: {
|
||||
prependIcon: 'mdi-delete',
|
||||
click: deleteHistory,
|
||||
},
|
||||
}
|
||||
])
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VDialog
|
||||
scrollable
|
||||
max-width="50rem"
|
||||
max-height="90vh"
|
||||
>
|
||||
<VCard
|
||||
class="mx-auto"
|
||||
width="100%"
|
||||
>
|
||||
<VCardItem class="pb-0">
|
||||
<VCardTitle>{{ props.type + '订阅历史' }}</VCardTitle>
|
||||
</VCardItem>
|
||||
<DialogCloseBtn @click="() => { emit('close') }" />
|
||||
<VList
|
||||
lines="two"
|
||||
>
|
||||
<VInfiniteScroll
|
||||
mode="intersect"
|
||||
side="end"
|
||||
:items="historyList"
|
||||
class="overflow-hidden"
|
||||
@load="loadHistory"
|
||||
>
|
||||
<template #loading>
|
||||
<LoadingBanner />
|
||||
</template>
|
||||
<template #empty />
|
||||
<template v-for="(item, i) in historyList" :key="i">
|
||||
<VListItem>
|
||||
<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 v-if="item.type == '电视剧'">
|
||||
{{ item.name }} <span class="text-sm">第 {{ item.season }} 季</span>
|
||||
</VListItemTitle>
|
||||
<VListItemTitle v-else>
|
||||
{{ item.name }}
|
||||
</VListItemTitle>
|
||||
<VListItemSubtitle class="mt-2">{{ formatDateDifference(item.date) }}</VListItemSubtitle>
|
||||
<VListItemSubtitle class="mt-2">{{ item.description }}</VListItemSubtitle>
|
||||
<template #append>
|
||||
<div class="me-n3">
|
||||
<IconBtn>
|
||||
<VIcon
|
||||
icon="mdi-dots-vertical"
|
||||
/>
|
||||
<VMenu
|
||||
activator="parent"
|
||||
close-on-content-click
|
||||
>
|
||||
<VList>
|
||||
<VListItem
|
||||
v-for="(menu, i) in dropdownItems"
|
||||
:key="i"
|
||||
variant="plain"
|
||||
:base-color="menu.color"
|
||||
@click="menu.props.click(item)"
|
||||
>
|
||||
<template #prepend>
|
||||
<VIcon :icon="menu.props.prependIcon" />
|
||||
</template>
|
||||
<VListItemTitle v-text="menu.title" />
|
||||
</VListItem>
|
||||
</VList>
|
||||
</VMenu>
|
||||
</IconBtn>
|
||||
</div>
|
||||
</template>
|
||||
</VListItem>
|
||||
</template>
|
||||
</VInfiniteScroll>
|
||||
</VList>
|
||||
</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>
|
||||
</VDialog>
|
||||
</template>
|
||||
@@ -4,7 +4,7 @@ import type { PropType } from 'vue'
|
||||
import { useConfirm } from 'vuetify-use-dialog'
|
||||
import axios from 'axios'
|
||||
import { useToast } from 'vue-toast-notification'
|
||||
import ReorganizeForm from '../form/ReorganizeForm.vue'
|
||||
import ReorganizeDialog from '../dialog/ReorganizeDialog.vue'
|
||||
import { formatBytes } from '@core/utils/formatters'
|
||||
import type { Context, EndPoints, FileItem } from '@/api/types'
|
||||
import store from '@/store'
|
||||
@@ -535,7 +535,7 @@ onMounted(() => {
|
||||
</VCard>
|
||||
</VDialog>
|
||||
<!-- 文件整理弹窗 -->
|
||||
<ReorganizeForm
|
||||
<ReorganizeDialog
|
||||
v-if="transferPopper"
|
||||
v-model="transferPopper"
|
||||
:path="currentItem?.path"
|
||||
@@ -1,39 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
// 输入参数
|
||||
const props = defineProps({
|
||||
title: String,
|
||||
})
|
||||
|
||||
// 定义事件
|
||||
const emit = defineEmits(['update:modelValue', 'close'])
|
||||
|
||||
// 代码
|
||||
const codeString = ref('')
|
||||
|
||||
// 导入
|
||||
function handleImport() {
|
||||
emit('update:modelValue', codeString.value)
|
||||
emit('close')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VCard
|
||||
:title="props.title"
|
||||
class="rounded-t"
|
||||
>
|
||||
<DialogCloseBtn @click="emit('close')" />
|
||||
<VCardText class="pt-2">
|
||||
<VTextarea v-model="codeString" />
|
||||
</VCardText>
|
||||
<VCardActions>
|
||||
<VSpacer />
|
||||
<VBtn
|
||||
variant="tonal"
|
||||
@click="handleImport"
|
||||
>
|
||||
导入
|
||||
</VBtn>
|
||||
</VCardActions>
|
||||
</VCard>
|
||||
</template>
|
||||
@@ -18,7 +18,12 @@ import 'vue-toast-notification/dist/theme-bootstrap.css'
|
||||
import { PerfectScrollbarPlugin } from 'vue3-perfect-scrollbar';
|
||||
import 'vue3-perfect-scrollbar/style.css';
|
||||
import DialogCloseBtn from '@/@core/components/DialogCloseBtn.vue'
|
||||
import { fixArrayAt } from '@/@core/utils/compatibility'
|
||||
|
||||
// 修复低版本Safari等浏览器数组不支持at函数的问题
|
||||
fixArrayAt()
|
||||
|
||||
// 加载字体
|
||||
loadFonts()
|
||||
|
||||
// 创建Vue实例
|
||||
|
||||
@@ -120,11 +120,12 @@ onMounted(() => {
|
||||
</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="!keyword" size="48" indeterminate color="primary" />
|
||||
<VProgressCircular v-if="keyword" class="mb-3" color="primary" :model-value="progressValue" size="64" />
|
||||
<span>{{ progressText }}</span>
|
||||
</div>
|
||||
<LoadingBanner
|
||||
v-if="!isRefreshed"
|
||||
class="mt-12"
|
||||
:text="progressText"
|
||||
:progress="progressValue"
|
||||
/>
|
||||
<NoDataFound
|
||||
v-if="dataList.length === 0 && isRefreshed"
|
||||
:error-title="errorTitle"
|
||||
|
||||
@@ -52,70 +52,63 @@ async function fetchData({ done }: { done: any }) {
|
||||
// 如果正在加载中,直接返回
|
||||
if (loading.value) {
|
||||
done('ok')
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 设置加载中
|
||||
loading.value = true
|
||||
|
||||
// 加载到满屏或者加载出错
|
||||
if (!hasScroll()) {
|
||||
// 加载多次
|
||||
while (!hasScroll()) {
|
||||
// 设置加载中
|
||||
loading.value = true
|
||||
// 请求API
|
||||
currData.value = await api.get(props.apipath, {
|
||||
params: getParams(),
|
||||
})
|
||||
|
||||
// 取消加载中
|
||||
loading.value = false
|
||||
// 标计为已请求完成
|
||||
isRefreshed.value = true
|
||||
if (currData.value.length === 0) {
|
||||
// 如果没有数据,跳出
|
||||
done('ok')
|
||||
|
||||
done('empty')
|
||||
return
|
||||
}
|
||||
|
||||
// 合并数据
|
||||
dataList.value = [...dataList.value, ...currData.value]
|
||||
|
||||
// 页码+1
|
||||
page.value++
|
||||
// 返回加载成功
|
||||
done('ok')
|
||||
}
|
||||
}
|
||||
else {
|
||||
// 加载一次
|
||||
// 设置加载中
|
||||
loading.value = true
|
||||
// 请求API
|
||||
currData.value = await api.get(props.apipath, {
|
||||
params: getParams(),
|
||||
})
|
||||
|
||||
// 标计为已请求完成
|
||||
isRefreshed.value = true
|
||||
if (currData.value.length === 0) {
|
||||
// 如果没有数据,跳出
|
||||
done('empty')
|
||||
} else {
|
||||
// 合并数据
|
||||
dataList.value = [...dataList.value, ...currData.value]
|
||||
// 页码+1
|
||||
page.value++
|
||||
// 返回加载成功
|
||||
done('ok')
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 合并数据
|
||||
dataList.value = [...dataList.value, ...currData.value]
|
||||
|
||||
// 页码+1
|
||||
page.value++
|
||||
}
|
||||
|
||||
// 取消加载中
|
||||
loading.value = false
|
||||
|
||||
// 返回加载成功
|
||||
done('ok')
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
|
||||
// 返回加载失败
|
||||
done('error')
|
||||
}
|
||||
@@ -123,16 +116,10 @@ async function fetchData({ done }: { done: any }) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
<LoadingBanner
|
||||
v-if="!isRefreshed"
|
||||
class="mt-12 w-full text-center text-gray-500 text-sm flex flex-col items-center"
|
||||
>
|
||||
<VProgressCircular
|
||||
size="48"
|
||||
indeterminate
|
||||
color="primary"
|
||||
/>
|
||||
</div>
|
||||
class="mt-12"
|
||||
/>
|
||||
<VInfiniteScroll
|
||||
mode="intersect"
|
||||
side="end"
|
||||
@@ -141,6 +128,7 @@ async function fetchData({ done }: { done: any }) {
|
||||
@load="fetchData"
|
||||
>
|
||||
<template #loading />
|
||||
<template #empty />
|
||||
<div
|
||||
v-if="dataList.length > 0"
|
||||
class="grid gap-4 grid-media-card mx-3"
|
||||
|
||||
@@ -8,7 +8,7 @@ import NoDataFound from '@/components/NoDataFound.vue'
|
||||
import { doneNProgress, startNProgress } from '@/api/nprogress'
|
||||
import { formatSeason } from '@/@core/utils/formatters'
|
||||
import router from '@/router'
|
||||
import SubscribeEditForm from '@/components/form/SubscribeEditForm.vue'
|
||||
import SubscribeEditDialog from '@/components/dialog/SubscribeEditDialog.vue'
|
||||
|
||||
// 输入参数
|
||||
const mediaProps = defineProps({
|
||||
@@ -46,11 +46,6 @@ const seasonsSubscribed = ref<{ [key: number]: boolean }>({})
|
||||
// 订阅编号
|
||||
const subscribeId = ref<number>()
|
||||
|
||||
// 订阅规则
|
||||
const subscribeRules = ref({
|
||||
show_edit_dialog: false,
|
||||
})
|
||||
|
||||
// 获得mediaid
|
||||
function getMediaId() {
|
||||
return mediaDetail.value?.tmdb_id
|
||||
@@ -230,9 +225,12 @@ async function addSubscribe(season = 0) {
|
||||
)
|
||||
|
||||
// 显示编辑弹窗
|
||||
if (result.success && subscribeRules.value.show_edit_dialog) {
|
||||
subscribeId.value = result.data.id
|
||||
subscribeEditDialog.value = true
|
||||
if (result.success) {
|
||||
const show_edit_dialog = await queryDefaultSubscribeConfig()
|
||||
if (show_edit_dialog) {
|
||||
subscribeId.value = result.data.id
|
||||
subscribeEditDialog.value = true
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
@@ -290,20 +288,6 @@ async function removeSubscribe(season: number) {
|
||||
doneNProgress()
|
||||
}
|
||||
|
||||
// 查询订阅弹窗规则
|
||||
async function querySubscribeRules() {
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.get(
|
||||
'system/setting/DefaultFilterRules',
|
||||
)
|
||||
if (result.data?.value)
|
||||
subscribeRules.value = result.data?.value
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 订阅按钮响应
|
||||
function handleSubscribe(season = 0) {
|
||||
if (isSubscribed.value)
|
||||
@@ -450,23 +434,35 @@ async function handlePlay() {
|
||||
}
|
||||
}
|
||||
|
||||
async function queryDefaultSubscribeConfig() {
|
||||
try {
|
||||
let subscribe_config_url = ''
|
||||
if (mediaProps.type === '电影')
|
||||
subscribe_config_url = 'system/setting/DefaultMovieSubscribeConfig'
|
||||
else
|
||||
subscribe_config_url = 'system/setting/DefaultTvSubscribeConfig'
|
||||
|
||||
const result: { [key: string]: any } = await api.get(subscribe_config_url)
|
||||
|
||||
if (result.data?.value)
|
||||
return result.data.value.show_edit_dialog
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
getMediaDetail()
|
||||
querySubscribeRules()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
<LoadingBanner
|
||||
v-if="!isRefreshed"
|
||||
class="mt-12 w-full text-center text-gray-500 text-sm flex flex-col items-center"
|
||||
>
|
||||
<VProgressCircular
|
||||
size="48"
|
||||
indeterminate
|
||||
color="primary"
|
||||
/>
|
||||
</div>
|
||||
class="mt-12"
|
||||
/>
|
||||
<div v-if="mediaDetail.tmdb_id || mediaDetail.douban_id || mediaDetail.bangumi_id" class="max-w-8xl mx-auto px-4">
|
||||
<template v-if="mediaDetail.backdrop_path || mediaDetail.poster_path">
|
||||
<div class="vue-media-back absolute left-0 top-0 w-full h-96">
|
||||
@@ -638,16 +634,10 @@ onBeforeMount(() => {
|
||||
</VExpansionPanelTitle>
|
||||
<VExpansionPanelText>
|
||||
<template #default>
|
||||
<div
|
||||
<LoadingBanner
|
||||
v-if="!seasonEpisodesInfo[season.season_number || 0]"
|
||||
class="mt-3 w-full text-center text-gray-500 text-sm flex flex-col items-center"
|
||||
>
|
||||
<VProgressCircular
|
||||
size="48"
|
||||
indeterminate
|
||||
color="primary"
|
||||
/>
|
||||
</div>
|
||||
class="mt-3"
|
||||
/>
|
||||
<div class="flex flex-col justify-center divide-y divide-gray-700">
|
||||
<div v-for="episode in seasonEpisodesInfo[season.season_number || 0]" :key="episode.episode_number" class="flex flex-col space-y-4 py-4 xl:flex-row xl:space-y-4 xl:space-x-4">
|
||||
<div class="flex-1">
|
||||
@@ -849,7 +839,7 @@ onBeforeMount(() => {
|
||||
error-description="未识别到媒体信息。"
|
||||
/>
|
||||
<!-- 订阅编辑弹窗 -->
|
||||
<SubscribeEditForm
|
||||
<SubscribeEditDialog
|
||||
v-model="subscribeEditDialog"
|
||||
:subid="subscribeId"
|
||||
@close="subscribeEditDialog = false"
|
||||
|
||||
@@ -42,74 +42,67 @@ async function fetchData({ done }: { done: any }) {
|
||||
// 如果正在加载中,直接返回
|
||||
if (loading.value) {
|
||||
done('ok')
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 设置加载中
|
||||
loading.value = true
|
||||
|
||||
// 加载到满屏或者加载出错
|
||||
if (!hasScroll()) {
|
||||
// 加载多次
|
||||
while (!hasScroll()) {
|
||||
// 设置加载中
|
||||
loading.value = true
|
||||
// 请求API
|
||||
currData.value = await api.get(props.apipath, {
|
||||
params: {
|
||||
page: page.value,
|
||||
},
|
||||
})
|
||||
|
||||
// 取消加载中
|
||||
loading.value = false
|
||||
// 标计为已请求完成
|
||||
isRefreshed.value = true
|
||||
if (currData.value.length === 0) {
|
||||
// 如果没有数据,跳出
|
||||
done('empty')
|
||||
} else {
|
||||
// 合并数据
|
||||
dataList.value = [...dataList.value, ...currData.value]
|
||||
// 页码+1
|
||||
page.value++
|
||||
// 返回加载成功
|
||||
done('ok')
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 合并数据
|
||||
dataList.value = [...dataList.value, ...currData.value]
|
||||
|
||||
// 页码+1
|
||||
page.value++
|
||||
}
|
||||
}
|
||||
else {
|
||||
// 加载一次
|
||||
// 设置加载中
|
||||
loading.value = true
|
||||
// 请求API
|
||||
currData.value = await api.get(props.apipath, {
|
||||
params: {
|
||||
page: page.value,
|
||||
},
|
||||
})
|
||||
|
||||
// 标计为已请求完成
|
||||
isRefreshed.value = true
|
||||
if (currData.value.length === 0) {
|
||||
// 如果没有数据,跳出
|
||||
done('empty')
|
||||
} else {
|
||||
// 合并数据
|
||||
dataList.value = [...dataList.value, ...currData.value]
|
||||
// 页码+1
|
||||
page.value++
|
||||
// 返回加载成功
|
||||
done('ok')
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 合并数据
|
||||
dataList.value = [...dataList.value, ...currData.value]
|
||||
|
||||
// 页码+1
|
||||
page.value++
|
||||
// 取消加载中
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
// 取消加载中
|
||||
loading.value = false
|
||||
|
||||
// 返回加载成功
|
||||
done('ok')
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
|
||||
// 返回加载失败
|
||||
done('error')
|
||||
}
|
||||
@@ -117,16 +110,10 @@ async function fetchData({ done }: { done: any }) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
<LoadingBanner
|
||||
v-if="!isRefreshed"
|
||||
class="mt-12 w-full text-center text-gray-500 text-sm flex flex-col items-center"
|
||||
>
|
||||
<VProgressCircular
|
||||
size="48"
|
||||
indeterminate
|
||||
color="primary"
|
||||
/>
|
||||
</div>
|
||||
class="mt-12"
|
||||
/>
|
||||
<VInfiniteScroll
|
||||
mode="intersect"
|
||||
side="end"
|
||||
@@ -135,6 +122,7 @@ async function fetchData({ done }: { done: any }) {
|
||||
@load="fetchData"
|
||||
>
|
||||
<template #loading />
|
||||
<template #empty />
|
||||
<div
|
||||
v-if="dataList.length > 0 && props.type === 'tmdb'"
|
||||
class="grid gap-4 grid-media-card mx-3"
|
||||
|
||||
@@ -48,16 +48,10 @@ onBeforeMount(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
<LoadingBanner
|
||||
v-if="!isRefreshed"
|
||||
class="mt-12 w-full text-center text-gray-500 text-sm flex flex-col items-center"
|
||||
>
|
||||
<VProgressCircular
|
||||
size="48"
|
||||
indeterminate
|
||||
color="primary"
|
||||
/>
|
||||
</div>
|
||||
class="mt-12"
|
||||
/>
|
||||
<div v-if="personDetail.id" class="max-w-8xl mx-auto px-4">
|
||||
<div class="relative z-10 mt-4 mb-8 flex flex-col items-center lg:flex-row ">
|
||||
<VAvatar
|
||||
|
||||
@@ -117,7 +117,7 @@ function pluginIcon(item: Plugin) {
|
||||
return noImage
|
||||
// 如果是网络图片则使用代理后返回
|
||||
if (item?.plugin_icon?.startsWith('http'))
|
||||
return `${import.meta.env.VITE_API_BASE_URL}system/img/1/${encodeURIComponent(item?.plugin_icon).replace(/%2F/g, '/')}`
|
||||
return `${import.meta.env.VITE_API_BASE_URL}system/img/1?imgurl=${encodeURIComponent(item?.plugin_icon)}`
|
||||
|
||||
return `./plugin_icon/${item?.plugin_icon}`
|
||||
}
|
||||
@@ -211,17 +211,10 @@ onBeforeMount(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
<LoadingBanner
|
||||
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>
|
||||
class="mt-12"
|
||||
/>
|
||||
<div
|
||||
v-if="dataList.length > 0"
|
||||
class="grid gap-4 grid-plugin-card"
|
||||
@@ -285,17 +278,10 @@ onBeforeMount(() => {
|
||||
</VToolbar>
|
||||
</div>
|
||||
<VCardText>
|
||||
<div
|
||||
<LoadingBanner
|
||||
v-if="!isAppMarketLoaded"
|
||||
class="mt-12 w-full text-center text-gray-500 text-sm flex flex-col items-center"
|
||||
>
|
||||
<VProgressCircular
|
||||
v-if="!isAppMarketLoaded"
|
||||
size="48"
|
||||
indeterminate
|
||||
color="primary"
|
||||
/>
|
||||
</div>
|
||||
class="mt-12"
|
||||
/>
|
||||
<div v-if="isAppMarketLoaded" class="grid gap-4 grid-plugin-card">
|
||||
<PluginAppCard
|
||||
v-for="data in sortedUninstalledList"
|
||||
|
||||
@@ -67,17 +67,10 @@ onUnmounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
<LoadingBanner
|
||||
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>
|
||||
class="mt-12"
|
||||
/>
|
||||
<PullRefresh
|
||||
v-model="loading"
|
||||
@refresh="onRefresh"
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ref, unref } from 'vue'
|
||||
import { useToast } from 'vue-toast-notification'
|
||||
import api from '@/api'
|
||||
import type { TransferHistory } from '@/api/types'
|
||||
import ReorganizeForm from '@/components/form/ReorganizeForm.vue'
|
||||
import ReorganizeDialog from '@/components/dialog/ReorganizeDialog.vue'
|
||||
|
||||
// 提示框
|
||||
const $toast = useToast()
|
||||
@@ -482,7 +482,7 @@ onMounted(fetchData)
|
||||
</VCard>
|
||||
</VBottomSheet>
|
||||
<!-- 文件整理弹窗 -->
|
||||
<ReorganizeForm
|
||||
<ReorganizeDialog
|
||||
v-if="redoDialog"
|
||||
v-model="redoDialog"
|
||||
:logids="redoIds"
|
||||
|
||||
@@ -4,7 +4,7 @@ import api from '@/api'
|
||||
import FilterRuleCard from '@/components/cards/FilterRuleCard.vue'
|
||||
import type { Site } from '@/api/types'
|
||||
import { copyToClipboard } from '@/@core/utils/navigator'
|
||||
import ImportCodeForm from '@/components/form/ImportCodeForm.vue'
|
||||
import ImportCodeDialog from '@/components/dialog/ImportCodeDialog.vue'
|
||||
|
||||
// 规则卡片类型
|
||||
interface FilterCard {
|
||||
@@ -420,7 +420,7 @@ onMounted(() => {
|
||||
width="60rem"
|
||||
scrollable
|
||||
>
|
||||
<ImportCodeForm
|
||||
<ImportCodeDialog
|
||||
v-model="importCodeString"
|
||||
title="导入优先级规则"
|
||||
@close="importCodeDialog = false"
|
||||
|
||||
@@ -4,7 +4,7 @@ import api from '@/api'
|
||||
import FilterRuleCard from '@/components/cards/FilterRuleCard.vue'
|
||||
import type { Site } from '@/api/types'
|
||||
import { copyToClipboard } from '@/@core/utils/navigator'
|
||||
import ImportCodeForm from '@/components/form/ImportCodeForm.vue'
|
||||
import ImportCodeDialog from '@/components/dialog/ImportCodeDialog.vue'
|
||||
|
||||
// 规则卡片类型
|
||||
interface FilterCard {
|
||||
@@ -41,8 +41,7 @@ const defaultFilterRules = ref({
|
||||
exclude: '',
|
||||
movie_size: '',
|
||||
tv_size: '',
|
||||
min_seeders: 0,
|
||||
show_edit_dialog: false,
|
||||
min_seeders: 0
|
||||
})
|
||||
|
||||
// 订阅模式选择项
|
||||
@@ -622,13 +621,6 @@ onMounted(() => {
|
||||
hint="小于该值的资源将被过滤掉,0表示不过滤"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
<VSwitch
|
||||
v-model="defaultFilterRules.show_edit_dialog"
|
||||
label="订阅时编辑更多规则"
|
||||
hint="开启后,添加订阅时将自动弹出订阅编辑框,要设置更多订阅选项"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
@@ -648,7 +640,7 @@ onMounted(() => {
|
||||
width="60rem"
|
||||
scrollable
|
||||
>
|
||||
<ImportCodeForm
|
||||
<ImportCodeDialog
|
||||
v-model="importCodeString"
|
||||
title="导入优先级规则"
|
||||
@close="importCodeDialog = false"
|
||||
|
||||
@@ -3,7 +3,7 @@ import api from '@/api'
|
||||
import type { Site } from '@/api/types'
|
||||
import SiteCard from '@/components/cards/SiteCard.vue'
|
||||
import NoDataFound from '@/components/NoDataFound.vue'
|
||||
import SiteAddEditForm from '@/components/form/SiteAddEditForm.vue'
|
||||
import SiteAddEditDialog from '@/components/dialog/SiteAddEditDialog.vue'
|
||||
import { useDefer } from '@/@core/utils/dom'
|
||||
|
||||
// 数据列表
|
||||
@@ -35,17 +35,10 @@ onBeforeMount(fetchData)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
<LoadingBanner
|
||||
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>
|
||||
class="mt-12"
|
||||
/>
|
||||
<div
|
||||
v-if="dataList.length > 0"
|
||||
class="grid gap-3 grid-site-card"
|
||||
@@ -80,7 +73,7 @@ onBeforeMount(fetchData)
|
||||
@click="siteAddDialog = true"
|
||||
/>
|
||||
<!-- 新增站点弹窗 -->
|
||||
<SiteAddEditForm
|
||||
<SiteAddEditDialog
|
||||
v-if="siteAddDialog"
|
||||
v-model="siteAddDialog"
|
||||
oper="add"
|
||||
|
||||
@@ -155,7 +155,7 @@ onMounted(() => {
|
||||
</VCard>
|
||||
</div>
|
||||
<div class="md:hidden">
|
||||
<VTooltip :text="`${arg.event.title} ${arg.event.extendedProps.subtitle}`">
|
||||
<VTooltip :text="`${arg.event.title} 第 ${arg.event.extendedProps.subtitle} 集`">
|
||||
<template #activator="{ props }">
|
||||
<VImg
|
||||
height="60"
|
||||
@@ -384,8 +384,8 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.v-application .fc .fc-daygrid-day-number {
|
||||
padding-block: 0rem;
|
||||
padding-inline: 0rem;
|
||||
padding-block: 0;
|
||||
padding-inline: 0;
|
||||
}
|
||||
|
||||
.v-application .fc .fc-list-event-dot {
|
||||
@@ -435,7 +435,7 @@ onMounted(() => {
|
||||
margin-inline-end: 0.25rem;
|
||||
}
|
||||
|
||||
@media (max-width: 1264px) {
|
||||
@media (width <= 1264px) {
|
||||
.v-application .fc .fc-toolbar-chunk .fc-button-group .fc-drawerToggler-button {
|
||||
display: block !important;
|
||||
}
|
||||
@@ -481,10 +481,10 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.v-application .fc .fc-button-primary {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
outline: none;
|
||||
background-color: transparent;
|
||||
color: var(--v-theme-on-surface);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.v-application .fc .fc-button-primary:hover {
|
||||
@@ -492,7 +492,7 @@ onMounted(() => {
|
||||
color: rgb(var(--v-theme-primary));
|
||||
}
|
||||
|
||||
@media (max-width: 776px) {
|
||||
@media (width <= 776px) {
|
||||
.fc-daygrid-event-harness {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -4,7 +4,8 @@ import api from '@/api'
|
||||
import type { Subscribe } from '@/api/types'
|
||||
import NoDataFound from '@/components/NoDataFound.vue'
|
||||
import SubscribeCard from '@/components/cards/SubscribeCard.vue'
|
||||
import SubscribeEditForm from '@/components/form/SubscribeEditForm.vue'
|
||||
import SubscribeEditDialog from '@/components/dialog/SubscribeEditDialog.vue'
|
||||
import SubscribeHistoryDialog from '@/components/dialog/SubscribeHistoryDialog.vue'
|
||||
import store from '@/store'
|
||||
|
||||
// 输入参数
|
||||
@@ -21,6 +22,9 @@ const dataList = ref<Subscribe[]>([])
|
||||
// 弹窗
|
||||
const subscribeEditDialog = ref(false)
|
||||
|
||||
// 历史记录弹窗
|
||||
const historyDialog = ref(false)
|
||||
|
||||
// 获取订阅列表数据
|
||||
async function fetchData() {
|
||||
try {
|
||||
@@ -58,17 +62,10 @@ const filteredDataList = computed(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
<LoadingBanner
|
||||
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>
|
||||
class="mt-12"
|
||||
/>
|
||||
<PullRefresh
|
||||
v-model="loading"
|
||||
@refresh="onRefresh"
|
||||
@@ -102,8 +99,19 @@ const filteredDataList = computed(() => {
|
||||
appear
|
||||
@click="subscribeEditDialog = true"
|
||||
/>
|
||||
<VFab
|
||||
icon="mdi-history"
|
||||
color="info"
|
||||
location="bottom end"
|
||||
class="mb-2"
|
||||
size="x-large"
|
||||
fixed
|
||||
app
|
||||
appear
|
||||
@click="historyDialog = true"
|
||||
/>
|
||||
<!-- 订阅编辑弹窗 -->
|
||||
<SubscribeEditForm
|
||||
<SubscribeEditDialog
|
||||
v-if="subscribeEditDialog"
|
||||
v-model="subscribeEditDialog"
|
||||
:default="true"
|
||||
@@ -111,6 +119,14 @@ const filteredDataList = computed(() => {
|
||||
@save="subscribeEditDialog = false"
|
||||
@close="subscribeEditDialog = false"
|
||||
/>
|
||||
<!-- 历史记录弹窗 -->
|
||||
<SubscribeHistoryDialog
|
||||
v-if="historyDialog"
|
||||
v-model="historyDialog"
|
||||
:type="props.type"
|
||||
@close="historyDialog = false"
|
||||
@save="() => {historyDialog = false; fetchData()}"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
@@ -55,38 +55,39 @@ async function loadMessages({ done }: { done: any }) {
|
||||
done('ok')
|
||||
return
|
||||
}
|
||||
// 设置加载中
|
||||
loading.value = true
|
||||
try {
|
||||
// 设置加载中
|
||||
loading.value = true
|
||||
currData.value = await api.get('message/web', {
|
||||
params: {
|
||||
page: page.value,
|
||||
size: 20,
|
||||
},
|
||||
})
|
||||
// 已加载过
|
||||
isLoaded.value = true
|
||||
if (currData.value.length > 0) {
|
||||
// 取最后一条时间为存量消息最新时间
|
||||
lastTime.value = currData.value[currData.value.length - 1].reg_time ?? ''
|
||||
// 合并数据
|
||||
messages.value = [...currData.value, ...messages.value]
|
||||
// 加载完成
|
||||
done('ok')
|
||||
if (page.value === 1) {
|
||||
// 滚动到底部
|
||||
emit('scroll')
|
||||
// 监听SSE消息
|
||||
startSSEMessager()
|
||||
}
|
||||
// 页码+1
|
||||
page.value++
|
||||
// 完成
|
||||
done('ok')
|
||||
}
|
||||
else {
|
||||
done('ok')
|
||||
// 监听SSE消息
|
||||
startSSEMessager()
|
||||
// 没有新数据
|
||||
done('empty')
|
||||
}
|
||||
// 取消加载中
|
||||
loading.value = false
|
||||
isLoaded.value = true
|
||||
// 监听SSE消息
|
||||
startSSEMessager()
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
@@ -110,20 +111,18 @@ onBeforeUnmount(() => {
|
||||
|
||||
<template>
|
||||
<VInfiniteScroll
|
||||
mode="intersect"
|
||||
:mode="!isLoaded ? 'intersect' : 'manual'"
|
||||
side="start"
|
||||
:items="messages"
|
||||
class="overflow-hidden"
|
||||
@load="loadMessages"
|
||||
load-more-text="加载更多 ..."
|
||||
>
|
||||
<template #loading>
|
||||
<VProgressCircular
|
||||
v-if="loading"
|
||||
indeterminate
|
||||
size="48"
|
||||
class="mb-5"
|
||||
color="primary"
|
||||
/>
|
||||
<LoadingBanner />
|
||||
</template>
|
||||
<template #empty>
|
||||
没有更多数据
|
||||
</template>
|
||||
<div>
|
||||
<VRow
|
||||
|
||||
Reference in New Issue
Block a user