From 4d78c3ab16af96545781348c05e0c5a89500df3f Mon Sep 17 00:00:00 2001 From: ssongliu Date: Mon, 8 Sep 2025 15:00:51 +0800 Subject: [PATCH] feat: Support watermark --- frontend/src/global/use-logo.ts | 5 + frontend/src/lang/modules/en.ts | 11 ++ frontend/src/lang/modules/ja.ts | 11 ++ frontend/src/lang/modules/ko.ts | 11 ++ frontend/src/lang/modules/ms.ts | 11 ++ frontend/src/lang/modules/pt-br.ts | 11 ++ frontend/src/lang/modules/ru.ts | 11 ++ frontend/src/lang/modules/tr.ts | 11 ++ frontend/src/lang/modules/zh-Hant.ts | 11 ++ frontend/src/lang/modules/zh.ts | 11 ++ frontend/src/layout/index.vue | 25 ++- frontend/src/store/interface/index.ts | 9 ++ frontend/src/store/modules/global.ts | 1 + frontend/src/utils/xpack.ts | 6 + frontend/src/views/setting/panel/index.vue | 52 ++++++ .../views/setting/panel/watermark/index.vue | 150 ++++++++++++++++++ 16 files changed, 346 insertions(+), 1 deletion(-) create mode 100644 frontend/src/views/setting/panel/watermark/index.vue diff --git a/frontend/src/global/use-logo.ts b/frontend/src/global/use-logo.ts index d61949b59d08..1b86eecdb661 100644 --- a/frontend/src/global/use-logo.ts +++ b/frontend/src/global/use-logo.ts @@ -14,6 +14,11 @@ export const useLogo = async () => { globalStore.themeConfig.loginBackground = res.data?.loginBackground; globalStore.themeConfig.loginBtnLinkColor = res.data?.loginBtnLinkColor; globalStore.themeConfig.favicon = res.data.favicon; + try { + globalStore.watermark = JSON.parse(res.data.watermark); + } catch { + globalStore.watermark = null; + } } const link = (document.querySelector("link[rel*='icon']") || document.createElement('link')) as HTMLLinkElement; diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index d4d254c60fce..af879c7f97a1 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -1590,6 +1590,17 @@ const message = { userChangeHelper: 'Changing the panel user will log you out. Continue?', passwd: 'Panel password', emailHelper: 'For password retrieval', + watermark: 'Watermark Settings', + watermarkContent: 'Watermark Content', + contentHelper: 'Use {0} to represent the current node name and node IP', + watermarkColor: 'Watermark Color', + watermarkFont: 'Watermark Font Size', + watermarkHeight: 'Watermark Height', + watermarkWidth: 'Watermark Width', + watermarkRotate: 'Rotation Angle', + watermarkGap: 'Spacing', + watermarkCloseHelper: 'Are you sure you want to turn off the system watermark settings?', + watermarkOpenHelper: 'Are you sure you want to save the current system watermark settings?', title: 'Panel alias', panelPort: 'Panel port', titleHelper: diff --git a/frontend/src/lang/modules/ja.ts b/frontend/src/lang/modules/ja.ts index a1a79c0a3ae0..81b15c3fb60a 100644 --- a/frontend/src/lang/modules/ja.ts +++ b/frontend/src/lang/modules/ja.ts @@ -1535,6 +1535,17 @@ const message = { userChangeHelper: 'パネルユーザーを変更すると、ログアウトします。続く?', passwd: 'パネルパスワード', emailHelper: 'パスワード取得用', + watermark: '透かし設定', + watermarkContent: '透かし内容', + contentHelper: '{0} を使用して現在のノード名とノードIPを表す', + watermarkColor: '透かしの色', + watermarkFont: '透かしのフォントサイズ', + watermarkHeight: '透かしの高さ', + watermarkWidth: '透かしの幅', + watermarkRotate: '回転角度', + watermarkGap: '間隔', + watermarkCloseHelper: 'システムの透かし設定を無効にしてもよろしいですか?', + watermarkOpenHelper: '現在のシステム透かし設定を保存してもよろしいですか?', title: 'パネルエイリアス', panelPort: 'パネルポート', titleHelper: '英語、漢字、数字、スペース、および一般的な特殊文字を含む3~30文字の長さをサポートします', diff --git a/frontend/src/lang/modules/ko.ts b/frontend/src/lang/modules/ko.ts index b204ea3d49d4..501dd866b373 100644 --- a/frontend/src/lang/modules/ko.ts +++ b/frontend/src/lang/modules/ko.ts @@ -1519,6 +1519,17 @@ const message = { userChangeHelper: '패널 사용자를 변경하면 로그아웃됩니다. 계속하시겠습니까?', passwd: '패널 비밀번호', emailHelper: '비밀번호 복구용', + watermark: '워터마크 설정', + watermarkContent: '워터마크 내용', + contentHelper: '{0}을 사용하여 현재 노드 이름과 노드 IP를 나타냅니다', + watermarkColor: '워터마크 색상', + watermarkFont: '워터마크 글꼴 크기', + watermarkHeight: '워터마크 높이', + watermarkWidth: '워터마크 너비', + watermarkRotate: '회전 각도', + watermarkGap: '간격', + watermarkCloseHelper: '시스템 워터마크 설정을 해제하시겠습니까?', + watermarkOpenHelper: '현재 시스템 워터마크 설정을 저장하시겠습니까?', title: '패널 별칭', panelPort: '패널 포트', titleHelper: '영문자, 한자, 숫자, 공백, 일반 특수 문자를 포함하여 3~30자의 길이를 지원합니다.', diff --git a/frontend/src/lang/modules/ms.ts b/frontend/src/lang/modules/ms.ts index 9c732d307e07..6c7c6fe32872 100644 --- a/frontend/src/lang/modules/ms.ts +++ b/frontend/src/lang/modules/ms.ts @@ -1579,6 +1579,17 @@ const message = { userChangeHelper: 'Menukar pengguna panel akan menyebabkan anda log keluar. Teruskan?', passwd: 'Kata laluan panel', emailHelper: 'Untuk pemulihan kata laluan', + watermark: 'Tetapan Tanda Air', + watermarkContent: 'Kandungan Tanda Air', + contentHelper: 'Gunakan {0} untuk mewakili nama nod semasa dan IP nod', + watermarkColor: 'Warna Tanda Air', + watermarkFont: 'Saiz Fon Tanda Air', + watermarkHeight: 'Ketinggian Tanda Air', + watermarkWidth: 'Lebar Tanda Air', + watermarkRotate: 'Sudut Putaran', + watermarkGap: 'Jarak', + watermarkCloseHelper: 'Adakah anda pasti ingin mematikan tetapan tanda air sistem?', + watermarkOpenHelper: 'Adakah anda pasti ingin menyimpan tetapan tanda air sistem semasa?', title: 'Alias panel', panelPort: 'Port panel', titleHelper: diff --git a/frontend/src/lang/modules/pt-br.ts b/frontend/src/lang/modules/pt-br.ts index d016b4089b25..20a1120a9697 100644 --- a/frontend/src/lang/modules/pt-br.ts +++ b/frontend/src/lang/modules/pt-br.ts @@ -1566,6 +1566,17 @@ const message = { userChangeHelper: 'Alterar o usuário do painel irá desconectá-lo. Continuar?', passwd: 'Senha do painel', emailHelper: 'Para recuperação de senha', + watermark: 'Configurações de Marca d Água', + watermarkContent: 'Conteúdo da Marca d Água', + contentHelper: 'Use {0} para representar o nome do nó atual e o IP do nó', + watermarkColor: 'Cor da Marca d Água', + watermarkFont: 'Tamanho da Fonte da Marca d Água', + watermarkHeight: 'Altura da Marca d Água', + watermarkWidth: 'Largura da Marca d Água', + watermarkRotate: 'Ângulo de Rotação', + watermarkGap: 'Espaçamento', + watermarkCloseHelper: 'Tem certeza de que deseja desativar as configurações de marca d água do sistema?', + watermarkOpenHelper: 'Tem certeza de que deseja salvar as configurações atuais de marca d água do sistema?', title: 'Alias do painel', panelPort: 'Porta do painel', titleHelper: diff --git a/frontend/src/lang/modules/ru.ts b/frontend/src/lang/modules/ru.ts index 9ddd275fa587..7063e4a63db0 100644 --- a/frontend/src/lang/modules/ru.ts +++ b/frontend/src/lang/modules/ru.ts @@ -1568,6 +1568,17 @@ const message = { userChangeHelper: 'Изменение пользователя панели приведет к выходу из системы. Продолжить?', passwd: 'Пароль панели', emailHelper: 'Для восстановления пароля', + watermark: 'Настройки Водяного Знака', + watermarkContent: 'Содержимое Водяного Знака', + contentHelper: 'Используйте {0} для обозначения текущего имени узла и IP-адреса узла', + watermarkColor: 'Цвет Водяного Знака', + watermarkFont: 'Размер Шрифта Водяного Знака', + watermarkHeight: 'Высота Водяного Знака', + watermarkWidth: 'Ширина Водяного Знака', + watermarkRotate: 'Угол Поворота', + watermarkGap: 'Интервал', + watermarkCloseHelper: 'Вы уверены, что хотите отключить настройки системного водяного знака?', + watermarkOpenHelper: 'Вы уверены, что хотите сохранить текущие настройки системного водяного знака?', title: 'Псевдоним панели', panelPort: 'Порт панели', titleHelper: diff --git a/frontend/src/lang/modules/tr.ts b/frontend/src/lang/modules/tr.ts index ac02f12da5c0..a77ef3ec89d1 100644 --- a/frontend/src/lang/modules/tr.ts +++ b/frontend/src/lang/modules/tr.ts @@ -1608,6 +1608,17 @@ const message = { userChangeHelper: 'Panel kullanıcısını değiştirmek sizi oturumdan çıkaracak. Devam etmek istiyor musunuz?', passwd: 'Panel parolası', emailHelper: 'Parola kurtarma için', + watermark: 'Filigran Ayarları', + watermarkContent: 'Filigran İçeriği', + contentHelper: 'Mevcut düğüm adını ve düğüm IP sini temsil etmek için {0} kullanın', + watermarkColor: 'Filigran Rengi', + watermarkFont: 'Filigran Yazı Tipi Boyutu', + watermarkHeight: 'Filigran Yüksekliği', + watermarkWidth: 'Filigran Genişliği', + watermarkRotate: 'Döndürme Açısı', + watermarkGap: 'Aralık', + watermarkCloseHelper: 'Sistem filigran ayarlarını kapatmak istediğinizden emin misiniz?', + watermarkOpenHelper: 'Mevcut sistem filigran ayarlarını kaydetmek istediğinizden emin misiniz?', title: 'Panel takma adı', panelPort: 'Panel portu', titleHelper: diff --git a/frontend/src/lang/modules/zh-Hant.ts b/frontend/src/lang/modules/zh-Hant.ts index 2b9fb58f35da..1733f4d5849f 100644 --- a/frontend/src/lang/modules/zh-Hant.ts +++ b/frontend/src/lang/modules/zh-Hant.ts @@ -1513,6 +1513,17 @@ const message = { userChangeHelper: '修改面板用户将退出登录,是否继续?', passwd: '面板密码', emailHelper: '用於密碼找回', + watermark: '浮水印設定', + watermarkContent: '浮水印內容', + contentHelper: '使用 {0} 作為目前節點名稱和節點 IP', + watermarkColor: '浮水印顏色', + watermarkFont: '浮水印字型大小', + watermarkHeight: '浮水印高度', + watermarkWidth: '浮水印寬度', + watermarkRotate: '旋轉角度', + watermarkGap: '間距', + watermarkCloseHelper: '是否確認關閉系統浮水印設定?', + watermarkOpenHelper: '是否確認儲存目前系統浮水印設定?', title: '面板別名', panelPort: '面板端口', titleHelper: '支援長度 3 至 30 的英文字母、中文、數字、空格和常見的特殊符號。', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 0adee0fe836f..b05def28755b 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -1509,6 +1509,17 @@ const message = { userChangeHelper: '修改面板用户将退出登陆,是否继续?', passwd: '面板密码', emailHelper: '用于密码找回', + watermark: '水印设置', + watermarkContent: '水印内容', + contentHelper: '使用 {0} 作为当前节点名称和节点 IP', + watermarkColor: '水印颜色', + watermarkFont: '水印字号', + watermarkHeight: '水印高度', + watermarkWidth: '水印宽度', + watermarkRotate: '旋转角', + watermarkGap: '间距', + watermarkCloseHelper: '是否确认关闭系统水印设置', + watermarkOpenHelper: '是否确认保存当前系统水印设置', title: '面板别名', titleHelper: '支持长度3-30的英文、中文、数字、空格和常见的特殊字符', panelPort: '面板端口', diff --git a/frontend/src/layout/index.vue b/frontend/src/layout/index.vue index 3a9358174a82..a35e5aa20b16 100644 --- a/frontend/src/layout/index.vue +++ b/frontend/src/layout/index.vue @@ -26,7 +26,20 @@
- + + + +
@@ -44,6 +57,7 @@ import { useRoute, useRouter } from 'vue-router'; import { loadMasterProductProFromDB, loadProductProFromDB } from '@/utils/xpack'; import { useTheme } from '@/global/use-theme'; import TaskList from '@/components/task-list/index.vue'; +import i18n from '@/lang'; const { switchTheme } = useTheme(); useResize(); @@ -84,6 +98,15 @@ const handleCollapse = () => { menuStore.setCollapse(); }; +const loadContent = () => { + let itemName = globalStore.watermark.content.replaceAll( + '${nodeName}', + globalStore.currentNode === 'local' ? i18n.global.t('xpack.node.master') : globalStore.currentNode, + ); + itemName = itemName.replaceAll('${nodeAddr}', globalStore.currentNodeAddr); + return itemName; +}; + watch( () => globalStore.isLoading, () => { diff --git a/frontend/src/store/interface/index.ts b/frontend/src/store/interface/index.ts index 3ec452b539d6..0d761c2a461d 100644 --- a/frontend/src/store/interface/index.ts +++ b/frontend/src/store/interface/index.ts @@ -17,6 +17,14 @@ export interface ThemeConfigProp { themeColor: string; } +export interface Watermark { + color: string, + fontSize: number, + content: string, + rotate: number, + gap: number, +} + export interface GlobalState { isLoading: boolean; loadingText: string; @@ -26,6 +34,7 @@ export interface GlobalState { themeConfig: ThemeConfigProp; isFullScreen: boolean; openMenuTabs: boolean; + watermark: Watermark; isOnRestart: boolean; agreeLicense: boolean; hasNewVersion: boolean; diff --git a/frontend/src/store/modules/global.ts b/frontend/src/store/modules/global.ts index 1c6aa6a83bed..1d50572e5a55 100644 --- a/frontend/src/store/modules/global.ts +++ b/frontend/src/store/modules/global.ts @@ -27,6 +27,7 @@ const GlobalStore = defineStore({ loginBgType: '', loginBtnLinkColor: '', }, + watermark: null, openMenuTabs: false, isFullScreen: false, isOnRestart: false, diff --git a/frontend/src/utils/xpack.ts b/frontend/src/utils/xpack.ts index b13d0b44c60b..c7e34a3e451c 100644 --- a/frontend/src/utils/xpack.ts +++ b/frontend/src/utils/xpack.ts @@ -9,6 +9,7 @@ export function resetXSetting() { globalStore.themeConfig.logo = ''; globalStore.themeConfig.logoWithText = ''; globalStore.themeConfig.favicon = ''; + globalStore.watermark = null; } export function initFavicon() { @@ -119,6 +120,11 @@ export async function getXpackSettingForTheme() { if (res2.data?.theme) { globalStore.themeConfig.theme = res2.data.theme; } + try { + globalStore.watermark = JSON.parse(res2.data.watermark); + } catch { + globalStore.watermark = null; + } } else { resetXSetting(); } diff --git a/frontend/src/views/setting/panel/index.vue b/frontend/src/views/setting/panel/index.vue index 06a68de8d40e..fc412f5e91ee 100644 --- a/frontend/src/views/setting/panel/index.vue +++ b/frontend/src/views/setting/panel/index.vue @@ -78,6 +78,24 @@ + + + + {{ $t('commons.button.enable') }} + + + {{ $t('commons.button.disable') }} + + +
+
+ + {{ $t('commons.button.view') }} + +
+
+
+ @@ -217,6 +236,7 @@ import { MsgSuccess } from '@/utils/message'; import ThemeColor from '@/views/setting/panel/theme-color/index.vue'; import ApiInterface from '@/views/setting/panel/api-interface/index.vue'; import Password from '@/views/setting/panel/password/index.vue'; +import Watermark from '@/views/setting/panel/watermark/index.vue'; import UserName from '@/views/setting/panel/username/index.vue'; import Timeout from '@/views/setting/panel/timeout/index.vue'; import PanelName from '@/views/setting/panel/name/index.vue'; @@ -253,6 +273,8 @@ const form = reactive({ sessionTimeout: 0, panelName: '', theme: '', + watermark: '', + watermarkItem: '', themeColor: {} as ThemeColor, menuTabs: '', language: '', @@ -286,6 +308,7 @@ const systemIPRef = ref(); const proxyRef = ref(); const timeoutRef = ref(); const hideMenuRef = ref(); +const watermarkRef = ref(); const themeColorRef = ref(); const apiInterfaceRef = ref(); const unset = ref(i18n.global.t('setting.unSetting')); @@ -347,6 +370,8 @@ const search = async () => { : '{"light":"#005eeb","dark":"#F0BE96"}'; globalStore.themeConfig.theme = form.theme; form.proxyDocker = xpackRes.data.proxyDocker; + form.watermark = xpackRes.data.watermark; + form.watermarkItem = xpackRes.data.watermark ? 'Enable' : 'Disable'; } } else { globalStore.themeConfig.theme = form.theme; @@ -389,6 +414,33 @@ const onChangeThemeColor = () => { themeColorRef.value.acceptParams({ themeColor: themeColor, theme: globalStore.themeConfig.theme }); }; +const onChangeWatermark = async () => { + if (form.watermarkItem === 'Enable') { + watermarkRef.value.acceptParams(form.watermark); + return; + } + ElMessageBox.confirm(i18n.global.t('setting.watermarkCloseHelper'), i18n.global.t('setting.watermark'), { + confirmButtonText: i18n.global.t('commons.button.confirm'), + cancelButtonText: i18n.global.t('commons.button.cancel'), + }) + .then(async () => { + loading.value = true; + await updateXpackSettingByKey('Watermark', '') + .then(() => { + loading.value = false; + globalStore.watermark = null; + search(); + MsgSuccess(i18n.global.t('commons.msg.operationSuccess')); + }) + .catch(() => { + loading.value = false; + }); + }) + .catch(() => { + form.watermarkItem = 'Enable'; + }); +}; + const onChangeApiInterfaceStatus = async () => { if (form.apiInterfaceStatus === 'Enable') { apiInterfaceRef.value.acceptParams({ diff --git a/frontend/src/views/setting/panel/watermark/index.vue b/frontend/src/views/setting/panel/watermark/index.vue new file mode 100644 index 000000000000..a47f72d5e3ae --- /dev/null +++ b/frontend/src/views/setting/panel/watermark/index.vue @@ -0,0 +1,150 @@ + + + +