feat(settings): add new AccountSettingsSystem.vue

- 将 原system更名为service,原service更名为scheduler
- 增加新的 AccountSettingSystem.vue。
- 调整 menu.ts 与settings.vue,适配新的 system 标签页
This commit is contained in:
Aqr-K
2024-10-31 03:38:01 +08:00
parent b91be6bb2f
commit bd9169bcd1
6 changed files with 815 additions and 709 deletions

View File

@@ -9,9 +9,9 @@ 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 AccountSettingNetwork from '@/views/setting/AccountSettingNetwork.vue'
import { SettingTabs } from '@/router/menu'
const route = useRoute()
@@ -41,7 +41,7 @@ function jumpTab(tab: string) {
</VTabs>
<VWindow v-model="activeTab" class="mt-5 disable-tab-transition" :touch="false">
<!-- 连接 -->
<!-- 系统 -->
<VWindowItem value="system">
<transition name="fade-slide" appear>
<div>
@@ -50,11 +50,11 @@ function jumpTab(tab: string) {
</transition>
</VWindowItem>
<!-- 网络 -->
<VWindowItem value="network">
<!-- 连接 -->
<VWindowItem value="service">
<transition name="fade-slide" appear>
<div>
<AccountSettingNetwork />
<AccountSettingService />
</div>
</transition>
</VWindowItem>
@@ -103,10 +103,10 @@ function jumpTab(tab: string) {
</VWindowItem>
<!-- 服务 -->
<VWindowItem value="service">
<VWindowItem value="scheduler">
<transition name="fade-slide" appear>
<div>
<AccountSettingService />
<AccountSettingScheduler />
</div>
</transition>
</VWindowItem>

View File

@@ -132,16 +132,16 @@ export const UserfulMenus = [
// 设定标签页
export const SettingTabs = [
{
title: '连接',
icon: 'mdi-server-network',
title: '系统',
icon: 'mdi-cog',
tab: 'system',
description: '下载器Qbittorrent、Transmission、媒体服务器Emby、Jellyfin、Plex',
description: '基本设定、网络设定、高级设定',
},
{
title: '网络',
icon: 'mdi-access-point-network',
tab: 'network',
description: 'Github、PIP、OCR、DOH',
title: '连接',
icon: 'mdi-server-network',
tab: 'service',
description: '下载器Qbittorrent、Transmission、媒体服务器Emby、Jellyfin、Plex',
},
{
title: '存储 & 目录',
@@ -174,9 +174,9 @@ export const SettingTabs = [
description: '订阅站点、订阅模式、订阅优先级、洗版优先级、默认过滤规则',
},
{
title: '服务',
title: '调度',
icon: 'mdi-list-box',
tab: 'service',
tab: 'scheduler',
description: '定时作业',
},
{

View File

@@ -1,350 +0,0 @@
<script lang="ts" setup>
import {useToast} from 'vue-toast-notification'
import {VRow} from 'vuetify/lib/components/index.mjs'
import api from '@/api'
import debounce from 'lodash/debounce'
// 系统设置项
const SystemSettings = ref({
DOH_ENABLE: false,
GITHUB_PROXY: '',
GITHUB_TOKEN: '',
PIP_PROXY: '',
DOH_SERVER: '',
DOH_DOMAINS: '',
OCR_HOST: '',
})
// 高级设置项
const AdvancedSettings = ref({
DOH_SERVER: '',
DOH_DOMAINS: '',
OCR_HOST: '',
})
// 防抖时间
const debounceTime = 500
// 高级设置弹窗
const isAdvanced = ref(false)
// 是否发送请求的总开关
const isRequest = ref(true)
// 提示框
const $toast = useToast()
// 重载系统生效配置
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)
}
}
// 加载系统设置
async function loadSystemSettings() {
try {
const result: { [key: string]: any } = await api.get('system/env')
if (result.success) {
const {
// 需要查询的变量
DOH_ENABLE,
GITHUB_PROXY,
GITHUB_TOKEN,
PIP_PROXY,
DOH_SERVER,
DOH_DOMAINS,
OCR_HOST,
} = result.data
SystemSettings.value = {
DOH_ENABLE: DOH_ENABLE || false,
GITHUB_PROXY: GITHUB_PROXY || null,
GITHUB_TOKEN: GITHUB_TOKEN || null,
PIP_PROXY: PIP_PROXY || null,
DOH_SERVER: DOH_SERVER || null,
DOH_DOMAINS: DOH_DOMAINS || null,
OCR_HOST: OCR_HOST || null,
}
}
} catch (error) {
console.log(error)
}
}
// 调用API保存系统设置
const saveSystemSetting = debounce(async () => {
try {
const result: { [key: string]: any } = await api.post('system/env', SystemSettings.value)
if (result.success) {
$toast.success('保存设置成功')
await reloadSystem()
} else $toast.error('保存设置失败!')
} catch (error) {
console.log(error)
}
}, debounceTime)
// 预设部分Github加速站
const githubMirrorsItems = [
'https://mirror.ghproxy.com/', // GitHub Proxy
'https://ghp.ci/', // GitHub Proxy 子站
]
// 预设部分PIP镜像站
const pipMirrorsItems = [
'https://pypi.tuna.tsinghua.edu.cn/simple', // 清华大学
'https://pypi.mirrors.ustc.edu.cn/simple', // 中国科技大学
'https://mirrors.pku.edu.cn/pypi/web/simple', // 北京大学
'https://mirrors.aliyun.com/pypi/simple', // 阿里云
'https://mirrors.cloud.tencent.com/pypi/simple', // 腾讯云
'https://mirrors.163.com/pypi/simple', // 网易云
'https://pypi.doubanio.com/simple', // 豆瓣
'https://mirrors.hust.edu.cn/pypi/web/simple', // 华中理工大学
'https://mirrors.bfsu.edu.cn/pypi/web/simple', // 北京外国语大学
]
// 打开高级设置弹窗
const openAdvancedSettings = () => {
for (const key in SystemSettings.value) {
AdvancedSettings.value[key] = SystemSettings.value[key]
}
isAdvanced.value = true
}
// 直接关闭高级设置弹窗
const closeAdvancedSettings = () => {
isAdvanced.value = false
for (const key in SystemSettings.value) {
AdvancedSettings.value[key] = SystemSettings.value[key]
}
}
// 保存高级设置
const saveAdvancedSettings = debounce(async () => {
$toast.info('高级设置确定成功,待保存后生效')
for (const key in AdvancedSettings.value) {
SystemSettings.value[key] = AdvancedSettings.value[key]
}
isAdvanced.value = false
}, debounceTime)
// 恢复高级设置默认值,直接赋值
const loadAdvancedSettings = () => {
AdvancedSettings.value = {
DOH_SERVER: "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",
OCR_HOST: "https://movie-pilot.org",
}
}
// 加载数据
onMounted(() => {
loadSystemSettings()
})
// 当isAdvanced变化时且为true时重新从SystemSettings赋值到AdvancedSettings
watch(isAdvanced, (value) => {
if (value) openAdvancedSettings()
})
</script>
<template>
<VRow>
<!-- DOH -->
<VCol cols="12">
<VCard>
<VCardItem>
<VCardTitle>基础设置</VCardTitle>
<VCardSubtitle>设置DNS over HTTPS服务器对特定域名使用DOH解析以避免DNS污染</VCardSubtitle>
</VCardItem>
<VCardText>
<VForm>
<VRow>
<VCol cols="12" md="6" class="flex align-center">
<div>
<VSwitch
v-model="SystemSettings.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="width: fit-content"
text="如果已经配置了 PROXY_HOST 则建议关闭 DOH 功能。"
/>
</div>
</VCol>
<VCol cols="12" md="6" class="flex align-center">
<div>
<VSwitch
v-model="isAdvanced"
label="高级设置"
hint="进入高级设置弹窗页面"
persistent-hint
/>
</div>
<div class="ml-10">
<VAlert
type="info"
variant="tonal"
class="whitespace-pre-line mr-3"
style="width: fit-content"
text="该页面包含一些高级设置,不建议修改,除非它们的使用方法!"
/>
</div>
</VCol>
</VRow>
</VForm>
</VCardText>
<VCardText>
<VForm @submit.prevent="() => {}">
<div class="d-flex flex-wrap gap-4 mt-4">
<VBtn mtype="submit" @click="saveSystemSetting"> 保存</VBtn>
</div>
</VForm>
</VCardText>
</VCard>
</VCol>
<!-- 加速 -->
<VCol cols="12">
<VCard>
<VCardItem>
<VCardTitle>Github PIP</VCardTitle>
<VCardSubtitle>设置PIP加速站Github加速站Github Token增加连接的限流阈值保证连通性</VCardSubtitle>
</VCardItem>
<VCardText>
<VForm>
<VRow>
<VCol cols="12" md="6">
<VCombobox
v-model="SystemSettings.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.GITHUB_TOKEN"
label="Github Token"
placeholder="ghp_**** 或 github_pat_****"
hint="用于提高Github API访问限流阈值"
persistent-hint
clearable
active
>
</VTextField>
</VCol>
<VCol cols="12" md="6">
<VCombobox
v-model="SystemSettings.PIP_PROXY"
label="PIP加速站"
placeholder="格式https://pypi.tuna.tsinghua.edu.cn/simple"
hint="留空则不使用;预设部分可选站点,也可手动输入自建站点,格式: https://pypi.tuna.tsinghua.edu.cn/simple"
persistent-hint
clearable
:items="pipMirrorsItems"
active
/>
</VCol>
</VRow>
</VForm>
</VCardText>
<VCardText>
<VForm @submit.prevent="() => {}">
<div class="d-flex flex-wrap gap-4 mt-4">
<VBtn mtype="submit" @click="saveSystemSetting"> 保存</VBtn>
</div>
</VForm>
</VCardText>
</VCard>
</VCol>
<!-- 高级设置弹窗 -->
<VDialog v-model="isAdvanced" scrollable max-width="40rem" persistent>
<VCard>
<VCardItem>
<VCardTitle>网络高级设置</VCardTitle>
<VCardSubtitle>修改前请先了解清除这些设置的作用</VCardSubtitle>
</VCardItem>
<DialogCloseBtn @click="closeAdvancedSettings"/>
<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_SERVER"
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">
<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>
</VCardActions>
</VCard>
</VDialog>
</VRow>
</template>

View File

@@ -0,0 +1,124 @@
<script lang="ts" setup>
import { useToast } from 'vue-toast-notification'
import api from '@/api'
import type { ScheduleInfo } from '@/api/types'
// 提示框
const $toast = useToast()
// 定时服务列表
const schedulerList = ref<ScheduleInfo[]>([])
// 定时器
let refreshTimer: NodeJS.Timeout | null = null
// 调用API加载定时服务列表
async function loadSchedulerList() {
try {
const res: ScheduleInfo[] = await api.get('dashboard/schedule')
schedulerList.value = res
} catch (e) {
console.log(e)
}
}
// 任务状态颜色
function getSchedulerColor(status: string) {
switch (status) {
case '正在运行':
return 'success'
case '已停止':
return 'error'
case '等待':
return ''
default:
return ''
}
}
// 执行命令
function runCommand(id: string) {
try {
// 异步提交
api.get('system/runscheduler', {
params: {
jobid: id,
},
})
$toast.success('定时作业执行请求提交成功!')
// 1秒后刷新数据
setTimeout(() => {
loadSchedulerList()
}, 1000)
} catch (e) {
console.log(e)
}
}
onMounted(() => {
loadSchedulerList()
// 启动定时器
refreshTimer = setInterval(() => {
loadSchedulerList()
}, 5000)
})
// 组件卸载时停止定时器
onUnmounted(() => {
if (refreshTimer) {
clearInterval(refreshTimer)
refreshTimer = null
}
})
</script>
<template>
<VCard>
<VCardItem>
<VCardTitle>定时作业</VCardTitle>
<VCardSubtitle>包含系统内置服务以及插件提供的服务手动执行不会影响作业正常的时间表</VCardSubtitle>
</VCardItem>
<VTable class="text-no-wrap">
<thead>
<tr>
<th scope="col">提供者</th>
<th scope="col">任务名称</th>
<th scope="col">任务状态</th>
<th scope="col">下一次执行时间</th>
<th scope="col" />
</tr>
</thead>
<tbody>
<tr v-for="scheduler in schedulerList" :key="scheduler.id">
<td>
{{ scheduler.provider }}
</td>
<td>
{{ scheduler.name }}
</td>
<td>
<VChip :color="getSchedulerColor(scheduler.status)">
{{ scheduler.status }}
</VChip>
</td>
<td>
{{ scheduler.next_run }}
</td>
<td>
<VBtn size="small" :disabled="scheduler.status === '正在运行'" @click="runCommand(scheduler.id)">
<template #prepend>
<VIcon>mdi-play</VIcon>
</template>
执行
</VBtn>
</td>
</tr>
<tr v-if="schedulerList.length === 0">
<td colspan="4" class="text-center">没有后台服务</td>
</tr>
</tbody>
</VTable>
</VCard>
</template>

View File

@@ -1,124 +1,356 @@
<!-- 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 type { ScheduleInfo } from '@/api/types'
import { DownloaderConf, MediaServerConf } from '@/api/types'
import DownloaderCard from '@/components/cards/DownloaderCard.vue'
import MediaServerCard from '@/components/cards/MediaServerCard.vue'
import debounce from 'lodash/debounce'
// 防抖时间
const debounceTime = 500
// 系统设置项
const SystemSettings = ref<any>({
MEDIASERVER_SYNC_INTERVAL: 6,
})
// 是否发送请求的总开关
const isRequest = ref(true)
// 选中的媒体服务器
const mediaServers = ref<MediaServerConf[]>([])
// 下载器
const downloaders = ref<DownloaderConf[]>([])
// 提示框
const $toast = useToast()
// 定时服务列表
const schedulerList = ref<ScheduleInfo[]>([])
// 定时器
let refreshTimer: NodeJS.Timeout | null = null
// 调用API加载定时服务列表
async function loadSchedulerList() {
// 调用API查询下载器设置
async function loadDownloaderSetting() {
try {
const res: ScheduleInfo[] = await api.get('dashboard/schedule')
schedulerList.value = res
} catch (e) {
console.log(e)
const result: { [key: string]: any } = await api.get('system/setting/Downloaders')
downloaders.value = result.data?.value ?? []
} catch (error) {
console.log(error)
}
}
// 任务状态颜色
function getSchedulerColor(status: string) {
switch (status) {
case '正在运行':
return 'success'
case '已停止':
return 'error'
case '等待':
return ''
default:
return ''
}
}
// 执行命令
function runCommand(id: string) {
// 重载系统生效配置
async function reloadSystem() {
try {
// 异步提交
api.get('system/runscheduler', {
params: {
jobid: id,
},
})
$toast.success('定时作业执行请求提交成功!')
// 1秒后刷新数据
setTimeout(() => {
loadSchedulerList()
}, 1000)
} catch (e) {
console.log(e)
const result: { [key: string]: any } = await api.get('system/reload')
if (result.success) $toast.success('系统配置已生效')
else $toast.error('重载系统失败!')
} catch (error) {
console.log(error)
}
}
onMounted(() => {
loadSchedulerList()
// 调用API保存下载器设置
const saveDownloaderSetting = debounce(async () => {
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('下载器设置保存失败!')
// 启动定时器
refreshTimer = setInterval(() => {
loadSchedulerList()
}, 5000)
await loadDownloaderSetting()
await reloadSystem()
} catch (error) {
console.log(error)
}
}, debounceTime)
// 处理默认下载器状态
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保存媒体服务器设置
const saveMediaServerSetting = debounce(async () => {
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)
}
}, debounceTime)
// 加载系统设置
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保存系统设置
const saveSystemSetting = debounce(async () => {
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)
}
}, debounceTime)
// 添加下载器
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) {
const index = downloaders.value.findIndex(item => item.name === downloader.name)
downloaders.value[index] = downloader
}
// 添加媒体服务器
const addMediaServer = debounce( (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: {},
})
}, debounceTime)
// 删除媒体服务器
function removeMediaServer(ele: MediaServerConf) {
const index = mediaServers.value.indexOf(ele)
mediaServers.value.splice(index, 1)
}
// 变更媒体服务器
function onMediaServerChange(mediaserver: MediaServerConf) {
const index = mediaServers.value.findIndex(item => item.name === mediaserver.name)
mediaServers.value[index] = mediaserver
}
// 禁止保存
const isSystemSettingsSaveDisabled = computed(() => {
return SystemSettings.value.MEDIASERVER_SYNC_INTERVAL < 1
})
// 组件卸载时停止定时器
onUnmounted(() => {
if (refreshTimer) {
clearInterval(refreshTimer)
refreshTimer = null
}
// 加载数据
onMounted(() => {
loadDownloaderSetting()
loadMediaServerSetting()
loadSystemSettings()
})
onActivated(async () => {
isRequest.value = true
})
onDeactivated(() => {
isRequest.value = false
})
</script>
<template>
<VCard>
<VCardItem>
<VCardTitle>定时作业</VCardTitle>
<VCardSubtitle>包含系统内置服务以及插件提供的服务手动执行不会影响作业正常的时间表</VCardSubtitle>
</VCardItem>
<VTable class="text-no-wrap">
<thead>
<tr>
<th scope="col">提供者</th>
<th scope="col">任务名称</th>
<th scope="col">任务状态</th>
<th scope="col">下一次执行时间</th>
<th scope="col" />
</tr>
</thead>
<tbody>
<tr v-for="scheduler in schedulerList" :key="scheduler.id">
<td>
{{ scheduler.provider }}
</td>
<td>
{{ scheduler.name }}
</td>
<td>
<VChip :color="getSchedulerColor(scheduler.status)">
{{ scheduler.status }}
</VChip>
</td>
<td>
{{ scheduler.next_run }}
</td>
<td>
<VBtn size="small" :disabled="scheduler.status === '正在运行'" @click="runCommand(scheduler.id)">
<template #prepend>
<VIcon>mdi-play</VIcon>
</template>
执行
</VBtn>
</td>
</tr>
<tr v-if="schedulerList.length === 0">
<td colspan="4" class="text-center">没有后台服务</td>
</tr>
</tbody>
</VTable>
</VCard>
<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="width: fit-content"
:rules="[
v => !!v || '必选项,请勿留空',
v => !isNaN(v) || '仅支持输入数字,请勿输入其他字符',
v => 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" :disabled="isSystemSettingsSaveDisabled"> 保存 </VBtn>
</div>
</VForm>
</VCardText>
</VCard>
</VCol>
<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>

View File

@@ -1,106 +1,68 @@
<!-- 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({
APP_DOMAIN: '',
// 系统设置默认值
const SystemSettings = ref<any>({
// 系统设置
Basis: {
// 基础设置
AUXILIARY_AUTH_ENABLE: false,
GLOBAL_IMAGE_CACHE: false,
APP_DOMAIN: '',
API_TOKEN: '',
WALLPAPER: 'tmdb',
PLUGIN_MARKET: '',
},
// 高级系统设置
Advanced: {
DEV: false,
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 isRequest = ref(true)
// 防抖时间
const debounceTime = 500
// 选中的媒体服务器
const mediaServers = ref<MediaServerConf[]>([])
// 下载器
const downloaders = ref<DownloaderConf[]>([])
// 高级设置弹窗
const isAdvancedSystemSettingsDialogOpen = ref(false)
const isAdvancedNetworkSettingsDialogOpen = ref(false)
// 提示框
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('下载器设置保存失败!')
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('媒体服务器设置保存失败!')
loadMediaServerSetting()
await reloadSystem()
if (result.success) {
$toast.success('系统配置已生效')
await loadSystemSettings()
} else $toast.error('重载系统失败!')
} catch (error) {
console.log(error)
}
@@ -111,114 +73,199 @@ async function loadSystemSettings() {
try {
const result: { [key: string]: any } = await api.get('system/env')
if (result.success) {
const { APP_DOMAIN } = result.data
SystemSettings.value = {
APP_DOMAIN,
// 将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
}
})
}
}
} catch (error) {
console.log(error)
$toast.error('系统设置加载失败')
}
}
// 调用API保存系统设置
async function saveSystemSetting() {
// 调用API保存设置
async function saveSystemSetting(value: { [key: string]: any }) {
console.log(`正在保存设置:${JSON.stringify(value)}`)
try {
const result: { [key: string]: any } = await api.post('system/env', SystemSettings.value)
const result: { [key: string]: any } = await api.post('system/env', value)
if (result.success) $toast.success('保存设置成功')
else $toast.error('保存设置失败!')
if (result.success) {
$toast.success('保存设置成功')
await reloadSystem()
await loadSystemSettings()
}
} catch (error) {
console.log(error)
$toast.error('保存设置失败!')
}
}
// 保存系统设置
const saveSystemSettings = debounce(async () => {
const Settings = { ...SystemSettings.value.Basis, ...SystemSettings.value.Advanced }
await saveSystemSetting(Settings)
}, debounceTime)
// 保存网络设置
const saveNetworkSettings = debounce(async () => {
const Settings = { ...SystemSettings.value.Network, ...SystemSettings.value.AdvancedNetwork }
// 查找PROXY_HOST并删除避免意外覆盖
if (Settings.PROXY_HOST) delete Settings.PROXY_HOST
await saveSystemSetting(Settings)
}, debounceTime)
// 高级设置变化,等待保存
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]
}
}
$toast.info('高级设置已更改,待保存后生效')
console.log(`保存后的SystemSettings${JSON.stringify(SystemSettings.value[key])}`)
}
// 快捷复制到剪贴板
function copyValue(value: string) {
try {
copyToClipboard(value)
$toast.success('已复制到剪贴板')
} catch (error) {
$toast.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: {},
})
// 登录首页壁纸来源
const wallpaperItems = [
{ title: 'TheMovieDB电影海报', value: 'tmdb' },
{ title: 'Bing每日壁纸', value: 'bing' },
]
// 创建随机字符串
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('')
}
// 删除下载器
function removeDownloader(ele: DownloaderConf) {
const index = downloaders.value.indexOf(ele)
downloaders.value.splice(index, 1)
}
// 预设部分Github加速站
const githubMirrorsItems = [
'https://mirror.ghproxy.com/', // GitHub Proxy
'https://ghp.ci/', // GitHub Proxy 子站
]
// 下载器变化
function onDownloaderChange(downloader: DownloaderConf) {
const index = downloaders.value.findIndex(item => item.name === downloader.name)
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)
mediaServers.value.splice(index, 1)
}
// 变更媒体服务器
function onMediaServerChange(mediaserver: MediaServerConf) {
const index = mediaServers.value.findIndex(item => item.name === mediaserver.name)
mediaServers.value[index] = mediaserver
}
// 预设部分PIP镜像站
const pipMirrorsItems = [
'https://pypi.tuna.tsinghua.edu.cn/simple', // 清华大学
'https://pypi.mirrors.ustc.edu.cn/simple', // 中国科技大学
'https://mirrors.pku.edu.cn/pypi/web/simple', // 北京大学
'https://mirrors.aliyun.com/pypi/simple', // 阿里云
'https://mirrors.cloud.tencent.com/pypi/simple', // 腾讯云
'https://mirrors.163.com/pypi/simple', // 网易云
'https://pypi.doubanio.com/simple', // 豆瓣
'https://mirrors.hust.edu.cn/pypi/web/simple', // 华中理工大学
'https://mirrors.bfsu.edu.cn/pypi/web/simple', // 北京外国语大学
]
// 加载数据
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="进入高级系统设置页面"
persistent-hint
/>
</VCol>
<VCol cols="12" md="3">
<VSelect
v-model="SystemSettings.Basis.WALLPAPER"
label="登录首页壁纸"
hint="选择登陆页面背景来源"
persistent-hint
:items="wallpaperItems"
/>
</VCol>
<VCol cols="12" md="6">
<VTextField
v-model="SystemSettings.APP_DOMAIN"
v-model="SystemSettings.Basis.APP_DOMAIN"
label="访问域名"
hint="用于通知跳转,格式http(s)://domain:port"
placeholder="格式http(s)://domain:port"
hint="用于发送通知时,添加快捷跳转地址"
persistent-hint
clearable
:appendInnerIcon="SystemSettings.Basis.APP_DOMAIN ? 'mdi-content-copy' : ''"
@click:appendInner="SystemSettings.Basis.APP_DOMAIN && copyValue(SystemSettings.Basis.APP_DOMAIN)"
/>
</VCol>
<VCol cols="12" md="6">
<VTextField
v-model="SystemSettings.Basis.API_TOKEN"
label="API Token"
hint="不得低于16位用于Jellyseerr/Overseerr、媒体服务器Webhook等配置以及部分支持API_TOKEN的API请求"
persistent-hint
clearable
prependInnerIcon="mdi-reload"
:appendInnerIcon="SystemSettings.Basis.API_TOKEN ? 'mdi-content-copy' : ''"
@click:prependInner="createRandomString"
@click:appendInner="SystemSettings.Basis.API_TOKEN && copyValue(SystemSettings.Basis.API_TOKEN)"
:rules="[(v: string) => !!v || '必填项请输入API Token', (v: string) => v.length >= 16 || 'API Token不得低于16位']"
/>
</VCol>
<VCol cols="12">
<VTextarea
v-model="SystemSettings.Basis.PLUGIN_MARKET"
label="插件市场"
placeholder="格式https://github.com/jxxghp/MoviePilot-Plugins/,https://github.com/xxxx/xxxxxx/"
hint="插件市场仓库地址,多个地址使用逗号分隔,确保每个地址以/结尾仅支持Github仓库"
persistent-hint
clearable
/>
</VCol>
</VRow>
@@ -227,109 +274,162 @@ onDeactivated(() => {
<VCardText>
<VForm @submit.prevent="() => {}">
<div class="d-flex flex-wrap gap-4 mt-4">
<VBtn mtype="submit" @click="saveSystemSetting"> 保存 </VBtn>
</div>
</VForm>
</VCardText>
</VCard>
</VCol>
<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 mtype="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>
<VBtn type="submit" @click="saveSystemSettings"> 保存 </VBtn>
</div>
</VForm>
</VCardText>
</VCard>
</VCol>
</VRow>
<VRow>
<VCol cols="12">
<VCard>
<VCardItem>
<VCardTitle>媒体服务器</VCardTitle>
<VCardSubtitle>所有启用的媒体服务器都会被使用</VCardSubtitle>
<VCardTitle>基础网络设置</VCardTitle>
<VCardSubtitle>设置DOHPIP加速站Github加速站Github Token等增加网络稳定性保证连通性</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>
<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"
label="Github Token"
placeholder="ghp_**** 或 github_pat_****"
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"
persistent-hint
clearable
:items="pipMirrorsItems"
active
/>
</VCol>
</VRow>
</VForm>
</VCardText>
<VCardText>
<VForm @submit.prevent="() => {}">
<div class="d-flex flex-wrap gap-4 mt-4">
<VBtn mtype="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>
<VBtn type="submit" @click="saveNetworkSettings"> 保存 </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"
/>
<!-- 高级网络设置 -->
<AdvancedNetworkSettingsDialog
v-if="isAdvancedNetworkSettingsDialogOpen"
v-model="isAdvancedNetworkSettingsDialogOpen"
max-width="60rem"
persistent
z-index="1010"
:AdvancedNetworkSettings="SystemSettings.AdvancedNetwork"
@close="isAdvancedNetworkSettingsDialogOpen = false"
@change="saveAdvancedSettings"
/>
</template>