mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-06-06 08:10:21 +08:00
Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc585a3900 | ||
|
|
973f8529c2 | ||
|
|
1ff9dc50fd | ||
|
|
065c9053da | ||
|
|
6905be1bcd | ||
|
|
a550f9616c | ||
|
|
bcee3e5373 | ||
|
|
d377ced6b6 | ||
|
|
6e0ceb093c | ||
|
|
745f99e52e | ||
|
|
7197034eda | ||
|
|
264748652f | ||
|
|
48e214564a | ||
|
|
5424e7e02a | ||
|
|
0c9c70b067 | ||
|
|
0ff24f4b09 | ||
|
|
cfa75b7643 | ||
|
|
b72ad1d78d | ||
|
|
5d1f293606 | ||
|
|
2dc0eca4aa | ||
|
|
f5808c1c81 | ||
|
|
321037477f | ||
|
|
43589c66e9 | ||
|
|
435f299a8b | ||
|
|
083db80251 | ||
|
|
92bf520cf4 | ||
|
|
ab354f21c4 | ||
|
|
c7a2c045c7 | ||
|
|
d33c8942e4 | ||
|
|
5e630097b9 | ||
|
|
3b5d03c1c8 | ||
|
|
298ae2c354 | ||
|
|
d936b68597 | ||
|
|
41471b9fd6 |
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "moviepilot",
|
"name": "moviepilot",
|
||||||
"version": "1.2.3-1",
|
"version": "1.2.7",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --host",
|
"dev": "vite --host",
|
||||||
|
|||||||
BIN
public/plugin/clean.png
Normal file
BIN
public/plugin/clean.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.2 KiB |
BIN
public/plugin/downloadmsg.png
Normal file
BIN
public/plugin/downloadmsg.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
@@ -832,6 +832,7 @@ export interface NotificationSwitch {
|
|||||||
wechat: boolean
|
wechat: boolean
|
||||||
telegram: boolean
|
telegram: boolean
|
||||||
slack: boolean
|
slack: boolean
|
||||||
|
synologychat: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
// 环境设置
|
// 环境设置
|
||||||
|
|||||||
@@ -2,19 +2,30 @@
|
|||||||
// 输入参数
|
// 输入参数
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
pri: String,
|
pri: String,
|
||||||
|
maxpri: String,
|
||||||
rules: Array as PropType<string[]>,
|
rules: Array as PropType<string[]>,
|
||||||
width: String,
|
width: String,
|
||||||
height: String,
|
height: String,
|
||||||
})
|
})
|
||||||
|
|
||||||
// 定义触发的自定义事件
|
// 定义触发的自定义事件
|
||||||
const emit = defineEmits(['close', 'changed'])
|
const emit = defineEmits(['close', 'changed', 'levelup', 'leveldown'])
|
||||||
|
|
||||||
// 按钮点击
|
// 按钮点击
|
||||||
function onClose() {
|
function onClose() {
|
||||||
emit('close')
|
emit('close')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 上升优先级
|
||||||
|
function onLevelUp() {
|
||||||
|
emit('levelup', props.pri)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下降优先级
|
||||||
|
function onLevelDown() {
|
||||||
|
emit('leveldown', props.pri)
|
||||||
|
}
|
||||||
|
|
||||||
// 选项变化
|
// 选项变化
|
||||||
function filtersChanged(value: string[]) {
|
function filtersChanged(value: string[]) {
|
||||||
emit('changed', props.pri, value)
|
emit('changed', props.pri, value)
|
||||||
@@ -54,6 +65,20 @@ const selectFilterOptions = ref<{ [key: string]: string }[]>([
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<VCard variant="tonal" :width="props.width" :height="props.height">
|
<VCard variant="tonal" :width="props.width" :height="props.height">
|
||||||
|
<span class="absolute top-3 right-14">
|
||||||
|
<IconBtn
|
||||||
|
v-if="props.pri !== '1'"
|
||||||
|
@click.stop="onLevelUp"
|
||||||
|
>
|
||||||
|
<VIcon icon="mdi-arrow-up" />
|
||||||
|
</IconBtn>
|
||||||
|
<IconBtn
|
||||||
|
v-if="props.pri !== props.maxpri"
|
||||||
|
@click.stop="onLevelDown"
|
||||||
|
>
|
||||||
|
<VIcon icon="mdi-arrow-down" />
|
||||||
|
</IconBtn>
|
||||||
|
</span>
|
||||||
<DialogCloseBtn @click="onClose" />
|
<DialogCloseBtn @click="onClose" />
|
||||||
<VCardItem>
|
<VCardItem>
|
||||||
<VCardTitle>优先级 {{ props.pri }}</VCardTitle>
|
<VCardTitle>优先级 {{ props.pri }}</VCardTitle>
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ async function addSubscribe(season = 0) {
|
|||||||
// 全部存在时洗版
|
// 全部存在时洗版
|
||||||
best_version = !seasonsNotExisted.value[season] ? 1 : 0
|
best_version = !seasonsNotExisted.value[season] ? 1 : 0
|
||||||
// 请求API
|
// 请求API
|
||||||
const result: { [key: string]: any } = await api.post('subscribe', {
|
const result: { [key: string]: any } = await api.post('subscribe/', {
|
||||||
name: props.media?.title,
|
name: props.media?.title,
|
||||||
type: props.media?.type,
|
type: props.media?.type,
|
||||||
year: props.media?.year,
|
year: props.media?.year,
|
||||||
@@ -360,14 +360,6 @@ onBeforeMount(() => {
|
|||||||
handleCheckExists()
|
handleCheckExists()
|
||||||
})
|
})
|
||||||
|
|
||||||
// 订阅季表头
|
|
||||||
const seasonsHeaders = [
|
|
||||||
{ title: '季', key: 'title', sortable: false },
|
|
||||||
{ title: '集数', key: 'episodes', sortable: false },
|
|
||||||
{ title: '评分', key: 'vote', sortable: false },
|
|
||||||
{ title: '状态', key: 'status', sortable: false },
|
|
||||||
]
|
|
||||||
|
|
||||||
// 计算图片地址
|
// 计算图片地址
|
||||||
const getImgUrl: Ref<string> = computed(() => {
|
const getImgUrl: Ref<string> = computed(() => {
|
||||||
if (imageLoadError.value)
|
if (imageLoadError.value)
|
||||||
@@ -379,6 +371,28 @@ const getImgUrl: Ref<string> = computed(() => {
|
|||||||
|
|
||||||
return url
|
return url
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 拼装季图片地址
|
||||||
|
function getSeasonPoster(posterPath: string) {
|
||||||
|
if (!posterPath)
|
||||||
|
return ''
|
||||||
|
return `https://image.tmdb.org/t/p/w500${posterPath}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将yyyy-mm-dd转换为yyyy年mm月dd日
|
||||||
|
function formatAirDate(airDate: string) {
|
||||||
|
if (!airDate)
|
||||||
|
return ''
|
||||||
|
const date = new Date(airDate)
|
||||||
|
return `${date.getFullYear()}年${date.getMonth() + 1}月${date.getDate()}日`
|
||||||
|
}
|
||||||
|
// 从yyyy-mm-dd中提取年份
|
||||||
|
function getYear(airDate: string) {
|
||||||
|
if (!airDate)
|
||||||
|
return ''
|
||||||
|
const date = new Date(airDate)
|
||||||
|
return date.getFullYear()
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -460,72 +474,89 @@ const getImgUrl: Ref<string> = computed(() => {
|
|||||||
</VCard>
|
</VCard>
|
||||||
</template>
|
</template>
|
||||||
</VHover>
|
</VHover>
|
||||||
<VDialog
|
<!-- 订阅季弹窗 -->
|
||||||
|
<VBottomSheet
|
||||||
v-model="subscribeSeasonDialog"
|
v-model="subscribeSeasonDialog"
|
||||||
max-width="50rem"
|
inset
|
||||||
content-class="whitespace-nowrap"
|
|
||||||
scrollable
|
scrollable
|
||||||
>
|
>
|
||||||
<!-- Dialog Content -->
|
<VCard>
|
||||||
<VCard title="选择订阅季">
|
<DialogCloseBtn @click="subscribeSeasonDialog = false" />
|
||||||
<VCardText style="padding: 0;">
|
<VCardTitle class="pe-10">
|
||||||
<VDataTable
|
订阅 - {{ props.media?.title }}
|
||||||
v-model="seasonsSelected"
|
</VCardTitle>
|
||||||
:headers="seasonsHeaders"
|
<VCardText>
|
||||||
:items="seasonInfos"
|
<VList
|
||||||
item-value="season_number"
|
v-model:selected="seasonsSelected"
|
||||||
return-object
|
lines="three"
|
||||||
fixed-header
|
select-strategy="classic"
|
||||||
show-select
|
|
||||||
:items-per-page="100"
|
|
||||||
density="compact"
|
|
||||||
height="auto"
|
|
||||||
>
|
>
|
||||||
<template #item.title="{ item }">
|
<VListItem
|
||||||
<span class="d-block whitespace-nowrap">第 {{ item.raw.season_number }} 季
|
v-for="(item, i) in seasonInfos" :key="i"
|
||||||
</span>
|
:value="item"
|
||||||
</template>
|
>
|
||||||
<template #item.episodes="{ item }">
|
<template #prepend>
|
||||||
<VChip
|
<VImg
|
||||||
variant="outlined"
|
height="90"
|
||||||
size="small"
|
width="60"
|
||||||
>
|
:src="getSeasonPoster(item.poster_path || '')"
|
||||||
{{ item.raw.episode_count }}
|
aspect-ratio="2/3"
|
||||||
</VChip>
|
class="object-cover rounded shadow ring-gray-500 me-3"
|
||||||
</template>
|
cover
|
||||||
<template #item.vote="{ item }">
|
>
|
||||||
{{ item.raw.vote_average }}
|
<template #placeholder>
|
||||||
</template>
|
<div class="w-full h-full">
|
||||||
<template #item.status="{ item }">
|
<VSkeletonLoader class="object-cover aspect-w-2 aspect-h-3" />
|
||||||
<VChip
|
</div>
|
||||||
v-if="seasonsNotExisted"
|
</template>
|
||||||
:color="getExistColor(item.raw.season_number)"
|
</VImg>
|
||||||
flat
|
</template>
|
||||||
size="small"
|
<VListItemTitle>
|
||||||
>
|
第 {{ item.season_number }} 季
|
||||||
{{ getExistText(item.raw.season_number) }}
|
</VListItemTitle>
|
||||||
</VChip>
|
<VListItemSubtitle class="mt-1 me-2">
|
||||||
</template>
|
<VChip
|
||||||
<template #no-data>
|
v-if="item.vote_average"
|
||||||
没有数据
|
color="primary"
|
||||||
</template>
|
size="small"
|
||||||
<template #bottom />
|
class="mb-1"
|
||||||
</VDataTable>
|
>
|
||||||
|
<VIcon icon="mdi-star" /> {{ item.vote_average }}
|
||||||
|
</VChip>
|
||||||
|
{{ getYear(item.air_date || '') }} • {{ item.episode_count }} 集
|
||||||
|
</VListItemSubtitle>
|
||||||
|
<VListItemSubtitle>
|
||||||
|
《{{ media?.title }}》第 {{ item.season_number }} 季于 {{ formatAirDate(item.air_date || '') }} 首播。
|
||||||
|
</VListItemSubtitle>
|
||||||
|
<VListItemSubtitle>
|
||||||
|
<VChip
|
||||||
|
v-if="seasonsNotExisted"
|
||||||
|
class="mt-2"
|
||||||
|
size="small"
|
||||||
|
:color="getExistColor(item.season_number || 0)"
|
||||||
|
>
|
||||||
|
{{ getExistText(item.season_number || 0) }}
|
||||||
|
</VChip>
|
||||||
|
</VListItemSubtitle>
|
||||||
|
<template #append="{ isSelected }">
|
||||||
|
<VListItemAction start>
|
||||||
|
<VSwitch :model-value="isSelected" />
|
||||||
|
</VListItemAction>
|
||||||
|
</template>
|
||||||
|
</VListItem>
|
||||||
|
</VList>
|
||||||
</VCardText>
|
</VCardText>
|
||||||
<VCardActions>
|
<div class="my-2 text-center">
|
||||||
<VBtn @click="subscribeSeasonDialog = false">
|
|
||||||
取消
|
|
||||||
</VBtn>
|
|
||||||
<VSpacer />
|
|
||||||
<VBtn
|
<VBtn
|
||||||
|
:disabled="seasonsSelected.length === 0"
|
||||||
|
width="30%"
|
||||||
@click="subscribeSeasons"
|
@click="subscribeSeasons"
|
||||||
@keydown.enter="subscribeSeasons"
|
|
||||||
>
|
>
|
||||||
确定
|
{{ seasonsSelected.length === 0 ? '请选择订阅季' : '提交订阅' }}
|
||||||
</VBtn>
|
</VBtn>
|
||||||
</VCardActions>
|
</div>
|
||||||
</VCard>
|
</VCard>
|
||||||
</VDialog>
|
</VBottomSheet>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ async function updateSiteInfo() {
|
|||||||
// 更新按钮状态
|
// 更新按钮状态
|
||||||
siteInfoDialog.value = false
|
siteInfoDialog.value = false
|
||||||
|
|
||||||
const result: { [key: string]: any } = await api.put('site', siteForm)
|
const result: { [key: string]: any } = await api.put('site/', siteForm)
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
$toast.success(`${cardProps.site?.name} 更新成功!`)
|
$toast.success(`${cardProps.site?.name} 更新成功!`)
|
||||||
emit('update')
|
emit('update')
|
||||||
@@ -647,6 +647,7 @@ onMounted(() => {
|
|||||||
<VListItemTitle>查看详情</VListItemTitle>
|
<VListItemTitle>查看详情</VListItemTitle>
|
||||||
</VListItem>
|
</VListItem>
|
||||||
<VListItem
|
<VListItem
|
||||||
|
v-if="item.raw.enclosure?.startsWith('http')"
|
||||||
variant="plain"
|
variant="plain"
|
||||||
@click="downloadTorrentFile(item.raw.enclosure)"
|
@click="downloadTorrentFile(item.raw.enclosure)"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ async function searchSubscribe() {
|
|||||||
async function updateSubscribeInfo() {
|
async function updateSubscribeInfo() {
|
||||||
subscribeInfoDialog.value = false
|
subscribeInfoDialog.value = false
|
||||||
try {
|
try {
|
||||||
const result: { [key: string]: any } = await api.put('subscribe', subscribeForm)
|
const result: { [key: string]: any } = await api.put('subscribe/', subscribeForm)
|
||||||
|
|
||||||
// 提示
|
// 提示
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ const keyword = ref('')
|
|||||||
// 加载中
|
// 加载中
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
|
|
||||||
|
// ref
|
||||||
|
const tmdbKeyword = ref<HTMLElement | null>(null)
|
||||||
|
|
||||||
// 选中条目
|
// 选中条目
|
||||||
function selectMedia(item: TmdbItem) {
|
function selectMedia(item: TmdbItem) {
|
||||||
console.log(item)
|
console.log(item)
|
||||||
@@ -68,6 +71,14 @@ async function searchMedias() {
|
|||||||
console.error(e)
|
console.error(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 加载时聚焦搜索框
|
||||||
|
onMounted(() => {
|
||||||
|
// 500ms后聚焦
|
||||||
|
setTimeout(() => {
|
||||||
|
tmdbKeyword.value?.focus()
|
||||||
|
}, 500)
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -75,16 +86,17 @@ async function searchMedias() {
|
|||||||
class="mx-auto"
|
class="mx-auto"
|
||||||
width="100%"
|
width="100%"
|
||||||
>
|
>
|
||||||
<VToolbar flat dense>
|
<VToolbar flat class="p-0">
|
||||||
<VTextField
|
<VTextField
|
||||||
|
ref="tmdbKeyword"
|
||||||
v-model="keyword"
|
v-model="keyword"
|
||||||
density="compact"
|
|
||||||
label="输入名称搜索"
|
label="输入名称搜索"
|
||||||
single-line
|
single-line
|
||||||
hide-details
|
placeholder="电影或电视剧名称"
|
||||||
flat
|
variant="solo"
|
||||||
class="mx-3"
|
|
||||||
append-inner-icon="mdi-magnify"
|
append-inner-icon="mdi-magnify"
|
||||||
|
flat
|
||||||
|
class="mx-1"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
@click:append-inner="searchMedias"
|
@click:append-inner="searchMedias"
|
||||||
@keydown.enter="searchMedias"
|
@keydown.enter="searchMedias"
|
||||||
@@ -97,7 +109,6 @@ async function searchMedias() {
|
|||||||
>
|
>
|
||||||
<template v-for="(item, i) in items" :key="i">
|
<template v-for="(item, i) in items" :key="i">
|
||||||
<VListItem
|
<VListItem
|
||||||
density="compact"
|
|
||||||
@click="selectMedia(item)"
|
@click="selectMedia(item)"
|
||||||
>
|
>
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
@@ -119,7 +130,7 @@ async function searchMedias() {
|
|||||||
<VListItemTitle>
|
<VListItemTitle>
|
||||||
{{ item.title }}
|
{{ item.title }}
|
||||||
</VListItemTitle>
|
</VListItemTitle>
|
||||||
<VListItemSubtitle v-html="item.overview" />
|
<VListItemSubtitle class="mt-2" v-html="item.overview" />
|
||||||
</VListItem>
|
</VListItem>
|
||||||
<VDivider v-if="i < items.length - 1" class="mt-1" inset />
|
<VDivider v-if="i < items.length - 1" class="mt-1" inset />
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ async function handleAddDownload(_site: any = undefined,
|
|||||||
async function addDownload(_media: any, _torrent: any) {
|
async function addDownload(_media: any, _torrent: any) {
|
||||||
startNProgress()
|
startNProgress()
|
||||||
try {
|
try {
|
||||||
const result: { [key: string]: any } = await api.post('download', {
|
const result: { [key: string]: any } = await api.post('download/', {
|
||||||
media_in: _media,
|
media_in: _media,
|
||||||
torrent_in: _torrent,
|
torrent_in: _torrent,
|
||||||
})
|
})
|
||||||
@@ -122,26 +122,6 @@ function getVolumeFactorClass(downloadVolume: number, uploadVolume: number) {
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getSiteIcon()
|
getSiteIcon()
|
||||||
})
|
})
|
||||||
|
|
||||||
// 弹出菜单
|
|
||||||
const dropdownItems = ref([
|
|
||||||
{
|
|
||||||
title: '查看详情',
|
|
||||||
value: 1,
|
|
||||||
props: {
|
|
||||||
prependIcon: 'mdi-information',
|
|
||||||
click: openTorrentDetail,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '下载种子',
|
|
||||||
value: 2,
|
|
||||||
props: {
|
|
||||||
prependIcon: 'mdi-download',
|
|
||||||
click: downloadTorrentFile,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
])
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -180,15 +160,23 @@ const dropdownItems = ref([
|
|||||||
>
|
>
|
||||||
<VList>
|
<VList>
|
||||||
<VListItem
|
<VListItem
|
||||||
v-for="(item, i) in dropdownItems"
|
|
||||||
:key="i"
|
|
||||||
variant="plain"
|
variant="plain"
|
||||||
@click="item.props.click"
|
@click="openTorrentDetail()"
|
||||||
>
|
>
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<VIcon :icon="item.props.prependIcon" />
|
<VIcon icon="mdi-information" />
|
||||||
</template>
|
</template>
|
||||||
<VListItemTitle v-text="item.title" />
|
<VListItemTitle>查看详情</VListItemTitle>
|
||||||
|
</VListItem>
|
||||||
|
<VListItem
|
||||||
|
v-if="props.torrent?.torrent_info?.enclosure?.startsWith('http')"
|
||||||
|
variant="plain"
|
||||||
|
@click="downloadTorrentFile()"
|
||||||
|
>
|
||||||
|
<template #prepend>
|
||||||
|
<VIcon icon="mdi-download" />
|
||||||
|
</template>
|
||||||
|
<VListItemTitle>下载种子</VListItemTitle>
|
||||||
</VListItem>
|
</VListItem>
|
||||||
</VList>
|
</VList>
|
||||||
</VMenu>
|
</VMenu>
|
||||||
|
|||||||
@@ -632,6 +632,7 @@ onMounted(() => {
|
|||||||
<VTextField
|
<VTextField
|
||||||
v-model="transferForm.target"
|
v-model="transferForm.target"
|
||||||
label="目的路径"
|
label="目的路径"
|
||||||
|
placeholder="留空自动"
|
||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
<VCol
|
<VCol
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { type PropType, ref } from 'vue'
|
|||||||
interface RenderProps {
|
interface RenderProps {
|
||||||
component: string
|
component: string
|
||||||
text: string
|
text: string
|
||||||
|
html: string
|
||||||
content?: any
|
content?: any
|
||||||
props?: any
|
props?: any
|
||||||
}
|
}
|
||||||
@@ -16,9 +17,10 @@ const elementProps = defineProps({
|
|||||||
})
|
})
|
||||||
|
|
||||||
// 配置元素
|
// 配置元素
|
||||||
const formItem = ref<RenderProps>(elementProps.config || {
|
const formItem = ref<RenderProps>(elementProps.config ?? {
|
||||||
component: 'div',
|
component: 'div',
|
||||||
text: '',
|
text: '',
|
||||||
|
html: '',
|
||||||
props: {},
|
props: {},
|
||||||
content: [],
|
content: [],
|
||||||
})
|
})
|
||||||
@@ -30,6 +32,7 @@ const formData = ref<any>(elementProps.form || {})
|
|||||||
<template>
|
<template>
|
||||||
<Component
|
<Component
|
||||||
:is="formItem.component"
|
:is="formItem.component"
|
||||||
|
v-if="!formItem.html"
|
||||||
v-bind="formItem.props"
|
v-bind="formItem.props"
|
||||||
v-model="formData[formItem.props?.model || '']"
|
v-model="formData[formItem.props?.model || '']"
|
||||||
>
|
>
|
||||||
@@ -42,4 +45,10 @@ const formData = ref<any>(elementProps.form || {})
|
|||||||
:form="formData"
|
:form="formData"
|
||||||
/>
|
/>
|
||||||
</Component>
|
</Component>
|
||||||
|
<Component
|
||||||
|
:is="formItem.component"
|
||||||
|
v-if="formItem.html"
|
||||||
|
v-bind="formItem.props"
|
||||||
|
v-html="formItem.html"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -13,11 +13,10 @@ interface RenderProps {
|
|||||||
// 输入参数
|
// 输入参数
|
||||||
const elementProps = defineProps({
|
const elementProps = defineProps({
|
||||||
config: Object as PropType<RenderProps>,
|
config: Object as PropType<RenderProps>,
|
||||||
handler: Boolean,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// 配置元素
|
// 配置元素
|
||||||
const formItem = ref<RenderProps>(elementProps.config || {
|
const formItem = ref<RenderProps>(elementProps.config ?? {
|
||||||
component: 'div',
|
component: 'div',
|
||||||
text: '',
|
text: '',
|
||||||
html: '',
|
html: '',
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import AccountSettingWords from '@/views/setting/AccountSettingWords.vue'
|
|||||||
import AccountSettingAbout from '@/views/setting/AccountSettingAbout.vue'
|
import AccountSettingAbout from '@/views/setting/AccountSettingAbout.vue'
|
||||||
import AccountSettingSearch from '@/views/setting/AccountSettingSearch.vue'
|
import AccountSettingSearch from '@/views/setting/AccountSettingSearch.vue'
|
||||||
import AccountSettingSubscribe from '@/views/setting/AccountSettingSubscribe.vue'
|
import AccountSettingSubscribe from '@/views/setting/AccountSettingSubscribe.vue'
|
||||||
|
import AccountSettingService from '@/views/setting/AccountSettingService.vue'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
@@ -35,6 +36,11 @@ const tabs = [
|
|||||||
icon: 'mdi-rss',
|
icon: 'mdi-rss',
|
||||||
tab: 'subscribe',
|
tab: 'subscribe',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: '服务',
|
||||||
|
icon: 'mdi-list-box',
|
||||||
|
tab: 'service',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: '规则',
|
title: '规则',
|
||||||
icon: 'mdi-filter-cog',
|
icon: 'mdi-filter-cog',
|
||||||
@@ -60,7 +66,10 @@ const tabs = [
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<VTabs v-model="activeTab" show-arrows>
|
<VTabs
|
||||||
|
v-model="activeTab"
|
||||||
|
show-arrows
|
||||||
|
>
|
||||||
<VTab v-for="item in tabs" :key="item.icon" :value="item.tab">
|
<VTab v-for="item in tabs" :key="item.icon" :value="item.tab">
|
||||||
<VIcon size="20" start :icon="item.icon" />
|
<VIcon size="20" start :icon="item.icon" />
|
||||||
{{ item.title }}
|
{{ item.title }}
|
||||||
@@ -68,15 +77,19 @@ const tabs = [
|
|||||||
</VTabs>
|
</VTabs>
|
||||||
<VDivider />
|
<VDivider />
|
||||||
|
|
||||||
<VWindow v-model="activeTab" class="mt-5 disable-tab-transition">
|
<VWindow
|
||||||
<!-- Account -->
|
v-model="activeTab"
|
||||||
|
class="mt-5 disable-tab-transition"
|
||||||
|
:touch="false"
|
||||||
|
>
|
||||||
|
<!-- 用户 -->
|
||||||
<VWindowItem value="account">
|
<VWindowItem value="account">
|
||||||
<transition name="fade-slide" appear>
|
<transition name="fade-slide" appear>
|
||||||
<AccountSettingAccount />
|
<AccountSettingAccount />
|
||||||
</transition>
|
</transition>
|
||||||
</VWindowItem>
|
</VWindowItem>
|
||||||
|
|
||||||
<!-- 用户 -->
|
<!-- 站点 -->
|
||||||
<VWindowItem value="site">
|
<VWindowItem value="site">
|
||||||
<transition name="fade-slide" appear>
|
<transition name="fade-slide" appear>
|
||||||
<AccountSettingSite />
|
<AccountSettingSite />
|
||||||
@@ -97,7 +110,14 @@ const tabs = [
|
|||||||
</transition>
|
</transition>
|
||||||
</VWindowItem>
|
</VWindowItem>
|
||||||
|
|
||||||
<!-- Notification -->
|
<!-- 服务 -->
|
||||||
|
<VWindowItem value="service">
|
||||||
|
<transition name="fade-slide" appear>
|
||||||
|
<AccountSettingService />
|
||||||
|
</transition>
|
||||||
|
</VWindowItem>
|
||||||
|
|
||||||
|
<!-- 规则 -->
|
||||||
<VWindowItem value="filter">
|
<VWindowItem value="filter">
|
||||||
<transition name="fade-slide" appear>
|
<transition name="fade-slide" appear>
|
||||||
<AccountSettingRule />
|
<AccountSettingRule />
|
||||||
|
|||||||
@@ -185,7 +185,7 @@ async function addSubscribe(season = 0) {
|
|||||||
// 全部存在时洗版
|
// 全部存在时洗版
|
||||||
best_version = !seasonsNotExisted.value[season] ? 1 : 0
|
best_version = !seasonsNotExisted.value[season] ? 1 : 0
|
||||||
// 请求API
|
// 请求API
|
||||||
const result: { [key: string]: any } = await api.post('subscribe', {
|
const result: { [key: string]: any } = await api.post('subscribe/', {
|
||||||
name: mediaDetail.value?.title,
|
name: mediaDetail.value?.title,
|
||||||
type: mediaDetail.value?.type,
|
type: mediaDetail.value?.type,
|
||||||
year: mediaDetail.value?.year,
|
year: mediaDetail.value?.year,
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ function pluginInstalled() {
|
|||||||
// 获取插件列表数据
|
// 获取插件列表数据
|
||||||
async function fetchData() {
|
async function fetchData() {
|
||||||
try {
|
try {
|
||||||
dataList.value = await api.get('plugin')
|
dataList.value = await api.get('plugin/')
|
||||||
isRefreshed.value = true
|
isRefreshed.value = true
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ const isRefreshed = ref(false)
|
|||||||
// 获取订阅列表数据
|
// 获取订阅列表数据
|
||||||
async function fetchData() {
|
async function fetchData() {
|
||||||
try {
|
try {
|
||||||
dataList.value = await api.get('download')
|
dataList.value = await api.get('download/')
|
||||||
isRefreshed.value = true
|
isRefreshed.value = true
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
|
|||||||
@@ -1,15 +1,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { useToast } from 'vue-toast-notification'
|
import { useToast } from 'vue-toast-notification'
|
||||||
import { useConfirm } from 'vuetify-use-dialog'
|
import { numberValidator } from '@/@validators'
|
||||||
import { numberValidator, requiredValidator } from '@/@validators'
|
|
||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import type { TransferHistory } from '@/api/types'
|
import type { TransferHistory } from '@/api/types'
|
||||||
import TmdbSelectorCard from '@/components/cards/TmdbSelectorCard.vue'
|
import TmdbSelectorCard from '@/components/cards/TmdbSelectorCard.vue'
|
||||||
|
|
||||||
// 确认框
|
|
||||||
const createConfirm = useConfirm()
|
|
||||||
|
|
||||||
// 提示框
|
// 提示框
|
||||||
const $toast = useToast()
|
const $toast = useToast()
|
||||||
|
|
||||||
@@ -24,6 +20,7 @@ const redoType = ref('电影')
|
|||||||
|
|
||||||
// 类型下拉框:电影、电视剧
|
// 类型下拉框:电影、电视剧
|
||||||
const redoTypeItems = ref([
|
const redoTypeItems = ref([
|
||||||
|
{ title: '自动', value: '' },
|
||||||
{ title: '电影', value: '电影' },
|
{ title: '电影', value: '电影' },
|
||||||
{ title: '电视剧', value: '电视剧' },
|
{ title: '电视剧', value: '电视剧' },
|
||||||
])
|
])
|
||||||
@@ -75,6 +72,12 @@ const progressValue = ref(0)
|
|||||||
// TMDB选择对话框
|
// TMDB选择对话框
|
||||||
const tmdbSelectorDialog = ref(false)
|
const tmdbSelectorDialog = ref(false)
|
||||||
|
|
||||||
|
// 删除确认对话框
|
||||||
|
const deleteConfirmDialog = ref(false)
|
||||||
|
|
||||||
|
// 确认框标题
|
||||||
|
const confirmTitle = ref('')
|
||||||
|
|
||||||
// 获取订阅列表数据
|
// 获取订阅列表数据
|
||||||
async function fetchData({
|
async function fetchData({
|
||||||
page,
|
page,
|
||||||
@@ -129,74 +132,50 @@ const TransferDict: { [key: string]: string } = {
|
|||||||
|
|
||||||
// 删除历史记录
|
// 删除历史记录
|
||||||
async function removeHistory(item: TransferHistory) {
|
async function removeHistory(item: TransferHistory) {
|
||||||
const isConfirmed = await createConfirm({
|
currentHistory.value = item
|
||||||
title: '确认',
|
confirmTitle.value = `确认删除 ${item.title} ${item.seasons}${item.episodes} ?`
|
||||||
content: `同步删除 ${item.title} 对应的媒体库文件 ?`,
|
deleteConfirmDialog.value = true
|
||||||
confirmationText: '同步删除文件',
|
|
||||||
cancellationText: '仅删除历史记录',
|
|
||||||
dialogProps: {
|
|
||||||
maxWidth: '50rem',
|
|
||||||
},
|
|
||||||
confirmationButtonProps: {
|
|
||||||
color: 'error',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if (isConfirmed === undefined)
|
|
||||||
return
|
|
||||||
|
|
||||||
// 执行删除
|
|
||||||
remove(item, isConfirmed || false)
|
|
||||||
// 清空选中项
|
|
||||||
selected.value = []
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 调用API删除记录
|
// 调用API删除记录
|
||||||
async function remove(item: TransferHistory, deleteFile: boolean) {
|
async function remove(item: TransferHistory, deleteSrc: boolean, deleteDest: boolean) {
|
||||||
try {
|
try {
|
||||||
// 调用删除API
|
// 调用删除API
|
||||||
const result: { [key: string]: any } = await api.delete(`history/transfer?delete_file=${deleteFile}`, {
|
const result: { [key: string]: any } = await api.delete(`history/transfer?deletesrc=${deleteSrc}&deletedest=${deleteDest}`, {
|
||||||
data: item,
|
data: item,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (result.success) {
|
if (!result.success)
|
||||||
fetchData({
|
|
||||||
page: currentPage.value,
|
|
||||||
itemsPerPage: itemsPerPage.value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$toast.error(`删除失败: ${result.msg}`)
|
$toast.error(`删除失败: ${result.msg}`)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 批量删除历史记录
|
// 删除单条记录
|
||||||
async function removeHistoryBatch() {
|
async function removeSingle(deleteSrc: boolean, deleteDest: boolean) {
|
||||||
if (selected.value.length === 0)
|
// 关闭弹窗
|
||||||
|
deleteConfirmDialog.value = false
|
||||||
|
if (!currentHistory.value)
|
||||||
return
|
return
|
||||||
// 确认
|
// 删除
|
||||||
const isConfirmed = await createConfirm({
|
await remove(currentHistory.value, deleteSrc, deleteDest)
|
||||||
title: '确认',
|
// 刷新
|
||||||
content: `同步删除 ${selected.value.length} 条记录对应的媒体库文件 ?`,
|
fetchData({
|
||||||
confirmationText: '同步删除文件',
|
page: currentPage.value,
|
||||||
cancellationText: '仅删除历史记录',
|
itemsPerPage: itemsPerPage.value,
|
||||||
dialogProps: {
|
|
||||||
maxWidth: '50rem',
|
|
||||||
},
|
|
||||||
confirmationButtonProps: {
|
|
||||||
color: 'error',
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
if (isConfirmed === undefined)
|
}
|
||||||
return
|
|
||||||
|
|
||||||
console.log(selected.value)
|
|
||||||
|
|
||||||
|
// 批量删除记录
|
||||||
|
async function removeBatch(deleteSrc: boolean, deleteDest: boolean) {
|
||||||
|
// 关闭弹窗
|
||||||
|
deleteConfirmDialog.value = false
|
||||||
// 总条数
|
// 总条数
|
||||||
const total = selected.value.length
|
const total = selected.value.length
|
||||||
|
if (total === 0)
|
||||||
|
return
|
||||||
// 已处理条数
|
// 已处理条数
|
||||||
let handled = 0
|
let handled = 0
|
||||||
// 显示进度条
|
// 显示进度条
|
||||||
@@ -205,7 +184,7 @@ async function removeHistoryBatch() {
|
|||||||
for (const item of selected.value) {
|
for (const item of selected.value) {
|
||||||
// 开始删除
|
// 开始删除
|
||||||
progressText.value = `正在删除 ${item.title} ${item.seasons}${item.episodes} ...`
|
progressText.value = `正在删除 ${item.title} ${item.seasons}${item.episodes} ...`
|
||||||
await remove(item, isConfirmed || false)
|
await remove(item, deleteSrc, deleteDest)
|
||||||
// 删除完成
|
// 删除完成
|
||||||
handled++
|
handled++
|
||||||
progressValue.value = handled / total * 100
|
progressValue.value = handled / total * 100
|
||||||
@@ -221,27 +200,46 @@ async function removeHistoryBatch() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重新整理
|
// 响应删除操作
|
||||||
async function rehandleHistory() {
|
async function deleteConfirmHandler(deleteSrc: boolean, deleteDest: boolean) {
|
||||||
|
if (currentHistory.value)
|
||||||
|
await removeSingle(deleteSrc, deleteDest)
|
||||||
|
else
|
||||||
|
await removeBatch(deleteSrc, deleteDest)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量删除历史记录
|
||||||
|
async function removeHistoryBatch() {
|
||||||
|
if (selected.value.length === 0)
|
||||||
|
return
|
||||||
|
// 清空当前操作记录
|
||||||
|
currentHistory.value = undefined
|
||||||
|
confirmTitle.value = `确认删除 ${selected.value.length} 条记录 ?`
|
||||||
|
// 打开确认弹窗
|
||||||
|
deleteConfirmDialog.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量重新整理
|
||||||
|
async function retransferBatch() {
|
||||||
|
if (selected.value.length === 0)
|
||||||
|
return
|
||||||
|
// 清空当前操作记录
|
||||||
|
currentHistory.value = undefined
|
||||||
|
// 打开识别弹窗
|
||||||
|
redoType.value = ''
|
||||||
|
redoDialog.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调API重新整理
|
||||||
|
async function retransfer(item: TransferHistory, redoType = '', redoTmdbId = 0) {
|
||||||
try {
|
try {
|
||||||
if (!redoTmdbId.value || !redoType.value)
|
|
||||||
return
|
|
||||||
|
|
||||||
redoDialog.value = false
|
|
||||||
$toast.info(`正在重新整理 ${currentHistory.value?.title} ...`)
|
|
||||||
|
|
||||||
// 调用API接口重新转移
|
|
||||||
const requestData = {
|
|
||||||
...currentHistory.value,
|
|
||||||
}
|
|
||||||
|
|
||||||
const result: { [key: string]: any } = await api.post(
|
const result: { [key: string]: any } = await api.post(
|
||||||
'history/transfer',
|
'history/transfer',
|
||||||
requestData,
|
item,
|
||||||
{
|
{
|
||||||
params: {
|
params: {
|
||||||
mtype: redoType.value,
|
mtype: redoType,
|
||||||
new_tmdbid: parseInt(redoTmdbId.value),
|
new_tmdbid: redoTmdbId,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -261,6 +259,50 @@ async function rehandleHistory() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 重新整理
|
||||||
|
async function rehandleHistory() {
|
||||||
|
try {
|
||||||
|
// 关闭弹窗
|
||||||
|
redoDialog.value = false
|
||||||
|
|
||||||
|
let tmdbid = 0
|
||||||
|
|
||||||
|
if (redoTmdbId.value)
|
||||||
|
tmdbid = parseInt(redoTmdbId.value)
|
||||||
|
|
||||||
|
// 转移当前选中记录
|
||||||
|
if (currentHistory.value) {
|
||||||
|
$toast.info(`正在重新整理 ${currentHistory.value?.title} ...`)
|
||||||
|
await retransfer(currentHistory.value, redoType.value, tmdbid)
|
||||||
|
}
|
||||||
|
else if (selected.value.length > 0) {
|
||||||
|
// 总条数
|
||||||
|
const total = selected.value.length
|
||||||
|
if (total === 0)
|
||||||
|
return
|
||||||
|
// 已处理条数
|
||||||
|
let handled = 0
|
||||||
|
// 显示进度条
|
||||||
|
progressDialog.value = true
|
||||||
|
for (const item of selected.value) {
|
||||||
|
progressText.value = `正在重新整理 ${item.src} ...`
|
||||||
|
await retransfer(item, redoType.value, tmdbid)
|
||||||
|
handled++
|
||||||
|
progressValue.value = handled / total * 100
|
||||||
|
}
|
||||||
|
// 清空选中项
|
||||||
|
selected.value = []
|
||||||
|
// 隐藏进度条
|
||||||
|
progressDialog.value = false
|
||||||
|
}
|
||||||
|
// 批量转移
|
||||||
|
else { $toast.error('没有选中任何记录!') }
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 弹出菜单
|
// 弹出菜单
|
||||||
const dropdownItems = ref([
|
const dropdownItems = ref([
|
||||||
{
|
{
|
||||||
@@ -269,6 +311,8 @@ const dropdownItems = ref([
|
|||||||
props: {
|
props: {
|
||||||
prependIcon: 'mdi-redo-variant',
|
prependIcon: 'mdi-redo-variant',
|
||||||
click: (item: TransferHistory) => {
|
click: (item: TransferHistory) => {
|
||||||
|
redoTmdbId.value = ''
|
||||||
|
redoType.value = ''
|
||||||
redoDialog.value = true
|
redoDialog.value = true
|
||||||
currentHistory.value = item
|
currentHistory.value = item
|
||||||
},
|
},
|
||||||
@@ -280,7 +324,9 @@ const dropdownItems = ref([
|
|||||||
props: {
|
props: {
|
||||||
prependIcon: 'mdi-trash-can-outline',
|
prependIcon: 'mdi-trash-can-outline',
|
||||||
color: 'error',
|
color: 'error',
|
||||||
click: removeHistory,
|
click: (item: TransferHistory) => {
|
||||||
|
removeHistory(item)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
@@ -407,7 +453,6 @@ const dropdownItems = ref([
|
|||||||
<VSelect
|
<VSelect
|
||||||
v-model="redoType"
|
v-model="redoType"
|
||||||
label="类型"
|
label="类型"
|
||||||
:rules="[requiredValidator]"
|
|
||||||
:items="redoTypeItems"
|
:items="redoTypeItems"
|
||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
@@ -415,9 +460,10 @@ const dropdownItems = ref([
|
|||||||
<VTextField
|
<VTextField
|
||||||
v-model="redoTmdbId"
|
v-model="redoTmdbId"
|
||||||
label="TMDB编号"
|
label="TMDB编号"
|
||||||
:rules="[requiredValidator, numberValidator]"
|
placeholder="留空自动识别"
|
||||||
|
:rules="[numberValidator]"
|
||||||
append-inner-icon="mdi-magnify"
|
append-inner-icon="mdi-magnify"
|
||||||
@click:append-inner="tmdbSelectorDialog = true"
|
@click:append-inner.stop="tmdbSelectorDialog = true"
|
||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
</VRow>
|
</VRow>
|
||||||
@@ -435,6 +481,13 @@ const dropdownItems = ref([
|
|||||||
</VCard>
|
</VCard>
|
||||||
</VDialog>
|
</VDialog>
|
||||||
<span v-if="selected.length > 0" class="fixed right-5 bottom-5">
|
<span v-if="selected.length > 0" class="fixed right-5 bottom-5">
|
||||||
|
<VBtn
|
||||||
|
icon="mdi-redo-variant"
|
||||||
|
class="me-2"
|
||||||
|
color="primary"
|
||||||
|
size="x-large"
|
||||||
|
@click="retransferBatch"
|
||||||
|
/>
|
||||||
<VBtn
|
<VBtn
|
||||||
icon="mdi-trash-can-outline"
|
icon="mdi-trash-can-outline"
|
||||||
color="error"
|
color="error"
|
||||||
@@ -472,6 +525,45 @@ const dropdownItems = ref([
|
|||||||
@close="tmdbSelectorDialog = false"
|
@close="tmdbSelectorDialog = false"
|
||||||
/>
|
/>
|
||||||
</vDialog>
|
</vDialog>
|
||||||
|
<!-- 底部弹窗 -->
|
||||||
|
<VBottomSheet v-model="deleteConfirmDialog" inset>
|
||||||
|
<VCard class="text-center">
|
||||||
|
<DialogCloseBtn @click="deleteConfirmDialog = false" />
|
||||||
|
<VCardTitle class="pe-10">
|
||||||
|
{{ confirmTitle }}
|
||||||
|
</VCardTitle>
|
||||||
|
<div class="d-flex flex-column flex-lg-row justify-center my-3">
|
||||||
|
<VBtn
|
||||||
|
color="primary"
|
||||||
|
class="mb-2 mx-2"
|
||||||
|
@click="deleteConfirmHandler(false, false)"
|
||||||
|
>
|
||||||
|
仅删除历史记录
|
||||||
|
</VBtn>
|
||||||
|
<VBtn
|
||||||
|
color="warning"
|
||||||
|
class="mb-2 mx-2"
|
||||||
|
@click="deleteConfirmHandler(true, false)"
|
||||||
|
>
|
||||||
|
删除历史记录和源文件
|
||||||
|
</VBtn>
|
||||||
|
<VBtn
|
||||||
|
color="info"
|
||||||
|
class="mb-2 mx-2"
|
||||||
|
@click="deleteConfirmHandler(false, true)"
|
||||||
|
>
|
||||||
|
删除历史记录和媒体库文件
|
||||||
|
</VBtn>
|
||||||
|
<VBtn
|
||||||
|
color="error"
|
||||||
|
class="mb-2 mx-2"
|
||||||
|
@click="deleteConfirmHandler(true, true)"
|
||||||
|
>
|
||||||
|
删除历史记录、源文件和媒体库文件
|
||||||
|
</VBtn>
|
||||||
|
</div>
|
||||||
|
</VCard>
|
||||||
|
</VBottomSheet>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ async function saveAccountInfo() {
|
|||||||
accountInfo.value.password = newPassword.value
|
accountInfo.value.password = newPassword.value
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const result: { [key: string]: any } = await api.put('user', accountInfo.value)
|
const result: { [key: string]: any } = await api.put('user/', accountInfo.value)
|
||||||
if (result.success)
|
if (result.success)
|
||||||
$toast.success('用户信息保存成功!')
|
$toast.success('用户信息保存成功!')
|
||||||
else
|
else
|
||||||
@@ -100,7 +100,7 @@ async function saveAccountInfo() {
|
|||||||
// 调用API,查询所有用户
|
// 调用API,查询所有用户
|
||||||
async function loadAllUsers() {
|
async function loadAllUsers() {
|
||||||
try {
|
try {
|
||||||
const result: User[] = await api.get('/user')
|
const result: User[] = await api.get('/user/')
|
||||||
|
|
||||||
allUsers.value = result
|
allUsers.value = result
|
||||||
}
|
}
|
||||||
@@ -131,7 +131,7 @@ async function deactivateUser(user: User) {
|
|||||||
try {
|
try {
|
||||||
user.is_active = !user.is_active
|
user.is_active = !user.is_active
|
||||||
|
|
||||||
const result: { [key: string]: any } = await api.put('user', user)
|
const result: { [key: string]: any } = await api.put('user/', user)
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
$toast.success('用户冻结成功!')
|
$toast.success('用户冻结成功!')
|
||||||
loadAllUsers()
|
loadAllUsers()
|
||||||
|
|||||||
@@ -64,6 +64,9 @@ onMounted(() => {
|
|||||||
<th scope="col">
|
<th scope="col">
|
||||||
Slack
|
Slack
|
||||||
</th>
|
</th>
|
||||||
|
<th scope="col">
|
||||||
|
SynologyChat
|
||||||
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -83,6 +86,9 @@ onMounted(() => {
|
|||||||
<td>
|
<td>
|
||||||
<VCheckbox v-model="message.slack" />
|
<VCheckbox v-model="message.slack" />
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
<VCheckbox v-model="message.synologychat" />
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="messagemTypes.length === 0">
|
<tr v-if="messagemTypes.length === 0">
|
||||||
<td
|
<td
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ const TorrentPriorityItems = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
// 包含与排除规则
|
// 包含与排除规则
|
||||||
const defaultIncludeExcludeFilter = ref({
|
const defaultFilterRules = ref({
|
||||||
include: '',
|
include: '',
|
||||||
exclude: '',
|
exclude: '',
|
||||||
})
|
})
|
||||||
@@ -35,13 +35,13 @@ async function queryTorrentPriority() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 查询包含与排除规则
|
// 查询包含与排除规则
|
||||||
async function queryIncludeExcludeFilter() {
|
async function queryDefaultFilter() {
|
||||||
try {
|
try {
|
||||||
const result: { [key: string]: any } = await api.get(
|
const result: { [key: string]: any } = await api.get(
|
||||||
'system/setting/DefaultIncludeExcludeFilter',
|
'system/setting/DefaultFilterRules',
|
||||||
)
|
)
|
||||||
if (result.data?.value)
|
if (result.data?.value)
|
||||||
defaultIncludeExcludeFilter.value = result.data?.value
|
defaultFilterRules.value = result.data?.value
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
console.log(error)
|
console.log(error)
|
||||||
@@ -68,11 +68,11 @@ async function saveTorrentPriority() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 保存包含与排除规则
|
// 保存包含与排除规则
|
||||||
async function saveIncludeExcludeFilter() {
|
async function saveDefaultFilter() {
|
||||||
try {
|
try {
|
||||||
const result: { [key: string]: any } = await api.post(
|
const result: { [key: string]: any } = await api.post(
|
||||||
'system/setting/DefaultIncludeExcludeFilter',
|
'system/setting/DefaultFilterRules',
|
||||||
defaultIncludeExcludeFilter.value,
|
defaultFilterRules.value,
|
||||||
)
|
)
|
||||||
if (result.success)
|
if (result.success)
|
||||||
$toast.success('默认包含/排除规则保存成功')
|
$toast.success('默认包含/排除规则保存成功')
|
||||||
@@ -86,7 +86,7 @@ async function saveIncludeExcludeFilter() {
|
|||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
queryTorrentPriority()
|
queryTorrentPriority()
|
||||||
queryIncludeExcludeFilter()
|
queryDefaultFilter()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -127,14 +127,14 @@ onMounted(() => {
|
|||||||
<VRow>
|
<VRow>
|
||||||
<VCol cols="12" md="6">
|
<VCol cols="12" md="6">
|
||||||
<VTextField
|
<VTextField
|
||||||
v-model="defaultIncludeExcludeFilter.include"
|
v-model="defaultFilterRules.include"
|
||||||
type="text"
|
type="text"
|
||||||
label="包含(关键字、正则式)"
|
label="包含(关键字、正则式)"
|
||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
<VCol cols="12" md="6">
|
<VCol cols="12" md="6">
|
||||||
<VTextField
|
<VTextField
|
||||||
v-model="defaultIncludeExcludeFilter.exclude"
|
v-model="defaultFilterRules.exclude"
|
||||||
type="text"
|
type="text"
|
||||||
label="排除(关键字、正则式)"
|
label="排除(关键字、正则式)"
|
||||||
/>
|
/>
|
||||||
@@ -145,7 +145,7 @@ onMounted(() => {
|
|||||||
<VCardItem>
|
<VCardItem>
|
||||||
<VBtn
|
<VBtn
|
||||||
type="submit"
|
type="submit"
|
||||||
@click="saveIncludeExcludeFilter"
|
@click="saveDefaultFilter"
|
||||||
>
|
>
|
||||||
保存
|
保存
|
||||||
</VBtn>
|
</VBtn>
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ function addFilterCard() {
|
|||||||
// 查询所有站点
|
// 查询所有站点
|
||||||
async function querySites() {
|
async function querySites() {
|
||||||
try {
|
try {
|
||||||
const data: Site[] = await api.get('site')
|
const data: Site[] = await api.get('site/')
|
||||||
|
|
||||||
// 过滤站点,只有启用的站点才显示
|
// 过滤站点,只有启用的站点才显示
|
||||||
allSites.value = data.filter(item => item.is_active)
|
allSites.value = data.filter(item => item.is_active)
|
||||||
@@ -148,6 +148,48 @@ async function saveSelectedSites() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 上调优先级
|
||||||
|
function onLevelUp(pri: string) {
|
||||||
|
// 找到当前卡片
|
||||||
|
const card = filterCards.value.find(card => card.pri === pri)
|
||||||
|
if (!card)
|
||||||
|
return
|
||||||
|
|
||||||
|
// 找到当前卡片的上一张卡片
|
||||||
|
const prevCard = filterCards.value.find(card => card.pri === (parseInt(pri) - 1).toString())
|
||||||
|
if (!prevCard)
|
||||||
|
return
|
||||||
|
|
||||||
|
// 交换两张卡片的优先级
|
||||||
|
const temp = card.pri
|
||||||
|
card.pri = prevCard.pri
|
||||||
|
prevCard.pri = temp
|
||||||
|
|
||||||
|
// 卡片重新按优先级排序
|
||||||
|
filterCards.value.sort((a, b) => parseInt(a.pri) - parseInt(b.pri))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下调优先级
|
||||||
|
function onLevelDown(pri: string) {
|
||||||
|
// 找到当前卡片
|
||||||
|
const card = filterCards.value.find(card => card.pri === pri)
|
||||||
|
if (!card)
|
||||||
|
return
|
||||||
|
|
||||||
|
// 找到当前卡片的下一张卡片
|
||||||
|
const nextCard = filterCards.value.find(card => card.pri === (parseInt(pri) + 1).toString())
|
||||||
|
if (!nextCard)
|
||||||
|
return
|
||||||
|
|
||||||
|
// 交换两张卡片的优先级
|
||||||
|
const temp = card.pri
|
||||||
|
card.pri = nextCard.pri
|
||||||
|
nextCard.pri = temp
|
||||||
|
|
||||||
|
// 卡片重新按优先级排序
|
||||||
|
filterCards.value.sort((a, b) => parseInt(a.pri) - parseInt(b.pri))
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
queryCustomFilters()
|
queryCustomFilters()
|
||||||
querySites()
|
querySites()
|
||||||
@@ -191,9 +233,12 @@ onMounted(() => {
|
|||||||
v-for="(card, index) in filterCards"
|
v-for="(card, index) in filterCards"
|
||||||
:key="index"
|
:key="index"
|
||||||
:pri="card.pri"
|
:pri="card.pri"
|
||||||
|
:maxpri="filterCards.length.toString()"
|
||||||
:rules="card.rules"
|
:rules="card.rules"
|
||||||
@changed="updateFilterCardValue"
|
@changed="updateFilterCardValue"
|
||||||
@close="filterCardClose(card.pri)"
|
@close="filterCardClose(card.pri)"
|
||||||
|
@leveldown="onLevelDown"
|
||||||
|
@levelup="onLevelUp"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</VCardItem>
|
</VCardItem>
|
||||||
|
|||||||
138
src/views/setting/AccountSettingService.vue
Normal file
138
src/views/setting/AccountSettingService.vue
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { useToast } from 'vue-toast-notification'
|
||||||
|
import api from '@/api'
|
||||||
|
import type { ScheduleInfo } from '@/api/types'
|
||||||
|
|
||||||
|
// 提示框
|
||||||
|
const $toast = useToast()
|
||||||
|
|
||||||
|
// 定时服务列表
|
||||||
|
const schedulerList = ref<ScheduleInfo[]>([])
|
||||||
|
|
||||||
|
// 定时器
|
||||||
|
let refreshTimer: NodeJS.Timer | null = null
|
||||||
|
|
||||||
|
// 调用API加载定时服务列表
|
||||||
|
async function loadSchedulerList() {
|
||||||
|
try {
|
||||||
|
const res: ScheduleInfo[] = await api.get('dashboard/schedule')
|
||||||
|
|
||||||
|
schedulerList.value = res
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 任务状态颜色
|
||||||
|
function getSchedulerColor(status: string) {
|
||||||
|
switch (status) {
|
||||||
|
case '正在运行':
|
||||||
|
return 'success'
|
||||||
|
case '已停止':
|
||||||
|
return 'error'
|
||||||
|
case '等待':
|
||||||
|
return ''
|
||||||
|
default:
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行命令
|
||||||
|
function runCommand(id: string) {
|
||||||
|
try {
|
||||||
|
// 异步提交
|
||||||
|
api.get('system/runscheduler', {
|
||||||
|
params: {
|
||||||
|
jobid: id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
$toast.success('定时作业执行请求提交成功!')
|
||||||
|
// 1秒后刷新数据
|
||||||
|
setTimeout(() => {
|
||||||
|
loadSchedulerList()
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadSchedulerList()
|
||||||
|
|
||||||
|
// 启动定时器
|
||||||
|
refreshTimer = setInterval(() => {
|
||||||
|
loadSchedulerList()
|
||||||
|
}, 5000)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 组件卸载时停止定时器
|
||||||
|
onUnmounted(() => {
|
||||||
|
if (refreshTimer) {
|
||||||
|
clearInterval(refreshTimer)
|
||||||
|
refreshTimer = null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<VCard title="定时作业">
|
||||||
|
<VCardText> 手动执行不会影响作业正常的时间表。 </VCardText>
|
||||||
|
|
||||||
|
<VTable class="text-no-wrap">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">
|
||||||
|
任务名称
|
||||||
|
</th>
|
||||||
|
<th scope="col">
|
||||||
|
任务状态
|
||||||
|
</th>
|
||||||
|
<th scope="col">
|
||||||
|
下一次执行时间
|
||||||
|
</th>
|
||||||
|
<th scope="col" />
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr
|
||||||
|
v-for="scheduler in schedulerList"
|
||||||
|
:key="scheduler.id"
|
||||||
|
>
|
||||||
|
<td>
|
||||||
|
{{ scheduler.name }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<VChip :color="getSchedulerColor(scheduler.status)">
|
||||||
|
{{ scheduler.status }}
|
||||||
|
</VChip>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ scheduler.next_run }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<VBtn
|
||||||
|
size="small"
|
||||||
|
:disabled="scheduler.status === '正在运行'"
|
||||||
|
@click="runCommand(scheduler.id)"
|
||||||
|
>
|
||||||
|
<template #prepend>
|
||||||
|
<VIcon>mdi-play</VIcon>
|
||||||
|
</template>
|
||||||
|
执行
|
||||||
|
</VBtn>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="schedulerList.length === 0">
|
||||||
|
<td
|
||||||
|
colspan="4"
|
||||||
|
class="text-center"
|
||||||
|
>
|
||||||
|
没有后台服务
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</VTable>
|
||||||
|
</VCard>
|
||||||
|
</template>
|
||||||
@@ -57,7 +57,7 @@ async function saveSelectedRssSites() {
|
|||||||
// 查询所有站点
|
// 查询所有站点
|
||||||
async function querySites() {
|
async function querySites() {
|
||||||
try {
|
try {
|
||||||
const data: Site[] = await api.get('site')
|
const data: Site[] = await api.get('site/')
|
||||||
|
|
||||||
// 过滤站点,只有启用的站点才显示
|
// 过滤站点,只有启用的站点才显示
|
||||||
allSites.value = data.filter(item => item.is_active)
|
allSites.value = data.filter(item => item.is_active)
|
||||||
@@ -165,6 +165,48 @@ function addFilterCard(ruleType: string) {
|
|||||||
cards.value.push(newCard)
|
cards.value.push(newCard)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 上调优先级
|
||||||
|
function onLevelUp(filterCards: FilterCard[], pri: string) {
|
||||||
|
// 找到当前卡片
|
||||||
|
const card = filterCards.find(card => card.pri === pri)
|
||||||
|
if (!card)
|
||||||
|
return
|
||||||
|
|
||||||
|
// 找到当前卡片的上一张卡片
|
||||||
|
const prevCard = filterCards.find(card => card.pri === (parseInt(pri) - 1).toString())
|
||||||
|
if (!prevCard)
|
||||||
|
return
|
||||||
|
|
||||||
|
// 交换两张卡片的优先级
|
||||||
|
const temp = card.pri
|
||||||
|
card.pri = prevCard.pri
|
||||||
|
prevCard.pri = temp
|
||||||
|
|
||||||
|
// 卡片重新按优先级排序
|
||||||
|
filterCards.sort((a, b) => parseInt(a.pri) - parseInt(b.pri))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下调优先级
|
||||||
|
function onLevelDown(filterCards: FilterCard[], pri: string) {
|
||||||
|
// 找到当前卡片
|
||||||
|
const card = filterCards.find(card => card.pri === pri)
|
||||||
|
if (!card)
|
||||||
|
return
|
||||||
|
|
||||||
|
// 找到当前卡片的下一张卡片
|
||||||
|
const nextCard = filterCards.find(card => card.pri === (parseInt(pri) + 1).toString())
|
||||||
|
if (!nextCard)
|
||||||
|
return
|
||||||
|
|
||||||
|
// 交换两张卡片的优先级
|
||||||
|
const temp = card.pri
|
||||||
|
card.pri = nextCard.pri
|
||||||
|
nextCard.pri = temp
|
||||||
|
|
||||||
|
// 卡片重新按优先级排序
|
||||||
|
filterCards.sort((a, b) => parseInt(a.pri) - parseInt(b.pri))
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
querySites()
|
querySites()
|
||||||
queryCustomFilters('SubscribeFilterRules')
|
queryCustomFilters('SubscribeFilterRules')
|
||||||
@@ -209,9 +251,12 @@ onMounted(() => {
|
|||||||
v-for="(card, index) in subscribeFilterCards"
|
v-for="(card, index) in subscribeFilterCards"
|
||||||
:key="index"
|
:key="index"
|
||||||
:pri="card.pri"
|
:pri="card.pri"
|
||||||
|
:maxpri="subscribeFilterCards.length.toString()"
|
||||||
:rules="card.rules"
|
:rules="card.rules"
|
||||||
@changed="updateFilterCardValue"
|
@changed="updateFilterCardValue"
|
||||||
@close="filterCardClose('SubscribeFilterRules', card.pri)"
|
@close="filterCardClose('SubscribeFilterRules', card.pri)"
|
||||||
|
@leveldown="onLevelDown(subscribeFilterCards, card.pri)"
|
||||||
|
@levelup="onLevelUp(subscribeFilterCards, card.pri)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</VCardItem>
|
</VCardItem>
|
||||||
@@ -242,9 +287,12 @@ onMounted(() => {
|
|||||||
v-for="(card, index) in bestVersionFilterCards"
|
v-for="(card, index) in bestVersionFilterCards"
|
||||||
:key="index"
|
:key="index"
|
||||||
:pri="card.pri"
|
:pri="card.pri"
|
||||||
|
:maxpri="bestVersionFilterCards.length.toString()"
|
||||||
:rules="card.rules"
|
:rules="card.rules"
|
||||||
@changed="updateFilterCardValue2"
|
@changed="updateFilterCardValue2"
|
||||||
@close="filterCardClose('BestVersionFilterRules', card.pri)"
|
@close="filterCardClose('BestVersionFilterRules', card.pri)"
|
||||||
|
@leveldown="onLevelDown(bestVersionFilterCards, card.pri)"
|
||||||
|
@levelup="onLevelUp(bestVersionFilterCards, card.pri)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</VCardItem>
|
</VCardItem>
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ const siteForm = reactive<Site>({
|
|||||||
// 获取站点列表数据
|
// 获取站点列表数据
|
||||||
async function fetchData() {
|
async function fetchData() {
|
||||||
try {
|
try {
|
||||||
dataList.value = await api.get('site')
|
dataList.value = await api.get('site/')
|
||||||
isRefreshed.value = true
|
isRefreshed.value = true
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
@@ -77,7 +77,7 @@ async function addSite() {
|
|||||||
addBtnState.value = true
|
addBtnState.value = true
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result: { [key: string]: string } = await api.post('site', siteForm)
|
const result: { [key: string]: string } = await api.post('site/', siteForm)
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
$toast.success('新增站点成功')
|
$toast.success('新增站点成功')
|
||||||
|
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ async function eventsHander(subscribe: Subscribe | Rss) {
|
|||||||
async function getSubscribes() {
|
async function getSubscribes() {
|
||||||
try {
|
try {
|
||||||
// 订阅
|
// 订阅
|
||||||
const subscribes: Subscribe[] = await api.get('subscribe')
|
const subscribes: Subscribe[] = await api.get('subscribe/')
|
||||||
|
|
||||||
const subEvents = await Promise.all(
|
const subEvents = await Promise.all(
|
||||||
subscribes.map(async sub => eventsHander(sub)),
|
subscribes.map(async sub => eventsHander(sub)),
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ const dataList = ref<Subscribe[]>([])
|
|||||||
// 获取订阅列表数据
|
// 获取订阅列表数据
|
||||||
async function fetchData() {
|
async function fetchData() {
|
||||||
try {
|
try {
|
||||||
dataList.value = await api.get('subscribe')
|
dataList.value = await api.get('subscribe/')
|
||||||
isRefreshed.value = true
|
isRefreshed.value = true
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user