mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-06-06 00:01:33 +08:00
Compare commits
51 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e4a0b29162 | ||
|
|
09234296f4 | ||
|
|
679228c8a7 | ||
|
|
a752e19878 | ||
|
|
0880c0e3b3 | ||
|
|
948e65d383 | ||
|
|
7cce57496d | ||
|
|
e54e851f61 | ||
|
|
17020cf62d | ||
|
|
0c7be28eaa | ||
|
|
0d5a183f2e | ||
|
|
c222594bea | ||
|
|
3df8bdfbf2 | ||
|
|
5722547d93 | ||
|
|
dea5ebd95d | ||
|
|
048e41c1ca | ||
|
|
5078036c51 | ||
|
|
e7a128bf0d | ||
|
|
0e46936231 | ||
|
|
d91d3ef0ef | ||
|
|
1f0dd907f9 | ||
|
|
3c555cbfca | ||
|
|
9a8e4d8600 | ||
|
|
444aaa5cdc | ||
|
|
25669d18fc | ||
|
|
03d6e46eca | ||
|
|
c44c7ed0f0 | ||
|
|
47ac7437c0 | ||
|
|
37f982e0ea | ||
|
|
139646369f | ||
|
|
3d4b84dc09 | ||
|
|
02866754e0 | ||
|
|
e4e1a75d44 | ||
|
|
4e5dd03456 | ||
|
|
506b6eea09 | ||
|
|
403ee4b925 | ||
|
|
193d1f550f | ||
|
|
d2a02a830c | ||
|
|
6cc9c1ac57 | ||
|
|
fffb1c6c02 | ||
|
|
985d1baff5 | ||
|
|
a70e467b69 | ||
|
|
b07010bebd | ||
|
|
353bdc5989 | ||
|
|
f135804c4b | ||
|
|
ae6d0ead2c | ||
|
|
6db3ad4e0d | ||
|
|
09e42d5a08 | ||
|
|
b9a09fd1be | ||
|
|
b08235b9f6 | ||
|
|
43e67893b4 |
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "moviepilot",
|
"name": "moviepilot",
|
||||||
"version": "1.1.3-3",
|
"version": "1.2.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --host",
|
"dev": "vite --host",
|
||||||
|
|||||||
BIN
public/plugin/brush.jpg
Normal file
BIN
public/plugin/brush.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
BIN
public/plugin/delete.png
Normal file
BIN
public/plugin/delete.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
BIN
public/plugin/fileupload.png
Normal file
BIN
public/plugin/fileupload.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 17 KiB |
@@ -17,7 +17,6 @@
|
|||||||
// ℹ️ This mixin is inspired from vuetify for adding hover styles via before pseudo element
|
// ℹ️ This mixin is inspired from vuetify for adding hover styles via before pseudo element
|
||||||
@mixin before-pseudo() {
|
@mixin before-pseudo() {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background: currentcolor;
|
background: currentcolor;
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
// ℹ️ This mixin is inspired from vuetify for adding hover styles via before pseudo element
|
// ℹ️ This mixin is inspired from vuetify for adding hover styles via before pseudo element
|
||||||
@mixin before-pseudo() {
|
@mixin before-pseudo() {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
border-radius: inherit;
|
border-radius: inherit;
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ export default defineComponent({
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
* {
|
* {
|
||||||
backface-visibility: hidden;
|
backface-visibility: hidden;
|
||||||
perspective: 1000px;
|
perspective: 62.5rem;
|
||||||
transform: translateZ(0);
|
transform: translateZ(0);
|
||||||
will-change: block-size;
|
will-change: block-size;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,12 +70,15 @@ function handleNavScroll(evt: Event) {
|
|||||||
<slot name="nav-items" :update-is-vertical-nav-scrolled="updateIsVerticalNavScrolled">
|
<slot name="nav-items" :update-is-vertical-nav-scrolled="updateIsVerticalNavScrolled">
|
||||||
<PerfectScrollbar
|
<PerfectScrollbar
|
||||||
tag="ul"
|
tag="ul"
|
||||||
class="nav-items"
|
class="nav-items d-none d-lg-block"
|
||||||
:options="{ wheelPropagation: false }"
|
:options="{ wheelPropagation: false }"
|
||||||
@ps-scroll-y="handleNavScroll"
|
@ps-scroll-y="handleNavScroll"
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</PerfectScrollbar>
|
</PerfectScrollbar>
|
||||||
|
<ul class="nav-items d-lg-none overflow-auto">
|
||||||
|
<slot />
|
||||||
|
</ul>
|
||||||
</slot>
|
</slot>
|
||||||
|
|
||||||
<slot name="after-nav-items" />
|
<slot name="after-nav-items" />
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
.layout-navbar {
|
.layout-navbar {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
width: calc(100vw - variables.$layout-vertical-nav-width - 1rem);
|
width: calc(100vw - variables.$layout-vertical-nav-width - 0.5rem);
|
||||||
z-index: variables.$layout-vertical-nav-layout-navbar-z-index;
|
z-index: variables.$layout-vertical-nav-layout-navbar-z-index;
|
||||||
inset-block-start: 0;
|
inset-block-start: 0;
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ body,
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
// TODO: Use grid gutter variable here
|
// TODO: Use grid gutter variable here
|
||||||
padding-block: 1.5rem;
|
padding-block: 1.5rem;
|
||||||
padding-top: calc(env(safe-area-inset-top) + 65px);
|
padding-top: calc(env(safe-area-inset-top) + 4.25rem);
|
||||||
// display: flex;
|
// display: flex;
|
||||||
|
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ body,
|
|||||||
& > div:first-child {
|
& > div:first-child {
|
||||||
flex: auto;
|
flex: auto;
|
||||||
position: relative;
|
position: relative;
|
||||||
width: calc(100vw - variables.$layout-vertical-nav-width - 1rem);
|
width: calc(100vw - variables.$layout-vertical-nav-width - 0.5rem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// 👉 Vertical nav
|
// 👉 Vertical nav
|
||||||
$layout-vertical-nav-z-index: 12 !default;
|
$layout-vertical-nav-z-index: 12 !default;
|
||||||
$layout-vertical-nav-width: 16.25rem !default;
|
$layout-vertical-nav-width: 16.25rem !default;
|
||||||
$layout-vertical-nav-collapsed-width: 80px !default;
|
$layout-vertical-nav-collapsed-width: 5rem !default;
|
||||||
|
|
||||||
// 👉 Horizontal nav
|
// 👉 Horizontal nav
|
||||||
$layout-horizontal-nav-z-index: 11 !default;
|
$layout-horizontal-nav-z-index: 11 !default;
|
||||||
@@ -16,7 +16,7 @@ $layout-vertical-nav-layout-navbar-z-index: 11 !default;
|
|||||||
$layout-horizontal-nav-layout-navbar-z-index: 11 !default;
|
$layout-horizontal-nav-layout-navbar-z-index: 11 !default;
|
||||||
|
|
||||||
// 👉 Main content
|
// 👉 Main content
|
||||||
$layout-boxed-content-width: 1440px !default;
|
$layout-boxed-content-width: 90rem !default;
|
||||||
|
|
||||||
// 👉Footer
|
// 👉Footer
|
||||||
$layout-vertical-nav-footer-height: 3.5rem !default;
|
$layout-vertical-nav-footer-height: 3.5rem !default;
|
||||||
|
|||||||
35
src/App.vue
35
src/App.vue
@@ -1,10 +1,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useToast } from 'vue-toast-notification'
|
import { useToast } from 'vue-toast-notification'
|
||||||
import { useTheme } from 'vuetify'
|
import { useTheme } from 'vuetify'
|
||||||
import api from './api'
|
|
||||||
import type { User } from './api/types'
|
|
||||||
import store from './store'
|
import store from './store'
|
||||||
import avatar1 from '@images/avatars/avatar-1.png'
|
|
||||||
|
|
||||||
// 第一时间应用主题
|
// 第一时间应用主题
|
||||||
const { global: globalTheme } = useTheme()
|
const { global: globalTheme } = useTheme()
|
||||||
@@ -36,44 +33,14 @@ function startSSEMessager() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 当前用户信息
|
|
||||||
const accountInfo = ref<User>({
|
|
||||||
id: 0,
|
|
||||||
name: '',
|
|
||||||
password: '',
|
|
||||||
email: '',
|
|
||||||
is_active: false,
|
|
||||||
is_superuser: false,
|
|
||||||
avatar: avatar1,
|
|
||||||
})
|
|
||||||
|
|
||||||
// 调用API,加载当前用户数据
|
|
||||||
async function loadAccountInfo() {
|
|
||||||
try {
|
|
||||||
const user: User = await api.get('user/current')
|
|
||||||
|
|
||||||
accountInfo.value = user
|
|
||||||
if (!accountInfo.value.avatar)
|
|
||||||
accountInfo.value.avatar = avatar1
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.log(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 页面加载时,加载当前用户数据
|
// 页面加载时,加载当前用户数据
|
||||||
onBeforeMount(async () => {
|
onBeforeMount(async () => {
|
||||||
await loadAccountInfo()
|
|
||||||
console.log('accountInfo', accountInfo.value)
|
|
||||||
startSSEMessager()
|
startSSEMessager()
|
||||||
})
|
})
|
||||||
|
|
||||||
// 提供给所有元素复用
|
|
||||||
provide('accountInfo', accountInfo)
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<VApp>
|
<VApp>
|
||||||
<RouterView :key="route.fullPath" />
|
<RouterView />
|
||||||
</VApp>
|
</VApp>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -526,6 +526,9 @@ export interface Plugin {
|
|||||||
|
|
||||||
// 运行状态
|
// 运行状态
|
||||||
state?: boolean
|
state?: boolean
|
||||||
|
|
||||||
|
// 是否有详情页面
|
||||||
|
has_page?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
// 种子信息
|
// 种子信息
|
||||||
@@ -837,55 +840,6 @@ export interface Setting {
|
|||||||
DOWNLOAD_PATH: string
|
DOWNLOAD_PATH: string
|
||||||
}
|
}
|
||||||
|
|
||||||
// 自定义订阅
|
|
||||||
export interface Rss {
|
|
||||||
id?: number
|
|
||||||
// 名称
|
|
||||||
name?: string
|
|
||||||
// RSS地址
|
|
||||||
url?: string
|
|
||||||
// 类型
|
|
||||||
type?: string
|
|
||||||
// 标题
|
|
||||||
title?: string
|
|
||||||
// 年份
|
|
||||||
year?: string
|
|
||||||
// TMDBID
|
|
||||||
tmdbid?: number
|
|
||||||
// 季号
|
|
||||||
season?: number
|
|
||||||
// 海报
|
|
||||||
poster?: string
|
|
||||||
// 背景图
|
|
||||||
backdrop?: string
|
|
||||||
// 评分
|
|
||||||
vote?: number
|
|
||||||
// 简介
|
|
||||||
description?: string
|
|
||||||
// 总集数
|
|
||||||
total_episode?: number
|
|
||||||
// 包含
|
|
||||||
include?: string
|
|
||||||
// 排除
|
|
||||||
exclude?: string
|
|
||||||
// 洗版
|
|
||||||
best_version?: number
|
|
||||||
// 是否使用代理服务器
|
|
||||||
proxy?: number
|
|
||||||
// 是否使用过滤规则
|
|
||||||
filter?: boolean
|
|
||||||
// 保存路径
|
|
||||||
save_path?: string
|
|
||||||
// 已处理数量
|
|
||||||
processed?: number
|
|
||||||
// 附加信息
|
|
||||||
note?: string
|
|
||||||
// 最后更新时间
|
|
||||||
last_update?: string
|
|
||||||
// 状态 0-停用,1-启用
|
|
||||||
state?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
// 文件浏览接口
|
// 文件浏览接口
|
||||||
export interface EndPoints {
|
export interface EndPoints {
|
||||||
list: any
|
list: any
|
||||||
@@ -905,4 +859,5 @@ export interface FileItem {
|
|||||||
extension: string
|
extension: string
|
||||||
size: number
|
size: number
|
||||||
children: FileItem[]
|
children: FileItem[]
|
||||||
|
modify_time: number
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,6 +57,8 @@ const loading = ref(0)
|
|||||||
const activeStorage = ref('local')
|
const activeStorage = ref('local')
|
||||||
// 刷新
|
// 刷新
|
||||||
const refreshPending = ref(false)
|
const refreshPending = ref(false)
|
||||||
|
// 排序
|
||||||
|
const sort = ref('name')
|
||||||
// axios实例
|
// axios实例
|
||||||
const axiosInstance = ref<Axios>()
|
const axiosInstance = ref<Axios>()
|
||||||
|
|
||||||
@@ -83,6 +85,12 @@ function pathChanged(_path: string) {
|
|||||||
emit('pathchanged', _path)
|
emit('pathchanged', _path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 排序变化
|
||||||
|
function sortChanged(s: string) {
|
||||||
|
sort.value = s
|
||||||
|
refreshPending.value = true
|
||||||
|
}
|
||||||
|
|
||||||
// 初始化
|
// 初始化
|
||||||
onBeforeMount(() => {
|
onBeforeMount(() => {
|
||||||
activeStorage.value = props.storage ?? 'local'
|
activeStorage.value = props.storage ?? 'local'
|
||||||
@@ -101,6 +109,7 @@ onBeforeMount(() => {
|
|||||||
@storagechanged="storageChanged"
|
@storagechanged="storageChanged"
|
||||||
@pathchanged="pathChanged"
|
@pathchanged="pathChanged"
|
||||||
@foldercreated="refreshPending = true"
|
@foldercreated="refreshPending = true"
|
||||||
|
@sortchanged="sortChanged"
|
||||||
/>
|
/>
|
||||||
<VRow no-gutters>
|
<VRow no-gutters>
|
||||||
<VCol v-if="tree" sm="auto" class="d-none d-md-block">
|
<VCol v-if="tree" sm="auto" class="d-none d-md-block">
|
||||||
@@ -125,6 +134,7 @@ onBeforeMount(() => {
|
|||||||
:endpoints="endpoints"
|
:endpoints="endpoints"
|
||||||
:axios="axiosInstance"
|
:axios="axiosInstance"
|
||||||
:refreshpending="refreshPending"
|
:refreshpending="refreshPending"
|
||||||
|
:sort="sort"
|
||||||
@pathchanged="pathChanged"
|
@pathchanged="pathChanged"
|
||||||
@loading="loadingChanged"
|
@loading="loadingChanged"
|
||||||
@refreshed="refreshPending = false"
|
@refreshed="refreshPending = false"
|
||||||
|
|||||||
@@ -50,9 +50,6 @@ const selectFilterOptions = ref<{ [key: string]: string }[]>([
|
|||||||
{ title: '排除: 国语配音', value: ' !CNVOI ' },
|
{ title: '排除: 国语配音', value: ' !CNVOI ' },
|
||||||
{ title: '促销: 免费', value: ' FREE ' },
|
{ title: '促销: 免费', value: ' FREE ' },
|
||||||
])
|
])
|
||||||
|
|
||||||
// 已选择的过滤规则
|
|
||||||
const selectedFilters = ref<string[]>(props.rules ?? [])
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -64,7 +61,7 @@ const selectedFilters = ref<string[]>(props.rules ?? [])
|
|||||||
<VCol>
|
<VCol>
|
||||||
<VSelect
|
<VSelect
|
||||||
:key="props.pri"
|
:key="props.pri"
|
||||||
v-model="selectedFilters"
|
v-model="props.rules"
|
||||||
variant="underlined"
|
variant="underlined"
|
||||||
:items="selectFilterOptions"
|
:items="selectFilterOptions"
|
||||||
chips
|
chips
|
||||||
|
|||||||
@@ -52,23 +52,23 @@ function openTmdbPage(type: string, tmdbId: number) {
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<VCardItem class="pb-1">
|
<VCardItem class="pb-1">
|
||||||
<VCardTitle>
|
<VCardTitle class="text-center text-md-left">
|
||||||
{{ context?.media_info?.title || context?.meta_info?.name }}
|
{{ context?.media_info?.title || context?.meta_info?.name }}
|
||||||
{{ context?.meta_info?.season_episode }}
|
{{ context?.meta_info?.season_episode }}
|
||||||
</VCardTitle>
|
</VCardTitle>
|
||||||
<VCardSubtitle>
|
<VCardSubtitle class="text-center text-md-left">
|
||||||
{{ context?.media_info?.year || context?.meta_info?.year }}
|
{{ context?.media_info?.year || context?.meta_info?.year }}
|
||||||
</VCardSubtitle>
|
</VCardSubtitle>
|
||||||
</VCardItem>
|
</VCardItem>
|
||||||
|
|
||||||
<VCardText
|
<VCardText
|
||||||
v-if="context?.media_info?.overview"
|
v-if="context?.media_info?.overview"
|
||||||
class="line-clamp-4 overflow-hidden text-ellipsis ..."
|
class="line-clamp-4 overflow-hidden text-ellipsis text-center text-md-left ..."
|
||||||
>
|
>
|
||||||
{{ context?.media_info?.overview }}
|
{{ context?.media_info?.overview }}
|
||||||
</VCardText>
|
</VCardText>
|
||||||
|
|
||||||
<VCardItem>
|
<VCardItem class="text-center text-md-left">
|
||||||
<!-- 类型 -->
|
<!-- 类型 -->
|
||||||
<VChip
|
<VChip
|
||||||
v-if="context?.media_info?.type || context?.meta_info?.type"
|
v-if="context?.media_info?.type || context?.meta_info?.type"
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ async function installPlugin() {
|
|||||||
:style="{ background: `${props.plugin?.plugin_color}` }"
|
:style="{ background: `${props.plugin?.plugin_color}` }"
|
||||||
>
|
>
|
||||||
<VAvatar
|
<VAvatar
|
||||||
size="128"
|
size="8rem"
|
||||||
:class="{ shadow: isImageLoaded }"
|
:class="{ shadow: isImageLoaded }"
|
||||||
>
|
>
|
||||||
<VImg
|
<VImg
|
||||||
|
|||||||
@@ -119,6 +119,8 @@ async function savePluginConf() {
|
|||||||
|
|
||||||
// 显示插件详情
|
// 显示插件详情
|
||||||
async function showPluginInfo() {
|
async function showPluginInfo() {
|
||||||
|
// 加载详情
|
||||||
|
await loadPluginPage()
|
||||||
pluginConfigDialog.value = false
|
pluginConfigDialog.value = false
|
||||||
pluginInfoDialog.value = true
|
pluginInfoDialog.value = true
|
||||||
}
|
}
|
||||||
@@ -129,17 +131,35 @@ async function showPluginConfig() {
|
|||||||
await loadPluginForm()
|
await loadPluginForm()
|
||||||
// 加载配置
|
// 加载配置
|
||||||
await loadPluginConf()
|
await loadPluginConf()
|
||||||
// 加载详情
|
|
||||||
await loadPluginPage()
|
|
||||||
// 显示对话框
|
// 显示对话框
|
||||||
|
pluginInfoDialog.value = false
|
||||||
pluginConfigDialog.value = true
|
pluginConfigDialog.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// 弹出菜单
|
// 弹出菜单
|
||||||
const dropdownItems = ref([
|
const dropdownItems = ref([
|
||||||
{
|
{
|
||||||
title: '卸载',
|
title: '查看详情',
|
||||||
value: 1,
|
value: 1,
|
||||||
|
show: props.plugin?.has_page,
|
||||||
|
props: {
|
||||||
|
prependIcon: 'mdi-information-outline',
|
||||||
|
click: showPluginInfo,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '配置',
|
||||||
|
value: 2,
|
||||||
|
show: true,
|
||||||
|
props: {
|
||||||
|
prependIcon: 'mdi-cog-outline',
|
||||||
|
click: showPluginConfig,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '卸载',
|
||||||
|
value: 3,
|
||||||
|
show: true,
|
||||||
props: {
|
props: {
|
||||||
prependIcon: 'mdi-trash-can-outline',
|
prependIcon: 'mdi-trash-can-outline',
|
||||||
color: 'error',
|
color: 'error',
|
||||||
@@ -155,7 +175,12 @@ const dropdownItems = ref([
|
|||||||
v-if="isVisible"
|
v-if="isVisible"
|
||||||
:width="props.width"
|
:width="props.width"
|
||||||
:height="props.height"
|
:height="props.height"
|
||||||
@click="showPluginConfig"
|
@click="() => {
|
||||||
|
if (props.plugin?.has_page)
|
||||||
|
showPluginInfo()
|
||||||
|
else
|
||||||
|
showPluginConfig()
|
||||||
|
}"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="relative pa-4 text-center card-cover-blurred"
|
class="relative pa-4 text-center card-cover-blurred"
|
||||||
@@ -171,6 +196,7 @@ const dropdownItems = ref([
|
|||||||
<VList>
|
<VList>
|
||||||
<VListItem
|
<VListItem
|
||||||
v-for="(item, i) in dropdownItems"
|
v-for="(item, i) in dropdownItems"
|
||||||
|
v-show="item.show"
|
||||||
:key="i"
|
:key="i"
|
||||||
variant="plain"
|
variant="plain"
|
||||||
:base-color="item.props.color"
|
:base-color="item.props.color"
|
||||||
@@ -186,7 +212,7 @@ const dropdownItems = ref([
|
|||||||
</IconBtn>
|
</IconBtn>
|
||||||
</div>
|
</div>
|
||||||
<VAvatar
|
<VAvatar
|
||||||
size="128"
|
size="8rem"
|
||||||
:class="{ shadow: isImageLoaded }"
|
:class="{ shadow: isImageLoaded }"
|
||||||
>
|
>
|
||||||
<VImg
|
<VImg
|
||||||
@@ -210,11 +236,11 @@ const dropdownItems = ref([
|
|||||||
<!-- 插件配置页面 -->
|
<!-- 插件配置页面 -->
|
||||||
<VDialog
|
<VDialog
|
||||||
v-model="pluginConfigDialog"
|
v-model="pluginConfigDialog"
|
||||||
max-width="800"
|
max-width="50rem"
|
||||||
scrollable
|
scrollable
|
||||||
persistent
|
persistent
|
||||||
>
|
>
|
||||||
<VCard :title="props.plugin?.plugin_name">
|
<VCard :title="`${props.plugin?.plugin_name} - 配置`">
|
||||||
<DialogCloseBtn @click="pluginConfigDialog = false" />
|
<DialogCloseBtn @click="pluginConfigDialog = false" />
|
||||||
<VCardText>
|
<VCardText>
|
||||||
<FormRender
|
<FormRender
|
||||||
@@ -226,7 +252,7 @@ const dropdownItems = ref([
|
|||||||
</VCardText>
|
</VCardText>
|
||||||
<VCardActions>
|
<VCardActions>
|
||||||
<VBtn v-if="pluginPageItems.length > 0" @click="showPluginInfo">
|
<VBtn v-if="pluginPageItems.length > 0" @click="showPluginInfo">
|
||||||
详情
|
查看详情
|
||||||
</VBtn>
|
</VBtn>
|
||||||
<VSpacer />
|
<VSpacer />
|
||||||
<VBtn @click="savePluginConf">
|
<VBtn @click="savePluginConf">
|
||||||
@@ -239,11 +265,11 @@ const dropdownItems = ref([
|
|||||||
<!-- 插件详情页面 -->
|
<!-- 插件详情页面 -->
|
||||||
<VDialog
|
<VDialog
|
||||||
v-model="pluginInfoDialog"
|
v-model="pluginInfoDialog"
|
||||||
max-width="1000"
|
max-width="62.5rem"
|
||||||
scrollable
|
scrollable
|
||||||
persistent
|
persistent
|
||||||
>
|
>
|
||||||
<VCard :title="`${props.plugin?.plugin_name} - 详情`">
|
<VCard :title="`${props.plugin?.plugin_name}`">
|
||||||
<DialogCloseBtn @click="pluginInfoDialog = false" />
|
<DialogCloseBtn @click="pluginInfoDialog = false" />
|
||||||
<VCardText>
|
<VCardText>
|
||||||
<PageRender
|
<PageRender
|
||||||
@@ -253,6 +279,9 @@ const dropdownItems = ref([
|
|||||||
/>
|
/>
|
||||||
</VCardText>
|
</VCardText>
|
||||||
<VCardActions>
|
<VCardActions>
|
||||||
|
<VBtn @click="showPluginConfig">
|
||||||
|
配置
|
||||||
|
</VBtn>
|
||||||
<VSpacer />
|
<VSpacer />
|
||||||
<VBtn @click="pluginInfoDialog = false">
|
<VBtn @click="pluginInfoDialog = false">
|
||||||
关闭
|
关闭
|
||||||
|
|||||||
@@ -1,590 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import { useToast } from 'vue-toast-notification'
|
|
||||||
import { calculateTimeDifference } from '@/@core/utils'
|
|
||||||
import { formatFileSize, formatSeason } from '@/@core/utils/formatters'
|
|
||||||
import api from '@/api'
|
|
||||||
import type { Rss, Site, TorrentInfo } from '@/api/types'
|
|
||||||
|
|
||||||
// 输入参数
|
|
||||||
const props = defineProps({
|
|
||||||
media: Object as PropType<Rss>,
|
|
||||||
})
|
|
||||||
|
|
||||||
// 定义触发的自定义事件
|
|
||||||
const emit = defineEmits(['remove', 'save'])
|
|
||||||
|
|
||||||
// 提示框
|
|
||||||
const $toast = useToast()
|
|
||||||
|
|
||||||
// 图片是否加载完成
|
|
||||||
const imageLoaded = ref(false)
|
|
||||||
|
|
||||||
// 订阅弹窗
|
|
||||||
const rssInfoDialog = ref(false)
|
|
||||||
|
|
||||||
// RSS预览窗口
|
|
||||||
const rssPreviewDialog = ref(false)
|
|
||||||
|
|
||||||
// 加载状态
|
|
||||||
const previewLoading = ref(false)
|
|
||||||
|
|
||||||
// 总条数
|
|
||||||
const previewTotalItems = ref(0)
|
|
||||||
|
|
||||||
// 每页条数
|
|
||||||
const previewItemsPerPage = ref(25)
|
|
||||||
|
|
||||||
// 预览表头
|
|
||||||
const previewHeaders = [
|
|
||||||
{ title: '标题', key: 'title', sortable: true },
|
|
||||||
{ title: '时间', key: 'pubdate', sortable: true },
|
|
||||||
{ title: '大小', key: 'size', sortable: true },
|
|
||||||
{ title: '', key: 'actions', sortable: false },
|
|
||||||
]
|
|
||||||
|
|
||||||
// 预览数据
|
|
||||||
const previewDataList = ref<TorrentInfo[]>([])
|
|
||||||
|
|
||||||
// 站点名称
|
|
||||||
const siteName = ref('')
|
|
||||||
|
|
||||||
// 订阅编辑表单
|
|
||||||
const rssForm = reactive<any>(props.media ?? {})
|
|
||||||
|
|
||||||
// 类型转换
|
|
||||||
rssForm.best_version = rssForm.best_version === 1
|
|
||||||
rssForm.proxy = rssForm.proxy === 1
|
|
||||||
rssForm.filter = rssForm.filter === 1
|
|
||||||
|
|
||||||
// 上一次更新时间
|
|
||||||
const lastUpdateText = ref(
|
|
||||||
`${
|
|
||||||
props.media?.last_update
|
|
||||||
? `${calculateTimeDifference(props.media?.last_update || '')}前`
|
|
||||||
: ''
|
|
||||||
}`,
|
|
||||||
)
|
|
||||||
|
|
||||||
// 图片加载完成响应
|
|
||||||
function imageLoadHandler() {
|
|
||||||
imageLoaded.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// 根据 type 返回不同的图标
|
|
||||||
function getIcon() {
|
|
||||||
if (props.media?.type === '电影')
|
|
||||||
return 'mdi-movie'
|
|
||||||
else if (props.media?.type === '电视剧')
|
|
||||||
return 'mdi-television-classic'
|
|
||||||
else
|
|
||||||
return 'mdi-help-circle'
|
|
||||||
}
|
|
||||||
|
|
||||||
// 计算文本颜色
|
|
||||||
function getTextColor() {
|
|
||||||
return imageLoaded.value ? 'white' : ''
|
|
||||||
}
|
|
||||||
|
|
||||||
// 计算文本类
|
|
||||||
function getTextClass() {
|
|
||||||
return imageLoaded.value ? 'text-white' : ''
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除订阅
|
|
||||||
async function removerRss() {
|
|
||||||
try {
|
|
||||||
const result: { [key: string]: any } = await api.delete(
|
|
||||||
`rss/${props.media?.id}`,
|
|
||||||
)
|
|
||||||
|
|
||||||
if (result.success) {
|
|
||||||
// 通知父组件刷新
|
|
||||||
emit('remove')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
console.log(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 调用API修改订阅
|
|
||||||
async function updateRssInfo() {
|
|
||||||
rssInfoDialog.value = false
|
|
||||||
try {
|
|
||||||
const result: { [key: string]: any } = await api.put('rss', rssForm)
|
|
||||||
|
|
||||||
// 提示
|
|
||||||
if (result.success) {
|
|
||||||
$toast.success(`${props.media?.name} 更新成功!`)
|
|
||||||
// 通知父组件刷新
|
|
||||||
emit('remove')
|
|
||||||
}
|
|
||||||
else { $toast.error(`${props.media?.name} 更新失败:${result.message}!`) }
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
console.log(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查询站点名称
|
|
||||||
async function querySiteName() {
|
|
||||||
try {
|
|
||||||
const result: Site = await api.get(
|
|
||||||
`site/domain/${props.media?.url?.split('/')[2]}`,
|
|
||||||
)
|
|
||||||
|
|
||||||
if (result)
|
|
||||||
siteName.value = result.name
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
// 截取URL中的主域名作为站点名称
|
|
||||||
siteName.value = props.media?.url?.split('/')[2] ?? '未知'
|
|
||||||
console.log(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 预览按钮响应
|
|
||||||
async function handleRssPreview() {
|
|
||||||
rssPreviewDialog.value = true
|
|
||||||
previewLoading.value = true
|
|
||||||
await previewRss()
|
|
||||||
previewLoading.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// 预览站点RSS
|
|
||||||
async function previewRss() {
|
|
||||||
try {
|
|
||||||
const result: TorrentInfo[] = await api.get(
|
|
||||||
`rss/preview/${props.media?.id}`,
|
|
||||||
)
|
|
||||||
previewDataList.value = result
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
console.log(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 编辑订阅响应
|
|
||||||
async function editRssDialog() {
|
|
||||||
rssInfoDialog.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// 刷新按钮响应
|
|
||||||
async function refreshRss() {
|
|
||||||
try {
|
|
||||||
const result: { [key: string]: any } = await api.get(
|
|
||||||
`rss/refresh/${props.media?.id}`,
|
|
||||||
)
|
|
||||||
|
|
||||||
if (result.success)
|
|
||||||
$toast.success(`${props.media?.name} 已提交刷新任务!`)
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
console.log(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生成1到50季的下拉框选项
|
|
||||||
const seasonItems = ref(
|
|
||||||
Array.from({ length: 50 }, (_, i) => i + 1).map(item => ({
|
|
||||||
title: `第 ${item} 季`,
|
|
||||||
value: item,
|
|
||||||
})),
|
|
||||||
)
|
|
||||||
|
|
||||||
// 打开种子详情页面
|
|
||||||
function openTorrentDetail(page_url: string) {
|
|
||||||
window.open(page_url, '_blank')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 下载种子文件
|
|
||||||
async function downloadTorrentFile(enclosure: string) {
|
|
||||||
window.open(enclosure, '_blank')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 弹出菜单
|
|
||||||
const dropdownItems = ref([
|
|
||||||
{
|
|
||||||
title: '编辑',
|
|
||||||
value: 1,
|
|
||||||
props: {
|
|
||||||
prependIcon: 'mdi-file-edit-outline',
|
|
||||||
click: editRssDialog,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '预览',
|
|
||||||
value: 2,
|
|
||||||
props: {
|
|
||||||
prependIcon: 'mdi-eye-outline',
|
|
||||||
click: handleRssPreview,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '刷新',
|
|
||||||
value: 3,
|
|
||||||
props: {
|
|
||||||
prependIcon: 'mdi-refresh',
|
|
||||||
click: refreshRss,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '取消订阅',
|
|
||||||
value: 4,
|
|
||||||
props: {
|
|
||||||
prependIcon: 'mdi-trash-can-outline',
|
|
||||||
color: 'error',
|
|
||||||
click: removerRss,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
])
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
querySiteName()
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<VCard
|
|
||||||
:key="props.media?.id"
|
|
||||||
:class="`${rssForm.best_version ? 'outline-dashed outline-1' : ''}`"
|
|
||||||
@click="editRssDialog"
|
|
||||||
>
|
|
||||||
<template #image>
|
|
||||||
<VImg
|
|
||||||
:src="props.media?.backdrop || props.media?.poster"
|
|
||||||
aspect-ratio="2/3"
|
|
||||||
cover
|
|
||||||
class="brightness-50"
|
|
||||||
@load="imageLoadHandler"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
<VCardItem>
|
|
||||||
<template #prepend>
|
|
||||||
<VIcon
|
|
||||||
size="1.9rem"
|
|
||||||
:color="getTextColor()"
|
|
||||||
:icon="getIcon()"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
<VCardTitle :class="getTextClass()">
|
|
||||||
{{ props.media?.name }}
|
|
||||||
{{ formatSeason(props.media?.season ? props.media?.season.toString() : "") }}
|
|
||||||
</VCardTitle>
|
|
||||||
<template #append>
|
|
||||||
<div class="me-n3">
|
|
||||||
<IconBtn>
|
|
||||||
<VIcon
|
|
||||||
icon="mdi-dots-vertical"
|
|
||||||
:color="getTextColor()"
|
|
||||||
/>
|
|
||||||
<VMenu
|
|
||||||
activator="parent"
|
|
||||||
close-on-content-click
|
|
||||||
>
|
|
||||||
<VList>
|
|
||||||
<VListItem
|
|
||||||
v-for="(item, i) in dropdownItems"
|
|
||||||
:key="i"
|
|
||||||
variant="plain"
|
|
||||||
:base-color="item.props.color"
|
|
||||||
@click="item.props.click"
|
|
||||||
>
|
|
||||||
<template #prepend>
|
|
||||||
<VIcon :icon="item.props.prependIcon" />
|
|
||||||
</template>
|
|
||||||
<VListItemTitle v-text="item.title" />
|
|
||||||
</VListItem>
|
|
||||||
</VList>
|
|
||||||
</VMenu>
|
|
||||||
</IconBtn>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</VCardItem>
|
|
||||||
|
|
||||||
<VCardText>
|
|
||||||
<p
|
|
||||||
class="clamp-text mb-0"
|
|
||||||
:class="getTextClass()"
|
|
||||||
>
|
|
||||||
{{ props.media?.description }}
|
|
||||||
</p>
|
|
||||||
</VCardText>
|
|
||||||
|
|
||||||
<VCardText class="d-flex justify-space-between align-center flex-wrap">
|
|
||||||
<div class="d-flex align-center">
|
|
||||||
<IconBtn
|
|
||||||
icon="mdi-star"
|
|
||||||
:color="getTextColor()"
|
|
||||||
class="me-1"
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
class="text-subtitle-2 me-4"
|
|
||||||
:class="getTextClass()"
|
|
||||||
>{{
|
|
||||||
props.media?.vote
|
|
||||||
}}</span>
|
|
||||||
<IconBtn
|
|
||||||
v-bind="props"
|
|
||||||
icon="mdi-progress-clock"
|
|
||||||
:color="getTextColor()"
|
|
||||||
class="me-1"
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
class="text-subtitle-2 me-4"
|
|
||||||
:class="getTextClass()"
|
|
||||||
>{{ props.media?.processed || 0 }}</span>
|
|
||||||
<IconBtn
|
|
||||||
v-if="siteName"
|
|
||||||
icon="mdi-web"
|
|
||||||
:color="getTextColor()"
|
|
||||||
class="me-1"
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
v-if="siteName"
|
|
||||||
class="text-subtitle-2 me-4"
|
|
||||||
:class="getTextClass()"
|
|
||||||
>
|
|
||||||
{{ siteName }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</VCardText>
|
|
||||||
<VCardText
|
|
||||||
v-if="lastUpdateText"
|
|
||||||
class="absolute right-0 bottom-0 d-flex align-center p-2 text-gray-300"
|
|
||||||
>
|
|
||||||
<VIcon
|
|
||||||
icon="mdi-download"
|
|
||||||
class="me-1"
|
|
||||||
/> {{ lastUpdateText }}
|
|
||||||
</VCardText>
|
|
||||||
</VCard>
|
|
||||||
<!-- 订阅编辑弹窗 -->
|
|
||||||
<VDialog
|
|
||||||
v-model="rssInfoDialog"
|
|
||||||
max-width="800"
|
|
||||||
persistent
|
|
||||||
scrollable
|
|
||||||
>
|
|
||||||
<!-- Dialog Content -->
|
|
||||||
<VCard :title="`订阅 - ${props.media?.name}`">
|
|
||||||
<VCardText class="pt-2">
|
|
||||||
<VForm @submit.prevent="() => {}">
|
|
||||||
<VRow>
|
|
||||||
<VCol
|
|
||||||
cols="12"
|
|
||||||
>
|
|
||||||
<VTextField
|
|
||||||
v-model="rssForm.url"
|
|
||||||
label="RSS地址"
|
|
||||||
placeholder="https://example.com/rss"
|
|
||||||
/>
|
|
||||||
</VCol>
|
|
||||||
<VCol
|
|
||||||
cols="12"
|
|
||||||
md="6"
|
|
||||||
>
|
|
||||||
<VSelect
|
|
||||||
v-model="rssForm.type"
|
|
||||||
label="类型"
|
|
||||||
:items="[{ title: '电影', value: '电影' }, { title: '电视剧', value: '电视剧' }]"
|
|
||||||
readonly
|
|
||||||
/>
|
|
||||||
</VCol>
|
|
||||||
<VCol
|
|
||||||
cols="12"
|
|
||||||
md="6"
|
|
||||||
>
|
|
||||||
<VTextField
|
|
||||||
v-model="rssForm.title"
|
|
||||||
label="标题"
|
|
||||||
readonly
|
|
||||||
/>
|
|
||||||
</VCol>
|
|
||||||
<VCol
|
|
||||||
cols="12"
|
|
||||||
md="6"
|
|
||||||
>
|
|
||||||
<VTextField
|
|
||||||
v-model="rssForm.year"
|
|
||||||
label="年份"
|
|
||||||
readonly
|
|
||||||
/>
|
|
||||||
</VCol>
|
|
||||||
<VCol
|
|
||||||
cols="12"
|
|
||||||
md="6"
|
|
||||||
>
|
|
||||||
<VSelect
|
|
||||||
v-show="rssForm.type === '电视剧'"
|
|
||||||
v-model="rssForm.season"
|
|
||||||
label="季"
|
|
||||||
:items="seasonItems"
|
|
||||||
readonly
|
|
||||||
/>
|
|
||||||
</VCol>
|
|
||||||
<VCol
|
|
||||||
cols="12"
|
|
||||||
>
|
|
||||||
<VTextField
|
|
||||||
v-model="rssForm.include"
|
|
||||||
label="包含"
|
|
||||||
/>
|
|
||||||
</VCol>
|
|
||||||
<VCol
|
|
||||||
cols="12"
|
|
||||||
>
|
|
||||||
<VTextField
|
|
||||||
v-model="rssForm.exclude"
|
|
||||||
label="排除"
|
|
||||||
/>
|
|
||||||
</VCol>
|
|
||||||
<VCol
|
|
||||||
cols="12"
|
|
||||||
>
|
|
||||||
<VTextField
|
|
||||||
v-model="rssForm.save_path"
|
|
||||||
label="保存路径"
|
|
||||||
placeholder="留空自动"
|
|
||||||
/>
|
|
||||||
</VCol>
|
|
||||||
<VCol
|
|
||||||
cols="12"
|
|
||||||
md="6"
|
|
||||||
>
|
|
||||||
<VSelect
|
|
||||||
v-model="rssForm.state"
|
|
||||||
label="状态"
|
|
||||||
:items="[{
|
|
||||||
title: '启用',
|
|
||||||
value: 1,
|
|
||||||
}, {
|
|
||||||
title: '停用',
|
|
||||||
value: 0,
|
|
||||||
}]"
|
|
||||||
/>
|
|
||||||
</VCol>
|
|
||||||
</VRow>
|
|
||||||
<VRow>
|
|
||||||
<VCol
|
|
||||||
cols="12"
|
|
||||||
md="4"
|
|
||||||
>
|
|
||||||
<VSwitch
|
|
||||||
v-model="rssForm.best_version"
|
|
||||||
label="洗版"
|
|
||||||
/>
|
|
||||||
</VCol>
|
|
||||||
<VCol
|
|
||||||
cols="12"
|
|
||||||
md="4"
|
|
||||||
>
|
|
||||||
<VSwitch
|
|
||||||
v-model="rssForm.proxy"
|
|
||||||
label="代理服务器"
|
|
||||||
/>
|
|
||||||
</VCol>
|
|
||||||
<VCol
|
|
||||||
cols="12"
|
|
||||||
md="4"
|
|
||||||
>
|
|
||||||
<VSwitch
|
|
||||||
v-model="rssForm.filter"
|
|
||||||
label="过滤规则"
|
|
||||||
/>
|
|
||||||
</VCol>
|
|
||||||
</VRow>
|
|
||||||
</VForm>
|
|
||||||
</VCardText>
|
|
||||||
|
|
||||||
<VCardActions>
|
|
||||||
<VBtn @click="rssInfoDialog = false">
|
|
||||||
取消
|
|
||||||
</VBtn>
|
|
||||||
<VSpacer />
|
|
||||||
<VBtn @click="updateRssInfo">
|
|
||||||
确定
|
|
||||||
</VBtn>
|
|
||||||
</VCardActions>
|
|
||||||
</VCard>
|
|
||||||
</VDialog>
|
|
||||||
<!-- RSS预览窗口 -->
|
|
||||||
<VDialog
|
|
||||||
v-model="rssPreviewDialog"
|
|
||||||
max-width="1280"
|
|
||||||
scrollable
|
|
||||||
>
|
|
||||||
<!-- Dialog Content -->
|
|
||||||
<VCard title="RSS预览">
|
|
||||||
<DialogCloseBtn @click="rssPreviewDialog = false" />
|
|
||||||
<VCardText class="pt-2">
|
|
||||||
<VDataTable
|
|
||||||
v-model:items-per-page="previewItemsPerPage"
|
|
||||||
:headers="previewHeaders"
|
|
||||||
:items="previewDataList"
|
|
||||||
:items-length="previewTotalItems"
|
|
||||||
:loading="previewLoading"
|
|
||||||
density="compact"
|
|
||||||
item-value="title"
|
|
||||||
return-object
|
|
||||||
fixed-header
|
|
||||||
items-per-page-text="每页条数"
|
|
||||||
page-text="{0}-{1} 共 {2} 条"
|
|
||||||
>
|
|
||||||
<template #item.title="{ item }">
|
|
||||||
<div class="text-high-emphasis">
|
|
||||||
{{ item.raw.title }}
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template #item.size="{ item }">
|
|
||||||
<div class="text-nowrap whitespace-nowrap">
|
|
||||||
{{ formatFileSize(item.raw.size) }}
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template #item.pubdate="{ item }">
|
|
||||||
<div class="text-sm">
|
|
||||||
{{ item.raw.pubdate }}
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template #item.actions="{ item }">
|
|
||||||
<div class="me-n3">
|
|
||||||
<IconBtn>
|
|
||||||
<VIcon
|
|
||||||
icon="mdi-dots-vertical"
|
|
||||||
/>
|
|
||||||
<VMenu
|
|
||||||
activator="parent"
|
|
||||||
close-on-content-click
|
|
||||||
>
|
|
||||||
<VList>
|
|
||||||
<VListItem
|
|
||||||
variant="plain"
|
|
||||||
@click="openTorrentDetail(item.raw.page_url)"
|
|
||||||
>
|
|
||||||
<template #prepend>
|
|
||||||
<VIcon icon="mdi-information" />
|
|
||||||
</template>
|
|
||||||
<VListItemTitle>查看详情</VListItemTitle>
|
|
||||||
</VListItem>
|
|
||||||
<VListItem
|
|
||||||
variant="plain"
|
|
||||||
@click="downloadTorrentFile(item.raw.enclosure)"
|
|
||||||
>
|
|
||||||
<template #prepend>
|
|
||||||
<VIcon icon="mdi-download" />
|
|
||||||
</template>
|
|
||||||
<VListItemTitle>下载种子</VListItemTitle>
|
|
||||||
</VListItem>
|
|
||||||
</VList>
|
|
||||||
</VMenu>
|
|
||||||
</IconBtn>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template #no-data>
|
|
||||||
没有数据
|
|
||||||
</template>
|
|
||||||
</VDataTable>
|
|
||||||
</VCardText>
|
|
||||||
</VCard>
|
|
||||||
</VDialog>
|
|
||||||
</template>
|
|
||||||
@@ -72,9 +72,6 @@ const resourceTotalItems = ref(0)
|
|||||||
// 每页条数
|
// 每页条数
|
||||||
const resourceItemsPerPage = ref(25)
|
const resourceItemsPerPage = ref(25)
|
||||||
|
|
||||||
// 当前页码
|
|
||||||
const resourceCurrentPage = ref(0)
|
|
||||||
|
|
||||||
// 用户名密码表单
|
// 用户名密码表单
|
||||||
const userPwForm = ref({
|
const userPwForm = ref({
|
||||||
username: '',
|
username: '',
|
||||||
@@ -244,13 +241,7 @@ function getVolumeFactorClass(downloadVolume: number, uploadVolume: number) {
|
|||||||
async function getResourceList() {
|
async function getResourceList() {
|
||||||
resourceLoading.value = true
|
resourceLoading.value = true
|
||||||
try {
|
try {
|
||||||
resourceDataList.value = await api.get('search/title', {
|
resourceDataList.value = await api.get(`site/resource/${cardProps.site?.id}`)
|
||||||
params: {
|
|
||||||
keyword: resourceSearch.value,
|
|
||||||
page: resourceCurrentPage.value,
|
|
||||||
site: cardProps.site?.id,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
resourceLoading.value = false
|
resourceLoading.value = false
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
@@ -437,7 +428,7 @@ onMounted(() => {
|
|||||||
<!-- 站点编辑弹窗 -->
|
<!-- 站点编辑弹窗 -->
|
||||||
<VDialog
|
<VDialog
|
||||||
v-model="siteInfoDialog"
|
v-model="siteInfoDialog"
|
||||||
max-width="1000"
|
max-width="50rem"
|
||||||
persistent
|
persistent
|
||||||
scrollable
|
scrollable
|
||||||
>
|
>
|
||||||
@@ -480,6 +471,12 @@ onMounted(() => {
|
|||||||
</VCol>
|
</VCol>
|
||||||
</VRow>
|
</VRow>
|
||||||
<VRow>
|
<VRow>
|
||||||
|
<VCol cols="12">
|
||||||
|
<VTextField
|
||||||
|
v-model="siteForm.rss"
|
||||||
|
label="RSS地址"
|
||||||
|
/>
|
||||||
|
</VCol>
|
||||||
<VCol cols="12">
|
<VCol cols="12">
|
||||||
<VTextarea
|
<VTextarea
|
||||||
v-model="siteForm.cookie"
|
v-model="siteForm.cookie"
|
||||||
@@ -562,7 +559,7 @@ onMounted(() => {
|
|||||||
<!-- 站点资源弹窗 -->
|
<!-- 站点资源弹窗 -->
|
||||||
<VDialog
|
<VDialog
|
||||||
v-model="resourceDialog"
|
v-model="resourceDialog"
|
||||||
max-width="1280"
|
max-width="80rem"
|
||||||
scrollable
|
scrollable
|
||||||
>
|
>
|
||||||
<!-- Dialog Content -->
|
<!-- Dialog Content -->
|
||||||
|
|||||||
@@ -325,7 +325,7 @@ const dropdownItems = ref([
|
|||||||
<!-- 订阅编辑弹窗 -->
|
<!-- 订阅编辑弹窗 -->
|
||||||
<VDialog
|
<VDialog
|
||||||
v-model="subscribeInfoDialog"
|
v-model="subscribeInfoDialog"
|
||||||
max-width="1000"
|
max-width="50rem"
|
||||||
persistent
|
persistent
|
||||||
scrollable
|
scrollable
|
||||||
>
|
>
|
||||||
|
|||||||
128
src/components/cards/TmdbSelectorCard.vue
Normal file
128
src/components/cards/TmdbSelectorCard.vue
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import api from '@/api'
|
||||||
|
import type { MediaInfo } from '@/api/types'
|
||||||
|
|
||||||
|
interface TmdbItem {
|
||||||
|
title: string
|
||||||
|
overview: string
|
||||||
|
tmdbid: number
|
||||||
|
poster: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// update:modelValue 事件
|
||||||
|
const emit = defineEmits(['update:modelValue', 'close'])
|
||||||
|
|
||||||
|
const items = ref<TmdbItem[]>([])
|
||||||
|
|
||||||
|
// 搜索词
|
||||||
|
const keyword = ref('')
|
||||||
|
|
||||||
|
// 加载中
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
// 选中条目
|
||||||
|
function selectMedia(item: TmdbItem) {
|
||||||
|
console.log(item)
|
||||||
|
emit('update:modelValue', item.tmdbid)
|
||||||
|
emit('close')
|
||||||
|
}
|
||||||
|
|
||||||
|
// TMDB图片转换为w500大小
|
||||||
|
function getW500Image(url = '') {
|
||||||
|
if (!url)
|
||||||
|
return ''
|
||||||
|
return url.replace('original', 'w500')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索词条
|
||||||
|
async function searchMedias() {
|
||||||
|
if (!keyword)
|
||||||
|
return
|
||||||
|
|
||||||
|
// 调用API搜索词条
|
||||||
|
try {
|
||||||
|
loading.value = true
|
||||||
|
const result: MediaInfo[] = await api.get('media/search', {
|
||||||
|
params: {
|
||||||
|
title: keyword.value,
|
||||||
|
page: 1,
|
||||||
|
count: 20,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// 清空
|
||||||
|
items.value = []
|
||||||
|
|
||||||
|
// 赋值
|
||||||
|
for (const item of result) {
|
||||||
|
items.value.push({
|
||||||
|
tmdbid: item.tmdb_id || 0,
|
||||||
|
poster: getW500Image(item.poster_path),
|
||||||
|
title: `${item.title}(${item.year})`,
|
||||||
|
overview: `<span class="text-primary">${item.type}</span> ${item.overview}`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<VCard
|
||||||
|
class="mx-auto"
|
||||||
|
width="100%"
|
||||||
|
>
|
||||||
|
<VToolbar flat dense>
|
||||||
|
<VTextField
|
||||||
|
v-model="keyword"
|
||||||
|
density="compact"
|
||||||
|
label="输入名称搜索"
|
||||||
|
single-line
|
||||||
|
hide-details
|
||||||
|
flat
|
||||||
|
class="mx-3"
|
||||||
|
append-inner-icon="mdi-magnify"
|
||||||
|
:loading="loading"
|
||||||
|
@click:append-inner="searchMedias"
|
||||||
|
@keydown.enter="searchMedias"
|
||||||
|
/>
|
||||||
|
</VToolbar>
|
||||||
|
|
||||||
|
<VList
|
||||||
|
v-if="items.length > 0"
|
||||||
|
lines="three"
|
||||||
|
>
|
||||||
|
<template v-for="(item, i) in items" :key="i">
|
||||||
|
<VListItem
|
||||||
|
density="compact"
|
||||||
|
@click="selectMedia(item)"
|
||||||
|
>
|
||||||
|
<template #prepend>
|
||||||
|
<VImg
|
||||||
|
height="75"
|
||||||
|
width="50"
|
||||||
|
:src="item.poster"
|
||||||
|
aspect-ratio="2/3"
|
||||||
|
class="object-cover rounded shadow ring-gray-500 me-3"
|
||||||
|
cover
|
||||||
|
>
|
||||||
|
<template #placeholder>
|
||||||
|
<div class="w-full h-full">
|
||||||
|
<VSkeletonLoader class="object-cover aspect-w-2 aspect-h-3" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</VImg>
|
||||||
|
</template>
|
||||||
|
<VListItemTitle>
|
||||||
|
{{ item.title }}
|
||||||
|
</VListItemTitle>
|
||||||
|
<VListItemSubtitle v-html="item.overview" />
|
||||||
|
</VListItem>
|
||||||
|
<VDivider v-if="i < items.length - 1" class="mt-1" inset />
|
||||||
|
</template>
|
||||||
|
</VList>
|
||||||
|
</VCard>
|
||||||
|
</template>
|
||||||
@@ -10,6 +10,7 @@ import type { Context, EndPoints, FileItem } from '@/api/types'
|
|||||||
import store from '@/store'
|
import store from '@/store'
|
||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import MediaInfoCard from '@/components/cards/MediaInfoCard.vue'
|
import MediaInfoCard from '@/components/cards/MediaInfoCard.vue'
|
||||||
|
import TmdbSelectorCard from '@/components/cards/TmdbSelectorCard.vue'
|
||||||
|
|
||||||
// 输入参数
|
// 输入参数
|
||||||
const inProps = defineProps({
|
const inProps = defineProps({
|
||||||
@@ -19,6 +20,7 @@ const inProps = defineProps({
|
|||||||
endpoints: Object as PropType<EndPoints>,
|
endpoints: Object as PropType<EndPoints>,
|
||||||
axios: Object as PropType<Axios>,
|
axios: Object as PropType<Axios>,
|
||||||
refreshpending: Boolean,
|
refreshpending: Boolean,
|
||||||
|
sort: String,
|
||||||
})
|
})
|
||||||
|
|
||||||
// 对外事件
|
// 对外事件
|
||||||
@@ -91,6 +93,9 @@ const nameTestResult = ref<Context>()
|
|||||||
// 识别结果对话框
|
// 识别结果对话框
|
||||||
const nameTestDialog = ref(false)
|
const nameTestDialog = ref(false)
|
||||||
|
|
||||||
|
// TMDB选择对话框
|
||||||
|
const tmdbSelectorDialog = ref(false)
|
||||||
|
|
||||||
// 生成1到50季的下拉框选项
|
// 生成1到50季的下拉框选项
|
||||||
const seasonItems = ref(
|
const seasonItems = ref(
|
||||||
Array.from({ length: 51 }, (_, i) => i).map(item => ({
|
Array.from({ length: 51 }, (_, i) => i).map(item => ({
|
||||||
@@ -125,18 +130,18 @@ const isImage = computed(() => {
|
|||||||
async function load() {
|
async function load() {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
emit('loading', true)
|
emit('loading', true)
|
||||||
if (isDir.value) {
|
// 参数
|
||||||
const url = inProps.endpoints?.list.url
|
const url = inProps.endpoints?.list.url
|
||||||
.replace(/{storage}/g, storage.value)
|
.replace(/{storage}/g, storage.value)
|
||||||
.replace(/{path}/g, encodeURIComponent(inProps.path || ''))
|
.replace(/{path}/g, encodeURIComponent(inProps.path || ''))
|
||||||
|
.replace(/{sort}/g, inProps.sort || 'name')
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
url,
|
url,
|
||||||
method: inProps.endpoints?.list.method || 'get',
|
method: inProps.endpoints?.list.method || 'get',
|
||||||
}
|
|
||||||
// 加载数据
|
|
||||||
items.value = await axiosInstance.value.request(config) ?? []
|
|
||||||
}
|
}
|
||||||
|
// 加载数据
|
||||||
|
items.value = await axiosInstance.value.request(config) ?? []
|
||||||
emit('loading', false)
|
emit('loading', false)
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
@@ -261,7 +266,7 @@ async function transfer() {
|
|||||||
stopLoadingProgress()
|
stopLoadingProgress()
|
||||||
// 显示结果
|
// 显示结果
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
$toast.success(`${currentItem.value?.name} 整理成功!`)
|
$toast.success(`${currentItem.value?.name} 整理完成!`)
|
||||||
// 重新加载
|
// 重新加载
|
||||||
load()
|
load()
|
||||||
}
|
}
|
||||||
@@ -275,6 +280,11 @@ async function transfer() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 将文件修改时间(timestape)转换为本地时间
|
||||||
|
function formatTime(timestape: number) {
|
||||||
|
return new Date(timestape * 1000).toLocaleString()
|
||||||
|
}
|
||||||
|
|
||||||
// 监听path变化
|
// 监听path变化
|
||||||
watch(
|
watch(
|
||||||
() => inProps.path,
|
() => inProps.path,
|
||||||
@@ -408,7 +418,9 @@ onMounted(() => {
|
|||||||
v-else-if="isFile && !isImage"
|
v-else-if="isFile && !isImage"
|
||||||
class="text-center break-all"
|
class="text-center break-all"
|
||||||
>
|
>
|
||||||
文件: {{ path }}
|
<strong>{{ items[0]?.name }}</strong><br>
|
||||||
|
大小:{{ formatBytes(items[0]?.size || 0) }}<br>
|
||||||
|
修改时间:{{ formatTime(items[0]?.modify_time || 0) }}
|
||||||
</VCardText>
|
</VCardText>
|
||||||
<VCardText
|
<VCardText
|
||||||
v-else-if="isFile && isImage"
|
v-else-if="isFile && isImage"
|
||||||
@@ -601,7 +613,7 @@ onMounted(() => {
|
|||||||
<!-- 文件整理弹窗 -->
|
<!-- 文件整理弹窗 -->
|
||||||
<VDialog
|
<VDialog
|
||||||
v-model="transferPopper"
|
v-model="transferPopper"
|
||||||
max-width="800"
|
max-width="50rem"
|
||||||
scrollable
|
scrollable
|
||||||
>
|
>
|
||||||
<template #activator="{ props }">
|
<template #activator="{ props }">
|
||||||
@@ -659,6 +671,8 @@ onMounted(() => {
|
|||||||
label="TMDBID"
|
label="TMDBID"
|
||||||
placeholder="留空自动识别"
|
placeholder="留空自动识别"
|
||||||
:rules="[numberValidator]"
|
:rules="[numberValidator]"
|
||||||
|
append-inner-icon="mdi-magnify"
|
||||||
|
@click:append-inner="tmdbSelectorDialog = true"
|
||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
<VCol
|
<VCol
|
||||||
@@ -714,10 +728,10 @@ onMounted(() => {
|
|||||||
</VForm>
|
</VForm>
|
||||||
</VCardText>
|
</VCardText>
|
||||||
<VCardActions>
|
<VCardActions>
|
||||||
<div class="flex-grow-1" />
|
|
||||||
<VBtn depressed @click="transferPopper = false">
|
<VBtn depressed @click="transferPopper = false">
|
||||||
取消
|
取消
|
||||||
</VBtn>
|
</VBtn>
|
||||||
|
<VSpacer />
|
||||||
<VBtn
|
<VBtn
|
||||||
@click="transfer"
|
@click="transfer"
|
||||||
>
|
>
|
||||||
@@ -730,7 +744,7 @@ onMounted(() => {
|
|||||||
<vDialog
|
<vDialog
|
||||||
v-model="progressDialog"
|
v-model="progressDialog"
|
||||||
:scrim="false"
|
:scrim="false"
|
||||||
width="400"
|
width="25rem"
|
||||||
>
|
>
|
||||||
<vCard
|
<vCard
|
||||||
color="primary"
|
color="primary"
|
||||||
@@ -749,8 +763,7 @@ onMounted(() => {
|
|||||||
<!-- 识别结果对话框 -->
|
<!-- 识别结果对话框 -->
|
||||||
<vDialog
|
<vDialog
|
||||||
v-model="nameTestDialog"
|
v-model="nameTestDialog"
|
||||||
:scrim="false"
|
width="50rem"
|
||||||
width="800"
|
|
||||||
>
|
>
|
||||||
<vCard>
|
<vCard>
|
||||||
<DialogCloseBtn @click="nameTestDialog = false" />
|
<DialogCloseBtn @click="nameTestDialog = false" />
|
||||||
@@ -759,6 +772,17 @@ onMounted(() => {
|
|||||||
</VCardItem>
|
</VCardItem>
|
||||||
</vCard>
|
</vCard>
|
||||||
</vDialog>
|
</vDialog>
|
||||||
|
<!-- TMDB ID搜索框 -->
|
||||||
|
<vDialog
|
||||||
|
v-model="tmdbSelectorDialog"
|
||||||
|
width="40rem"
|
||||||
|
scrollable
|
||||||
|
>
|
||||||
|
<TmdbSelectorCard
|
||||||
|
v-model="transferForm.tmdbid"
|
||||||
|
@close="tmdbSelectorDialog = false"
|
||||||
|
/>
|
||||||
|
</vDialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const inProps = defineProps({
|
|||||||
})
|
})
|
||||||
|
|
||||||
// 对外事件
|
// 对外事件
|
||||||
const emit = defineEmits(['storagechanged', 'pathchanged', 'loading', 'foldercreated'])
|
const emit = defineEmits(['storagechanged', 'pathchanged', 'loading', 'foldercreated', 'sortchanged'])
|
||||||
|
|
||||||
// 新建文件夹名称
|
// 新建文件夹名称
|
||||||
const newFolderPopper = ref(false)
|
const newFolderPopper = ref(false)
|
||||||
@@ -20,6 +20,19 @@ const newFolderPopper = ref(false)
|
|||||||
// 新建文件名称
|
// 新建文件名称
|
||||||
const newFolderName = ref('')
|
const newFolderName = ref('')
|
||||||
|
|
||||||
|
// 排序方式
|
||||||
|
const sort = ref('name')
|
||||||
|
|
||||||
|
// 调整排序方式
|
||||||
|
function changeSort() {
|
||||||
|
if (sort.value === 'name')
|
||||||
|
sort.value = 'time'
|
||||||
|
else
|
||||||
|
sort.value = 'name'
|
||||||
|
|
||||||
|
emit('sortchanged', sort.value)
|
||||||
|
}
|
||||||
|
|
||||||
// 计算PATH面包屑
|
// 计算PATH面包屑
|
||||||
const pathSegments = computed(() => {
|
const pathSegments = computed(() => {
|
||||||
let path_str = ''
|
let path_str = ''
|
||||||
@@ -81,6 +94,14 @@ async function mkdir() {
|
|||||||
// 通知重新加载
|
// 通知重新加载
|
||||||
emit('foldercreated')
|
emit('foldercreated')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 计算排序图标
|
||||||
|
const sortIcon = computed(() => {
|
||||||
|
if (sort.value === 'time')
|
||||||
|
return 'mdi-sort-clock-ascending-outline'
|
||||||
|
else
|
||||||
|
return 'mdi-sort-alphabetical-ascending'
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -123,6 +144,9 @@ async function mkdir() {
|
|||||||
</template>
|
</template>
|
||||||
</VToolbarItems>
|
</VToolbarItems>
|
||||||
<div class="flex-grow-1" />
|
<div class="flex-grow-1" />
|
||||||
|
<IconBtn @click="changeSort">
|
||||||
|
<VIcon :icon="sortIcon" />
|
||||||
|
</IconBtn>
|
||||||
<IconBtn v-if="pathSegments.length > 0" @click="goUp">
|
<IconBtn v-if="pathSegments.length > 0" @click="goUp">
|
||||||
<VIcon icon="mdi-arrow-up-bold-outline" />
|
<VIcon icon="mdi-arrow-up-bold-outline" />
|
||||||
</IconBtn>
|
</IconBtn>
|
||||||
@@ -156,9 +180,3 @@ async function mkdir() {
|
|||||||
</VDialog>
|
</VDialog>
|
||||||
</VToolbar>
|
</VToolbar>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.v-toolbar{
|
|
||||||
background: rgb(var(--v-table-header-background));
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ function init() {
|
|||||||
name: 'root',
|
name: 'root',
|
||||||
children: [],
|
children: [],
|
||||||
size: 0,
|
size: 0,
|
||||||
|
modify_time: 0,
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { User } from '@/api/types'
|
|
||||||
import VerticalNavSectionTitle from '@/@layouts/components/VerticalNavSectionTitle.vue'
|
import VerticalNavSectionTitle from '@/@layouts/components/VerticalNavSectionTitle.vue'
|
||||||
import VerticalNavLayout from '@layouts/components/VerticalNavLayout.vue'
|
import VerticalNavLayout from '@layouts/components/VerticalNavLayout.vue'
|
||||||
import VerticalNavLink from '@layouts/components/VerticalNavLink.vue'
|
import VerticalNavLink from '@layouts/components/VerticalNavLink.vue'
|
||||||
@@ -10,9 +9,10 @@ import NavbarThemeSwitcher from '@/layouts/components/NavbarThemeSwitcher.vue'
|
|||||||
import SearchBar from '@/layouts/components/SearchBar.vue'
|
import SearchBar from '@/layouts/components/SearchBar.vue'
|
||||||
import ShortcutBar from '@/layouts/components/ShortcutBar.vue'
|
import ShortcutBar from '@/layouts/components/ShortcutBar.vue'
|
||||||
import UserProfile from '@/layouts/components/UserProfile.vue'
|
import UserProfile from '@/layouts/components/UserProfile.vue'
|
||||||
|
import store from '@/store'
|
||||||
|
|
||||||
// 获取当前用户信息
|
// 从Vuex Store中获取superuser信息
|
||||||
const accountInfo: User = inject('accountInfo') as User
|
const superUser = store.state.auth.superUser
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -91,7 +91,7 @@ const accountInfo: User = inject('accountInfo') as User
|
|||||||
}"
|
}"
|
||||||
/>
|
/>
|
||||||
<VerticalNavLink
|
<VerticalNavLink
|
||||||
v-if="accountInfo.is_superuser"
|
v-if="superUser"
|
||||||
:item="{
|
:item="{
|
||||||
title: '电影',
|
title: '电影',
|
||||||
icon: 'mdi-movie-check-outline',
|
icon: 'mdi-movie-check-outline',
|
||||||
@@ -99,21 +99,13 @@ const accountInfo: User = inject('accountInfo') as User
|
|||||||
}"
|
}"
|
||||||
/>
|
/>
|
||||||
<VerticalNavLink
|
<VerticalNavLink
|
||||||
v-if="accountInfo.is_superuser"
|
v-if="superUser"
|
||||||
:item="{
|
:item="{
|
||||||
title: '电视剧',
|
title: '电视剧',
|
||||||
icon: 'mdi-television-classic',
|
icon: 'mdi-television-classic',
|
||||||
to: '/subscribe-tv',
|
to: '/subscribe-tv',
|
||||||
}"
|
}"
|
||||||
/>
|
/>
|
||||||
<VerticalNavLink
|
|
||||||
v-if="accountInfo.is_superuser"
|
|
||||||
:item="{
|
|
||||||
title: '自定义',
|
|
||||||
icon: 'mdi-rss',
|
|
||||||
to: '/subscribe-rss',
|
|
||||||
}"
|
|
||||||
/>
|
|
||||||
<VerticalNavLink
|
<VerticalNavLink
|
||||||
:item="{
|
:item="{
|
||||||
title: '日历',
|
title: '日历',
|
||||||
@@ -135,7 +127,7 @@ const accountInfo: User = inject('accountInfo') as User
|
|||||||
}"
|
}"
|
||||||
/>
|
/>
|
||||||
<VerticalNavLink
|
<VerticalNavLink
|
||||||
v-if="accountInfo.is_superuser"
|
v-if="superUser"
|
||||||
:item="{
|
:item="{
|
||||||
title: '历史记录',
|
title: '历史记录',
|
||||||
icon: 'mdi-history',
|
icon: 'mdi-history',
|
||||||
@@ -143,7 +135,7 @@ const accountInfo: User = inject('accountInfo') as User
|
|||||||
}"
|
}"
|
||||||
/>
|
/>
|
||||||
<VerticalNavLink
|
<VerticalNavLink
|
||||||
v-if="accountInfo.is_superuser"
|
v-if="superUser"
|
||||||
:item="{
|
:item="{
|
||||||
title: '文件管理',
|
title: '文件管理',
|
||||||
icon: 'mdi-folder-multiple-outline',
|
icon: 'mdi-folder-multiple-outline',
|
||||||
@@ -153,13 +145,13 @@ const accountInfo: User = inject('accountInfo') as User
|
|||||||
|
|
||||||
<!-- 👉 系统 -->
|
<!-- 👉 系统 -->
|
||||||
<VerticalNavSectionTitle
|
<VerticalNavSectionTitle
|
||||||
v-if="accountInfo.is_superuser"
|
v-if="superUser"
|
||||||
:item="{
|
:item="{
|
||||||
heading: '系统',
|
heading: '系统',
|
||||||
}"
|
}"
|
||||||
/>
|
/>
|
||||||
<VerticalNavLink
|
<VerticalNavLink
|
||||||
v-if="accountInfo.is_superuser"
|
v-if="superUser"
|
||||||
:item="{
|
:item="{
|
||||||
title: '插件',
|
title: '插件',
|
||||||
icon: 'mdi-apps',
|
icon: 'mdi-apps',
|
||||||
@@ -167,7 +159,7 @@ const accountInfo: User = inject('accountInfo') as User
|
|||||||
}"
|
}"
|
||||||
/>
|
/>
|
||||||
<VerticalNavLink
|
<VerticalNavLink
|
||||||
v-if="accountInfo.is_superuser"
|
v-if="superUser"
|
||||||
:item="{
|
:item="{
|
||||||
title: '站点管理',
|
title: '站点管理',
|
||||||
icon: 'mdi-web',
|
icon: 'mdi-web',
|
||||||
@@ -175,7 +167,7 @@ const accountInfo: User = inject('accountInfo') as User
|
|||||||
}"
|
}"
|
||||||
/>
|
/>
|
||||||
<VerticalNavLink
|
<VerticalNavLink
|
||||||
v-if="accountInfo.is_superuser"
|
v-if="superUser"
|
||||||
:item="{
|
:item="{
|
||||||
title: '设定',
|
title: '设定',
|
||||||
icon: 'mdi-cog',
|
icon: 'mdi-cog',
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ const themes: ThemeSwitcherTheme[] = [
|
|||||||
name: 'dark',
|
name: 'dark',
|
||||||
icon: 'mdi-weather-night',
|
icon: 'mdi-weather-night',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'purple',
|
||||||
|
icon: 'mdi-brightness-4',
|
||||||
|
},
|
||||||
]
|
]
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,23 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useStore } from 'vuex'
|
import { useStore } from 'vuex'
|
||||||
|
import { useConfirm } from 'vuetify-use-dialog'
|
||||||
|
import { useToast } from 'vue-toast-notification'
|
||||||
import router from '@/router'
|
import router from '@/router'
|
||||||
import type { User } from '@/api/types'
|
import avatar1 from '@images/avatars/avatar-1.png'
|
||||||
|
import api from '@/api'
|
||||||
|
|
||||||
// Vuex Store
|
// Vuex Store
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
|
|
||||||
|
// 确认框
|
||||||
|
const createConfirm = useConfirm()
|
||||||
|
|
||||||
|
// 提示框
|
||||||
|
const $toast = useToast()
|
||||||
|
|
||||||
|
// 进度框
|
||||||
|
const progressDialog = ref(false)
|
||||||
|
|
||||||
// 执行注销操作
|
// 执行注销操作
|
||||||
function logout() {
|
function logout() {
|
||||||
// 清除登录状态信息
|
// 清除登录状态信息
|
||||||
@@ -15,8 +27,45 @@ function logout() {
|
|||||||
router.push('/login')
|
router.push('/login')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取当前用户信息
|
// 执行重启操作
|
||||||
const accountInfo: User = inject('accountInfo') as User
|
async function restart() {
|
||||||
|
// 弹出提示
|
||||||
|
const confirmed = await createConfirm({
|
||||||
|
title: '确认',
|
||||||
|
content: '确认重启系统吗?',
|
||||||
|
confirmationText: '确认',
|
||||||
|
cancellationText: '取消',
|
||||||
|
dialogProps: {
|
||||||
|
maxWidth: '30rem',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (confirmed) {
|
||||||
|
// 调用API重启
|
||||||
|
try {
|
||||||
|
// 显示等待框
|
||||||
|
progressDialog.value = true
|
||||||
|
const result: { [key: string]: any } = await api.get('system/restart')
|
||||||
|
if (!result?.success) {
|
||||||
|
// 隐藏等待框
|
||||||
|
progressDialog.value = false
|
||||||
|
// 重启不成功
|
||||||
|
$toast.error(result.message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
// 注销
|
||||||
|
logout()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从Vuex Store中获取信息
|
||||||
|
const superUser = store.state.auth.superUser
|
||||||
|
const userName = store.state.auth.userName
|
||||||
|
const avatar = store.state.auth.avatar
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -25,7 +74,7 @@ const accountInfo: User = inject('accountInfo') as User
|
|||||||
color="primary"
|
color="primary"
|
||||||
variant="tonal"
|
variant="tonal"
|
||||||
>
|
>
|
||||||
<VImg :src="accountInfo.avatar" />
|
<VImg :src="avatar ?? avatar1" />
|
||||||
|
|
||||||
<!-- SECTION Menu -->
|
<!-- SECTION Menu -->
|
||||||
<VMenu
|
<VMenu
|
||||||
@@ -43,21 +92,21 @@ const accountInfo: User = inject('accountInfo') as User
|
|||||||
color="primary"
|
color="primary"
|
||||||
variant="tonal"
|
variant="tonal"
|
||||||
>
|
>
|
||||||
<VImg :src="accountInfo.avatar" />
|
<VImg :src="avatar ?? avatar1" />
|
||||||
</VAvatar>
|
</VAvatar>
|
||||||
</VListItemAction>
|
</VListItemAction>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<VListItemTitle class="font-weight-semibold">
|
<VListItemTitle class="font-weight-semibold">
|
||||||
{{ accountInfo.is_superuser ? "管理员" : "普通用户" }}
|
{{ superUser ? "管理员" : "普通用户" }}
|
||||||
</VListItemTitle>
|
</VListItemTitle>
|
||||||
<VListItemSubtitle>{{ accountInfo.name }}</VListItemSubtitle>
|
<VListItemSubtitle>{{ userName }}</VListItemSubtitle>
|
||||||
</VListItem>
|
</VListItem>
|
||||||
<VDivider class="my-2" />
|
<VDivider class="my-2" />
|
||||||
|
|
||||||
<!-- 👉 Profile -->
|
<!-- 👉 Profile -->
|
||||||
<VListItem
|
<VListItem
|
||||||
v-if="accountInfo.is_superuser"
|
v-if="superUser"
|
||||||
link
|
link
|
||||||
to="setting"
|
to="setting"
|
||||||
>
|
>
|
||||||
@@ -91,6 +140,19 @@ const accountInfo: User = inject('accountInfo') as User
|
|||||||
<!-- Divider -->
|
<!-- Divider -->
|
||||||
<VDivider class="my-2" />
|
<VDivider class="my-2" />
|
||||||
|
|
||||||
|
<!-- 👉 restart -->
|
||||||
|
<VListItem @click="restart">
|
||||||
|
<template #prepend>
|
||||||
|
<VIcon
|
||||||
|
class="me-2"
|
||||||
|
icon="mdi-restart"
|
||||||
|
size="22"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<VListItemTitle>重启</VListItemTitle>
|
||||||
|
</VListItem>
|
||||||
|
|
||||||
<!-- 👉 Logout -->
|
<!-- 👉 Logout -->
|
||||||
<VListItem @click="logout">
|
<VListItem @click="logout">
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
@@ -107,4 +169,22 @@ const accountInfo: User = inject('accountInfo') as User
|
|||||||
</VMenu>
|
</VMenu>
|
||||||
<!-- !SECTION -->
|
<!-- !SECTION -->
|
||||||
</VAvatar>
|
</VAvatar>
|
||||||
|
<!-- 重启进度框 -->
|
||||||
|
<vDialog
|
||||||
|
v-model="progressDialog"
|
||||||
|
width="25rem"
|
||||||
|
>
|
||||||
|
<vCard
|
||||||
|
color="primary"
|
||||||
|
>
|
||||||
|
<vCardText class="text-center">
|
||||||
|
正在重启 ...
|
||||||
|
<vProgressLinear
|
||||||
|
indeterminate
|
||||||
|
color="white"
|
||||||
|
class="mb-0 mt-1"
|
||||||
|
/>
|
||||||
|
</vCardText>
|
||||||
|
</vCard>
|
||||||
|
</vDialog>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -6,14 +6,14 @@ import DefaultLayoutWithVerticalNav from './components/DefaultLayoutWithVertical
|
|||||||
<DefaultLayoutWithVerticalNav>
|
<DefaultLayoutWithVerticalNav>
|
||||||
<router-view v-slot="{ Component }">
|
<router-view v-slot="{ Component }">
|
||||||
<keep-alive>
|
<keep-alive>
|
||||||
<component :is="Component" v-if="$route.meta.keepAlive" :key="$route.name" />
|
<component :is="Component" v-if="$route.meta.keepAlive" :key="$route.fullPath" />
|
||||||
</keep-alive>
|
</keep-alive>
|
||||||
<component :is="Component" v-if="!$route.meta.keepAlive" :key="$route.name" />
|
<component :is="Component" v-if="!$route.meta.keepAlive" :key="$route.fullPath" />
|
||||||
</router-view>
|
</router-view>
|
||||||
</DefaultLayoutWithVerticalNav>
|
</DefaultLayoutWithVerticalNav>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
// As we are using `layouts` plugin we need its styles to be imported
|
// As we are using `layouts` plugin we need its styles to be imported
|
||||||
@use '@layouts/styles/default-layout';
|
@use "@layouts/styles/default-layout";
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -66,10 +66,16 @@ function login() {
|
|||||||
.then((response: any) => {
|
.then((response: any) => {
|
||||||
// 获取token
|
// 获取token
|
||||||
const token = response.access_token
|
const token = response.access_token
|
||||||
|
const superuser = response.super_user
|
||||||
|
const username = response.user_name
|
||||||
|
const avatar = response.avatar
|
||||||
|
|
||||||
// 更新token和remember状态到Vuex Store
|
// 更新token和remember状态到Vuex Store
|
||||||
store.dispatch('auth/updateToken', token)
|
store.dispatch('auth/updateToken', token)
|
||||||
store.dispatch('auth/updateRemember', form.value.remember)
|
store.dispatch('auth/updateRemember', form.value.remember)
|
||||||
|
store.dispatch('auth/updateSuperUser', superuser)
|
||||||
|
store.dispatch('auth/updateUserName', username)
|
||||||
|
store.dispatch('auth/updateAvatar', avatar)
|
||||||
|
|
||||||
// 跳转到首页
|
// 跳转到首页
|
||||||
router.push('/')
|
router.push('/')
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import RssListView from '@/views/subscribe/RssListView.vue'
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<RssListView />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -57,7 +57,7 @@ const theme: VuetifyOptions['theme'] = {
|
|||||||
dark: {
|
dark: {
|
||||||
dark: true,
|
dark: true,
|
||||||
colors: {
|
colors: {
|
||||||
'primary': '#9155FD',
|
'primary': '#6E66ED',
|
||||||
'secondary': '#8A8D93',
|
'secondary': '#8A8D93',
|
||||||
'on-secondary': '#fff',
|
'on-secondary': '#fff',
|
||||||
'success': '#56CA00',
|
'success': '#56CA00',
|
||||||
@@ -104,6 +104,57 @@ const theme: VuetifyOptions['theme'] = {
|
|||||||
'shadow-key-ambient-opacity': 'rgba(20, 18, 33, 0.04)',
|
'shadow-key-ambient-opacity': 'rgba(20, 18, 33, 0.04)',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
purple: {
|
||||||
|
dark: true,
|
||||||
|
colors: {
|
||||||
|
'primary': '#9155FD',
|
||||||
|
'secondary': '#8A8D93',
|
||||||
|
'on-secondary': '#fff',
|
||||||
|
'success': '#56CA00',
|
||||||
|
'info': '#16B1FF',
|
||||||
|
'warning': '#FFB400',
|
||||||
|
'error': '#FF4C51',
|
||||||
|
'on-primary': '#FFFFFF',
|
||||||
|
'on-success': '#FFFFFF',
|
||||||
|
'on-warning': '#FFFFFF',
|
||||||
|
'background': '#28243D',
|
||||||
|
'on-background': '#E7E3FC',
|
||||||
|
'surface': '#312D4B',
|
||||||
|
'on-surface': '#E7E3FC',
|
||||||
|
'grey-50': '#2A2E42',
|
||||||
|
'grey-100': '#474360',
|
||||||
|
'grey-200': '#4A5072',
|
||||||
|
'grey-300': '#5E6692',
|
||||||
|
'grey-400': '#7983BB',
|
||||||
|
'grey-500': '#8692D0',
|
||||||
|
'grey-600': '#AAB3DE',
|
||||||
|
'grey-700': '#B6BEE3',
|
||||||
|
'grey-800': '#CFD3EC',
|
||||||
|
'grey-900': '#E7E9F6',
|
||||||
|
'perfect-scrollbar-thumb': '#4A5072',
|
||||||
|
'skin-bordered-background': '#312d4b',
|
||||||
|
'skin-bordered-surface': '#312d4b',
|
||||||
|
},
|
||||||
|
variables: {
|
||||||
|
'code-color': '#d400ff',
|
||||||
|
'overlay-scrim-background': '#2C2942',
|
||||||
|
'overlay-scrim-opacity': 0.6,
|
||||||
|
'hover-opacity': 0.04,
|
||||||
|
'focus-opacity': 0.1,
|
||||||
|
'selected-opacity': 0.12,
|
||||||
|
'activated-opacity': 0.1,
|
||||||
|
'pressed-opacity': 0.14,
|
||||||
|
'dragged-opacity': 0.1,
|
||||||
|
'border-color': '#E7E3FC',
|
||||||
|
'table-header-background': '#3D3759',
|
||||||
|
'custom-background': '#373452',
|
||||||
|
|
||||||
|
// Shadows
|
||||||
|
'shadow-key-umbra-opacity': 'rgba(20, 18, 33, 0.08)',
|
||||||
|
'shadow-key-penumbra-opacity': 'rgba(20, 18, 33, 0.12)',
|
||||||
|
'shadow-key-ambient-opacity': 'rgba(20, 18, 33, 0.04)',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,13 +48,6 @@ const router = createRouter({
|
|||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: 'subscribe-rss',
|
|
||||||
component: () => import('../pages/subscribe-rss.vue'),
|
|
||||||
meta: {
|
|
||||||
requiresAuth: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: 'calendar',
|
path: 'calendar',
|
||||||
component: () => import('../pages/calendar.vue'),
|
component: () => import('../pages/calendar.vue'),
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ import type { Module } from 'vuex'
|
|||||||
interface AuthState {
|
interface AuthState {
|
||||||
token: string | null
|
token: string | null
|
||||||
remember: boolean
|
remember: boolean
|
||||||
|
superUser: boolean
|
||||||
|
userName: string
|
||||||
|
avatar: string
|
||||||
}
|
}
|
||||||
|
|
||||||
// 定义根状态类型
|
// 定义根状态类型
|
||||||
@@ -17,6 +20,9 @@ const authModule: Module<AuthState, RootState> = {
|
|||||||
state: {
|
state: {
|
||||||
token: null,
|
token: null,
|
||||||
remember: false,
|
remember: false,
|
||||||
|
superUser: false,
|
||||||
|
userName: '',
|
||||||
|
avatar: '',
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
setToken(state, token: string) {
|
setToken(state, token: string) {
|
||||||
@@ -28,6 +34,15 @@ const authModule: Module<AuthState, RootState> = {
|
|||||||
setRemember(state, remember: boolean) {
|
setRemember(state, remember: boolean) {
|
||||||
state.remember = remember
|
state.remember = remember
|
||||||
},
|
},
|
||||||
|
setSuperUser(state, superUser: boolean) {
|
||||||
|
state.superUser = superUser
|
||||||
|
},
|
||||||
|
setUserName(state, userName: string) {
|
||||||
|
state.userName = userName
|
||||||
|
},
|
||||||
|
setAvatar(state, avatar: string) {
|
||||||
|
state.avatar = avatar
|
||||||
|
},
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
updateToken({ commit }, token: string) {
|
updateToken({ commit }, token: string) {
|
||||||
@@ -39,10 +54,22 @@ const authModule: Module<AuthState, RootState> = {
|
|||||||
updateRemember({ commit }, remember: boolean) {
|
updateRemember({ commit }, remember: boolean) {
|
||||||
commit('setRemember', remember)
|
commit('setRemember', remember)
|
||||||
},
|
},
|
||||||
|
updateSuperUser({ commit }, superUser: boolean) {
|
||||||
|
commit('setSuperUser', superUser)
|
||||||
|
},
|
||||||
|
updateUserName({ commit }, userName: string) {
|
||||||
|
commit('setUserName', userName)
|
||||||
|
},
|
||||||
|
updateAvatar({ commit }, avatar: string) {
|
||||||
|
commit('setAvatar', avatar)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
getToken: state => state.token,
|
getToken: state => state.token,
|
||||||
getRemember: state => state.remember,
|
getRemember: state => state.remember,
|
||||||
|
getSuperUser: state => state.superUser,
|
||||||
|
getUserName: state => state.userName,
|
||||||
|
getAvatar: state => state.avatar,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,14 +3,13 @@
|
|||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
|
|
||||||
#nprogress .bar {
|
#nprogress .bar {
|
||||||
background: #7D34FD !important;
|
background: rgb(var(--v-theme-primary)) !important;
|
||||||
top: env(safe-area-inset-top) !important;
|
top: env(safe-area-inset-top) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
#nprogress .peg {
|
#nprogress .peg {
|
||||||
box-shadow: 0 0 10px #7D34FD, 0 0 5px #7D34FD !important;
|
box-shadow: 0 0 10px rgb(var(--v-theme-primary)), 0 0 5px rgb(var(--v-theme-primary)) !important;
|
||||||
-webkit-transform: rotate(0deg) translate(0px, -1px);
|
-webkit-transform: rotate(0deg) translate(0px, -1px);
|
||||||
-ms-transform: rotate(0deg) translate(0px, -1px);
|
-ms-transform: rotate(0deg) translate(0px, -1px);
|
||||||
transform: rotate(0deg) translate(0px, -1px);
|
transform: rotate(0deg) translate(0px, -1px);
|
||||||
@@ -123,3 +122,7 @@
|
|||||||
-webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)!important;
|
-webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)!important;
|
||||||
backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)!important;
|
backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)!important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.v-toolbar{
|
||||||
|
background: rgb(var(--v-table-header-background));
|
||||||
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ const chartOptions = controlledComputed(() => vuetifyTheme.name.value, () => {
|
|||||||
chart: {
|
chart: {
|
||||||
parentHeightOffset: 0,
|
parentHeightOffset: 0,
|
||||||
toolbar: { show: false },
|
toolbar: { show: false },
|
||||||
|
animations: { enabled: false },
|
||||||
},
|
},
|
||||||
tooltip: { enabled: false },
|
tooltip: { enabled: false },
|
||||||
grid: {
|
grid: {
|
||||||
@@ -75,6 +76,7 @@ const chartOptions = controlledComputed(() => vuetifyTheme.name.value, () => {
|
|||||||
},
|
},
|
||||||
yaxis: {
|
yaxis: {
|
||||||
labels: { show: false },
|
labels: { show: false },
|
||||||
|
max: 100,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -20,14 +20,17 @@ const series = ref([
|
|||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
// 当前值
|
// 占用的内存
|
||||||
const current = ref(0)
|
const usedMemory = ref(0)
|
||||||
|
// 内存使用百分比
|
||||||
|
const memoryUsage = ref(0)
|
||||||
|
|
||||||
const chartOptions = controlledComputed(() => vuetifyTheme.name.value, () => {
|
const chartOptions = controlledComputed(() => vuetifyTheme.name.value, () => {
|
||||||
return {
|
return {
|
||||||
chart: {
|
chart: {
|
||||||
parentHeightOffset: 0,
|
parentHeightOffset: 0,
|
||||||
toolbar: { show: false },
|
toolbar: { show: false },
|
||||||
|
animations: { enabled: false },
|
||||||
},
|
},
|
||||||
tooltip: { enabled: false },
|
tooltip: { enabled: false },
|
||||||
grid: {
|
grid: {
|
||||||
@@ -79,6 +82,7 @@ const chartOptions = controlledComputed(() => vuetifyTheme.name.value, () => {
|
|||||||
},
|
},
|
||||||
yaxis: {
|
yaxis: {
|
||||||
labels: { show: false },
|
labels: { show: false },
|
||||||
|
max: 100,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -87,9 +91,8 @@ const chartOptions = controlledComputed(() => vuetifyTheme.name.value, () => {
|
|||||||
async function getMemorgUsage() {
|
async function getMemorgUsage() {
|
||||||
try {
|
try {
|
||||||
// 请求数据
|
// 请求数据
|
||||||
current.value = await api.get('dashboard/memory') ?? 0
|
[usedMemory.value, memoryUsage.value] = await api.get('dashboard/memory')
|
||||||
// 添加到序列
|
series.value[0].data.push(memoryUsage.value)
|
||||||
series.value[0].data.push(current.value / 1024 / 1024 / 1024)
|
|
||||||
// 序列超过30条记录时,清掉前面的
|
// 序列超过30条记录时,清掉前面的
|
||||||
if (series.value[0].data.length > 30)
|
if (series.value[0].data.length > 30)
|
||||||
series.value[0].data.shift()
|
series.value[0].data.shift()
|
||||||
@@ -130,7 +133,7 @@ onUnmounted(() => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<p class="text-center font-weight-medium mb-0">
|
<p class="text-center font-weight-medium mb-0">
|
||||||
当前:{{ formatBytes(current) }}
|
当前:{{ formatBytes(usedMemory) }}
|
||||||
</p>
|
</p>
|
||||||
</VCardText>
|
</VCardText>
|
||||||
</VCard>
|
</VCard>
|
||||||
|
|||||||
@@ -430,9 +430,13 @@ onBeforeMount(() => {
|
|||||||
<div class="relative z-20 flex items-center false"><span>已入库</span></div>
|
<div class="relative z-20 flex items-center false"><span>已入库</span></div>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<h1 class="flex flex-row items-baseline justify-start lg:justify-center">
|
<h1 class="d-flex flex-column flex-lg-row align-baseline justify-center justify-lg-start">
|
||||||
<span>{{ mediaDetail.title }}</span>
|
<div class="align-self-center align-self-lg-end">
|
||||||
<span v-if="mediaDetail.year" class="text-lg">({{ mediaDetail.year }})</span>
|
{{ mediaDetail.title }}
|
||||||
|
</div>
|
||||||
|
<div v-if="mediaDetail.year" class="text-lg align-self-center align-self-lg-end">
|
||||||
|
({{ mediaDetail.year }})
|
||||||
|
</div>
|
||||||
</h1>
|
</h1>
|
||||||
<span class="media-attributes">
|
<span class="media-attributes">
|
||||||
<span v-if="mediaDetail.runtime || mediaDetail.episode_run_time[0]">{{ mediaDetail.runtime || mediaDetail.episode_run_time[0] }} 分钟</span>
|
<span v-if="mediaDetail.runtime || mediaDetail.episode_run_time[0]">{{ mediaDetail.runtime || mediaDetail.episode_run_time[0] }} 分钟</span>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import api from '@/api'
|
|||||||
import FileBrowser from '@/components/FileBrowser.vue'
|
import FileBrowser from '@/components/FileBrowser.vue'
|
||||||
|
|
||||||
const endpoints = {
|
const endpoints = {
|
||||||
list: { url: '/filebrowser/list?path={path}', method: 'get' },
|
list: { url: '/filebrowser/list?path={path}&sort={sort}', method: 'get' },
|
||||||
mkdir: { url: '/filebrowser/mkdir?path={path}', method: 'get' },
|
mkdir: { url: '/filebrowser/mkdir?path={path}', method: 'get' },
|
||||||
delete: { url: '/filebrowser/delete?path={path}', method: 'get' },
|
delete: { url: '/filebrowser/delete?path={path}', method: 'get' },
|
||||||
download: { url: '/filebrowser/download?path={path}', method: 'get' },
|
download: { url: '/filebrowser/download?path={path}', method: 'get' },
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { useConfirm } from 'vuetify-use-dialog'
|
|||||||
import { numberValidator, requiredValidator } 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'
|
||||||
|
|
||||||
// 确认框
|
// 确认框
|
||||||
const createConfirm = useConfirm()
|
const createConfirm = useConfirm()
|
||||||
@@ -71,6 +72,9 @@ const progressText = ref('请稍候 ...')
|
|||||||
// 进度值
|
// 进度值
|
||||||
const progressValue = ref(0)
|
const progressValue = ref(0)
|
||||||
|
|
||||||
|
// TMDB选择对话框
|
||||||
|
const tmdbSelectorDialog = ref(false)
|
||||||
|
|
||||||
// 获取订阅列表数据
|
// 获取订阅列表数据
|
||||||
async function fetchData({
|
async function fetchData({
|
||||||
page,
|
page,
|
||||||
@@ -412,6 +416,8 @@ const dropdownItems = ref([
|
|||||||
v-model="redoTmdbId"
|
v-model="redoTmdbId"
|
||||||
label="TMDB编号"
|
label="TMDB编号"
|
||||||
:rules="[requiredValidator, numberValidator]"
|
:rules="[requiredValidator, numberValidator]"
|
||||||
|
append-inner-icon="mdi-magnify"
|
||||||
|
@click:append-inner="tmdbSelectorDialog = true"
|
||||||
/>
|
/>
|
||||||
</VCol>
|
</VCol>
|
||||||
</VRow>
|
</VRow>
|
||||||
@@ -440,7 +446,7 @@ const dropdownItems = ref([
|
|||||||
<vDialog
|
<vDialog
|
||||||
v-model="progressDialog"
|
v-model="progressDialog"
|
||||||
:scrim="false"
|
:scrim="false"
|
||||||
width="400"
|
width="25rem"
|
||||||
>
|
>
|
||||||
<vCard
|
<vCard
|
||||||
color="primary"
|
color="primary"
|
||||||
@@ -455,6 +461,17 @@ const dropdownItems = ref([
|
|||||||
</vCardText>
|
</vCardText>
|
||||||
</vCard>
|
</vCard>
|
||||||
</vDialog>
|
</vDialog>
|
||||||
|
<!-- TMDB ID搜索框 -->
|
||||||
|
<vDialog
|
||||||
|
v-model="tmdbSelectorDialog"
|
||||||
|
width="600"
|
||||||
|
scrollable
|
||||||
|
>
|
||||||
|
<TmdbSelectorCard
|
||||||
|
v-model="redoTmdbId"
|
||||||
|
@close="tmdbSelectorDialog = false"
|
||||||
|
/>
|
||||||
|
</vDialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|||||||
@@ -408,7 +408,7 @@ onMounted(() => {
|
|||||||
<!-- 站点编辑弹窗 -->
|
<!-- 站点编辑弹窗 -->
|
||||||
<VDialog
|
<VDialog
|
||||||
v-model="addUserDialog"
|
v-model="addUserDialog"
|
||||||
max-width="800"
|
max-width="50rem"
|
||||||
persistent
|
persistent
|
||||||
>
|
>
|
||||||
<!-- Dialog Content -->
|
<!-- Dialog Content -->
|
||||||
|
|||||||
@@ -5,15 +5,10 @@ import FilterRuleCard from '@/components/cards/FilterRuleCard.vue'
|
|||||||
|
|
||||||
// 规则卡片类型
|
// 规则卡片类型
|
||||||
interface FilterCard {
|
interface FilterCard {
|
||||||
|
|
||||||
// 优先级
|
// 优先级
|
||||||
pri: string
|
pri: string
|
||||||
|
|
||||||
// 已选规则
|
// 已选规则
|
||||||
rules: string[]
|
rules: string[]
|
||||||
|
|
||||||
// 是否可见
|
|
||||||
visible: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 提示框
|
// 提示框
|
||||||
@@ -48,7 +43,6 @@ async function queryCustomFilters(ruleType: string) {
|
|||||||
return {
|
return {
|
||||||
pri: (index + 1).toString(),
|
pri: (index + 1).toString(),
|
||||||
rules: group.split('&'),
|
rules: group.split('&'),
|
||||||
visible: true,
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -138,23 +132,18 @@ function updateFilterCardValue2(pri: string, rules: string[]) {
|
|||||||
|
|
||||||
// 移除卡片
|
// 移除卡片
|
||||||
function filterCardClose(ruleType: string, pri: string) {
|
function filterCardClose(ruleType: string, pri: string) {
|
||||||
// 将卡片从列表中删除,并更新剩余卡片的序号
|
// 将pri对应的卡片从列表中删除,并更新剩余卡片的序号
|
||||||
const cards = ruleType === 'FilterRules' ? filterCards : filterCards2
|
const updatedCards = (ruleType === 'FilterRules' ? filterCards.value : filterCards2.value)
|
||||||
const index = cards.value.findIndex(card => card.pri === pri)
|
.filter(card => card.pri !== pri)
|
||||||
if (index !== -1) {
|
.map((card, index) => {
|
||||||
// 创建新的数组,然后使用 splice 方法来删除元素
|
card.pri = (index + 1).toString()
|
||||||
const updatedCards = [...cards.value]
|
return card
|
||||||
|
|
||||||
updatedCards.splice(index, 1)
|
|
||||||
|
|
||||||
// 更新剩余卡片的序号
|
|
||||||
updatedCards.forEach((card, i) => {
|
|
||||||
card.pri = (i + 1).toString()
|
|
||||||
})
|
})
|
||||||
|
// 更新 filterCards.value
|
||||||
// 更新 filterCards.value
|
if (ruleType === 'FilterRules')
|
||||||
cards.value = updatedCards
|
filterCards.value = updatedCards
|
||||||
}
|
else
|
||||||
|
filterCards2.value = updatedCards
|
||||||
}
|
}
|
||||||
|
|
||||||
// 增加卡片
|
// 增加卡片
|
||||||
@@ -164,7 +153,7 @@ function addFilterCard(ruleType: string) {
|
|||||||
const pri = (cards.value.length + 1).toString()
|
const pri = (cards.value.length + 1).toString()
|
||||||
|
|
||||||
// 新卡片
|
// 新卡片
|
||||||
const newCard: FilterCard = { pri, rules: [], visible: true }
|
const newCard: FilterCard = { pri, rules: [] }
|
||||||
|
|
||||||
// 添加到列表
|
// 添加到列表
|
||||||
cards.value.push(newCard)
|
cards.value.push(newCard)
|
||||||
@@ -189,7 +178,6 @@ onMounted(() => {
|
|||||||
:key="index"
|
:key="index"
|
||||||
:pri="card.pri"
|
:pri="card.pri"
|
||||||
:rules="card.rules"
|
:rules="card.rules"
|
||||||
:visible="card.visible"
|
|
||||||
@changed="updateFilterCardValue"
|
@changed="updateFilterCardValue"
|
||||||
@close="filterCardClose('FilterRules', card.pri)"
|
@close="filterCardClose('FilterRules', card.pri)"
|
||||||
/>
|
/>
|
||||||
@@ -224,7 +212,6 @@ onMounted(() => {
|
|||||||
:key="index"
|
:key="index"
|
||||||
:pri="card.pri"
|
:pri="card.pri"
|
||||||
:rules="card.rules"
|
:rules="card.rules"
|
||||||
:visible="card.visible"
|
|
||||||
@changed="updateFilterCardValue2"
|
@changed="updateFilterCardValue2"
|
||||||
@close="filterCardClose('FilterRules2', card.pri)"
|
@close="filterCardClose('FilterRules2', card.pri)"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -129,10 +129,11 @@ onMounted(() => {
|
|||||||
<VTextarea
|
<VTextarea
|
||||||
v-model="customIdentifiers"
|
v-model="customIdentifiers"
|
||||||
auto-grow
|
auto-grow
|
||||||
placeholder="支持正则表达式,特殊字符需要\转义,一行为一组,支持三种配置格式:
|
placeholder="支持正则表达式,特殊字符需要\转义,一行为一组,支持以下几种配置格式:
|
||||||
屏蔽词
|
屏蔽词
|
||||||
被替换词 => 替换词
|
被替换词 => 替换词
|
||||||
前定位词 <> 后定位词 >> 偏移量(EP)"
|
前定位词 <> 后定位词 >> 集偏移量(EP)
|
||||||
|
被替换词 => 替换词 && 前定位词 <> 后定位词 >> 集偏移量(EP)"
|
||||||
/>
|
/>
|
||||||
</VCardItem>
|
</VCardItem>
|
||||||
<VCardItem>
|
<VCardItem>
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ onBeforeMount(fetchData)
|
|||||||
<!-- Dialog Content -->
|
<!-- Dialog Content -->
|
||||||
<VDialog
|
<VDialog
|
||||||
v-model="siteAddDialog"
|
v-model="siteAddDialog"
|
||||||
max-width="800"
|
max-width="50rem"
|
||||||
persistent
|
persistent
|
||||||
scrollable
|
scrollable
|
||||||
>
|
>
|
||||||
@@ -149,6 +149,7 @@ onBeforeMount(fetchData)
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<VCard title="新增站点">
|
<VCard title="新增站点">
|
||||||
|
<DialogCloseBtn @click="siteAddDialog = false" />
|
||||||
<VCardText class="pt-2">
|
<VCardText class="pt-2">
|
||||||
<VForm @submit.prevent="() => {}">
|
<VForm @submit.prevent="() => {}">
|
||||||
<VRow>
|
<VRow>
|
||||||
@@ -185,6 +186,12 @@ onBeforeMount(fetchData)
|
|||||||
</VCol>
|
</VCol>
|
||||||
</VRow>
|
</VRow>
|
||||||
<VRow>
|
<VRow>
|
||||||
|
<VCol cols="12">
|
||||||
|
<VTextField
|
||||||
|
v-model="siteForm.rss"
|
||||||
|
label="RSS地址"
|
||||||
|
/>
|
||||||
|
</VCol>
|
||||||
<VCol cols="12">
|
<VCol cols="12">
|
||||||
<VTextarea
|
<VTextarea
|
||||||
v-model="siteForm.cookie"
|
v-model="siteForm.cookie"
|
||||||
|
|||||||
@@ -79,17 +79,7 @@ async function getSubscribes() {
|
|||||||
subscribes.map(async sub => eventsHander(sub)),
|
subscribes.map(async sub => eventsHander(sub)),
|
||||||
)
|
)
|
||||||
|
|
||||||
// 自定义订阅
|
calendarOptions.value.events = subEvents.flat().filter(event => event.start) as EventSourceInput
|
||||||
const rsses: Rss[] = await api.get('rss')
|
|
||||||
|
|
||||||
const rssEvents = await Promise.all(
|
|
||||||
rsses.map(async rss => eventsHander(rss)),
|
|
||||||
)
|
|
||||||
|
|
||||||
// 合并事件
|
|
||||||
const events = [...subEvents, ...rssEvents]
|
|
||||||
|
|
||||||
calendarOptions.value.events = events.flat().filter(event => event.start) as EventSourceInput
|
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
|
|||||||
@@ -1,332 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import PullRefresh from 'pull-refresh-vue3'
|
|
||||||
import { useToast } from 'vue-toast-notification'
|
|
||||||
import api from '@/api'
|
|
||||||
import type { Rss } from '@/api/types'
|
|
||||||
import NoDataFound from '@/components/NoDataFound.vue'
|
|
||||||
import RssCard from '@/components/cards/RssCard.vue'
|
|
||||||
import { numberValidator, requiredValidator } from '@/@validators'
|
|
||||||
import { doneNProgress, startNProgress } from '@/api/nprogress'
|
|
||||||
|
|
||||||
// 提示框
|
|
||||||
const $toast = useToast()
|
|
||||||
|
|
||||||
// 是否刷新过
|
|
||||||
const isRefreshed = ref(false)
|
|
||||||
|
|
||||||
// 新增按钮文本
|
|
||||||
const addBtnText = ref('新增订阅')
|
|
||||||
// 新增按钮状态
|
|
||||||
const addBtnState = ref(false)
|
|
||||||
|
|
||||||
// 新增自定义订阅对话框
|
|
||||||
const rssAddDialog = ref(false)
|
|
||||||
|
|
||||||
// 新增订阅表单
|
|
||||||
const rssForm = reactive({
|
|
||||||
// RSS地址
|
|
||||||
url: '',
|
|
||||||
// 类型
|
|
||||||
type: '电影',
|
|
||||||
// 标题
|
|
||||||
title: '',
|
|
||||||
// 年份
|
|
||||||
year: '',
|
|
||||||
// 季号
|
|
||||||
season: 1,
|
|
||||||
// 包含
|
|
||||||
include: '',
|
|
||||||
// 排除
|
|
||||||
exclude: '',
|
|
||||||
// 洗版
|
|
||||||
best_version: false,
|
|
||||||
// 是否使用代理服务器
|
|
||||||
proxy: false,
|
|
||||||
// 是否使用过滤规则
|
|
||||||
filter: true,
|
|
||||||
// 保存路径
|
|
||||||
save_path: '',
|
|
||||||
// 状态 0-停用,1-启用
|
|
||||||
state: 1,
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
// 数据列表
|
|
||||||
const dataList = ref<Rss[]>([])
|
|
||||||
|
|
||||||
// 获取订阅列表数据
|
|
||||||
async function fetchData() {
|
|
||||||
try {
|
|
||||||
dataList.value = await api.get('rss')
|
|
||||||
isRefreshed.value = true
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.error(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 调用API 新增自定义订阅
|
|
||||||
async function addRss() {
|
|
||||||
if (!rssForm.url || !rssForm.title)
|
|
||||||
return
|
|
||||||
|
|
||||||
startNProgress()
|
|
||||||
|
|
||||||
addBtnText.value = '新增中...'
|
|
||||||
addBtnState.value = true
|
|
||||||
|
|
||||||
if (rssForm.type === '电影')
|
|
||||||
rssForm.season = 0
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result: { [key: string]: string } = await api.post('rss', rssForm)
|
|
||||||
if (result.success) {
|
|
||||||
$toast.success('新增自定义订阅成功')
|
|
||||||
|
|
||||||
// 刷新数据
|
|
||||||
fetchData()
|
|
||||||
}
|
|
||||||
else { $toast.error(`新增自定义订阅失败:${result.message}`) }
|
|
||||||
rssAddDialog.value = false
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.error(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
doneNProgress()
|
|
||||||
|
|
||||||
addBtnText.value = '新增订阅'
|
|
||||||
addBtnState.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生成1到50季的下拉框选项
|
|
||||||
const seasonItems = ref(
|
|
||||||
Array.from({ length: 50 }, (_, i) => i + 1).map(item => ({
|
|
||||||
title: `第 ${item} 季`,
|
|
||||||
value: item,
|
|
||||||
})),
|
|
||||||
)
|
|
||||||
|
|
||||||
// 加载时获取数据
|
|
||||||
onBeforeMount(fetchData)
|
|
||||||
|
|
||||||
// 刷新状态
|
|
||||||
const loading = ref(false)
|
|
||||||
|
|
||||||
// 下拉刷新
|
|
||||||
function onRefresh() {
|
|
||||||
loading.value = true
|
|
||||||
fetchData()
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div
|
|
||||||
v-if="!isRefreshed"
|
|
||||||
class="mt-12 w-full text-center text-gray-500 text-sm flex flex-col items-center"
|
|
||||||
>
|
|
||||||
<VProgressCircular
|
|
||||||
v-if="!isRefreshed"
|
|
||||||
size="48"
|
|
||||||
indeterminate
|
|
||||||
color="primary"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<PullRefresh
|
|
||||||
v-model="loading"
|
|
||||||
@refresh="onRefresh"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
v-if="dataList.length > 0"
|
|
||||||
class="grid gap-3 grid-rss-card p-1"
|
|
||||||
>
|
|
||||||
<RssCard
|
|
||||||
v-for="data in dataList"
|
|
||||||
:key="data.id"
|
|
||||||
:media="data"
|
|
||||||
@remove="fetchData"
|
|
||||||
@save="fetchData"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<NoDataFound
|
|
||||||
v-if="dataList.length === 0 && isRefreshed"
|
|
||||||
error-code="404"
|
|
||||||
error-title="没有自定义订阅"
|
|
||||||
error-description="点击右下角按钮新增订阅。"
|
|
||||||
/>
|
|
||||||
</PullRefresh>
|
|
||||||
|
|
||||||
<!-- 新增订阅 -->
|
|
||||||
<VDialog
|
|
||||||
v-model="rssAddDialog"
|
|
||||||
max-width="800"
|
|
||||||
persistent
|
|
||||||
scrollable
|
|
||||||
>
|
|
||||||
<!-- Dialog Activator -->
|
|
||||||
<template #activator="{ props }">
|
|
||||||
<VBtn
|
|
||||||
icon="mdi-plus"
|
|
||||||
v-bind="props"
|
|
||||||
size="x-large"
|
|
||||||
class="fixed right-5 bottom-5"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- Dialog Content -->
|
|
||||||
<VCard title="新增自定义订阅">
|
|
||||||
<VCardText class="pt-2">
|
|
||||||
<VForm @submit.prevent="() => {}">
|
|
||||||
<VRow>
|
|
||||||
<VCol
|
|
||||||
cols="12"
|
|
||||||
>
|
|
||||||
<VTextField
|
|
||||||
v-model="rssForm.url"
|
|
||||||
label="RSS地址"
|
|
||||||
placeholder="https://example.com/rss"
|
|
||||||
:rules="[requiredValidator]"
|
|
||||||
/>
|
|
||||||
</VCol>
|
|
||||||
<VCol
|
|
||||||
cols="12"
|
|
||||||
md="6"
|
|
||||||
>
|
|
||||||
<VSelect
|
|
||||||
v-model="rssForm.type"
|
|
||||||
label="类型"
|
|
||||||
:items="[{ title: '电影', value: '电影' }, { title: '电视剧', value: '电视剧' }]"
|
|
||||||
/>
|
|
||||||
</VCol>
|
|
||||||
<VCol
|
|
||||||
cols="12"
|
|
||||||
md="6"
|
|
||||||
>
|
|
||||||
<VTextField
|
|
||||||
v-model="rssForm.title"
|
|
||||||
label="标题"
|
|
||||||
:rules="[requiredValidator]"
|
|
||||||
/>
|
|
||||||
</VCol>
|
|
||||||
<VCol
|
|
||||||
cols="12"
|
|
||||||
md="6"
|
|
||||||
>
|
|
||||||
<VTextField
|
|
||||||
v-model="rssForm.year"
|
|
||||||
label="年份"
|
|
||||||
:rules="[numberValidator]"
|
|
||||||
/>
|
|
||||||
</VCol>
|
|
||||||
<VCol
|
|
||||||
v-if="rssForm.type === '电视剧'"
|
|
||||||
cols="12"
|
|
||||||
md="6"
|
|
||||||
>
|
|
||||||
<VSelect
|
|
||||||
v-model="rssForm.season"
|
|
||||||
label="季"
|
|
||||||
:items="seasonItems"
|
|
||||||
/>
|
|
||||||
</VCol>
|
|
||||||
<VCol
|
|
||||||
cols="12"
|
|
||||||
>
|
|
||||||
<VTextField
|
|
||||||
v-model="rssForm.include"
|
|
||||||
label="包含"
|
|
||||||
placeholder="支持正则表达式"
|
|
||||||
/>
|
|
||||||
</VCol>
|
|
||||||
<VCol
|
|
||||||
cols="12"
|
|
||||||
>
|
|
||||||
<VTextField
|
|
||||||
v-model="rssForm.exclude"
|
|
||||||
label="排除"
|
|
||||||
placeholder="支持正则表达式"
|
|
||||||
/>
|
|
||||||
</VCol>
|
|
||||||
<VCol
|
|
||||||
cols="12"
|
|
||||||
>
|
|
||||||
<VTextField
|
|
||||||
v-model="rssForm.save_path"
|
|
||||||
label="保存路径"
|
|
||||||
placeholder="留空自动"
|
|
||||||
/>
|
|
||||||
</VCol>
|
|
||||||
<VCol
|
|
||||||
cols="12"
|
|
||||||
md="6"
|
|
||||||
>
|
|
||||||
<VSelect
|
|
||||||
v-model="rssForm.state"
|
|
||||||
label="状态"
|
|
||||||
:items="[{
|
|
||||||
title: '启用',
|
|
||||||
value: 1,
|
|
||||||
}, {
|
|
||||||
title: '停用',
|
|
||||||
value: 0,
|
|
||||||
}]"
|
|
||||||
/>
|
|
||||||
</VCol>
|
|
||||||
</VRow>
|
|
||||||
<VRow>
|
|
||||||
<VCol
|
|
||||||
cols="12"
|
|
||||||
md="4"
|
|
||||||
>
|
|
||||||
<VSwitch
|
|
||||||
v-model="rssForm.best_version"
|
|
||||||
label="洗版"
|
|
||||||
/>
|
|
||||||
</VCol>
|
|
||||||
<VCol
|
|
||||||
cols="12"
|
|
||||||
md="4"
|
|
||||||
>
|
|
||||||
<VSwitch
|
|
||||||
v-model="rssForm.proxy"
|
|
||||||
label="代理服务器"
|
|
||||||
/>
|
|
||||||
</VCol>
|
|
||||||
<VCol
|
|
||||||
cols="12"
|
|
||||||
md="4"
|
|
||||||
>
|
|
||||||
<VSwitch
|
|
||||||
v-model="rssForm.filter"
|
|
||||||
label="过滤规则"
|
|
||||||
/>
|
|
||||||
</VCol>
|
|
||||||
</VRow>
|
|
||||||
</VForm>
|
|
||||||
</VCardText>
|
|
||||||
<VCardActions>
|
|
||||||
<VBtn
|
|
||||||
@click="rssAddDialog = false"
|
|
||||||
>
|
|
||||||
取消
|
|
||||||
</VBtn>
|
|
||||||
<VSpacer />
|
|
||||||
<VBtn
|
|
||||||
color="primary"
|
|
||||||
:disabled="addBtnState"
|
|
||||||
@click="addRss"
|
|
||||||
>
|
|
||||||
{{ addBtnText }}
|
|
||||||
</VBtn>
|
|
||||||
</VCardActions>
|
|
||||||
</VCard>
|
|
||||||
</VDialog>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.grid-rss-card {
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
|
|
||||||
padding-block-end: 1rem;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1836,6 +1836,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
|
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
|
||||||
integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==
|
integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==
|
||||||
|
|
||||||
|
"@types/lodash@^4.14.197":
|
||||||
|
version "4.14.198"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.198.tgz#4d27465257011aedc741a809f1269941fa2c5d4c"
|
||||||
|
integrity sha512-trNJ/vtMZYMLhfN45uLq4ShQSw0/S7xCTLLVM+WM1rmFpba/VS42jVUgaO3w/NOLiWR/09lnYk0yMaA/atdIsg==
|
||||||
|
|
||||||
"@types/mdast@^3.0.0":
|
"@types/mdast@^3.0.0":
|
||||||
version "3.0.11"
|
version "3.0.11"
|
||||||
resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.11.tgz#dc130f7e7d9306124286f6d6cee40cf4d14a3dc0"
|
resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.11.tgz#dc130f7e7d9306124286f6d6cee40cf4d14a3dc0"
|
||||||
|
|||||||
Reference in New Issue
Block a user