From 0a5b553bb84dc58a7f7be83b19e6dd0fa2a56856 Mon Sep 17 00:00:00 2001 From: Aqr-K <95741669+Aqr-K@users.noreply.github.com> Date: Thu, 24 Oct 2024 17:16:47 +0800 Subject: [PATCH 01/21] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=20network=20?= =?UTF-8?q?=E6=A0=87=E7=AD=BE=E9=A1=B5=EF=BC=8C=E8=B0=83=E6=95=B4=E6=A0=87?= =?UTF-8?q?=E7=AD=BE=E5=AF=B9=E9=BD=90=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/setting.vue | 12 +- src/router/menu.ts | 6 + src/views/setting/AccountSettingNetwork.vue | 262 ++++++++++++++++++++ 3 files changed, 279 insertions(+), 1 deletion(-) create mode 100644 src/views/setting/AccountSettingNetwork.vue diff --git a/src/pages/setting.vue b/src/pages/setting.vue index 9fc87a16..6d704dd7 100644 --- a/src/pages/setting.vue +++ b/src/pages/setting.vue @@ -11,6 +11,7 @@ import AccountSettingService from '@/views/setting/AccountSettingService.vue' import AccountSettingSystem from '@/views/setting/AccountSettingSystem.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() @@ -32,7 +33,7 @@ function jumpTab(tab: string) { @click="jumpTab(item.tab)" selected-class="v-slide-group-item--active v-tab--selected" > -
+
{{ item.title }}
@@ -49,6 +50,15 @@ function jumpTab(tab: string) { + + + +
+ +
+
+
+ diff --git a/src/router/menu.ts b/src/router/menu.ts index 38b48bd0..675fece9 100644 --- a/src/router/menu.ts +++ b/src/router/menu.ts @@ -133,6 +133,12 @@ export const SettingTabs = [ tab: 'system', description: '下载器(Qbittorrent、Transmission)、媒体服务器(Emby、Jellyfin、Plex)', }, + { + title: '网络', + icon: 'mdi-access-point-network', + tab: 'network', + description: 'Github、PIP、OCR、DOH', + }, { title: '存储 & 目录', icon: 'mdi-folder', diff --git a/src/views/setting/AccountSettingNetwork.vue b/src/views/setting/AccountSettingNetwork.vue new file mode 100644 index 00000000..568e5c7d --- /dev/null +++ b/src/views/setting/AccountSettingNetwork.vue @@ -0,0 +1,262 @@ + + + From c72fcbd10d147b0e4aa53f36acc0472f1c9f1309 Mon Sep 17 00:00:00 2001 From: Aqr-K <95741669+Aqr-K@users.noreply.github.com> Date: Fri, 25 Oct 2024 11:31:10 +0800 Subject: [PATCH 02/21] =?UTF-8?q?feat:=20=E7=BD=91=E7=BB=9C=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E9=AB=98=E7=BA=A7=E8=AE=BE=E7=BD=AE=E5=BC=B9=E7=AA=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/setting/AccountSettingNetwork.vue | 168 +++++++++++++++----- 1 file changed, 128 insertions(+), 40 deletions(-) diff --git a/src/views/setting/AccountSettingNetwork.vue b/src/views/setting/AccountSettingNetwork.vue index 568e5c7d..859b5324 100644 --- a/src/views/setting/AccountSettingNetwork.vue +++ b/src/views/setting/AccountSettingNetwork.vue @@ -1,25 +1,32 @@ @@ -149,7 +166,7 @@ onDeactivated(() => { - +
{
- 保存 + 保存
@@ -232,7 +249,8 @@ onDeactivated(() => { persistent-hint clearable active - /> + > + {
- 保存 + 保存
+ + + + + + 网络高级设置 + 修改前,请先了解清除这些设置的作用。 + + + + + + + + + + + + + + + + + + + + + 默认值 + + + 确定 + + + + From bd9169bcd1c74a52b785fd189dba6f8dbcae164f Mon Sep 17 00:00:00 2001 From: Aqr-K <95741669+Aqr-K@users.noreply.github.com> Date: Thu, 31 Oct 2024 03:38:01 +0800 Subject: [PATCH 03/21] feat(settings): add new AccountSettingsSystem.vue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将 原system更名为service,原service更名为scheduler - 增加新的 AccountSettingSystem.vue。 - 调整 menu.ts 与settings.vue,适配新的 system 标签页 --- src/pages/setting.vue | 14 +- src/router/menu.ts | 18 +- src/views/setting/AccountSettingNetwork.vue | 350 ----------- src/views/setting/AccountSettingScheduler.vue | 124 ++++ src/views/setting/AccountSettingService.vue | 434 ++++++++++--- src/views/setting/AccountSettingSystem.vue | 584 ++++++++++-------- 6 files changed, 815 insertions(+), 709 deletions(-) delete mode 100644 src/views/setting/AccountSettingNetwork.vue create mode 100644 src/views/setting/AccountSettingScheduler.vue diff --git a/src/pages/setting.vue b/src/pages/setting.vue index 6d704dd7..43899608 100644 --- a/src/pages/setting.vue +++ b/src/pages/setting.vue @@ -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) { - +
@@ -50,11 +50,11 @@ function jumpTab(tab: string) { - - + +
- +
@@ -103,10 +103,10 @@ function jumpTab(tab: string) {
- +
- +
diff --git a/src/router/menu.ts b/src/router/menu.ts index f5ebe33b..807570e6 100644 --- a/src/router/menu.ts +++ b/src/router/menu.ts @@ -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: '定时作业', }, { diff --git a/src/views/setting/AccountSettingNetwork.vue b/src/views/setting/AccountSettingNetwork.vue deleted file mode 100644 index 859b5324..00000000 --- a/src/views/setting/AccountSettingNetwork.vue +++ /dev/null @@ -1,350 +0,0 @@ - - - diff --git a/src/views/setting/AccountSettingScheduler.vue b/src/views/setting/AccountSettingScheduler.vue new file mode 100644 index 00000000..30b0aa1f --- /dev/null +++ b/src/views/setting/AccountSettingScheduler.vue @@ -0,0 +1,124 @@ + + + diff --git a/src/views/setting/AccountSettingService.vue b/src/views/setting/AccountSettingService.vue index 30b0aa1f..bde3a50e 100644 --- a/src/views/setting/AccountSettingService.vue +++ b/src/views/setting/AccountSettingService.vue @@ -1,124 +1,356 @@ + diff --git a/src/views/setting/AccountSettingSystem.vue b/src/views/setting/AccountSettingSystem.vue index 332bd704..b7663404 100644 --- a/src/views/setting/AccountSettingSystem.vue +++ b/src/views/setting/AccountSettingSystem.vue @@ -1,106 +1,68 @@ - From c90ed003f7d560720893e51980fa0f943ffeb283 Mon Sep 17 00:00:00 2001 From: Aqr-K <95741669+Aqr-K@users.noreply.github.com> Date: Thu, 31 Oct 2024 04:15:12 +0800 Subject: [PATCH 04/21] feat(settings): add systemSettingsDialog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 增加 AdvancedNetworkSettingsDialog 与 AdvancedSystemSettingsDialog,适配 system 预设的高级设置弹窗。 --- .../dialog/AdvancedNetworkSettingsDialog.vue | 98 +++++++++++++++++++ .../dialog/AdvancedSystemSettingsDialog.vue | 93 ++++++++++++++++++ 2 files changed, 191 insertions(+) create mode 100644 src/components/dialog/AdvancedNetworkSettingsDialog.vue create mode 100644 src/components/dialog/AdvancedSystemSettingsDialog.vue diff --git a/src/components/dialog/AdvancedNetworkSettingsDialog.vue b/src/components/dialog/AdvancedNetworkSettingsDialog.vue new file mode 100644 index 00000000..c0c69a74 --- /dev/null +++ b/src/components/dialog/AdvancedNetworkSettingsDialog.vue @@ -0,0 +1,98 @@ + + + diff --git a/src/components/dialog/AdvancedSystemSettingsDialog.vue b/src/components/dialog/AdvancedSystemSettingsDialog.vue new file mode 100644 index 00000000..41e15ab5 --- /dev/null +++ b/src/components/dialog/AdvancedSystemSettingsDialog.vue @@ -0,0 +1,93 @@ + + + From eab2f0df20d3b638a1bf4b7afd180ac1e4e2041e Mon Sep 17 00:00:00 2001 From: Aqr-K <95741669+Aqr-K@users.noreply.github.com> Date: Thu, 31 Oct 2024 16:46:05 +0800 Subject: [PATCH 05/21] feat(settings): AccountSettingNotification MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 增加防抖。 - `card` 从父组件获取到的值改为深复制,解决 `card` 内修改数据,会直接导致在父组件中同步更新的问题。 - 微调图标位置。 --- .../cards/NotificationChannelCard.vue | 32 +++++++++---------- .../setting/AccountSettingNotification.vue | 31 ++++++++++++------ 2 files changed, 36 insertions(+), 27 deletions(-) diff --git a/src/components/cards/NotificationChannelCard.vue b/src/components/cards/NotificationChannelCard.vue index 3e767b1a..93af2bde 100644 --- a/src/components/cards/NotificationChannelCard.vue +++ b/src/components/cards/NotificationChannelCard.vue @@ -7,6 +7,7 @@ import synologychat_image from '@images/logos/synologychat.png' import slack_image from '@images/logos/slack.webp' import chrome_image from '@images/logos/chrome.png' import { useToast } from 'vue-toast-notification' +import { cloneDeep } from "lodash" // 定义输入 const props = defineProps({ @@ -31,9 +32,6 @@ const $toast = useToast() // 通知详情弹窗 const notificationInfoDialog = ref(false) -// 通知名称 -const notificationName = ref('') - // 通知详情 const notificationInfo = ref({ name: '', @@ -66,26 +64,26 @@ const notificationTypes = [ // 打开详情弹窗 function openNotificationInfoDialog() { - notificationInfo.value = props.notification - notificationName.value = props.notification.name + // 替换成深复制,避免修改时影响原数据 + notificationInfo.value = cloneDeep(props.notification) + console.log(`当前卡片的通知信息:${JSON.stringify(notificationInfo.value)}`) notificationInfoDialog.value = true } // 保存详情数据 function saveNotificationInfo() { // 为空不保存,跳出警告框 - if (!notificationName.value) { + if (!notificationInfo.value.name) { $toast.error('名称不能为空,请输入后再确定') return } // 重名判断 - if (props.notifications.some(item => item.name === notificationName.value && item !== props.notification)) { - $toast.error(`【${notificationName.value}】已存在,请替换为其他名称`) + if (props.notifications.some(item => item.name === notificationInfo.value.name && item !== props.notification)) { + $toast.error(`通知渠道【${notificationInfo.value.name}】已存在,请替换`) return } notificationInfoDialog.value = false - notificationInfo.value.name = notificationName.value - emit('change', notificationInfo.value) + emit('change', notificationInfo.value, props.notification.name) emit('done') } @@ -131,7 +129,7 @@ function onClose() {
{{ notificationTypeNames[notification.type] }}
- +
@@ -160,7 +158,7 @@ function onClose() { ([]) +// 防抖时间 +const debounceTime = 500 + // 提示框 const $toast = useToast() @@ -58,8 +62,8 @@ async function reloadSystem() { } } -// 添加媒体服务器 -function addNotification(notification: string) { +// 添加通知渠道 +const addNotification = debounce((notification: string) => { let name = `通知${notifications.value.length + 1}`; while (notifications.value.some(item => item.name === name)) { name = `通知${parseInt(name.split('通知')[1]) + 1}`; @@ -70,15 +74,15 @@ function addNotification(notification: string) { enabled: false, config: {}, }) -} +}, debounceTime) -// 移除媒体服务器 +// 移除通知渠道 function removeNotification(notification: NotificationConf) { const index = notifications.value.indexOf(notification) if (index > -1) notifications.value.splice(index, 1) } -// 调用API查询通知设置 +// 调用API查询通知渠道设置 async function loadNotificationSetting() { try { const result: { [key: string]: any } = await api.get('system/setting/Notifications') @@ -89,7 +93,7 @@ async function loadNotificationSetting() { } // 调用API保存通知设置 -async function saveNotificationSetting() { +const saveNotificationSetting = debounce(async () => { try { const result: { [key: string]: any } = await api.post('system/setting/Notifications', notifications.value) if (result.success) { @@ -99,6 +103,12 @@ async function saveNotificationSetting() { } catch (error) { console.log(error) } +}, debounceTime) + +// 通知渠道设置变化时赋值 +function changNotificationSetting(notification: NotificationConf, name: string) { + const index = notifications.value.findIndex(item => item.name === name) + if (index !== -1) notifications.value[index] = notification } // 加载消息类型开关 @@ -112,7 +122,7 @@ async function loadNotificationSwitchs() { } // 保存消息类型开关 -async function saveNotificationSwitchs() { +const saveNotificationSwitchs = debounce(async () => { try { const result: { [key: string]: any } = await api.post( 'system/setting/NotificationSwitchs', @@ -123,7 +133,7 @@ async function saveNotificationSwitchs() { } catch (error) { console.log(error) } -} +}, debounceTime) // 加载数据 onMounted(() => { @@ -152,6 +162,7 @@ onMounted(() => { @@ -161,7 +172,7 @@ onMounted(() => {
保存 - + @@ -225,7 +236,7 @@ onMounted(() => {
- 保存 + 保存
From db0325a59ce4099f32b39689b7e6a96f6ad58027 Mon Sep 17 00:00:00 2001 From: Aqr-K <95741669+Aqr-K@users.noreply.github.com> Date: Thu, 31 Oct 2024 17:08:23 +0800 Subject: [PATCH 06/21] feat(settings): AccountSettingRule MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 增加防抖。 - `card` 从父组件获取到的值改为深复制,解决 `card` 内修改数据,会直接导致在父组件中同步更新的问题。 - 修复 规则id与name 只缺少一项时,仍能正常确定的问题。 - 保存前增加一次检查,避免通过分享导入的规则存在重名与空名引发的错误。 --- src/components/cards/CustomRuleCard.vue | 38 ++++------- src/components/cards/FilterRuleGroupCard.vue | 30 ++++---- src/views/setting/AccountSettingRule.vue | 72 +++++++++++++++----- 3 files changed, 81 insertions(+), 59 deletions(-) diff --git a/src/components/cards/CustomRuleCard.vue b/src/components/cards/CustomRuleCard.vue index 83ed6863..87682acf 100644 --- a/src/components/cards/CustomRuleCard.vue +++ b/src/components/cards/CustomRuleCard.vue @@ -2,6 +2,7 @@ import { CustomRule } from '@/api/types' import { useToast } from 'vue-toast-notification' import filter_svg from '@images/svg/filter.svg' +import { cloneDeep } from 'lodash' // 输入参数 const props = defineProps({ @@ -37,50 +38,41 @@ const ruleInfo = ref({ publish_time: '', }) -// 规则ID -const ruleId = ref('') - -// 规则名称 -const ruleName = ref('') - // 打开详情弹窗 function openRuleInfoDialog() { - ruleInfo.value = props.rule - ruleId.value = props.rule.id - ruleName.value = props.rule.name + // 深复制 + ruleInfo.value = cloneDeep(props.rule) ruleInfoDialog.value = true } // 保存详情数据 function saveRuleInfo() { // 有空值 - if (!ruleId.value && !ruleName.value) { - if (!ruleId.value && ruleName.value) { + if (!ruleInfo.value.id || !ruleInfo.value.name) { + if (!ruleInfo.value.id && ruleInfo.value.name) { $toast.error('规则ID不能为空') } - if (ruleId.value && !ruleName.value) { + if (ruleInfo.value.id && !ruleInfo.value.name) { $toast.error('规则名称不能为空') } - if (!ruleId.value && !ruleName.value) { + if (!ruleInfo.value.id && !ruleInfo.value.name) { $toast.error('规则ID和规则名称不能为空') } return } // ID已存在 - if (ruleId.value !== props.rule.id && props.rules.find(rule => rule.id === ruleId.value)) { - $toast.error(`规则ID【${ruleId.value}】已存在,请替换`) + if (ruleInfo.value.id !== props.rule.id && props.rules.find(rule => rule.id === ruleInfo.value.id)) { + $toast.error(`规则ID【${ruleInfo.value.id}】已存在,请替换`) return } // 规则名称已存在 - if (ruleName.value !== props.rule.name && props.rules.find(rule => rule.name === ruleName.value)) { - $toast.error(`规则名称【${ruleName.value}】已存在,请替换`) + if (ruleInfo.value.name !== props.rule.name && props.rules.find(rule => rule.name === ruleInfo.value.name)) { + $toast.error(`规则名称【${ruleInfo.value.name}】已存在,请替换`) return } // 保存数据 ruleInfoDialog.value = false - ruleInfo.value.id = ruleId.value - ruleInfo.value.name = ruleName.value - emit('change', ruleInfo.value) + emit('change', ruleInfo.value, props.rule.id) emit('done') } @@ -107,7 +99,7 @@ function onClose() { - + @@ -116,7 +108,7 @@ function onClose() { ({ category: props.group?.category, }) -// 规则组名称 -const groupName = ref('') - // 媒体类型字典 const mediaTypeItems = [ { title: '通用', value: '' }, @@ -88,15 +86,12 @@ function updateFilterCardValue(pri: string, rules: string[]) { // 移除卡片 function filterCardClose(pri: string) { - // 将pri对应的卡片从列表中删除,并更新剩余卡片的序号 - const updatedCards = filterRuleCards.value + filterRuleCards.value = filterRuleCards.value .filter(card => card.pri !== pri) .map((card, index) => { card.pri = (index + 1).toString() return card }) - // 更新 filterRuleCards.value - filterRuleCards.value = updatedCards } // 分享规则 @@ -163,8 +158,8 @@ function dragOrderEnd() { // 打开详情弹窗 function opengroupInfoDialog() { - groupInfo.value = props.group - groupName.value = props.group.name + // 深复制 + groupInfo.value = cloneDeep(props.group) if (props.group.rule_string) { filterRuleCards.value = props.group.rule_string.split('>').map((group: string, index: number) => { return { @@ -177,26 +172,25 @@ function opengroupInfoDialog() { } // 保存详情数据 -function savegroupInfo() { +function saveGroupInfo() { // 为空 - if (!groupName.value) { + if (!groupInfo.value.name) { $toast.error('规则组名称不能为空') return } // 重名判断 - if (props.groups.some(item => item.name === groupName.value && item !== props.group)) { - $toast.error(`规则组名称【${groupName.value}】已存在,请替换`) + if (props.groups.some(item => item.name === groupInfo.value.name && item !== props.group)) { + $toast.error(`规则组名称【${groupInfo.value.name}】已存在,请替换`) return } // 保存 groupInfoDialog.value = false - groupInfo.value.name = groupName.value // 更新到 groupInfo的rule_string groupInfo.value.rule_string = filterRuleCards.value .filter(card => card.rules.length > 0) .map(card => card.rules.join('&')) .join('>') - emit('change', groupInfo.value) + emit('change', groupInfo.value, props.group.name) emit('done') } @@ -226,7 +220,7 @@ function onClose() { - + @@ -234,7 +228,7 @@ function onClose() {
- 确定 + 确定 diff --git a/src/views/setting/AccountSettingRule.vue b/src/views/setting/AccountSettingRule.vue index 5db32609..97be59a5 100644 --- a/src/views/setting/AccountSettingRule.vue +++ b/src/views/setting/AccountSettingRule.vue @@ -9,6 +9,10 @@ import { CustomRule, FilterRuleGroup } from '@/api/types' import CustomerRuleCard from '@/components/cards/CustomRuleCard.vue' import FilterRuleGroupCard from '@/components/cards/FilterRuleGroupCard.vue' import ImportCodeDialog from '@/components/dialog/ImportCodeDialog.vue' +import debounce from 'lodash/debounce' + +// 防抖时间 +const debounceTime = 500 // 自定义规则列表 const customRules = ref([]) @@ -52,7 +56,29 @@ async function loadMediaCategories() { } // 保存自定义规则 -async function saveCustomRules() { +const saveCustomRules = debounce(async () => { + // 检查是否存在空id规则 + if (customRules.value.some(item => !item.id)) { + $toast.error('存在空ID的规则!无法保存,请修改!') + return + } + // 检查是否存在空的规则名称 + if (customRules.value.some(item => !item.name)) { + $toast.error('存在空名字的规则!无法保存,请修改!') + return + } + // 检查是否存在重名的规则ID + const ids = customRules.value.map(item => item.id) + if (new Set(ids).size !== ids.length) { + $toast.error('存在重复规则ID!无法保存,请修改!') + return + } + // 检查是否存在重名规 + const names = customRules.value.map(item => item.name) + if (new Set(names).size !== names.length) { + $toast.error('存在重复规则名称!无法保存,请修改!') + return + } try { const result: { [key: string]: any } = await api.post('system/setting/CustomFilterRules', customRules.value) if (result.success) $toast.success('自定义规则保存成功') @@ -60,10 +86,10 @@ async function saveCustomRules() { } catch (error) { console.log(error) } -} +}, debounceTime) // 添加自定义规则 -function addCustomRule() { +const addCustomRule = debounce(async () => { let id = `RULE${customRules.value.length + 1}` while (customRules.value.some(item => item.id === id)) { id = `RULE${parseInt(id.split('RULE')[1]) + 1}` @@ -78,7 +104,7 @@ function addCustomRule() { include: '', exclude: '', }) -} +}, debounceTime) // 移除自定义规则 function removeCustomRule(rule: CustomRule) { @@ -97,7 +123,18 @@ async function queryFilterRuleGroups() { } // 保存规则组 -async function saveFilterRuleGroups() { +const saveFilterRuleGroups = debounce(async () => { + // 检查是否存在空的规则组名称 + if (filterRuleGroups.value.some(item => !item.name)) { + $toast.error('存在空名字的规则组!无法保存,请修改!') + return + } + // 检查是否存在重名规则组 + const names = filterRuleGroups.value.map(item => item.name) + if (new Set(names).size !== names.length) { + $toast.error('存在重复规则组名称!无法保存,请修改!') + return + } try { const result: { [key: string]: any } = await api.post('system/setting/UserFilterRuleGroups', filterRuleGroups.value) if (result.success) $toast.success('优先级规则组保存成功') @@ -105,10 +142,10 @@ async function saveFilterRuleGroups() { } catch (error) { console.log(error) } -} +}, debounceTime) // 添加规则组 -function addFilterRuleGroup() { +const addFilterRuleGroup = debounce(() => { let name = `规则组${filterRuleGroups.value.length + 1}` while (filterRuleGroups.value.some(item => item.name === name)) { name = `规则组${parseInt(name.split('规则组')[1]) + 1}` @@ -119,10 +156,11 @@ function addFilterRuleGroup() { media_type: '', category: '', }) -} +}, debounceTime) // 分享规则 -function shareRules(rules: CustomRule[] | FilterRuleGroup[]) { +// function shareRules(rules: CustomRule[] | FilterRuleGroup[]) { +const shareRules = debounce((rules: CustomRule[] | FilterRuleGroup[]) => { if (!rules || rules.length === 0) return // 将卡片规则接装为字符串 @@ -135,7 +173,7 @@ function shareRules(rules: CustomRule[] | FilterRuleGroup[]) { } catch (error) { $toast.error('优先级规则复制失败!') } -} +}, debounceTime) // 导入规则 async function importRules(ruleType: string) { @@ -179,8 +217,8 @@ watchEffect(() => { }) // 规则变化时赋值 -function onRuleChange(rule: CustomRule) { - const index = customRules.value.findIndex(item => item.id === rule.id) +function onRuleChange(rule: CustomRule, id: string) { + const index = customRules.value.findIndex(item => item.id === id) if (index !== -1) customRules.value[index] = rule } @@ -191,8 +229,8 @@ function removeFilterRuleGroup(rule: FilterRuleGroup) { } // 规则组变化时赋值 -function changeRuleGroup(group: FilterRuleGroup) { - const index = filterRuleGroups.value.findIndex(item => item.name === group.name) +function changeRuleGroup(group: FilterRuleGroup, name: string) { + const index = filterRuleGroups.value.findIndex(item => item.name === name) if (index !== -1) filterRuleGroups.value[index] = group } @@ -218,20 +256,18 @@ async function queryCustomRules() { } // 保存种子优先规则 -async function saveTorrentPriority() { +const saveTorrentPriority = debounce(async () => { try { - // 用户名密码 const result: { [key: string]: any } = await api.post( 'system/setting/TorrentsPriority', selectedTorrentPriority.value, ) - if (result.success) $toast.success('优先规则保存成功') else $toast.error('优先规则保存失败!') } catch (error) { console.log(error) } -} +}, debounceTime) // 加载数据 onMounted(() => { From 6bc420d57f215caebccd6ea2f9b2cb03ff16d600 Mon Sep 17 00:00:00 2001 From: Aqr-K <95741669+Aqr-K@users.noreply.github.com> Date: Thu, 31 Oct 2024 17:53:45 +0800 Subject: [PATCH 07/21] feat(settings): AccountSettingService MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 增加防抖。 - `card` 从父组件获取到的值改为深复制,解决 `card` 内修改数据,会直接导致在父组件中同步更新的问题。 --- src/components/cards/DownloaderCard.vue | 23 +++++++++------------ src/components/cards/MediaServerCard.vue | 23 +++++++++------------ src/views/setting/AccountSettingService.vue | 22 ++++++++++---------- 3 files changed, 31 insertions(+), 37 deletions(-) diff --git a/src/components/cards/DownloaderCard.vue b/src/components/cards/DownloaderCard.vue index 0c8e3196..01f27ada 100644 --- a/src/components/cards/DownloaderCard.vue +++ b/src/components/cards/DownloaderCard.vue @@ -6,6 +6,7 @@ import { useToast } from 'vue-toast-notification' import type { DownloaderInfo } from '@/api/types' import qbittorrent_image from '@images/logos/qbittorrent.png' import transmission_image from '@images/logos/transmission.png' +import {cloneDeep} from "lodash"; // 定义输入 const props = defineProps({ @@ -44,9 +45,6 @@ const download_rate = ref(0) // 下载器详情弹窗 const downloaderInfoDialog = ref(false) -// 下载器名称 -const downloaderName = ref('') - // 下载器详情 const downloaderInfo = ref({ name: '', @@ -84,21 +82,21 @@ async function loadDownloaderInfo() { // 打开详情弹窗 function openDownloaderInfoDialog() { - downloaderInfo.value = props.downloader - downloaderName.value = props.downloader.name + // 深复制 + downloaderInfo.value = cloneDeep(props.downloader) downloaderInfoDialog.value = true } // 保存详情数据 function saveDownloaderInfo() { // 为空不保存,跳出警告框 - if (!downloaderName.value) { + if (!downloaderInfo.value.name) { $toast.error('名称不能为空,请输入后再确定') return } // 重名判断 - if (props.downloaders.some(item => item.name === downloaderName.value && item !== props.downloader)) { - $toast.error(`【${downloaderName.value}】已存在,请替换为其他名称`) + if (props.downloaders.some(item => item.name === downloaderInfo.value.name && item !== props.downloader)) { + $toast.error(`【${downloaderInfo.value.name}】已存在,请替换为其他名称`) return } // 默认下载器去重 @@ -106,14 +104,13 @@ function saveDownloaderInfo() { props.downloaders.forEach(item => { if (item.default && item !== props.downloader) { item.default = false - $toast.info(`【${item.name}】存在默认下载器,已替换成【${downloaderName.value}】`) + $toast.info(`【${item.name}】存在默认下载器,已替换成【${downloaderInfo.value.name}】`) } }) } // 执行保存 downloaderInfoDialog.value = false - downloaderInfo.value.name = downloaderName.value - emit('change', downloaderInfo.value) + emit('change', downloaderInfo.value, props.downloader.name) emit('done') } @@ -192,7 +189,7 @@ onUnmounted(() => { { ([ // 媒体服务器详情弹窗 const mediaServerInfoDialog = ref(false) -// 媒体服务器名称 -const mediaServerName = ref('') - // 媒体服务器详情 const mediaServerInfo = ref({ name: '', @@ -70,8 +68,8 @@ const mediaServerInfo = ref({ // 打开详情弹窗 function openMediaServerInfoDialog() { loadLibrary(props.mediaserver.name) - mediaServerInfo.value = props.mediaserver - mediaServerName.value = props.mediaserver.name + // 深复制 + mediaServerInfo.value = cloneDeep(props.mediaserver) mediaServerInfoDialog.value = true if (!props.mediaserver.sync_libraries) { mediaServerInfo.value.sync_libraries = ['all'] @@ -81,19 +79,18 @@ function openMediaServerInfoDialog() { // 保存详情数据 function saveMediaServerInfo() { // 为空不保存,跳出警告框 - if (!mediaServerName.value) { + if (!mediaServerInfo.value.name) { $toast.error('名称不能为空,请输入后再确定') return } // 重名判断 - if (props.mediaservers.some(item => item.name === mediaServerName.value && item !== props.mediaserver)) { - $toast.error(`【${mediaServerName.value}】已存在,请替换为其他名称`) + if (props.mediaservers.some(item => item.name === mediaServerInfo.value.name && item !== props.mediaserver)) { + $toast.error(`【${mediaServerInfo.value.name}】已存在,请替换为其他名称`) return } // 执行保存 mediaServerInfoDialog.value = false - mediaServerInfo.value.name = mediaServerName.value - emit('change', mediaServerInfo.value) + emit('change', mediaServerInfo.value, props.mediaserver.name) emit('done') } @@ -202,7 +199,7 @@ onMounted(() => { { { { const index = downloaders.value.indexOf(ele) downloaders.value.splice(index, 1) -} +}, debounceTime) // 下载器变化 -function onDownloaderChange(downloader: DownloaderConf) { - const index = downloaders.value.findIndex(item => item.name === downloader.name) - downloaders.value[index] = downloader +function onDownloaderChange(downloader: DownloaderConf, name: string) { + const index = downloaders.value.findIndex(item => item.name === name) + if (index !== -1) downloaders.value[index] = downloader } // 添加媒体服务器 @@ -181,15 +181,15 @@ const addMediaServer = debounce( (mediaserver: string) => { }, debounceTime) // 删除媒体服务器 -function removeMediaServer(ele: MediaServerConf) { +const removeMediaServer = debounce((ele: MediaServerConf) => { const index = mediaServers.value.indexOf(ele) - mediaServers.value.splice(index, 1) -} + if (index !== -1) mediaServers.value.splice(index, 1) +}, debounceTime) // 变更媒体服务器 -function onMediaServerChange(mediaserver: MediaServerConf) { - const index = mediaServers.value.findIndex(item => item.name === mediaserver.name) - mediaServers.value[index] = mediaserver +function onMediaServerChange(mediaserver: MediaServerConf, name: string) { + const index = mediaServers.value.findIndex(item => item.name === name) + if (index !== -1) mediaServers.value[index] = mediaserver } // 禁止保存 From 6a4a2181524784e0fa682e48f01cfe5a758ba745 Mon Sep 17 00:00:00 2001 From: Aqr-K <95741669+Aqr-K@users.noreply.github.com> Date: Thu, 31 Oct 2024 18:05:15 +0800 Subject: [PATCH 08/21] fix(settings): AccountSettingSite bug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 拆分 CC 与 站点刷新,解决CC保存时,站点刷新也会被提交保存的问题。 --- src/views/setting/AccountSettingSite.vue | 108 +++++++++++++---------- 1 file changed, 59 insertions(+), 49 deletions(-) diff --git a/src/views/setting/AccountSettingSite.vue b/src/views/setting/AccountSettingSite.vue index 8df2f888..d43fa9b5 100644 --- a/src/views/setting/AccountSettingSite.vue +++ b/src/views/setting/AccountSettingSite.vue @@ -1,6 +1,10 @@ From 1c4d806e5814353123d89ad8401a80ab70c9c25a Mon Sep 17 00:00:00 2001 From: Aqr-K <95741669+Aqr-K@users.noreply.github.com> Date: Sat, 2 Nov 2024 10:54:27 +0800 Subject: [PATCH 20/21] feat(settings): add AccountSettingTransfer.vue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 增加 整理标签页,增加相关设置功能。 --- src/pages/setting.vue | 10 + src/router/menu.ts | 6 + src/views/setting/AccountSettingTransfer.vue | 194 +++++++++++++++++++ 3 files changed, 210 insertions(+) create mode 100644 src/views/setting/AccountSettingTransfer.vue diff --git a/src/pages/setting.vue b/src/pages/setting.vue index 43899608..4f996d0a 100644 --- a/src/pages/setting.vue +++ b/src/pages/setting.vue @@ -12,6 +12,7 @@ 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() @@ -93,6 +94,15 @@ function jumpTab(tab: string) { + + + +
+ +
+
+
+ diff --git a/src/router/menu.ts b/src/router/menu.ts index 807570e6..7ca7c2ae 100644 --- a/src/router/menu.ts +++ b/src/router/menu.ts @@ -167,6 +167,12 @@ export const SettingTabs = [ tab: 'search', description: '媒体数据源(TheMovieDb、豆瓣、Bangumi)、搜索站点、搜索优先级、默认过滤规则', }, + { + title: '整理', + icon: 'mdi-folder-multiple-outline', + tab: 'transfer', + description: '转移重命名、刮削来源', + }, { title: '订阅', icon: 'mdi-rss', diff --git a/src/views/setting/AccountSettingTransfer.vue b/src/views/setting/AccountSettingTransfer.vue new file mode 100644 index 00000000..62028f82 --- /dev/null +++ b/src/views/setting/AccountSettingTransfer.vue @@ -0,0 +1,194 @@ + + + + From 9cbafdfab85c9b1ecdef1886bbb4f606641818f7 Mon Sep 17 00:00:00 2001 From: Aqr-K <95741669+Aqr-K@users.noreply.github.com> Date: Sun, 3 Nov 2024 18:28:07 +0800 Subject: [PATCH 21/21] feat(user): Add file type check and size determination MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 增加 文件类型检查 - 增加 文件大小限制,800KB --- src/components/dialog/UserAddEditDialog.vue | 18 ++++++++++++++++-- src/views/user/UserProfileView.vue | 18 +++++++++++++++--- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/components/dialog/UserAddEditDialog.vue b/src/components/dialog/UserAddEditDialog.vue index e732831c..cf42e778 100644 --- a/src/components/dialog/UserAddEditDialog.vue +++ b/src/components/dialog/UserAddEditDialog.vue @@ -82,10 +82,24 @@ function changeAvatar(file: Event) { const fileReader = new FileReader() const { files } = file.target as HTMLInputElement if (files && files.length > 0) { - fileReader.readAsDataURL(files[0]) + const selectedFile = files[0] + const allowedTypes = ['image/jpeg', 'image/png', 'image/gif'] + const maxSize = 800 * 1024 + // 检查文件是否为图片 + if (!allowedTypes.includes(selectedFile.type)) { + $toast.error('上传的文件不符合要求,请重新选择头像'); + return; + } + // 检查文件大小 + if (selectedFile.size > maxSize) { + $toast.error('文件大小不得大于800KB') + return + } + fileReader.readAsDataURL(selectedFile) fileReader.onload = () => { if (typeof fileReader.result === 'string') { currentAvatar.value = fileReader.result + $toast.success('新头像上传成功,待保存后生效!') } } } @@ -289,7 +303,7 @@ onMounted(() => {
-

允许 JPG、PNG、GIF 格式, 最大尺寸 800K。

+

允许 JPG、PNG、GIF 格式, 最大尺寸 800KB。

diff --git a/src/views/user/UserProfileView.vue b/src/views/user/UserProfileView.vue index 13b42ba8..f65bb5c0 100644 --- a/src/views/user/UserProfileView.vue +++ b/src/views/user/UserProfileView.vue @@ -73,9 +73,21 @@ const qrCode = ref('') function changeAvatar(file: Event) { const fileReader = new FileReader() const { files } = file.target as HTMLInputElement - if (files && files.length > 0) { - fileReader.readAsDataURL(files[0]) + const selectedFile = files[0] + const allowedTypes = ['image/jpeg', 'image/png', 'image/gif'] + const maxSize = 800 * 1024 + // 检查文件是否为图片 + if (!allowedTypes.includes(selectedFile.type)) { + $toast.error('上传的文件不符合要求,请重新选择头像'); + return; + } + // 检查文件大小 + if (selectedFile.size > maxSize) { + $toast.error('文件大小不得大于800KB') + return + } + fileReader.readAsDataURL(selectedFile) fileReader.onload = () => { if (typeof fileReader.result === 'string') { currentAvatar.value = fileReader.result @@ -285,7 +297,7 @@ watch(
-

允许 JPG、PNG、GIF 格式, 最大尺寸 800K。

+

允许 JPG、PNG、GIF 格式, 最大尺寸 800KB。