mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-05-30 21:00:43 +08:00
refactor(setting): 重构设置界面布局
This commit is contained in:
@@ -1,98 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { cloneDeep } from "lodash"
|
||||
|
||||
const props = defineProps({
|
||||
AdvancedNetworkSettings: Object as any,
|
||||
})
|
||||
|
||||
// 高级设置默认值,使用深复制,避免引用传递
|
||||
const AdvancedSettings = ref(cloneDeep(props.AdvancedNetworkSettings))
|
||||
|
||||
// 定义触发的自定义事件
|
||||
const emit = defineEmits(['change', 'close'])
|
||||
|
||||
// 保存高级设置
|
||||
function saveAdvancedSettings() {
|
||||
emit('change', AdvancedSettings.value, 'AdvancedNetwork')
|
||||
}
|
||||
|
||||
// 恢复默认值
|
||||
function loadAdvancedSettings() {
|
||||
// 将AdvancedNetworkSettings中部分值赋值为默认值
|
||||
AdvancedSettings.value = {
|
||||
OCR_HOST: 'https://movie-pilot.org',
|
||||
DOH_RESOLVERS: '1.0.0.1,1.1.1.1,9.9.9.9,149.112.112.112',
|
||||
DOH_DOMAINS:
|
||||
'api.themoviedb.org,api.tmdb.org,webservice.fanart.tv,api.github.com,github.com,raw.githubusercontent.com,api.telegram.org',
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VDialog scrollable max-width="60rem" persistent>
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<VCardTitle>高级网络设置</VCardTitle>
|
||||
<VCardSubtitle>修改前,请先了解清楚这些设置的作用。</VCardSubtitle>
|
||||
</VCardItem>
|
||||
<DialogCloseBtn @click="emit('close')" />
|
||||
<VDivider />
|
||||
<VCardText>
|
||||
<VForm>
|
||||
<VRow>
|
||||
<VCol cols="12">
|
||||
<VTextField
|
||||
v-model="AdvancedSettings.OCR_HOST"
|
||||
label="验证码识别服务器"
|
||||
hint="用于识别验证码"
|
||||
persistent-hint
|
||||
clearable
|
||||
active
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
<VTextarea
|
||||
v-model="AdvancedSettings.DOH_RESOLVERS"
|
||||
label="DOH 服务器"
|
||||
placeholder="格式:https://dns.google/dns-query,1.1.1.1"
|
||||
hint="多个服务器使用逗号分隔"
|
||||
persistent-hint
|
||||
clearable
|
||||
active
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
<VTextarea
|
||||
v-model="AdvancedSettings.DOH_DOMAINS"
|
||||
label="DOH 域名"
|
||||
placeholder="格式:example.com,example2.com"
|
||||
hint="多个域名使用逗号分隔"
|
||||
persistent-hint
|
||||
clearable
|
||||
active
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
<VCardActions class="pt-3">
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<div class="d-flex flex-wrap gap-4 mt-4">
|
||||
<VBtn color="info" variant="elevated" prepend-icon="mdi-reload" @click="loadAdvancedSettings" class="px-5">
|
||||
恢复默认值
|
||||
</VBtn>
|
||||
<VBtn
|
||||
color="primary"
|
||||
variant="elevated"
|
||||
prepend-icon="mdi-content-save"
|
||||
@click="saveAdvancedSettings"
|
||||
class="px-5"
|
||||
>
|
||||
确定
|
||||
</VBtn>
|
||||
</div>
|
||||
</VForm>
|
||||
</VCardActions>
|
||||
</VCard>
|
||||
</VDialog>
|
||||
</template>
|
||||
@@ -1,93 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { cloneDeep } from "lodash";
|
||||
|
||||
const props = defineProps({
|
||||
AdvancedSystemSettings: Object as any,
|
||||
})
|
||||
|
||||
// 高级设置默认值,使用深复制,避免引用传递
|
||||
const AdvancedSettings = ref(cloneDeep(props.AdvancedSystemSettings))
|
||||
|
||||
// 定义触发事件
|
||||
const emit = defineEmits(['change', 'close'])
|
||||
|
||||
// 保存高级设置
|
||||
function saveAdvancedSettings() {
|
||||
emit('change', AdvancedSettings.value, 'Advanced')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VDialog scrollable max-width="60rem" persistent>
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<VCardTitle>高级系统设置</VCardTitle>
|
||||
<VCardSubtitle>修改前,请先了解清楚这些设置的作用。</VCardSubtitle>
|
||||
</VCardItem>
|
||||
<DialogCloseBtn @click="emit('close')" />
|
||||
<VDivider />
|
||||
<VCardText>
|
||||
<VRow>
|
||||
<VCol cols="12" md="4">
|
||||
<VSwitch
|
||||
v-model="AdvancedSettings.DEV"
|
||||
label="DEV模式"
|
||||
hint="包括DEBUG日志、插件热加载"
|
||||
persistent-hint
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="4">
|
||||
<VSwitch
|
||||
v-model="AdvancedSettings.DEBUG"
|
||||
label="DEBUG日志"
|
||||
hint="显示DEBUG日志"
|
||||
persistent-hint
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="4">
|
||||
<VSwitch
|
||||
v-model="AdvancedSettings.PLUGIN_AUTO_RELOAD"
|
||||
label="插件热加载"
|
||||
hint="插件热加载调试模式"
|
||||
persistent-hint
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" class="justify-center">
|
||||
<VAlert
|
||||
type="error"
|
||||
variant="tonal"
|
||||
style="inline-size: fit-content"
|
||||
text="以上三项开关,需要在保存设置后,再重启MP,才能生效!"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
<VTextarea
|
||||
v-model="AdvancedSettings.REPO_GITHUB_TOKEN"
|
||||
label="指定仓库Github token"
|
||||
placeholder="格式:{user1}/{repo1}:ghp_****,{user2}/{repo2}:github_pat_****"
|
||||
hint="指定的单个仓库Github token。支持多个仓库使用,分隔"
|
||||
persistent-hint
|
||||
clearable
|
||||
active
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VCardText>
|
||||
<VCardActions class="pt-3">
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<div class="d-flex flex-wrap gap-4 mt-4">
|
||||
<VBtn
|
||||
color="primary"
|
||||
variant="elevated"
|
||||
prepend-icon="mdi-content-save"
|
||||
@click="saveAdvancedSettings"
|
||||
class="px-5"
|
||||
>
|
||||
确定
|
||||
</VBtn>
|
||||
</div>
|
||||
</VForm>
|
||||
</VCardActions>
|
||||
</VCard>
|
||||
</VDialog>
|
||||
</template>
|
||||
@@ -7,12 +7,10 @@ import AccountSettingWords from '@/views/setting/AccountSettingWords.vue'
|
||||
import AccountSettingAbout from '@/views/setting/AccountSettingAbout.vue'
|
||||
import AccountSettingSearch from '@/views/setting/AccountSettingSearch.vue'
|
||||
import AccountSettingSubscribe from '@/views/setting/AccountSettingSubscribe.vue'
|
||||
import AccountSettingService from '@/views/setting/AccountSettingService.vue'
|
||||
import AccountSettingSystem from '@/views/setting/AccountSettingSystem.vue'
|
||||
import AccountSettingScheduler from '@/views/setting/AccountSettingScheduler.vue'
|
||||
import AccountSettingDirectory from '@/views/setting/AccountSettingDirectory.vue'
|
||||
import AccountSettingRule from '@/views/setting/AccountSettingRule.vue'
|
||||
import AccountSettingTransfer from "@/views/setting/AccountSettingTransfer.vue"
|
||||
import { SettingTabs } from '@/router/menu'
|
||||
|
||||
const route = useRoute()
|
||||
@@ -51,15 +49,6 @@ function jumpTab(tab: string) {
|
||||
</transition>
|
||||
</VWindowItem>
|
||||
|
||||
<!-- 连接 -->
|
||||
<VWindowItem value="service">
|
||||
<transition name="fade-slide" appear>
|
||||
<div>
|
||||
<AccountSettingService />
|
||||
</div>
|
||||
</transition>
|
||||
</VWindowItem>
|
||||
|
||||
<!-- 目录 -->
|
||||
<VWindowItem value="directory">
|
||||
<transition name="fade-slide" appear>
|
||||
@@ -94,15 +83,6 @@ function jumpTab(tab: string) {
|
||||
</transition>
|
||||
</VWindowItem>
|
||||
|
||||
<!-- 整理 -->
|
||||
<VWindowItem value="transfer">
|
||||
<transition name="fade-slide" appear>
|
||||
<div>
|
||||
<AccountSettingTransfer />
|
||||
</div>
|
||||
</transition>
|
||||
</VWindowItem>
|
||||
|
||||
<!-- 订阅 -->
|
||||
<VWindowItem value="subscribe">
|
||||
<transition name="fade-slide" appear>
|
||||
|
||||
@@ -133,15 +133,9 @@ export const UserfulMenus = [
|
||||
export const SettingTabs = [
|
||||
{
|
||||
title: '系统',
|
||||
icon: 'mdi-cog',
|
||||
tab: 'system',
|
||||
description: '基本设定、网络设定、高级设定',
|
||||
},
|
||||
{
|
||||
title: '连接',
|
||||
icon: 'mdi-server-network',
|
||||
tab: 'service',
|
||||
description: '下载器(Qbittorrent、Transmission)、媒体服务器(Emby、Jellyfin、Plex)',
|
||||
tab: 'system',
|
||||
description: '系统设置、下载器(Qbittorrent、Transmission)、媒体服务器(Emby、Jellyfin、Plex)',
|
||||
},
|
||||
{
|
||||
title: '存储 & 目录',
|
||||
@@ -167,12 +161,6 @@ export const SettingTabs = [
|
||||
tab: 'search',
|
||||
description: '媒体数据源(TheMovieDb、豆瓣、Bangumi)、搜索站点、搜索优先级、默认过滤规则',
|
||||
},
|
||||
{
|
||||
title: '整理',
|
||||
icon: 'mdi-folder-multiple-outline',
|
||||
tab: 'transfer',
|
||||
description: '转移重命名、刮削来源',
|
||||
},
|
||||
{
|
||||
title: '订阅',
|
||||
icon: 'mdi-rss',
|
||||
@@ -180,7 +168,7 @@ export const SettingTabs = [
|
||||
description: '订阅站点、订阅模式、订阅优先级、洗版优先级、默认过滤规则',
|
||||
},
|
||||
{
|
||||
title: '调度',
|
||||
title: '服务',
|
||||
icon: 'mdi-list-box',
|
||||
tab: 'scheduler',
|
||||
description: '定时作业',
|
||||
|
||||
@@ -20,12 +20,33 @@ const mediaCategories = ref<{ [key: string]: any }>({})
|
||||
// 提示框
|
||||
const $toast = useToast()
|
||||
|
||||
// 重载系统生效配置
|
||||
async function reloadSystem() {
|
||||
// 数据源
|
||||
const sourceItems = [
|
||||
{ 'title': 'TheMovieDb', 'value': 'themoviedb' },
|
||||
{ 'title': '豆瓣', 'value': 'douban' },
|
||||
]
|
||||
|
||||
// 系统设置
|
||||
const SystemSettings = ref<any>({
|
||||
Basic: {
|
||||
SCRAP_SOURCE: 'themoviedb',
|
||||
MOVIE_RENAME_FORMAT: null,
|
||||
TV_RENAME_FORMAT: null,
|
||||
},
|
||||
})
|
||||
|
||||
// 加载系统设置
|
||||
async function loadSystemSettings() {
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.get('system/reload')
|
||||
if (result.success) $toast.success('系统配置已生效')
|
||||
else $toast.error('重载系统失败!')
|
||||
const result: { [key: string]: any } = await api.get('system/env')
|
||||
if (result.success) {
|
||||
// 将API返回的值赋值给SystemSettings
|
||||
for (const sectionKey of Object.keys(SystemSettings.value) as Array<keyof typeof SystemSettings.value>) {
|
||||
Object.keys(SystemSettings.value[sectionKey]).forEach((key: string) => {
|
||||
if (result.data.hasOwnProperty(key)) (SystemSettings.value[sectionKey] as any)[key] = result.data[key]
|
||||
})
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
@@ -88,7 +109,6 @@ async function saveDirectories() {
|
||||
const result: { [key: string]: any } = await api.post('system/setting/Directories', directories.value)
|
||||
if (result.success) {
|
||||
$toast.success('目录设置保存成功')
|
||||
await reloadSystem()
|
||||
} else $toast.error('目录设置保存失败!')
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
@@ -130,11 +150,24 @@ async function loadMediaCategories() {
|
||||
}
|
||||
}
|
||||
|
||||
// 保存设置
|
||||
async function saveSystemSettings(value: any) {
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.post('system/env', value)
|
||||
if (result.success) {
|
||||
$toast.success('整理选项设置保存成功')
|
||||
} else $toast.error('整理选项设置保存失败!')
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 加载数据
|
||||
onMounted(() => {
|
||||
loadDirectories()
|
||||
loadStorages()
|
||||
loadMediaCategories()
|
||||
loadSystemSettings()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -208,4 +241,54 @@ onMounted(() => {
|
||||
</VCard>
|
||||
</VCol>
|
||||
</VRow>
|
||||
<VRow>
|
||||
<VCol cols="12">
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<VCardTitle>整理 & 刮削</VCardTitle>
|
||||
<VCardSubtitle>设置重命名格式、刮削选项等。</VCardSubtitle>
|
||||
</VCardItem>
|
||||
<VCardText>
|
||||
<VRow>
|
||||
<VCol cols="12" md="6">
|
||||
<VSelect
|
||||
v-model="SystemSettings.Basic.SCRAP_SOURCE"
|
||||
:items="sourceItems"
|
||||
label="刮削数据源"
|
||||
hint="刮削时的元数据来源"
|
||||
persistent-hint
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
<VTextarea
|
||||
v-model="SystemSettings.Basic.MOVIE_RENAME_FORMAT"
|
||||
label="电影重命名格式"
|
||||
hint="使用Jinja2语法,格式参考:https://jinja.palletsprojects.com/en/3.0.x/templates"
|
||||
persistent-hint
|
||||
clearable
|
||||
active
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
<VTextarea
|
||||
v-model="SystemSettings.Basic.TV_RENAME_FORMAT"
|
||||
label="电视剧重命名格式"
|
||||
hint="使用Jinja2语法,格式参考:https://jinja.palletsprojects.com/en/3.0.x/templates"
|
||||
persistent-hint
|
||||
clearable
|
||||
active
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VCardText>
|
||||
<VCardText>
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<div class="d-flex flex-wrap gap-4 mt-4">
|
||||
<VBtn type="submit" @click="saveSystemSettings(SystemSettings.Basic)"> 保存</VBtn>
|
||||
</div>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
|
||||
@@ -14,11 +14,10 @@ const selectedSites = ref<number[]>([])
|
||||
|
||||
// 系统设置
|
||||
const SystemSettings = ref<any>({
|
||||
Basis: {},
|
||||
Advanced: {
|
||||
Basic: {
|
||||
SEARCH_MULTIPLE_NAME: false,
|
||||
DOWNLOAD_SUBTITLE: false,
|
||||
AUTO_DOWNLOAD_USER: '',
|
||||
AUTO_DOWNLOAD_USER: null,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -113,6 +112,18 @@ async function loadSearchSetting() {
|
||||
}
|
||||
}
|
||||
|
||||
// 调用API保存设置
|
||||
async function saveSystemSetting(value: { [key: string]: any }) {
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.post('system/env', value)
|
||||
|
||||
if (result.success) {
|
||||
return true
|
||||
}
|
||||
} catch (error) {}
|
||||
return false
|
||||
}
|
||||
|
||||
// 调用API保存设置
|
||||
async function saveSearchSetting() {
|
||||
try {
|
||||
@@ -126,11 +137,12 @@ async function saveSearchSetting() {
|
||||
selectedFilterGroup.value,
|
||||
)
|
||||
|
||||
if (result1.success && result2.success) {
|
||||
$toast.success('保存设置成功')
|
||||
await reloadSystem()
|
||||
const result3 = await saveSystemSetting(SystemSettings.value.Basic)
|
||||
|
||||
if (result1.success && result2.success && result3) {
|
||||
$toast.success('搜索基础设置保存成功')
|
||||
} else {
|
||||
$toast.error('保存设置失败!')
|
||||
$toast.error('搜索基础设置保存失败!')
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
@@ -145,52 +157,15 @@ async function loadSystemSettings() {
|
||||
// 将API返回的值赋值给SystemSettings
|
||||
for (const sectionKey of Object.keys(SystemSettings.value) as Array<keyof typeof SystemSettings.value>) {
|
||||
Object.keys(SystemSettings.value[sectionKey]).forEach((key: string) => {
|
||||
let v: any
|
||||
if (result.data.hasOwnProperty(key)) {
|
||||
v = result.data[key]
|
||||
// 空字符串转为null,避免空字符串导致前端显示问题
|
||||
if (v === '') {
|
||||
v = null
|
||||
}
|
||||
;(SystemSettings.value[sectionKey] as any)[key] = v
|
||||
}
|
||||
if (result.data.hasOwnProperty(key)) (SystemSettings.value[sectionKey] as any)[key] = result.data[key]
|
||||
})
|
||||
}
|
||||
} else $toast.error('加载设置失败!')
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 保存设置
|
||||
async function saveSystemSettings(value: any) {
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.post('system/env', value)
|
||||
if (result.success) {
|
||||
$toast.success('保存设置成功')
|
||||
await reloadSystem()
|
||||
await loadSystemSettings()
|
||||
} else {
|
||||
$toast.error('保存设置失败!')
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 重载系统生效配置
|
||||
async function reloadSystem() {
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.get('system/reload')
|
||||
if (result.success) {
|
||||
$toast.success('系统配置已生效')
|
||||
await loadSystemSettings()
|
||||
} else $toast.error('重载系统失败!')
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
querySites()
|
||||
queryFilterRuleGroups()
|
||||
@@ -205,7 +180,7 @@ onMounted(() => {
|
||||
<VCol cols="12">
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<VCardTitle>数据源 & 规则</VCardTitle>
|
||||
<VCardTitle>基础设置</VCardTitle>
|
||||
<VCardSubtitle>设定数据源、规则组等基础信息。</VCardSubtitle>
|
||||
</VCardItem>
|
||||
<VCardText>
|
||||
@@ -235,6 +210,33 @@ onMounted(() => {
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
<VRow>
|
||||
<VCol cols="12">
|
||||
<VCombobox
|
||||
v-model="SystemSettings.Basic.AUTO_DOWNLOAD_USER"
|
||||
label="远程搜索自动下载用户名单"
|
||||
placeholder="用户ID1,用户ID2"
|
||||
hint="使用Telegram、微信等搜索时是否自动下载,使用逗号分割,设置为 all 代表所有用户自动择优下载"
|
||||
persistent-hint
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
<VSwitch
|
||||
v-model="SystemSettings.Basic.SEARCH_MULTIPLE_NAME"
|
||||
label="多名称资源搜索"
|
||||
hint="使用中英文等多个名称搜索站点资源并合并搜索结果,将会增加站点访问频率"
|
||||
persistent-hint
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
<VSwitch
|
||||
v-model="SystemSettings.Basic.DOWNLOAD_SUBTITLE"
|
||||
label="下载站点字幕"
|
||||
hint="检查站点资源是否有独立的字幕文件,有则自动下载"
|
||||
persistent-hint
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VCardText>
|
||||
<VCardText>
|
||||
<VForm @submit.prevent="() => {}">
|
||||
@@ -277,49 +279,4 @@ onMounted(() => {
|
||||
</VCard>
|
||||
</VCol>
|
||||
</VRow>
|
||||
<VRow>
|
||||
<VCol cols="12">
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<VCardTitle>高级设置</VCardTitle>
|
||||
<VCardSubtitle>设置交互搜索自动下载用户ID、字幕。</VCardSubtitle>
|
||||
</VCardItem>
|
||||
<VCardText>
|
||||
<VRow>
|
||||
<VCol cols="12" md="6">
|
||||
<VSwitch
|
||||
v-model="SystemSettings.Advanced.SEARCH_MULTIPLE_NAME"
|
||||
label="整合多名称资源搜索结果"
|
||||
hint="搜索多个名称的资源时,整合多名称的结果"
|
||||
persistent-hint
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
<VSwitch
|
||||
v-model="SystemSettings.Advanced.DOWNLOAD_SUBTITLE"
|
||||
label="下载站点字幕"
|
||||
hint="当选定的资源所在站点中,存在字幕文件时,同步自动下载"
|
||||
persistent-hint
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
<VCombobox
|
||||
v-model="SystemSettings.Advanced.AUTO_DOWNLOAD_USER"
|
||||
label="交互式搜索自动下载用户"
|
||||
hint="针对使用tg、微信等第三方交互的特化功能。使用逗号分割,设置为 all 代表所有用户自动择优下载,未设置时,需要用户手动选择资源 或 回复 ` 0 ` 才自动择优下载"
|
||||
persistent-hint
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VCardText>
|
||||
<VCardText>
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<div class="d-flex flex-wrap gap-4 mt-4">
|
||||
<VBtn type="submit" @click="saveSystemSettings(SystemSettings.Advanced)"> 保存 </VBtn>
|
||||
</div>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
|
||||
@@ -1,347 +0,0 @@
|
||||
<!-- eslint-disable sonarjs/no-duplicate-string -->
|
||||
<script lang="ts" setup>
|
||||
import { useToast } from 'vue-toast-notification'
|
||||
import { VRow } from 'vuetify/lib/components/index.mjs'
|
||||
import draggable from 'vuedraggable'
|
||||
import api from '@/api'
|
||||
import { DownloaderConf, MediaServerConf } from '@/api/types'
|
||||
import DownloaderCard from '@/components/cards/DownloaderCard.vue'
|
||||
import MediaServerCard from '@/components/cards/MediaServerCard.vue'
|
||||
|
||||
// 系统设置项
|
||||
const SystemSettings = ref<any>({
|
||||
MEDIASERVER_SYNC_INTERVAL: 6,
|
||||
})
|
||||
|
||||
// 是否发送请求的总开关
|
||||
const isRequest = ref(true)
|
||||
|
||||
// 选中的媒体服务器
|
||||
const mediaServers = ref<MediaServerConf[]>([])
|
||||
|
||||
// 下载器
|
||||
const downloaders = ref<DownloaderConf[]>([])
|
||||
|
||||
// 提示框
|
||||
const $toast = useToast()
|
||||
|
||||
// 调用API查询下载器设置
|
||||
async function loadDownloaderSetting() {
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.get('system/setting/Downloaders')
|
||||
downloaders.value = result.data?.value ?? []
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 重载系统生效配置
|
||||
async function reloadSystem() {
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.get('system/reload')
|
||||
if (result.success) $toast.success('系统配置已生效')
|
||||
else $toast.error('重载系统失败!')
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 调用API保存下载器设置
|
||||
async function saveDownloaderSetting() {
|
||||
try {
|
||||
// 提取启用的下载器
|
||||
const enabledDownloaders = downloaders.value.filter(item => item.enabled)
|
||||
// 有启动的下载器时
|
||||
if (enabledDownloaders.length > 0) {
|
||||
downloaders.value = handleDefaultDownloaders(enabledDownloaders, downloaders.value)
|
||||
}
|
||||
const result: { [key: string]: any } = await api.post('system/setting/Downloaders', downloaders.value)
|
||||
if (result.success) $toast.success('下载器设置保存成功')
|
||||
else $toast.error('下载器设置保存失败!')
|
||||
|
||||
await loadDownloaderSetting()
|
||||
await reloadSystem()
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 处理默认下载器状态
|
||||
function handleDefaultDownloaders(enabledDownloaders: any[], downloaders: any[]) {
|
||||
const enabledDefaultDownloader = enabledDownloaders.find(item => item.default)
|
||||
if (enabledDownloaders.length > 0 && !enabledDefaultDownloader) {
|
||||
downloaders = downloaders.map(item => {
|
||||
if (item === enabledDownloaders[0]) {
|
||||
$toast.info(`未设置默认下载器,已将【${item.name}】作为默认下载器`)
|
||||
return { ...item, default: true }
|
||||
}
|
||||
// 清除其他下载器的默认下载器状态
|
||||
return { ...item, default: false }
|
||||
})
|
||||
}
|
||||
return downloaders
|
||||
}
|
||||
|
||||
// 调用API查询媒体服务器设置
|
||||
async function loadMediaServerSetting() {
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.get('system/setting/MediaServers')
|
||||
mediaServers.value = result.data?.value ?? []
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 调用API保存媒体服务器设置
|
||||
async function saveMediaServerSetting() {
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.post('system/setting/MediaServers', mediaServers.value)
|
||||
if (result.success) $toast.success('媒体服务器设置保存成功')
|
||||
else $toast.error('媒体服务器设置保存失败!')
|
||||
|
||||
await loadMediaServerSetting()
|
||||
await reloadSystem()
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 加载系统设置
|
||||
async function loadSystemSettings() {
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.get('system/env')
|
||||
if (result.success) {
|
||||
const { MEDIASERVER_SYNC_INTERVAL } = result.data
|
||||
SystemSettings.value = {
|
||||
MEDIASERVER_SYNC_INTERVAL,
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 调用API保存系统设置
|
||||
async function saveSystemSetting() {
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.post('system/env', SystemSettings.value)
|
||||
|
||||
if (result.success) $toast.success('保存设置成功')
|
||||
else $toast.error('保存设置失败!')
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 添加下载器
|
||||
function addDownloader(downloader: string) {
|
||||
let name = `下载器${downloaders.value.length + 1}`
|
||||
while (downloaders.value.some(item => item.name === name)) {
|
||||
name = `下载器${parseInt(name.split('下载器')[1]) + 1}`
|
||||
}
|
||||
downloaders.value.push({
|
||||
name: name,
|
||||
type: downloader,
|
||||
default: false,
|
||||
enabled: false,
|
||||
config: {},
|
||||
})
|
||||
}
|
||||
|
||||
// 删除下载器
|
||||
function removeDownloader(ele: DownloaderConf) {
|
||||
const index = downloaders.value.indexOf(ele)
|
||||
downloaders.value.splice(index, 1)
|
||||
}
|
||||
|
||||
// 下载器变化
|
||||
function onDownloaderChange(downloader: DownloaderConf, name: string) {
|
||||
const index = downloaders.value.findIndex(item => item.name === name)
|
||||
if (index !== -1) downloaders.value[index] = downloader
|
||||
}
|
||||
|
||||
// 添加媒体服务器
|
||||
function addMediaServer(mediaserver: string) {
|
||||
let name = `服务器${mediaServers.value.length + 1}`
|
||||
while (mediaServers.value.some(item => item.name === name)) {
|
||||
name = `服务器${parseInt(name.split('服务器')[1]) + 1}`
|
||||
}
|
||||
mediaServers.value.push({
|
||||
name: name,
|
||||
type: mediaserver,
|
||||
enabled: false,
|
||||
config: {},
|
||||
})
|
||||
}
|
||||
|
||||
// 删除媒体服务器
|
||||
function removeMediaServer(ele: MediaServerConf) {
|
||||
const index = mediaServers.value.indexOf(ele)
|
||||
if (index !== -1) mediaServers.value.splice(index, 1)
|
||||
}
|
||||
|
||||
// 变更媒体服务器
|
||||
function onMediaServerChange(mediaserver: MediaServerConf, name: string) {
|
||||
const index = mediaServers.value.findIndex(item => item.name === name)
|
||||
if (index !== -1) mediaServers.value[index] = mediaserver
|
||||
}
|
||||
|
||||
// 加载数据
|
||||
onMounted(() => {
|
||||
loadDownloaderSetting()
|
||||
loadMediaServerSetting()
|
||||
loadSystemSettings()
|
||||
})
|
||||
|
||||
onActivated(async () => {
|
||||
isRequest.value = true
|
||||
})
|
||||
|
||||
onDeactivated(() => {
|
||||
isRequest.value = false
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow>
|
||||
<VCol cols="12">
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<VCardTitle>基础设置</VCardTitle>
|
||||
<VCardSubtitle>设置服务器的全局功能。</VCardSubtitle>
|
||||
</VCardItem>
|
||||
<VCardText>
|
||||
<VForm>
|
||||
<VRow>
|
||||
<VCol cols="12" md="6">
|
||||
<VTextField
|
||||
v-model="SystemSettings.MEDIASERVER_SYNC_INTERVAL"
|
||||
label="媒体服务器同步间隔"
|
||||
hint="不宜设置间隔过短的时间,这会导致服务器性能占用增高。"
|
||||
persistent-hint
|
||||
clearable
|
||||
suffix="小时"
|
||||
type="number"
|
||||
min="1"
|
||||
style="inline-size: fit-content"
|
||||
:rules="[
|
||||
(v: any) => !!v || '必选项,请勿留空',
|
||||
(v: any) => !isNaN(v) || '仅支持输入数字,请勿输入其他字符',
|
||||
(v: any) => v >= 1 || '间隔不能小于1个小时',
|
||||
]"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
<VCardText>
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<div class="d-flex flex-wrap gap-4 mt-4">
|
||||
<VBtn type="submit" @click="saveSystemSetting"> 保存 </VBtn>
|
||||
</div>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
</VRow>
|
||||
<VRow>
|
||||
<VCol cols="12">
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<VCardTitle>下载器</VCardTitle>
|
||||
<VCardSubtitle>只有默认下载器才会被默认使用。</VCardSubtitle>
|
||||
</VCardItem>
|
||||
<VCardText>
|
||||
<draggable
|
||||
v-model="downloaders"
|
||||
handle=".cursor-move"
|
||||
item-key="name"
|
||||
tag="div"
|
||||
:component-data="{ 'class': 'grid gap-3 grid-app-card' }"
|
||||
>
|
||||
<template #item="{ element }">
|
||||
<DownloaderCard
|
||||
:downloader="element"
|
||||
:downloaders="downloaders"
|
||||
@close="removeDownloader(element)"
|
||||
@change="onDownloaderChange"
|
||||
:allow-refresh="isRequest"
|
||||
/>
|
||||
</template>
|
||||
</draggable>
|
||||
</VCardText>
|
||||
<VCardText>
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<div class="d-flex flex-wrap gap-4 mt-4">
|
||||
<VBtn type="submit" @click="saveDownloaderSetting"> 保存 </VBtn>
|
||||
<VBtn color="success" variant="tonal">
|
||||
<VIcon icon="mdi-plus" />
|
||||
<VMenu activator="parent" close-on-content-click>
|
||||
<VList>
|
||||
<VListItem variant="plain" @click="addDownloader('qbittorrent')">
|
||||
<VListItemTitle>Qbittorrent</VListItemTitle>
|
||||
</VListItem>
|
||||
<VListItem variant="plain" @click="addDownloader('transmission')">
|
||||
<VListItemTitle>Transmission</VListItemTitle>
|
||||
</VListItem>
|
||||
</VList>
|
||||
</VMenu>
|
||||
</VBtn>
|
||||
</div>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
</VRow>
|
||||
<VRow>
|
||||
<VCol cols="12">
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<VCardTitle>媒体服务器</VCardTitle>
|
||||
<VCardSubtitle>所有启用的媒体服务器都会被使用。</VCardSubtitle>
|
||||
</VCardItem>
|
||||
<VCardText>
|
||||
<draggable
|
||||
v-model="mediaServers"
|
||||
handle=".cursor-move"
|
||||
item-key="name"
|
||||
tag="div"
|
||||
:component-data="{ 'class': 'grid gap-3 grid-app-card' }"
|
||||
>
|
||||
<template #item="{ element }">
|
||||
<MediaServerCard
|
||||
:mediaserver="element"
|
||||
:mediaservers="mediaServers"
|
||||
@close="removeMediaServer(element)"
|
||||
@change="onMediaServerChange"
|
||||
/>
|
||||
</template>
|
||||
</draggable>
|
||||
</VCardText>
|
||||
<VCardText>
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<div class="d-flex flex-wrap gap-4 mt-4">
|
||||
<VBtn type="submit" @click="saveMediaServerSetting"> 保存 </VBtn>
|
||||
<VBtn color="success" variant="tonal">
|
||||
<VIcon icon="mdi-plus" />
|
||||
<VMenu activator="parent" close-on-content-click>
|
||||
<VList>
|
||||
<VListItem variant="plain" @click="addMediaServer('emby')">
|
||||
<VListItemTitle>Emby</VListItemTitle>
|
||||
</VListItem>
|
||||
<VListItem variant="plain" @click="addMediaServer('jellyfin')">
|
||||
<VListItemTitle>Jellyfin</VListItemTitle>
|
||||
</VListItem>
|
||||
<VListItem variant="plain" @click="addMediaServer('plex')">
|
||||
<VListItemTitle>Plex</VListItemTitle>
|
||||
</VListItem>
|
||||
</VList>
|
||||
</VMenu>
|
||||
</VBtn>
|
||||
</div>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
@@ -95,6 +95,17 @@ async function loadSiteSettings() {
|
||||
}
|
||||
}
|
||||
|
||||
// 重载系统生效配置
|
||||
async function reloadSystem() {
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.get('system/reload')
|
||||
if (result.success) $toast.success('系统配置已生效')
|
||||
else $toast.error('重载系统失败!')
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 调用API保存设置
|
||||
async function saveSiteSetting(value: { [key: string]: any }) {
|
||||
console.log(`正在保存设置:${JSON.stringify(value)}`)
|
||||
@@ -102,6 +113,7 @@ async function saveSiteSetting(value: { [key: string]: any }) {
|
||||
const result: { [key: string]: any } = await api.post('system/env', value)
|
||||
if (result.success) {
|
||||
$toast.success('保存设置成功')
|
||||
await reloadSystem()
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
|
||||
@@ -18,21 +18,12 @@ const selectedFilterRuleGroup = ref([])
|
||||
// 选中的洗版规则组
|
||||
const selectedBestVersionRuleGroup = ref([])
|
||||
|
||||
// 是否开启订阅定时搜索
|
||||
const enableIntervalSearch = ref(false)
|
||||
|
||||
// 是否检查本地媒体库是否存在资源
|
||||
const enableDirExistsSearch = ref(false)
|
||||
|
||||
// 订阅模式选择项
|
||||
const subscribeModeItems = [
|
||||
{ title: '自动', value: 'spider' },
|
||||
{ title: '站点RSS', value: 'rss' },
|
||||
]
|
||||
|
||||
// 选择的订阅模式
|
||||
const selectedSubscribeMode = ref('spider')
|
||||
|
||||
// 所有规则组列表
|
||||
const filterRuleGroups = ref<FilterRuleGroup[]>([])
|
||||
|
||||
@@ -55,8 +46,16 @@ const rssIntervalItems = [
|
||||
{ title: '1天', value: 1440 },
|
||||
]
|
||||
|
||||
// 选择的RSS运行周期
|
||||
const selectedRssInterval = ref<number>(5)
|
||||
// 系统设置项
|
||||
const SystemSettings = ref<any>({
|
||||
// 基础设置
|
||||
Basic: {
|
||||
SUBSCRIBE_MODE: 'auto',
|
||||
SUBSCRIBE_SEARCH: false,
|
||||
SUBSCRIBE_RSS_INTERVAL: 30,
|
||||
LOCAL_EXISTS_SEARCH: false,
|
||||
},
|
||||
})
|
||||
|
||||
// 查询所有站点
|
||||
async function querySites() {
|
||||
@@ -103,27 +102,55 @@ async function saveSelectedRssSites() {
|
||||
}
|
||||
}
|
||||
|
||||
// 查询订阅设置
|
||||
async function querySubscribeSetting() {
|
||||
// 加载系统设置
|
||||
async function loadSystemSettings() {
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.get('system/env')
|
||||
if (result.success) {
|
||||
// 将API返回的值赋值给SystemSettings
|
||||
for (const sectionKey of Object.keys(SystemSettings.value) as Array<keyof typeof SystemSettings.value>) {
|
||||
Object.keys(SystemSettings.value[sectionKey]).forEach((key: string) => {
|
||||
if (result.data.hasOwnProperty(key)) (SystemSettings.value[sectionKey] as any)[key] = result.data[key]
|
||||
})
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 调用API保存设置
|
||||
async function saveSystemSetting(value: { [key: string]: any }) {
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.post('system/env', value)
|
||||
|
||||
if (result.success) {
|
||||
return true
|
||||
}
|
||||
} catch (error) {}
|
||||
return false
|
||||
}
|
||||
|
||||
// 查询订阅设置
|
||||
async function querySubscribeRules() {
|
||||
try {
|
||||
// 查询订阅搜索开关
|
||||
const result: { [key: string]: any } = await api.get('system/setting/SUBSCRIBE_SEARCH')
|
||||
if (result.success) enableIntervalSearch.value = result.data?.value
|
||||
// 查询订阅模式
|
||||
const result2: { [key: string]: any } = await api.get('system/setting/SUBSCRIBE_MODE')
|
||||
if (result2.success) selectedSubscribeMode.value = result2.data?.value
|
||||
// 查询站点RSS周期
|
||||
const result3: { [key: string]: any } = await api.get('system/setting/SUBSCRIBE_RSS_INTERVAL')
|
||||
if (result3.success) selectedRssInterval.value = result3.data?.value
|
||||
// 查询订阅规则组
|
||||
const result4: { [key: string]: any } = await api.get('system/setting/SubscribeFilterRuleGroups')
|
||||
if (result4.success) selectedFilterRuleGroup.value = result4.data?.value
|
||||
const result1: { [key: string]: any } = await api.get('system/setting/SubscribeFilterRuleGroups')
|
||||
if (result1.success) selectedFilterRuleGroup.value = result1.data?.value
|
||||
// 查询洗版规则组
|
||||
const result5: { [key: string]: any } = await api.get('system/setting/BestVersionFilterRuleGroups')
|
||||
if (result5.success) selectedBestVersionRuleGroup.value = result5.data?.value
|
||||
// 查询检查本地媒体库是否存在资源开关
|
||||
const result6: { [key: string]: any } = await api.get('system/setting/LOCAL_EXISTS_SEARCH')
|
||||
if (result6.success) enableDirExistsSearch.value = result6.data?.value
|
||||
const result2: { [key: string]: any } = await api.get('system/setting/BestVersionFilterRuleGroups')
|
||||
if (result2.success) selectedBestVersionRuleGroup.value = result2.data?.value
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 重载系统生效配置
|
||||
async function reloadSystem() {
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.get('system/reload')
|
||||
if (result.success) $toast.success('系统配置已生效')
|
||||
else $toast.error('重载系统失败!')
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
@@ -133,35 +160,21 @@ async function querySubscribeSetting() {
|
||||
async function saveSubscribeSetting() {
|
||||
try {
|
||||
const result1: { [key: string]: any } = await api.post(
|
||||
'system/setting/SUBSCRIBE_SEARCH',
|
||||
enableIntervalSearch.value,
|
||||
)
|
||||
|
||||
const result2: { [key: string]: any } = await api.post('system/setting/SUBSCRIBE_MODE', selectedSubscribeMode.value)
|
||||
|
||||
const result3: { [key: string]: any } = await api.post(
|
||||
'system/setting/SUBSCRIBE_RSS_INTERVAL',
|
||||
selectedRssInterval.value,
|
||||
)
|
||||
|
||||
const result4: { [key: string]: any } = await api.post(
|
||||
'system/setting/SubscribeFilterRuleGroups',
|
||||
selectedFilterRuleGroup.value,
|
||||
)
|
||||
|
||||
const result5: { [key: string]: any } = await api.post(
|
||||
const result2: { [key: string]: any } = await api.post(
|
||||
'system/setting/BestVersionFilterRuleGroups',
|
||||
selectedBestVersionRuleGroup.value,
|
||||
)
|
||||
|
||||
const result6: { [key: string]: any } = await api.post(
|
||||
'system/setting/LOCAL_EXISTS_SEARCH',
|
||||
enableDirExistsSearch.value,
|
||||
)
|
||||
const result3 = await saveSystemSetting(SystemSettings.value.Basic)
|
||||
|
||||
if (result1.success && result2.success && result3.success && result4.success && result5.success && result6.success)
|
||||
$toast.success('订阅设置保存成功')
|
||||
else $toast.error('订阅设置保存失败!')
|
||||
if (result1.success && result2.success && result3) {
|
||||
$toast.success('订阅基础设置保存成功')
|
||||
await reloadSystem()
|
||||
} else $toast.error('订阅基础设置保存失败!')
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
@@ -171,7 +184,8 @@ onMounted(() => {
|
||||
querySites()
|
||||
queryFilterRuleGroups()
|
||||
querySelectedRssSites()
|
||||
querySubscribeSetting()
|
||||
querySubscribeRules()
|
||||
loadSystemSettings()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -180,7 +194,7 @@ onMounted(() => {
|
||||
<VCol cols="12">
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<VCardTitle>订阅模式 & 规则</VCardTitle>
|
||||
<VCardTitle>基础设置</VCardTitle>
|
||||
<VCardSubtitle>设定订阅模式、周期等基础设置</VCardSubtitle>
|
||||
</VCardItem>
|
||||
<VCardText>
|
||||
@@ -188,7 +202,7 @@ onMounted(() => {
|
||||
<VRow>
|
||||
<VCol cols="12" md="6">
|
||||
<VSelect
|
||||
v-model="selectedSubscribeMode"
|
||||
v-model="SystemSettings.Basic.SUBSCRIBE_MODE"
|
||||
:items="subscribeModeItems"
|
||||
label="订阅模式"
|
||||
hint="自动:自动爬取站点首页,站点RSS:通过站点RSS链接订阅"
|
||||
@@ -197,7 +211,7 @@ onMounted(() => {
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
<VSelect
|
||||
v-model="selectedRssInterval"
|
||||
v-model="SystemSettings.Basic.SUBSCRIBE_RSS_INTERVAL"
|
||||
:items="rssIntervalItems"
|
||||
label="站点RSS周期"
|
||||
hint="设置站点RSS运行周期,在订阅模式为`站点RSS`时生效"
|
||||
@@ -232,7 +246,7 @@ onMounted(() => {
|
||||
<VRow>
|
||||
<VCol cols="12" md="6">
|
||||
<VSwitch
|
||||
v-model="enableIntervalSearch"
|
||||
v-model="SystemSettings.Basic.SUBSCRIBE_SEARCH"
|
||||
label="订阅定时搜索"
|
||||
hint="每隔24小时全站搜索,以补全订阅可能漏掉的资源"
|
||||
persistent-hint
|
||||
@@ -240,7 +254,7 @@ onMounted(() => {
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
<VSwitch
|
||||
v-model="enableDirExistsSearch"
|
||||
v-model="SystemSettings.Basic.LOCAL_EXISTS_SEARCH"
|
||||
label="检查本地媒体库资源"
|
||||
hint="检查存储盘是否存在资源,以避免重复下载"
|
||||
persistent-hint
|
||||
|
||||
@@ -1,64 +1,142 @@
|
||||
<!-- eslint-disable sonarjs/no-duplicate-string -->
|
||||
<script lang="ts" setup>
|
||||
import { useToast } from 'vue-toast-notification'
|
||||
import { VRow } from 'vuetify/lib/components/index.mjs'
|
||||
import draggable from 'vuedraggable'
|
||||
import api from '@/api'
|
||||
import { DownloaderConf, MediaServerConf } from '@/api/types'
|
||||
import DownloaderCard from '@/components/cards/DownloaderCard.vue'
|
||||
import MediaServerCard from '@/components/cards/MediaServerCard.vue'
|
||||
import { copyToClipboard } from '@/@core/utils/navigator'
|
||||
import debounce from 'lodash/debounce'
|
||||
import AdvancedNetworkSettingsDialog from '@/components/dialog/AdvancedNetworkSettingsDialog.vue'
|
||||
import AdvancedSystemSettingsDialog from '@/components/dialog/AdvancedSystemSettingsDialog.vue'
|
||||
|
||||
// 系统设置默认值
|
||||
// 系统设置项
|
||||
const SystemSettings = ref<any>({
|
||||
// 系统设置
|
||||
Basis: {
|
||||
// 基础设置
|
||||
AUXILIARY_AUTH_ENABLE: false,
|
||||
GLOBAL_IMAGE_CACHE: false,
|
||||
APP_DOMAIN: '',
|
||||
API_TOKEN: '',
|
||||
// 基础设置
|
||||
Basic: {
|
||||
APP_DOMAIN: null,
|
||||
API_TOKEN: null,
|
||||
WALLPAPER: 'tmdb',
|
||||
MEDIASERVER_SYNC_INTERVAL: null,
|
||||
RECOGNIZE_SOURCE: 'themoviedb',
|
||||
GITHUB_TOKEN: null,
|
||||
OCR_HOST: null,
|
||||
},
|
||||
// 高级系统设置
|
||||
Advanced: {
|
||||
DEV: false,
|
||||
// 全局
|
||||
AUXILIARY_AUTH_ENABLE: false,
|
||||
GLOBAL_IMAGE_CACHE: false,
|
||||
// 媒体
|
||||
TMDB_API_DOMAIN: null,
|
||||
TMDB_IMAGE_DOMAIN: null,
|
||||
META_CACHE_EXPIRE: 0,
|
||||
FANART_ENABLE: false,
|
||||
// 网络
|
||||
PROXY_HOST: null,
|
||||
GITHUB_PROXY: null,
|
||||
PIP_PROXY: null,
|
||||
DOH_ENABLE: false,
|
||||
DOH_RESOLVERS: null,
|
||||
DOH_DOMAINS: null,
|
||||
// 开发
|
||||
DEBUG: false,
|
||||
PLUGIN_AUTO_RELOAD: false,
|
||||
REPO_GITHUB_TOKEN: '',
|
||||
},
|
||||
// 网络设置
|
||||
Network: {
|
||||
// 基础网络设置
|
||||
TMDB_API_DOMAIN: '',
|
||||
TMDB_IMAGE_DOMAIN: '',
|
||||
DOH_ENABLE: true,
|
||||
GITHUB_PROXY: '',
|
||||
GITHUB_TOKEN: null,
|
||||
PIP_PROXY: null,
|
||||
// 隐藏设置,不在页面显示
|
||||
PROXY_HOST: '',
|
||||
},
|
||||
// 高级网络设置
|
||||
AdvancedNetwork: {
|
||||
DOH_RESOLVERS: '',
|
||||
DOH_DOMAINS: '',
|
||||
OCR_HOST: '',
|
||||
},
|
||||
})
|
||||
|
||||
// 高级设置弹窗
|
||||
const isAdvancedSystemSettingsDialogOpen = ref(false)
|
||||
const isAdvancedNetworkSettingsDialogOpen = ref(false)
|
||||
// 是否发送请求的总开关
|
||||
const isRequest = ref(true)
|
||||
|
||||
// 选中的媒体服务器
|
||||
const mediaServers = ref<MediaServerConf[]>([])
|
||||
|
||||
// 下载器
|
||||
const downloaders = ref<DownloaderConf[]>([])
|
||||
|
||||
// 提示框
|
||||
const $toast = useToast()
|
||||
|
||||
// 高级设置对话框
|
||||
const advancedDialog = ref(false)
|
||||
|
||||
const activeTab = ref('system')
|
||||
|
||||
// 调用API查询下载器设置
|
||||
async function loadDownloaderSetting() {
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.get('system/setting/Downloaders')
|
||||
downloaders.value = result.data?.value ?? []
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 重载系统生效配置
|
||||
async function reloadSystem() {
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.get('system/reload')
|
||||
if (result.success) {
|
||||
$toast.success('系统配置已生效')
|
||||
await loadSystemSettings()
|
||||
} else $toast.error('重载系统失败!')
|
||||
if (result.success) $toast.success('系统配置已生效')
|
||||
else $toast.error('重载系统失败!')
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 调用API保存下载器设置
|
||||
async function saveDownloaderSetting() {
|
||||
try {
|
||||
// 提取启用的下载器
|
||||
const enabledDownloaders = downloaders.value.filter(item => item.enabled)
|
||||
// 有启动的下载器时
|
||||
if (enabledDownloaders.length > 0) {
|
||||
downloaders.value = handleDefaultDownloaders(enabledDownloaders, downloaders.value)
|
||||
}
|
||||
const result: { [key: string]: any } = await api.post('system/setting/Downloaders', downloaders.value)
|
||||
if (result.success) $toast.success('下载器设置保存成功')
|
||||
else $toast.error('下载器设置保存失败!')
|
||||
|
||||
await loadDownloaderSetting()
|
||||
await reloadSystem()
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 处理默认下载器状态
|
||||
function handleDefaultDownloaders(enabledDownloaders: any[], downloaders: any[]) {
|
||||
const enabledDefaultDownloader = enabledDownloaders.find(item => item.default)
|
||||
if (enabledDownloaders.length > 0 && !enabledDefaultDownloader) {
|
||||
downloaders = downloaders.map(item => {
|
||||
if (item === enabledDownloaders[0]) {
|
||||
$toast.info(`未设置默认下载器,已将【${item.name}】作为默认下载器`)
|
||||
return { ...item, default: true }
|
||||
}
|
||||
// 清除其他下载器的默认下载器状态
|
||||
return { ...item, default: false }
|
||||
})
|
||||
}
|
||||
return downloaders
|
||||
}
|
||||
|
||||
// 调用API查询媒体服务器设置
|
||||
async function loadMediaServerSetting() {
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.get('system/setting/MediaServers')
|
||||
mediaServers.value = result.data?.value ?? []
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 调用API保存媒体服务器设置
|
||||
async function saveMediaServerSetting() {
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.post('system/setting/MediaServers', mediaServers.value)
|
||||
if (result.success) $toast.success('媒体服务器设置保存成功')
|
||||
else $toast.error('媒体服务器设置保存失败!')
|
||||
|
||||
await loadMediaServerSetting()
|
||||
await reloadSystem()
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
@@ -72,21 +150,12 @@ async function loadSystemSettings() {
|
||||
// 将API返回的值赋值给SystemSettings
|
||||
for (const sectionKey of Object.keys(SystemSettings.value) as Array<keyof typeof SystemSettings.value>) {
|
||||
Object.keys(SystemSettings.value[sectionKey]).forEach((key: string) => {
|
||||
let v: any
|
||||
if (result.data.hasOwnProperty(key)) {
|
||||
v = result.data[key]
|
||||
// 空字符串转为null,避免空字符串导致前端显示问题
|
||||
if (v === '') {
|
||||
v = null
|
||||
}
|
||||
;(SystemSettings.value[sectionKey] as any)[key] = v
|
||||
}
|
||||
if (result.data.hasOwnProperty(key)) (SystemSettings.value[sectionKey] as any)[key] = result.data[key]
|
||||
})
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
$toast.error('系统设置加载失败')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,43 +163,34 @@ async function loadSystemSettings() {
|
||||
async function saveSystemSetting(value: { [key: string]: any }) {
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.post('system/env', value)
|
||||
|
||||
if (result.success) {
|
||||
$toast.success('保存设置成功')
|
||||
await reloadSystem()
|
||||
await loadSystemSettings()
|
||||
return true
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
$toast.error('保存设置失败!')
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 保存系统设置
|
||||
async function saveSystemSettings() {
|
||||
const Settings = { ...SystemSettings.value.Basis, ...SystemSettings.value.Advanced }
|
||||
await saveSystemSetting(Settings)
|
||||
}
|
||||
|
||||
// 保存网络设置
|
||||
async function saveNetworkSettings() {
|
||||
const Settings = { ...SystemSettings.value.Network, ...SystemSettings.value.AdvancedNetwork }
|
||||
// 查找PROXY_HOST,并删除,避免意外覆盖
|
||||
if (Settings.PROXY_HOST) delete Settings.PROXY_HOST
|
||||
await saveSystemSetting(Settings)
|
||||
// 保存基础设置
|
||||
async function saveBasicSettings() {
|
||||
if (await saveSystemSetting(SystemSettings.value.Basic)) {
|
||||
$toast.success('基础设置保存成功')
|
||||
await reloadSystem()
|
||||
} else {
|
||||
$toast.error('基础设置保存失败!')
|
||||
}
|
||||
}
|
||||
|
||||
// 高级设置变化,等待保存
|
||||
function saveAdvancedSettings(Settings: any, key: string) {
|
||||
if (!Settings) return
|
||||
if (!key) return
|
||||
// 检查Settings中的键是否在SystemSettings的[key]中存在,有则使用Settings的值替换SystemSettings中的值
|
||||
for (const settingKey in Settings) {
|
||||
if (SystemSettings.value[key].hasOwnProperty(settingKey)) {
|
||||
;(SystemSettings.value[key] as any)[settingKey] = Settings[settingKey]
|
||||
}
|
||||
async function saveAdvancedSettings() {
|
||||
if (await saveSystemSetting(SystemSettings.value.Advanced)) {
|
||||
advancedDialog.value = false
|
||||
$toast.success('高级设置保存成功')
|
||||
await reloadSystem()
|
||||
} else {
|
||||
$toast.error('高级设置保存失败!')
|
||||
}
|
||||
$toast.info('高级设置已更改,待保存后生效')
|
||||
}
|
||||
|
||||
// 快捷复制到剪贴板
|
||||
@@ -148,16 +208,9 @@ function copyValue(value: string) {
|
||||
const wallpaperItems = [
|
||||
{ title: 'TheMovieDB电影海报', value: 'tmdb' },
|
||||
{ title: 'Bing每日壁纸', value: 'bing' },
|
||||
{ title: '媒体库海报', value: 'mediaserver' },
|
||||
]
|
||||
|
||||
// 创建随机字符串
|
||||
function createRandomString() {
|
||||
const charset = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_'
|
||||
const array = new Uint8Array(16)
|
||||
window.crypto.getRandomValues(array)
|
||||
SystemSettings.value.Basis.API_TOKEN = Array.from(array, byte => charset[byte % charset.length]).join('')
|
||||
}
|
||||
|
||||
// 预设部分Github加速站
|
||||
const githubMirrorsItems = [
|
||||
'https://mirror.ghproxy.com/', // GitHub Proxy
|
||||
@@ -177,208 +230,160 @@ const pipMirrorsItems = [
|
||||
'https://mirrors.bfsu.edu.cn/pypi/web/simple', // 北京外国语大学
|
||||
]
|
||||
|
||||
// 创建随机字符串
|
||||
function createRandomString() {
|
||||
const charset = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_'
|
||||
const array = new Uint8Array(16)
|
||||
window.crypto.getRandomValues(array)
|
||||
SystemSettings.value.Basic.API_TOKEN = Array.from(array, byte => charset[byte % charset.length]).join('')
|
||||
}
|
||||
|
||||
// 添加下载器
|
||||
function addDownloader(downloader: string) {
|
||||
let name = `下载器${downloaders.value.length + 1}`
|
||||
while (downloaders.value.some(item => item.name === name)) {
|
||||
name = `下载器${parseInt(name.split('下载器')[1]) + 1}`
|
||||
}
|
||||
downloaders.value.push({
|
||||
name: name,
|
||||
type: downloader,
|
||||
default: false,
|
||||
enabled: false,
|
||||
config: {},
|
||||
})
|
||||
}
|
||||
|
||||
// 删除下载器
|
||||
function removeDownloader(ele: DownloaderConf) {
|
||||
const index = downloaders.value.indexOf(ele)
|
||||
downloaders.value.splice(index, 1)
|
||||
}
|
||||
|
||||
// 下载器变化
|
||||
function onDownloaderChange(downloader: DownloaderConf, name: string) {
|
||||
const index = downloaders.value.findIndex(item => item.name === name)
|
||||
if (index !== -1) downloaders.value[index] = downloader
|
||||
}
|
||||
|
||||
// 添加媒体服务器
|
||||
function addMediaServer(mediaserver: string) {
|
||||
let name = `服务器${mediaServers.value.length + 1}`
|
||||
while (mediaServers.value.some(item => item.name === name)) {
|
||||
name = `服务器${parseInt(name.split('服务器')[1]) + 1}`
|
||||
}
|
||||
mediaServers.value.push({
|
||||
name: name,
|
||||
type: mediaserver,
|
||||
enabled: false,
|
||||
config: {},
|
||||
})
|
||||
}
|
||||
|
||||
// 删除媒体服务器
|
||||
function removeMediaServer(ele: MediaServerConf) {
|
||||
const index = mediaServers.value.indexOf(ele)
|
||||
if (index !== -1) mediaServers.value.splice(index, 1)
|
||||
}
|
||||
|
||||
// 变更媒体服务器
|
||||
function onMediaServerChange(mediaserver: MediaServerConf, name: string) {
|
||||
const index = mediaServers.value.findIndex(item => item.name === name)
|
||||
if (index !== -1) mediaServers.value[index] = mediaserver
|
||||
}
|
||||
|
||||
// 加载数据
|
||||
onMounted(() => {
|
||||
loadDownloaderSetting()
|
||||
loadMediaServerSetting()
|
||||
loadSystemSettings()
|
||||
})
|
||||
|
||||
onActivated(async () => {
|
||||
isRequest.value = true
|
||||
})
|
||||
|
||||
onDeactivated(() => {
|
||||
isRequest.value = false
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow>
|
||||
<!-- 系统设置 -->
|
||||
<VCol cols="12">
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<VCardTitle>基础系统设置</VCardTitle>
|
||||
<VCardSubtitle>设置用户辅助认证、登录首页壁纸、访问域名、插件市场等基础化设置。</VCardSubtitle>
|
||||
<VCardTitle>基础设置</VCardTitle>
|
||||
<VCardSubtitle>设置服务器的全局功能。</VCardSubtitle>
|
||||
</VCardItem>
|
||||
<VCardText>
|
||||
<VForm>
|
||||
<VRow>
|
||||
<VCol cols="12" md="3">
|
||||
<VSwitch
|
||||
v-model="SystemSettings.Basis.AUXILIARY_AUTH_ENABLE"
|
||||
label="用户辅助认证"
|
||||
hint="允许通过外部服务,进行认证、单点登录以及自动创建用户"
|
||||
persistent-hint
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="3">
|
||||
<VSwitch
|
||||
v-model="SystemSettings.Basis.GLOBAL_IMAGE_CACHE"
|
||||
label="全局图片缓存"
|
||||
hint="将媒体图片缓存到本地,增强用户体验"
|
||||
persistent-hint
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="3">
|
||||
<VSwitch
|
||||
v-model="isAdvancedSystemSettingsDialogOpen"
|
||||
label="高级系统设置"
|
||||
hint="进入高级系统设置页面"
|
||||
<VCol cols="12" md="6">
|
||||
<VTextField
|
||||
v-model="SystemSettings.Basic.APP_DOMAIN"
|
||||
label="访问域名"
|
||||
placeholder="格式:http(s)://domain:port"
|
||||
hint="用于发送通知时,添加快捷跳转地址"
|
||||
persistent-hint
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="3">
|
||||
<VSelect
|
||||
v-model="SystemSettings.Basis.WALLPAPER"
|
||||
v-model="SystemSettings.Basic.WALLPAPER"
|
||||
label="登录首页壁纸"
|
||||
hint="选择登陆页面背景来源"
|
||||
persistent-hint
|
||||
:items="wallpaperItems"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
|
||||
<VCol cols="12" md="3">
|
||||
<VTextField
|
||||
v-model="SystemSettings.Basis.APP_DOMAIN"
|
||||
label="访问域名"
|
||||
placeholder="格式:http(s)://domain:port"
|
||||
hint="用于发送通知时,添加快捷跳转地址"
|
||||
v-model="SystemSettings.Basic.MEDIASERVER_SYNC_INTERVAL"
|
||||
label="媒体服务器同步间隔"
|
||||
hint="定时同步媒体服务器数据到本地的时间间隔"
|
||||
persistent-hint
|
||||
clearable
|
||||
:appendInnerIcon="SystemSettings.Basis.APP_DOMAIN ? 'mdi-content-copy' : ''"
|
||||
@click:appendInner="SystemSettings.Basis.APP_DOMAIN && copyValue(SystemSettings.Basis.APP_DOMAIN)"
|
||||
suffix="小时"
|
||||
type="number"
|
||||
min="1"
|
||||
:rules="[
|
||||
(v: any) => !!v || '必选项,请勿留空',
|
||||
(v: any) => !isNaN(v) || '仅支持输入数字,请勿输入其他字符',
|
||||
(v: any) => v >= 1 || '间隔不能小于1个小时',
|
||||
]"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
<VTextField
|
||||
v-model="SystemSettings.Basis.API_TOKEN"
|
||||
label="API Token"
|
||||
hint="不得低于16位,用于Jellyseerr/Overseerr、媒体服务器Webhook等配置以及部分支持API_TOKEN的API请求"
|
||||
v-model="SystemSettings.Basic.API_TOKEN"
|
||||
label="API令牌"
|
||||
hint="设置外部请求MoviePilot API时使用的token值"
|
||||
placeholder="不能小于16位字符"
|
||||
persistent-hint
|
||||
clearable
|
||||
prependInnerIcon="mdi-reload"
|
||||
:appendInnerIcon="SystemSettings.Basis.API_TOKEN ? 'mdi-content-copy' : ''"
|
||||
:appendInnerIcon="SystemSettings.Basic.API_TOKEN ? 'mdi-content-copy' : ''"
|
||||
@click:prependInner="createRandomString"
|
||||
@click:appendInner="SystemSettings.Basis.API_TOKEN && copyValue(SystemSettings.Basis.API_TOKEN)"
|
||||
@click:appendInner="copyValue(SystemSettings.Basic.API_TOKEN)"
|
||||
:rules="[(v: string) => !!v || '必填项;请输入API Token', (v: string) => v.length >= 16 || 'API Token不得低于16位']"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
<VCardText>
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<div class="d-flex flex-wrap gap-4 mt-4">
|
||||
<VBtn type="submit" @click="saveSystemSettings"> 保存 </VBtn>
|
||||
</div>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
</VRow>
|
||||
|
||||
<VRow>
|
||||
<VCol cols="12">
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<VCardTitle>基础网络设置</VCardTitle>
|
||||
<VCardSubtitle>设置DOH、PIP加速站、Github加速站、Github Token等,增加网络稳定性,保证连通性。</VCardSubtitle>
|
||||
</VCardItem>
|
||||
<VCardText>
|
||||
<VForm>
|
||||
<VRow>
|
||||
<VCol cols="12" md="6" class="flex align-center">
|
||||
<div>
|
||||
<VSwitch
|
||||
v-model="SystemSettings.Network.DOH_ENABLE"
|
||||
label="DNS over HTTPS解析"
|
||||
hint="使用DOH服务器解析域名"
|
||||
persistent-hint
|
||||
/>
|
||||
</div>
|
||||
<div class="ml-10">
|
||||
<VAlert type="info" variant="tonal" class="whitespace-pre-line" style="inline-size: fit-content">
|
||||
<span v-if="SystemSettings.Network.PROXY_HOST"
|
||||
>当前已成功配置 PROXY_HOST ,建议关闭 DOH 功能。</span
|
||||
>
|
||||
<span v-else>暂未配置 PROXY_HOST,如出现网络连通性问题,可考虑开启 DOH 功能。 </span>
|
||||
</VAlert>
|
||||
</div>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6" class="flex align-center">
|
||||
<div>
|
||||
<VSwitch
|
||||
v-model="isAdvancedNetworkSettingsDialogOpen"
|
||||
label="高级网络设置"
|
||||
hint="进入高级网络设置页面"
|
||||
persistent-hint
|
||||
/>
|
||||
</div>
|
||||
<div class="ml-10">
|
||||
<VAlert
|
||||
type="info"
|
||||
variant="tonal"
|
||||
class="whitespace-pre-line mr-3"
|
||||
style="inline-size: fit-content"
|
||||
text="不建议修改,除非你知道它们的使用方法!"
|
||||
/>
|
||||
</div>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
<VCombobox
|
||||
v-model="SystemSettings.Network.TMDB_API_DOMAIN"
|
||||
label="TMDB API域名"
|
||||
placeholder="格式:api.themoviedb.org"
|
||||
hint="可替换为自定义的API域名"
|
||||
persistent-hint
|
||||
clearable
|
||||
active
|
||||
:items="['api.themoviedb.org']"
|
||||
:rules="[(v: string) => !!v || '必填项;请输入TMDB API域名']"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
<VCombobox
|
||||
v-model="SystemSettings.Network.TMDB_IMAGE_DOMAIN"
|
||||
label="TMDB 图片服务器"
|
||||
placeholder="格式:image.tmdb.org"
|
||||
hint="可替换为自定义的图片域名"
|
||||
persistent-hint
|
||||
clearable
|
||||
active
|
||||
:items="['image.tmdb.org', 'static-mdb.v.geilijiasu.com']"
|
||||
:rules="[(v: string) => !!v || '必填项;请输入TMDB API域名']"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
<VCombobox
|
||||
v-model="SystemSettings.Network.GITHUB_PROXY"
|
||||
label="Github加速站"
|
||||
placeholder="格式:https://mirror.ghproxy.com/"
|
||||
hint="留空则不使用;预设部分可选站点,也可手动输入自建站点。格式:https://mirror.ghproxy.com/ 末尾需要带 /"
|
||||
persistent-hint
|
||||
clearable
|
||||
:items="githubMirrorsItems"
|
||||
active
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
<VTextField
|
||||
v-model="SystemSettings.Network.GITHUB_TOKEN"
|
||||
v-model="SystemSettings.Basic.GITHUB_TOKEN"
|
||||
label="Github Token"
|
||||
placeholder="ghp_**** 或 github_pat_****"
|
||||
hint="用于提高Github API访问限流阈值"
|
||||
hint="用于提高插件等访问Github API时的限流阈值"
|
||||
persistent-hint
|
||||
clearable
|
||||
active
|
||||
:appendInnerIcon="SystemSettings.Network.GITHUB_TOKEN ? 'mdi-content-copy' : ''"
|
||||
@click:appendInner="
|
||||
SystemSettings.Network.GITHUB_TOKEN && copyValue(SystemSettings.Network.GITHUB_TOKEN)
|
||||
"
|
||||
>
|
||||
</VTextField>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
<VCombobox
|
||||
v-model="SystemSettings.Network.PIP_PROXY"
|
||||
label="PIP加速站"
|
||||
placeholder="格式:https://pypi.tuna.tsinghua.edu.cn/simple"
|
||||
hint="留空则不使用;预设部分可选站点,也可手动输入自建站点,格式: https://pypi.tuna.tsinghua.edu.cn/simple"
|
||||
<VTextField
|
||||
v-model="SystemSettings.Basic.OCR_HOST"
|
||||
label="验证码识别服务器"
|
||||
placeholder="https://movie-pilot.org"
|
||||
hint="用于站点签到、更新站点Cookie等识别验证码"
|
||||
persistent-hint
|
||||
clearable
|
||||
:items="pipMirrorsItems"
|
||||
active
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
@@ -387,35 +392,303 @@ onMounted(() => {
|
||||
<VCardText>
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<div class="d-flex flex-wrap gap-4 mt-4">
|
||||
<VBtn type="submit" @click="saveNetworkSettings"> 保存 </VBtn>
|
||||
<VBtn type="submit" @click="saveBasicSettings"> 保存 </VBtn>
|
||||
<VSpacer />
|
||||
<VBtn color="warning" @click="advancedDialog = true" append-icon="mdi-dots-horizontal"> 高级设置 </VBtn>
|
||||
</div>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
</VRow>
|
||||
<VRow>
|
||||
<VCol cols="12">
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<VCardTitle>下载器</VCardTitle>
|
||||
<VCardSubtitle>只有默认下载器才会被默认使用。</VCardSubtitle>
|
||||
</VCardItem>
|
||||
<VCardText>
|
||||
<draggable
|
||||
v-model="downloaders"
|
||||
handle=".cursor-move"
|
||||
item-key="name"
|
||||
tag="div"
|
||||
:component-data="{ 'class': 'grid gap-3 grid-app-card' }"
|
||||
>
|
||||
<template #item="{ element }">
|
||||
<DownloaderCard
|
||||
:downloader="element"
|
||||
:downloaders="downloaders"
|
||||
@close="removeDownloader(element)"
|
||||
@change="onDownloaderChange"
|
||||
:allow-refresh="isRequest"
|
||||
/>
|
||||
</template>
|
||||
</draggable>
|
||||
</VCardText>
|
||||
<VCardText>
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<div class="d-flex flex-wrap gap-4 mt-4">
|
||||
<VBtn type="submit" @click="saveDownloaderSetting"> 保存 </VBtn>
|
||||
<VBtn color="success" variant="tonal">
|
||||
<VIcon icon="mdi-plus" />
|
||||
<VMenu activator="parent" close-on-content-click>
|
||||
<VList>
|
||||
<VListItem variant="plain" @click="addDownloader('qbittorrent')">
|
||||
<VListItemTitle>Qbittorrent</VListItemTitle>
|
||||
</VListItem>
|
||||
<VListItem variant="plain" @click="addDownloader('transmission')">
|
||||
<VListItemTitle>Transmission</VListItemTitle>
|
||||
</VListItem>
|
||||
</VList>
|
||||
</VMenu>
|
||||
</VBtn>
|
||||
</div>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
</VRow>
|
||||
<VRow>
|
||||
<VCol cols="12">
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<VCardTitle>媒体服务器</VCardTitle>
|
||||
<VCardSubtitle>所有启用的媒体服务器都会被使用。</VCardSubtitle>
|
||||
</VCardItem>
|
||||
<VCardText>
|
||||
<draggable
|
||||
v-model="mediaServers"
|
||||
handle=".cursor-move"
|
||||
item-key="name"
|
||||
tag="div"
|
||||
:component-data="{ 'class': 'grid gap-3 grid-app-card' }"
|
||||
>
|
||||
<template #item="{ element }">
|
||||
<MediaServerCard
|
||||
:mediaserver="element"
|
||||
:mediaservers="mediaServers"
|
||||
@close="removeMediaServer(element)"
|
||||
@change="onMediaServerChange"
|
||||
/>
|
||||
</template>
|
||||
</draggable>
|
||||
</VCardText>
|
||||
<VCardText>
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<div class="d-flex flex-wrap gap-4 mt-4">
|
||||
<VBtn type="submit" @click="saveMediaServerSetting"> 保存 </VBtn>
|
||||
<VBtn color="success" variant="tonal">
|
||||
<VIcon icon="mdi-plus" />
|
||||
<VMenu activator="parent" close-on-content-click>
|
||||
<VList>
|
||||
<VListItem variant="plain" @click="addMediaServer('emby')">
|
||||
<VListItemTitle>Emby</VListItemTitle>
|
||||
</VListItem>
|
||||
<VListItem variant="plain" @click="addMediaServer('jellyfin')">
|
||||
<VListItemTitle>Jellyfin</VListItemTitle>
|
||||
</VListItem>
|
||||
<VListItem variant="plain" @click="addMediaServer('plex')">
|
||||
<VListItemTitle>Plex</VListItemTitle>
|
||||
</VListItem>
|
||||
</VList>
|
||||
</VMenu>
|
||||
</VBtn>
|
||||
</div>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
</VRow>
|
||||
|
||||
<!-- 高级系统设置 -->
|
||||
<AdvancedSystemSettingsDialog
|
||||
v-if="isAdvancedSystemSettingsDialogOpen"
|
||||
v-model="isAdvancedSystemSettingsDialogOpen"
|
||||
max-width="60rem"
|
||||
persistent
|
||||
z-index="1010"
|
||||
:AdvancedSystemSettings="SystemSettings.Advanced"
|
||||
@close="isAdvancedSystemSettingsDialogOpen = false"
|
||||
@change="saveAdvancedSettings"
|
||||
/>
|
||||
<VDialog v-if="advancedDialog" v-model="advancedDialog" scrollable max-width="60rem" persistent>
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<DialogCloseBtn @click="advancedDialog = false" />
|
||||
<VCardTitle>高级设置</VCardTitle>
|
||||
<VCardSubtitle>系统进阶设置,特殊情况下才需要调整</VCardSubtitle>
|
||||
</VCardItem>
|
||||
<VCardText>
|
||||
<VTabs v-model="activeTab" show-arrows>
|
||||
<VTab value="system">
|
||||
<div>系统</div>
|
||||
</VTab>
|
||||
<VTab value="media">
|
||||
<div>媒体</div>
|
||||
</VTab>
|
||||
<VTab value="network">
|
||||
<div>网络</div>
|
||||
</VTab>
|
||||
<VTab value="dev">
|
||||
<div>实验室</div>
|
||||
</VTab>
|
||||
</VTabs>
|
||||
<VWindow v-model="activeTab" class="mt-5 disable-tab-transition" :touch="false">
|
||||
<VWindowItem value="system">
|
||||
<div>
|
||||
<VRow>
|
||||
<VCol cols="12" md="6">
|
||||
<VSwitch
|
||||
v-model="SystemSettings.Advanced.AUXILIARY_AUTH_ENABLE"
|
||||
label="用户辅助认证"
|
||||
hint="允许外部服务进行登录认证以及自动创建用户"
|
||||
persistent-hint
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
<VSwitch
|
||||
v-model="SystemSettings.Advanced.GLOBAL_IMAGE_CACHE"
|
||||
label="全局图片缓存"
|
||||
hint="将媒体图片缓存到本地,提升图片加载速度"
|
||||
persistent-hint
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</div>
|
||||
</VWindowItem>
|
||||
<VWindowItem value="media">
|
||||
<div>
|
||||
<VRow>
|
||||
<VCol cols="12" md="6">
|
||||
<VCombobox
|
||||
v-model="SystemSettings.Advanced.TMDB_API_DOMAIN"
|
||||
label="TMDB API服务地址"
|
||||
placeholder="api.themoviedb.org"
|
||||
hint="自定义themoviedb API域名或代理地址"
|
||||
persistent-hint
|
||||
:items="['api.themoviedb.org']"
|
||||
:rules="[(v: string) => !!v || '请输入TMDB API域名']"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
<VCombobox
|
||||
v-model="SystemSettings.Advanced.TMDB_IMAGE_DOMAIN"
|
||||
label="TMDB 图片服务地址"
|
||||
placeholder="image.tmdb.org"
|
||||
hint="自定义themoviedb图片服务域名或代理地址"
|
||||
persistent-hint
|
||||
:items="['image.tmdb.org', 'static-mdb.v.geilijiasu.com']"
|
||||
:rules="[(v: string) => !!v || '请输入图片服务域名']"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
<VTextField
|
||||
v-model="SystemSettings.Advanced.META_CACHE_EXPIRE"
|
||||
label="媒体元数据缓存过期时间"
|
||||
hint="识别元数据本地缓存时间,为 0 时使用内置默认值"
|
||||
persistent-hint
|
||||
min="0"
|
||||
type="number"
|
||||
suffix="小时"
|
||||
:rules="[(v: any) => v === 0 || !!v || '请输入元数据缓存时间', (v: any) => v >= 0 || '元数据缓存时间必须大于等于0']"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
<VSwitch
|
||||
v-model="SystemSettings.Basic.FANART_ENABLE"
|
||||
label="Fanart图片数据源"
|
||||
hint="使用 fanart.tv 的图片数据"
|
||||
persistent-hint
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</div>
|
||||
</VWindowItem>
|
||||
<VWindowItem value="network">
|
||||
<div>
|
||||
<VRow>
|
||||
<VCol cols="12" md="6">
|
||||
<VCombobox
|
||||
v-model="SystemSettings.Advanced.GITHUB_PROXY"
|
||||
label="Github加速代理"
|
||||
placeholder="https://mirror.ghproxy.com/"
|
||||
hint="使用代理加速Github访问速度"
|
||||
persistent-hint
|
||||
:items="githubMirrorsItems"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
<VCombobox
|
||||
v-model="SystemSettings.Advanced.PIP_PROXY"
|
||||
label="PIP加速代理"
|
||||
placeholder="https://pypi.tuna.tsinghua.edu.cn/simple"
|
||||
hint="使用代理加速插件等pip库安装速度"
|
||||
persistent-hint
|
||||
:items="pipMirrorsItems"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
<VRow>
|
||||
<VCol cols="12" md="6">
|
||||
<VSwitch
|
||||
v-model="SystemSettings.Advanced.DOH_ENABLE"
|
||||
label="DNS Over HTTPS"
|
||||
hint="使用DOH对特定域名进行解析,以防止DNS污染"
|
||||
persistent-hint
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" v-show="SystemSettings.Advanced.DOH_ENABLE">
|
||||
<VTextarea
|
||||
v-model="SystemSettings.Advanced.DOH_RESOLVERS"
|
||||
label="DOH 服务器"
|
||||
placeholder="https://dns.google/dns-query,1.1.1.1"
|
||||
hint="DNS解析服务器地址,多个地址使用逗号分隔"
|
||||
persistent-hint
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" v-show="SystemSettings.Advanced.DOH_ENABLE">
|
||||
<VTextarea
|
||||
v-model="SystemSettings.Advanced.DOH_DOMAINS"
|
||||
label="DOH 域名"
|
||||
placeholder="example.com,example2.com"
|
||||
hint="使用DOH解析的域名,多个域名使用逗号分隔"
|
||||
persistent-hint
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</div>
|
||||
</VWindowItem>
|
||||
|
||||
<!-- 高级网络设置 -->
|
||||
<AdvancedNetworkSettingsDialog
|
||||
v-if="isAdvancedNetworkSettingsDialogOpen"
|
||||
v-model="isAdvancedNetworkSettingsDialogOpen"
|
||||
max-width="60rem"
|
||||
persistent
|
||||
z-index="1010"
|
||||
:AdvancedNetworkSettings="SystemSettings.AdvancedNetwork"
|
||||
@close="isAdvancedNetworkSettingsDialogOpen = false"
|
||||
@change="saveAdvancedSettings"
|
||||
/>
|
||||
<VWindowItem value="dev">
|
||||
<div>
|
||||
<VRow>
|
||||
<VCol cols="12" md="6">
|
||||
<VSwitch
|
||||
v-model="SystemSettings.Advanced.DEBUG"
|
||||
label="DEBUG日志"
|
||||
hint="显示DEBUG级别日志,方便排查问题"
|
||||
persistent-hint
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
<VSwitch
|
||||
v-model="SystemSettings.Advanced.PLUGIN_AUTO_RELOAD"
|
||||
label="插件热加载"
|
||||
hint="修改插件文件后自动重新加载,开发插件时使用"
|
||||
persistent-hint
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</div>
|
||||
</VWindowItem>
|
||||
</VWindow>
|
||||
</VCardText>
|
||||
<VCardActions class="pt-3">
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<div class="d-flex flex-wrap gap-4 mt-4">
|
||||
<VBtn
|
||||
color="primary"
|
||||
variant="elevated"
|
||||
prepend-icon="mdi-content-save"
|
||||
@click="saveAdvancedSettings"
|
||||
class="px-5"
|
||||
>
|
||||
保存
|
||||
</VBtn>
|
||||
</div>
|
||||
</VForm>
|
||||
</VCardActions>
|
||||
</VCard>
|
||||
</VDialog>
|
||||
</template>
|
||||
|
||||
@@ -1,188 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { useToast } from 'vue-toast-notification'
|
||||
import api from '@/api'
|
||||
|
||||
// 提示框
|
||||
const $toast = useToast()
|
||||
|
||||
// 系统设置
|
||||
const SystemSettings = ref<any>({
|
||||
Basis: {
|
||||
FANART_ENABLE: false,
|
||||
RECOGNIZE_SOURCE: 'themoviedb',
|
||||
SCRAP_SOURCE: 'themoviedb',
|
||||
META_CACHE_EXPIRE: 0,
|
||||
MOVIE_RENAME_FORMAT: '',
|
||||
TV_RENAME_FORMAT: '',
|
||||
},
|
||||
})
|
||||
|
||||
// 加载系统设置
|
||||
async function loadSystemSettings() {
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.get('system/env')
|
||||
if (result.success) {
|
||||
// 将API返回的值赋值给SystemSettings
|
||||
for (const sectionKey of Object.keys(SystemSettings.value) as Array<keyof typeof SystemSettings.value>) {
|
||||
Object.keys(SystemSettings.value[sectionKey]).forEach((key: string) => {
|
||||
let v: any
|
||||
if (result.data.hasOwnProperty(key)) {
|
||||
v = result.data[key]
|
||||
// 空字符串转为null,避免空字符串导致前端显示问题
|
||||
if (v === '') {
|
||||
v = null
|
||||
}
|
||||
;(SystemSettings.value[sectionKey] as any)[key] = v
|
||||
}
|
||||
})
|
||||
}
|
||||
} else $toast.error('加载设置失败!')
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 保存设置
|
||||
async function saveSystemSettings(value: any) {
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.post('system/env', value)
|
||||
if (result.success) {
|
||||
$toast.success('保存设置成功')
|
||||
await reloadSystem()
|
||||
await loadSystemSettings()
|
||||
} else $toast.error('保存设置失败!')
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 重载系统生效配置
|
||||
async function reloadSystem() {
|
||||
try {
|
||||
const result: { [key: string]: any } = await api.get('system/reload')
|
||||
if (result.success) {
|
||||
$toast.success('系统配置已生效')
|
||||
await loadSystemSettings()
|
||||
} else $toast.error('重载系统失败!')
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 恢复电影设置默认值
|
||||
async function loadDefaultMovieSetting() {
|
||||
SystemSettings.value.Basis.MOVIE_RENAME_FORMAT =
|
||||
'{{title}}{% if year %} ({{year}}){% endif %}/{{title}}{% if year %} ({{year}}){% endif %}{% if part %}-{{part}}{% endif %}{% if videoFormat %} - {{videoFormat}}{% endif %}{{fileExt}}'
|
||||
}
|
||||
|
||||
// 恢复电视剧设置默认值
|
||||
async function loadDefaultTVSetting() {
|
||||
SystemSettings.value.Basis.TV_RENAME_FORMAT =
|
||||
'{{title}}{% if year %} ({{year}}){% endif %}/Season {{season}}/{{title}} - {{season_episode}}{% if part %}-{{part}}{% endif %}{% if episode %} - 第 {{episode}} 集{% endif %}{{fileExt}}'
|
||||
}
|
||||
|
||||
// 数据源
|
||||
const sourceItems = [
|
||||
{ 'title': 'TheMovieDb', 'value': 'themoviedb' },
|
||||
{ 'title': '豆瓣', 'value': 'douban' },
|
||||
]
|
||||
|
||||
// 加载数据
|
||||
onMounted(() => {
|
||||
loadSystemSettings()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow>
|
||||
<VCol cols="12">
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<VCardTitle>基础设置</VCardTitle>
|
||||
<VCardSubtitle>设置通用的整理转移功能。</VCardSubtitle>
|
||||
</VCardItem>
|
||||
<VCardText>
|
||||
<VRow>
|
||||
<VCol cols="12" md="3">
|
||||
<VSwitch
|
||||
v-model="SystemSettings.Basis.FANART_ENABLE"
|
||||
label="Fanart图片数据源"
|
||||
hint="启用Fanart图片数据源"
|
||||
persistent-hint
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="3">
|
||||
<VSelect
|
||||
v-model="SystemSettings.Basis.RECOGNIZE_SOURCE"
|
||||
:items="sourceItems"
|
||||
label="媒体信息识别来源"
|
||||
hint="刮削时的媒体信息识别使用的数据源"
|
||||
persistent-hint
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="3">
|
||||
<VSelect
|
||||
v-model="SystemSettings.Basis.SCRAP_SOURCE"
|
||||
:items="sourceItems"
|
||||
label="媒体刮削数据源"
|
||||
hint="刮削元数据及图片使用的数据源"
|
||||
persistent-hint
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12" md="3">
|
||||
<VTextField
|
||||
v-model="SystemSettings.Basis.META_CACHE_EXPIRE"
|
||||
label="元数据缓存过期时间"
|
||||
hint="当缓存过期时间为 0 时,则使用内置默认值"
|
||||
persistent-hint
|
||||
min="0"
|
||||
type="number"
|
||||
suffix="小时"
|
||||
:rules="[(v: any) => v === 0 || !!v || '请输入元数据缓存时间', (v: any) => v >= 0 || '元数据缓存时间必须大于等于0']"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
<VTextarea
|
||||
v-model="SystemSettings.Basis.MOVIE_RENAME_FORMAT"
|
||||
label="电影重命名格式"
|
||||
hint="使用Jinja2语法"
|
||||
persistent-hint
|
||||
clearable
|
||||
prependInnerIcon="mdi-reload"
|
||||
@click:prependInner="loadDefaultMovieSetting"
|
||||
active
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
<VTextarea
|
||||
v-model="SystemSettings.Basis.TV_RENAME_FORMAT"
|
||||
label="电视剧重命名格式"
|
||||
hint="使用Jinja2语法"
|
||||
persistent-hint
|
||||
clearable
|
||||
prependInnerIcon="mdi-reload"
|
||||
@click:prependInner="loadDefaultTVSetting"
|
||||
active
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
<VAlert type="info" variant="tonal" class="whitespace-pre-line" style="inline-size: fit-content">
|
||||
<span>Jinja2语法参考:</span>
|
||||
<a href="https://jinja.palletsprojects.com/en/3.0.x/templates" target="_blank">
|
||||
<u>https://jinja.palletsprojects.com/en/3.0.x/templates</u>
|
||||
</a>
|
||||
</VAlert>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VCardText>
|
||||
<VCardText>
|
||||
<VForm @submit.prevent="() => {}">
|
||||
<div class="d-flex flex-wrap gap-4 mt-4">
|
||||
<VBtn type="submit" @click="saveSystemSettings(SystemSettings.Basis)"> 保存</VBtn>
|
||||
</div>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
Reference in New Issue
Block a user