feat:订阅历史

This commit is contained in:
jxxghp
2024-04-18 21:00:35 +08:00
parent 2d7880351b
commit 62bb6de80d
12 changed files with 403 additions and 347 deletions

View File

@@ -88,6 +88,9 @@ export interface Subscribe {
// 保存目录
save_path: string
// 时间
date: string
}
// 历史记录

View File

@@ -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"

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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"
/>

View File

@@ -43,6 +43,7 @@ const subscribeForm = ref<Subscribe>({
username: '',
current_priority: 0,
save_path: '',
date: ''
})
// 提示框
@@ -265,169 +266,164 @@ onMounted(() => {
</script>
<template>
<VDialog
scrollable
max-width="60rem"
<VCard
:title="`${props.default ? `${props.type}默认订阅规则` : `编辑订阅 - ${subscribeForm.name} ${subscribeForm.season ? `第 ${subscribeForm.season} 季` : ''}`}`"
class="rounded-t"
>
<VCard
:title="`${props.default ? `设置${props.type}默认订阅规则` : `编辑订阅 - ${subscribeForm.name} ${subscribeForm.season ? `第 ${subscribeForm.season} 季` : ''}`}`"
class="rounded-t"
>
<VCardText class="pt-2">
<DialogCloseBtn @click="emit('close')" />
<VForm @submit.prevent="() => {}">
<VRow>
<VCol
cols="12"
md="8"
>
<VTextField
v-if="!props.default"
v-model="subscribeForm.keyword"
label="搜索关键词"
hint="设定搜索关键词后将使用此关键词搜索站点资源否则自动使用themoviedb中的名称搜索"
/>
</VCol>
<VCol
v-if="subscribeForm.type === '电视剧'"
cols="12"
md="2"
>
<VTextField
v-model="subscribeForm.total_episode"
label="总集数"
:rules="[numberValidator]"
hint="设定剧集的总集数以应对themoviedb中剧集信息未维护完整导致提前结束订阅的情况"
/>
</VCol>
<VCol
v-if="subscribeForm.type === '电视剧'"
cols="12"
md="2"
>
<VTextField
v-model="subscribeForm.start_episode"
label="开始集数"
:rules="[numberValidator]"
hint="只订阅下载此集数及之后的剧集"
/>
</VCol>
</VRow>
<VRow>
<VCol
cols="12"
md="4"
>
<VSelect
v-model="subscribeForm.quality"
label="质量"
:items="qualityOptions"
/>
</VCol>
<VCol
cols="12"
md="4"
>
<VSelect
v-model="subscribeForm.resolution"
label="分辨率"
:items="resolutionOptions"
/>
</VCol>
<VCol
cols="12"
md="4"
>
<VSelect
v-model="subscribeForm.effect"
label="特效"
:items="effectOptions"
/>
</VCol>
</VRow>
<VRow>
<VCol
cols="12"
md="4"
>
<VTextField
v-model="subscribeForm.include"
label="包含(关键字、正则式)"
hint="支持正则表达式,多个关键字用 | 分隔表示或"
/>
</VCol>
<VCol
cols="12"
md="4"
>
<VTextField
v-model="subscribeForm.exclude"
label="排除(关键字、正则式)"
hint="支持正则表达式,多个关键字用 | 分隔表示或"
/>
</VCol>
<VCol
cols="12"
md="4"
>
<VSelect
v-model="subscribeForm.sites"
:items="selectSitesOptions"
chips
label="订阅站点"
multiple
hint="只订阅选中的订阅站点,不选则订阅所有可订阅站点"
/>
</VCol>
</VRow>
<VRow>
<VCol
cols="12"
>
<VTextField
v-model="subscribeForm.save_path"
label="保存路径"
hint="指定该订阅的下载保存路径,留空自动使用设定的下载目录"
/>
</VCol>
</VRow>
<VRow>
<VCol
cols="12"
md="4"
>
<VSwitch
v-model="subscribeForm.best_version"
label="洗版"
hint="开启后不管媒体库是否存在,均会根据洗版优先级进行过滤下载,直到下载到了最高优先级的资源为止"
/>
</VCol>
<VCol
cols="12"
md="4"
>
<VSwitch
v-model="subscribeForm.search_imdbid"
label="使用 ImdbID 搜索"
hint="开启后将使用 ImdbID 搜索资源,搜索结果更精确,但不是所有站点都支持"
/>
</VCol>
</VRow>
</VForm>
</VCardText>
<VCardText class="pt-2">
<DialogCloseBtn @click="emit('close')" />
<VForm @submit.prevent="() => {}">
<VRow>
<VCol
cols="12"
md="8"
>
<VTextField
v-if="!props.default"
v-model="subscribeForm.keyword"
label="搜索关键词"
hint="设定搜索关键词后将使用此关键词搜索站点资源否则自动使用themoviedb中的名称搜索"
/>
</VCol>
<VCol
v-if="subscribeForm.type === '电视剧'"
cols="12"
md="2"
>
<VTextField
v-model="subscribeForm.total_episode"
label="总集数"
:rules="[numberValidator]"
hint="设定剧集的总集数以应对themoviedb中剧集信息未维护完整导致提前结束订阅的情况"
/>
</VCol>
<VCol
v-if="subscribeForm.type === '电视剧'"
cols="12"
md="2"
>
<VTextField
v-model="subscribeForm.start_episode"
label="开始集数"
:rules="[numberValidator]"
hint="只订阅下载此集数及之后的剧集"
/>
</VCol>
</VRow>
<VRow>
<VCol
cols="12"
md="4"
>
<VSelect
v-model="subscribeForm.quality"
label="质量"
:items="qualityOptions"
/>
</VCol>
<VCol
cols="12"
md="4"
>
<VSelect
v-model="subscribeForm.resolution"
label="分辨率"
:items="resolutionOptions"
/>
</VCol>
<VCol
cols="12"
md="4"
>
<VSelect
v-model="subscribeForm.effect"
label="特效"
:items="effectOptions"
/>
</VCol>
</VRow>
<VRow>
<VCol
cols="12"
md="4"
>
<VTextField
v-model="subscribeForm.include"
label="包含(关键字、正则式)"
hint="支持正则表达式,多个关键字用 | 分隔表示或"
/>
</VCol>
<VCol
cols="12"
md="4"
>
<VTextField
v-model="subscribeForm.exclude"
label="排除(关键字、正则式)"
hint="支持正则表达式,多个关键字用 | 分隔表示或"
/>
</VCol>
<VCol
cols="12"
md="4"
>
<VSelect
v-model="subscribeForm.sites"
:items="selectSitesOptions"
chips
label="订阅站点"
multiple
hint="只订阅选中的订阅站点,不选则订阅所有可订阅站点"
/>
</VCol>
</VRow>
<VRow>
<VCol
cols="12"
>
<VTextField
v-model="subscribeForm.save_path"
label="保存路径"
hint="指定该订阅的下载保存路径,留空自动使用设定的下载目录"
/>
</VCol>
</VRow>
<VRow>
<VCol
cols="12"
md="4"
>
<VSwitch
v-model="subscribeForm.best_version"
label="洗版"
hint="开启后不管媒体库是否存在,均会根据洗版优先级进行过滤下载,直到下载到了最高优先级的资源为止"
/>
</VCol>
<VCol
cols="12"
md="4"
>
<VSwitch
v-model="subscribeForm.search_imdbid"
label="使用 ImdbID 搜索"
hint="开启后将使用 ImdbID 搜索资源,搜索结果更精确,但不是所有站点都支持"
/>
</VCol>
</VRow>
</VForm>
</VCardText>
<VCardActions>
<VBtn v-if="!props.default" color="error" @click="removeSubscribe">
取消订阅
</VBtn>
<VSpacer />
<VBtn
variant="tonal"
@click="`${props.default ? saveDefaultSubscribeConfig() : updateSubscribeInfo()}`"
>
保存
</VBtn>
</VCardActions>
</VCard>
</VDialog>
<VCardActions>
<VBtn v-if="!props.default" color="error" @click="removeSubscribe">
取消订阅
</VBtn>
<VSpacer />
<VBtn
variant="tonal"
@click="`${props.default ? saveDefaultSubscribeConfig() : updateSubscribeInfo()}`"
>
保存
</VBtn>
</VCardActions>
</VCard>
</template>

View File

@@ -0,0 +1,197 @@
<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'])
// 订阅历史列表
const historyList = ref<Subscribe[]>([])
// 当前加载数据
const currData = ref<Subscribe[]>([])
// 当前页
const currentPage = ref(1)
// 每页数量
const pageSize = ref(30)
// 是否加载中
const loading = ref(false)
// 是否加载完成
const isRefreshed = ref(false)
// 调用API查询列表
async function loadHistory({ done }: { done: any }) {
// 如果正在加载中,直接返回
if (loading.value) {
done('ok')
return
}
// 设置加载中
loading.value = true
// 调用API查询列表
try {
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('ok')
return
}
// 合并数据
historyList.value = [...historyList.value, ...currData.value]
// 页码+1
currentPage.value++
// 取消加载中
loading.value = false
// 返回加载成功
done('ok')
} catch (e) {
console.error(e)
// 返回加载失败
done('error')
}
}
// 重新订阅
function reSubscribe(item: Subscribe) {
}
// 删除记录
function deleteHistory(item: Subscribe) {
}
// 弹出菜单
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>
<VCard
class="mx-auto"
width="100%"
:title = "props.type + '订阅历史'"
>
<DialogCloseBtn @click="() => { emit('close') }" />
<div
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>
<VList
lines="two"
>
<VInfiniteScroll
mode="intersect"
side="end"
:items="historyList"
class="overflow-hidden"
@load="loadHistory"
>
<template #loading />
<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="(item, i) in dropdownItems"
:key="i"
variant="plain"
:base-color="item.color"
@click="item.props.click"
>
<template #prepend>
<VIcon :icon="item.props.prependIcon" />
</template>
<VListItemTitle v-text="item.title" />
</VListItem>
</VList>
</VMenu>
</IconBtn>
</div>
</template>
</VListItem>
</template>
</VInfiniteScroll>
</VList>
<VCardText v-if="historyList.length == 0 && isRefreshed" class="text-center">
没有数据
</VCardText>
</VCard>
</template>

View File

@@ -5,6 +5,7 @@ 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 SubscribeHistory from '@/components/misc/SubscribeHistory.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 {
@@ -102,15 +106,43 @@ 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
<VDialog
v-if="subscribeEditDialog"
v-model="subscribeEditDialog"
:default="true"
:type="props.type"
@save="subscribeEditDialog = false"
@close="subscribeEditDialog = false"
/>
scrollable
max-width="60rem"
>
<SubscribeEditForm
:default="true"
:type="props.type"
@save="subscribeEditDialog = false"
@close="subscribeEditDialog = false"
/>
</VDialog>
<!-- 历史记录弹窗 -->
<VDialog
v-if="historyDialog"
v-model="historyDialog"
scrollable
max-width="50rem"
>
<SubscribeHistory
:type="props.type"
@close="historyDialog = false"
/>
</VDialog>
</template>
<style lang="scss">