From 49c675562256dfaeba0460dbedd34c9ea9cbb0f3 Mon Sep 17 00:00:00 2001 From: fengsilin Date: Fri, 22 May 2026 18:07:08 +0800 Subject: [PATCH 1/3] feat: add customLink to navigation hook with filtering logic Co-Authored-By: Claude Opus 4.7 --- web/src/hooks/common/useNavigation.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/web/src/hooks/common/useNavigation.js b/web/src/hooks/common/useNavigation.js index 65d8d9f..e0b86e1 100644 --- a/web/src/hooks/common/useNavigation.js +++ b/web/src/hooks/common/useNavigation.js @@ -47,6 +47,13 @@ export const useNavigation = (t, headerNavModules) => { itemKey: 'pricing', to: '/pricing', }, + { + text: modules.customLink?.text || '', + itemKey: 'customLink', + to: modules.customLink?.url || '#', + isExternal: modules.customLink?.url?.startsWith('http'), + externalLink: modules.customLink?.url || '#', + }, ]; // 根据配置过滤导航链接 @@ -57,6 +64,9 @@ export const useNavigation = (t, headerNavModules) => { ? modules.pricing.enabled : modules.pricing; } + if (link.itemKey === 'customLink') { + return modules.customLink?.enabled === true && modules.customLink?.text; + } return modules[link.itemKey] === true; }); }, [t, headerNavModules]); From 54573991a4b75e040a75875f7df6b575b673b0a4 Mon Sep 17 00:00:00 2001 From: fengsilin Date: Fri, 22 May 2026 18:09:43 +0800 Subject: [PATCH 2/3] feat: add customLink configuration UI in header nav settings Co-Authored-By: Claude Opus 4.7 --- .../Operation/SettingsHeaderNavModules.jsx | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/web/src/pages/Setting/Operation/SettingsHeaderNavModules.jsx b/web/src/pages/Setting/Operation/SettingsHeaderNavModules.jsx index 19943c1..8b4398f 100644 --- a/web/src/pages/Setting/Operation/SettingsHeaderNavModules.jsx +++ b/web/src/pages/Setting/Operation/SettingsHeaderNavModules.jsx @@ -23,6 +23,7 @@ import { Card, Col, Form, + Input, Row, Switch, Typography, @@ -48,6 +49,11 @@ export default function SettingsHeaderNavModules(props) { }, docs: true, about: true, + customLink: { + enabled: false, + text: '', + url: '', + }, }); // 处理顶栏模块配置变更 @@ -60,6 +66,11 @@ export default function SettingsHeaderNavModules(props) { ...newModules[moduleKey], enabled: checked, }; + } else if (moduleKey === 'customLink') { + newModules.customLink = { + ...newModules.customLink, + enabled: checked, + }; } else { newModules[moduleKey] = checked; } @@ -77,6 +88,24 @@ export default function SettingsHeaderNavModules(props) { setHeaderNavModules(newModules); } + function handleCustomLinkTextChange(value) { + const newModules = { ...headerNavModules }; + newModules.customLink = { + ...newModules.customLink, + text: value, + }; + setHeaderNavModules(newModules); + } + + function handleCustomLinkUrlChange(value) { + const newModules = { ...headerNavModules }; + newModules.customLink = { + ...newModules.customLink, + url: value, + }; + setHeaderNavModules(newModules); + } + // 重置顶栏模块为默认配置 function resetHeaderNavModules() { const defaultModules = { @@ -88,6 +117,11 @@ export default function SettingsHeaderNavModules(props) { }, docs: true, about: true, + customLink: { + enabled: false, + text: '', + url: '', + }, }; setHeaderNavModules(defaultModules); showSuccess(t('已重置为默认配置')); @@ -142,6 +176,15 @@ export default function SettingsHeaderNavModules(props) { }; } + // 确保 customLink 有完整结构 + if (!modules.customLink || typeof modules.customLink !== 'object') { + modules.customLink = { + enabled: false, + text: '', + url: '', + }; + } + setHeaderNavModules(modules); } catch (error) { // 使用默认配置 @@ -154,6 +197,11 @@ export default function SettingsHeaderNavModules(props) { }, docs: true, about: true, + customLink: { + enabled: false, + text: '', + url: '', + }, }; setHeaderNavModules(defaultModules); } @@ -188,6 +236,12 @@ export default function SettingsHeaderNavModules(props) { title: t('关于'), description: t('关于系统的详细信息'), }, + { + key: 'customLink', + title: t('自定义链接'), + description: t('自定义导航链接,可设置文案和跳转地址'), + hasSubConfig: true, + }, ]; return ( @@ -247,6 +301,8 @@ export default function SettingsHeaderNavModules(props) { checked={ module.key === 'pricing' ? headerNavModules[module.key]?.enabled + : module.key === 'customLink' + ? headerNavModules.customLink?.enabled : headerNavModules[module.key] } onChange={handleHeaderNavModuleChange(module.key)} @@ -310,6 +366,53 @@ export default function SettingsHeaderNavModules(props) { )} + {module.key === 'customLink' && + headerNavModules.customLink?.enabled && ( +
+
+
+ {t('导航文案')} +
+ +
+
+
+ {t('跳转链接')} +
+ +
+
+ )} ))} From d32050d08132a6b41cb8477d905c9b3e564d514e Mon Sep 17 00:00:00 2001 From: fengsilin Date: Mon, 25 May 2026 09:45:09 +0800 Subject: [PATCH 3/3] fix: restore login widget bundle and guard custom nav link --- web/src/hooks/common/useNavigation.js | 6 ++++- .../Operation/SettingsHeaderNavModules.jsx | 22 +++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/web/src/hooks/common/useNavigation.js b/web/src/hooks/common/useNavigation.js index e0b86e1..f325ae5 100644 --- a/web/src/hooks/common/useNavigation.js +++ b/web/src/hooks/common/useNavigation.js @@ -65,7 +65,11 @@ export const useNavigation = (t, headerNavModules) => { : modules.pricing; } if (link.itemKey === 'customLink') { - return modules.customLink?.enabled === true && modules.customLink?.text; + return ( + modules.customLink?.enabled === true && + modules.customLink?.text && + modules.customLink?.url + ); } return modules[link.itemKey] === true; }); diff --git a/web/src/pages/Setting/Operation/SettingsHeaderNavModules.jsx b/web/src/pages/Setting/Operation/SettingsHeaderNavModules.jsx index 8b4398f..4cefa18 100644 --- a/web/src/pages/Setting/Operation/SettingsHeaderNavModules.jsx +++ b/web/src/pages/Setting/Operation/SettingsHeaderNavModules.jsx @@ -129,6 +129,15 @@ export default function SettingsHeaderNavModules(props) { // 保存配置 async function onSubmit() { + const customLinkText = (headerNavModules.customLink?.text || '').trim(); + const customLinkUrl = (headerNavModules.customLink?.url || '').trim(); + if ( + headerNavModules.customLink?.enabled && + (!customLinkText || !customLinkUrl) + ) { + showError(t('启用自定义链接前请先填写完整的导航文案和跳转链接')); + return; + } setLoading(true); try { const res = await API.put('/api/option/', { @@ -410,6 +419,19 @@ export default function SettingsHeaderNavModules(props) { placeholder={t('如 https://example.com 或 /docs')} size='small' /> + + {t('未填写跳转链接时,自定义链接不会在前台显示')} + )}