From ad3a7ff05f5b92ddc711eec15dbbfc9172b9d607 Mon Sep 17 00:00:00 2001 From: CN-Scars Date: Thu, 12 Feb 2026 23:02:06 +0800 Subject: [PATCH] feat: Terminal supports custom fonts --- core/app/dto/setting.go | 1 + core/app/service/setting.go | 3 +++ core/init/migration/migrate.go | 1 + core/init/migration/migrations/init.go | 20 ++++++++++++++ frontend/src/api/interface/setting.ts | 1 + frontend/src/components/terminal/index.vue | 11 +++++++- frontend/src/lang/modules/en.ts | 3 +++ frontend/src/lang/modules/es-es.ts | 3 +++ frontend/src/lang/modules/ja.ts | 3 +++ frontend/src/lang/modules/ko.ts | 3 +++ frontend/src/lang/modules/ms.ts | 3 +++ frontend/src/lang/modules/pt-br.ts | 3 +++ frontend/src/lang/modules/ru.ts | 3 +++ frontend/src/lang/modules/tr.ts | 3 +++ frontend/src/lang/modules/zh-Hant.ts | 3 +++ frontend/src/lang/modules/zh.ts | 3 +++ frontend/src/store/interface/index.ts | 1 + frontend/src/store/modules/terminal.ts | 4 +++ frontend/src/views/terminal/setting/index.vue | 26 ++++++++++++++++++- 19 files changed, 96 insertions(+), 2 deletions(-) diff --git a/core/app/dto/setting.go b/core/app/dto/setting.go index 9b847e5c17e3..37c27e3ff4f8 100644 --- a/core/app/dto/setting.go +++ b/core/app/dto/setting.go @@ -226,6 +226,7 @@ type TerminalInfo struct { LineHeight string `json:"lineHeight"` LetterSpacing string `json:"letterSpacing"` FontSize string `json:"fontSize"` + FontFamily string `json:"fontFamily"` CursorBlink string `json:"cursorBlink"` CursorStyle string `json:"cursorStyle"` Scrollback string `json:"scrollback"` diff --git a/core/app/service/setting.go b/core/app/service/setting.go index 7a78445cf134..33b43295e025 100644 --- a/core/app/service/setting.go +++ b/core/app/service/setting.go @@ -509,6 +509,9 @@ func (u *SettingService) UpdateTerminal(req dto.TerminalInfo) error { if err := settingRepo.Update("FontSize", req.FontSize); err != nil { return err } + if err := settingRepo.Update("FontFamily", req.FontFamily); err != nil { + return err + } if err := settingRepo.Update("CursorBlink", req.CursorBlink); err != nil { return err } diff --git a/core/init/migration/migrate.go b/core/init/migration/migrate.go index 8f7ac7cbf50c..590b3b7e36d3 100644 --- a/core/init/migration/migrate.go +++ b/core/init/migration/migrate.go @@ -31,6 +31,7 @@ func Init() { migrations.AdjustXpackNode, migrations.UpdateAiAgentsMenu, migrations.AddDashboardCarouselSetting, + migrations.AddTerminalFontFamily, }) if err := m.Migrate(); err != nil { global.LOG.Error(err) diff --git a/core/init/migration/migrations/init.go b/core/init/migration/migrations/init.go index d66c1f380ee8..b32566d390b4 100644 --- a/core/init/migration/migrations/init.go +++ b/core/init/migration/migrations/init.go @@ -785,3 +785,23 @@ var UpdateAiAgentsMenu = &gormigrate.Migration{ return tx.Model(&model.Setting{}).Where("key = ?", "HideMenu").Update("value", string(updatedJSON)).Error }, } + +var AddTerminalFontFamily = &gormigrate.Migration{ + ID: "20260212-add-terminal-font-family", + Migrate: func(tx *gorm.DB) error { + var addSettingsIfMissing = func(tx *gorm.DB, key, value string) error { + var setting model.Setting + if err := tx.Where("key = ?", key).First(&setting).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return tx.Create(&model.Setting{Key: key, Value: value}).Error + } + return err + } + return nil + } + if err := addSettingsIfMissing(tx, "FontFamily", ""); err != nil { + return err + } + return nil + }, +} diff --git a/frontend/src/api/interface/setting.ts b/frontend/src/api/interface/setting.ts index 902fbc694a46..0beb300d726d 100644 --- a/frontend/src/api/interface/setting.ts +++ b/frontend/src/api/interface/setting.ts @@ -71,6 +71,7 @@ export namespace Setting { lineHeight: string; letterSpacing: string; fontSize: string; + fontFamily: string; cursorBlink: string; cursorStyle: string; scrollback: string; diff --git a/frontend/src/components/terminal/index.vue b/frontend/src/components/terminal/index.vue index 12bf0b830b21..7eb02e929830 100644 --- a/frontend/src/components/terminal/index.vue +++ b/frontend/src/components/terminal/index.vue @@ -41,6 +41,11 @@ watch([lineHeight, fontSize, letterSpacing], ([newLineHeight, newFontSize, newLe term.value.options.fontSize = newFontSize; changeTerminalSize(); }); +const fontFamily = computed(() => terminalStore.fontFamily); +watch(fontFamily, (newFontFamily) => { + const defaultFontFamily = "Monaco, Menlo, Consolas, 'Courier New', monospace"; + term.value.options.fontFamily = newFontFamily || defaultFontFamily; +}); const cursorStyle = computed(() => terminalStore.cursorStyle); watch(cursorStyle, (newCursorStyle) => { term.value.options.cursorStyle = newCursorStyle; @@ -77,10 +82,14 @@ const acceptParams = (props: WsProps) => { const newTerm = () => { const background = getComputedStyle(document.documentElement).getPropertyValue('--panel-terminal-bg-color').trim(); + const defaultFontFamily = "Monaco, Menlo, Consolas, 'Courier New', monospace"; + // fontFamily 从后端获取,如果为空则使用默认值 + const fontFamily = terminalStore.fontFamily || defaultFontFamily; + term.value = new Terminal({ lineHeight: terminalStore.lineHeight || 1.2, fontSize: terminalStore.fontSize || 12, - fontFamily: "Monaco, Menlo, Consolas, 'Courier New', monospace", + fontFamily: fontFamily, theme: { background: background, }, diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index 9c0accaa03f1..5822cd0d8502 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -1362,6 +1362,9 @@ const message = { lineHeight: 'Line Height', letterSpacing: 'Letter Spacing', fontSize: 'Font Size', + fontFamily: 'Custom Font', + fontFamilyHelper: + 'Leave empty to use default fonts. If you enter a custom font name, ensure the font is installed on your local system, otherwise the default font will be used', cursorBlink: 'Cursor Blink', cursorStyle: 'Cursor Style', cursorUnderline: 'Underline', diff --git a/frontend/src/lang/modules/es-es.ts b/frontend/src/lang/modules/es-es.ts index 4524f7769ddb..a7376766eea4 100644 --- a/frontend/src/lang/modules/es-es.ts +++ b/frontend/src/lang/modules/es-es.ts @@ -1374,6 +1374,9 @@ const message = { lineHeight: 'Altura de línea', letterSpacing: 'Espaciado de letras', fontSize: 'Tamaño de fuente', + fontFamily: 'Fuente personalizada', + fontFamilyHelper: + 'Dejar vacío para usar fuentes predeterminadas. Si ingresa un nombre de fuente personalizado, asegúrese de que la fuente esté instalada en su sistema operativo local, de lo contrario se renderizará con la fuente predeterminada', cursorBlink: 'Parpadeo del cursor', cursorStyle: 'Estilo de cursor', cursorUnderline: 'Subrayado', diff --git a/frontend/src/lang/modules/ja.ts b/frontend/src/lang/modules/ja.ts index 4a41ea0673d4..51d7ac199eb9 100644 --- a/frontend/src/lang/modules/ja.ts +++ b/frontend/src/lang/modules/ja.ts @@ -1325,6 +1325,9 @@ const message = { key: '秘密鍵', keyPassword: '秘密キーパスワード', emptyTerminal: '現在接続されている端子はありません。', + fontFamily: 'カスタムフォント', + fontFamilyHelper: + '空欄の場合はデフォルトフォントを使用します。カスタムフォント名を入力する場合は、ローカルOSにそのフォントがインストールされていることを確認してください。インストールされていない場合はデフォルトフォントでレンダリングされます', }, toolbox: { common: { diff --git a/frontend/src/lang/modules/ko.ts b/frontend/src/lang/modules/ko.ts index 1cb2f050c54d..920b6a3af2e6 100644 --- a/frontend/src/lang/modules/ko.ts +++ b/frontend/src/lang/modules/ko.ts @@ -1310,6 +1310,9 @@ const message = { key: '개인 키', keyPassword: '개인 키 비밀번호', emptyTerminal: '현재 연결된 터미널이 없습니다.', + fontFamily: '사용자 정의 글꼴', + fontFamilyHelper: + '비워두면 기본 글꼴을 사용합니다. 사용자 정의 글꼴 이름을 입력한 후에는 로컬 운영 체제에 해당 글꼴이 설치되어 있는지 확인하세요. 그렇지 않으면 기본 글꼴로 렌더링됩니다', }, toolbox: { common: { diff --git a/frontend/src/lang/modules/ms.ts b/frontend/src/lang/modules/ms.ts index 1a0f656476cb..738df79e3fb9 100644 --- a/frontend/src/lang/modules/ms.ts +++ b/frontend/src/lang/modules/ms.ts @@ -1353,6 +1353,9 @@ const message = { key: 'Kunci peribadi', keyPassword: 'Kata laluan kunci peribadi', emptyTerminal: 'Tiada terminal yang sedang disambungkan.', + fontFamily: 'Fon tersuai', + fontFamilyHelper: + 'Biarkan kosong untuk menggunakan fon lalai. Jika anda memasukkan nama fon tersuai, pastikan fon tersebut dipasang pada sistem operasi tempatan anda, jika tidak ia akan dipaparkan dengan fon lalai', }, toolbox: { common: { diff --git a/frontend/src/lang/modules/pt-br.ts b/frontend/src/lang/modules/pt-br.ts index d6268a4dc70c..3074f1998eda 100644 --- a/frontend/src/lang/modules/pt-br.ts +++ b/frontend/src/lang/modules/pt-br.ts @@ -1348,6 +1348,9 @@ const message = { key: 'Chave privada', keyPassword: 'Senha da chave privada', emptyTerminal: 'Nenhum terminal está conectado no momento.', + fontFamily: 'Fonte personalizada', + fontFamilyHelper: + 'Deixe em branco para usar fontes padrão. Se você inserir um nome de fonte personalizado, certifique-se de que a fonte esteja instalada no seu sistema operacional local, caso contrário será renderizada com a fonte padrão', }, toolbox: { common: { diff --git a/frontend/src/lang/modules/ru.ts b/frontend/src/lang/modules/ru.ts index 35379bf6bf3c..8158bb7e5eab 100644 --- a/frontend/src/lang/modules/ru.ts +++ b/frontend/src/lang/modules/ru.ts @@ -1349,6 +1349,9 @@ const message = { key: 'Приватный ключ', keyPassword: 'Пароль приватного ключа', emptyTerminal: 'В настоящее время нет подключенных терминалов.', + fontFamily: 'Пользовательский шрифт', + fontFamilyHelper: + 'Оставьте пустым для использования шрифтов по умолчанию. Если вы введете имя пользовательского шрифта, убедитесь, что шрифт установлен в вашей локальной операционной системе, иначе будет использован шрифт по умолчанию', }, toolbox: { common: { diff --git a/frontend/src/lang/modules/tr.ts b/frontend/src/lang/modules/tr.ts index aae797e78605..7922e871d376 100644 --- a/frontend/src/lang/modules/tr.ts +++ b/frontend/src/lang/modules/tr.ts @@ -1376,6 +1376,9 @@ const message = { lineHeight: 'Satır Yüksekliği', letterSpacing: 'Harf Aralığı', fontSize: 'Font Boyutu', + fontFamily: 'Özel yazı tipi', + fontFamilyHelper: + 'Varsayılan yazı tiplerini kullanmak için boş bırakın. Özel bir yazı tipi adı girerseniz, yazı tipinin yerel işletim sisteminizde yüklü olduğundan emin olun, aksi takdirde varsayılan yazı tipiyle işlenecektir', cursorBlink: 'İmleç Yanıp Sönme', cursorStyle: 'İmleç Stili', cursorUnderline: 'Alt Çizgi', diff --git a/frontend/src/lang/modules/zh-Hant.ts b/frontend/src/lang/modules/zh-Hant.ts index 890a458bfdef..9844872dff8e 100644 --- a/frontend/src/lang/modules/zh-Hant.ts +++ b/frontend/src/lang/modules/zh-Hant.ts @@ -1288,6 +1288,9 @@ const message = { lineHeight: '字體行高', letterSpacing: '字體間距', fontSize: '字體大小', + fontFamily: '自訂字體', + fontFamilyHelper: + '留空則使用預設字體。輸入自訂字體名稱後,需確保本機作業系統已安裝該字體,否則將使用預設字體渲染', cursorBlink: '游標閃爍', cursorStyle: '游標樣式', cursorUnderline: '下劃線', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 6b24523a8835..e5e369a093e6 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -1296,6 +1296,9 @@ const message = { lineHeight: '字体行高', letterSpacing: '字体间距', fontSize: '字体大小', + fontFamily: '自定义字体', + fontFamilyHelper: + '留空则使用默认字体。输入自定义字体名后,需确保本地操作系统已安装该字体,否则将使用默认字体渲染', cursorBlink: '光标闪烁', cursorStyle: '光标样式', cursorUnderline: '下划线', diff --git a/frontend/src/store/interface/index.ts b/frontend/src/store/interface/index.ts index 09e670dcaa1b..7090239469aa 100644 --- a/frontend/src/store/interface/index.ts +++ b/frontend/src/store/interface/index.ts @@ -72,6 +72,7 @@ export interface TerminalState { lineHeight: number; letterSpacing: number; fontSize: number; + fontFamily: string; cursorBlink: string; cursorStyle: string; scrollback: number; diff --git a/frontend/src/store/modules/terminal.ts b/frontend/src/store/modules/terminal.ts index 885a63c98742..e6acbe3e74c6 100644 --- a/frontend/src/store/modules/terminal.ts +++ b/frontend/src/store/modules/terminal.ts @@ -8,6 +8,7 @@ export const TerminalStore = defineStore({ lineHeight: 1.2, letterSpacing: 1.2, fontSize: 12, + fontFamily: '', cursorBlink: 'enable', cursorStyle: 'underline', scrollback: 1000, @@ -23,6 +24,9 @@ export const TerminalStore = defineStore({ setFontSize(fontSize: number) { this.fontSize = fontSize; }, + setFontFamily(fontFamily: string) { + this.fontFamily = fontFamily; + }, setCursorBlink(cursorBlink: string) { this.cursorBlink = cursorBlink; }, diff --git a/frontend/src/views/terminal/setting/index.vue b/frontend/src/views/terminal/setting/index.vue index c764b7e4febb..22741d172c63 100644 --- a/frontend/src/views/terminal/setting/index.vue +++ b/frontend/src/views/terminal/setting/index.vue @@ -38,6 +38,15 @@ @change="changeItem()" /> + + + {{ $t('terminal.fontFamilyHelper') }} +
@@ -142,6 +151,7 @@ const form = reactive({ lineHeight: 1.2, letterSpacing: 1.2, fontSize: 12, + fontFamily: '', cursorBlink: 'Enable', cursorStyle: 'underline', scrollback: 1000, @@ -168,11 +178,15 @@ const search = async (withReset?: boolean) => { form.lineHeight = Number(res.data.lineHeight); form.letterSpacing = Number(res.data.letterSpacing); form.fontSize = Number(res.data.fontSize); + form.fontFamily = res.data.fontFamily || ''; form.cursorBlink = res.data.cursorBlink; form.cursorStyle = res.data.cursorStyle; form.scrollback = Number(res.data.scrollback); form.scrollSensitivity = Number(res.data.scrollSensitivity); + // 同步到 store,确保已打开的终端也能使用新字体 + terminalStore.setFontFamily(res.data.fontFamily || ''); + if (withReset) { changeItem(); } @@ -221,10 +235,13 @@ const submitChangeShow = async () => { }; const iniTerm = () => { + const defaultFontFamily = "Monaco, Menlo, Consolas, 'Courier New', monospace"; + const fontFamily = form.fontFamily || defaultFontFamily; + term.value = new Terminal({ lineHeight: 1.2, fontSize: 12, - fontFamily: "Monaco, Menlo, Consolas, 'Courier New', monospace", + fontFamily: fontFamily, theme: { background: '#000000', }, @@ -240,9 +257,13 @@ const iniTerm = () => { }; const changeItem = () => { + const defaultFontFamily = "Monaco, Menlo, Consolas, 'Courier New', monospace"; + const fontFamily = form.fontFamily || defaultFontFamily; + term.value.options.lineHeight = form.lineHeight; term.value.options.letterSpacing = form.letterSpacing; term.value.options.fontSize = form.fontSize; + term.value.options.fontFamily = fontFamily; term.value.options.cursorBlink = form.cursorBlink === 'Enable'; term.value.options.cursorStyle = form.cursorStyle; term.value.options.scrollback = form.scrollback; @@ -255,6 +276,7 @@ const onSetDefault = () => { form.lineHeight = 1.2; form.letterSpacing = 0; form.fontSize = 12; + form.fontFamily = ''; form.cursorBlink = 'Enable'; form.cursorStyle = 'block'; form.scrollback = 1000; @@ -274,6 +296,7 @@ const onSave = () => { lineHeight: form.lineHeight + '', letterSpacing: form.letterSpacing + '', fontSize: form.fontSize + '', + fontFamily: form.fontFamily, cursorBlink: form.cursorBlink, cursorStyle: form.cursorStyle, scrollback: form.scrollback + '', @@ -286,6 +309,7 @@ const onSave = () => { terminalStore.setLineHeight(form.lineHeight); terminalStore.setLetterSpacing(form.letterSpacing); terminalStore.setFontSize(form.fontSize); + terminalStore.setFontFamily(form.fontFamily); terminalStore.setCursorBlink(form.cursorBlink); terminalStore.setCursorStyle(form.cursorStyle); terminalStore.setScrollback(form.scrollback);