From e6d049cc112a827d4eb177aed705f20e9851f9d1 Mon Sep 17 00:00:00 2001 From: Alfred Date: Mon, 9 Feb 2026 23:02:24 +0800 Subject: [PATCH] =?UTF-8?q?webui:=20i18n=20v75=20-=20=E5=85=AC=E9=92=A5/?= =?UTF-8?q?=E7=A7=81=E9=92=A5/=E5=AF=BC=E5=87=BA=E4=B8=BB=E6=9C=BA/?= =?UTF-8?q?=E6=92=A4=E9=94=80=E4=B8=BB=E6=9C=BA/=E8=BF=90=E8=A1=8C?= =?UTF-8?q?=E6=97=B6=E9=97=B4/=E4=BC=98=E5=8C=96=E5=BB=BA=E8=AE=AE/?= =?UTF-8?q?=E5=8A=A0=E8=BD=BD=E4=B8=AD/ssh=20=E5=91=BD=E4=BB=A4=E7=BC=96?= =?UTF-8?q?=E8=BE=91=20key=EF=BC=9B=E5=8E=BB=20emoji=EF=BC=9B=E6=8E=92?= =?UTF-8?q?=E7=89=88=20Logic/=E5=88=97=E5=AE=BD/nowrap?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/ts_api/src/ts_api_system.c | 4 +- components/ts_webui/web/css/style.css | 52 +- components/ts_webui/web/index.html | 128 +- components/ts_webui/web/js/app.js | 2835 +++++++++++----------- components/ts_webui/web/js/i18n.js | 3 +- components/ts_webui/web/js/lang/en-US.js | 650 ++++- components/ts_webui/web/js/lang/zh-CN.js | 624 ++++- components/ts_webui/web/js/router.js | 13 +- components/ts_webui/web/js/terminal.js | 6 +- partitions.csv | 10 +- 10 files changed, 2787 insertions(+), 1538 deletions(-) diff --git a/components/ts_api/src/ts_api_system.c b/components/ts_api/src/ts_api_system.c index 873745b..88ebbc0 100644 --- a/components/ts_api/src/ts_api_system.c +++ b/components/ts_api/src/ts_api_system.c @@ -697,7 +697,7 @@ static esp_err_t api_system_memory_detail(const cJSON *params, ts_api_result_t * if (dram_free > 0) { float frag = 100.0f * (1.0f - (float)dram_largest / (float)dram_free); if (frag > 60) { - cJSON_AddItemToArray(tips, cJSON_CreateString("warning:DRAM 碎片化严重,建议重启系统")); + cJSON_AddItemToArray(tips, cJSON_CreateString("warning:dram_fragmented")); } } } @@ -705,7 +705,7 @@ static esp_err_t api_system_memory_detail(const cJSON *params, ts_api_result_t * if (psram_total > 0) { int psram_used_pct = 100 * (psram_total - psram_free) / psram_total; if (psram_used_pct < 50) { - cJSON_AddItemToArray(tips, cJSON_CreateString("info:PSRAM 空间充足,可用于大型缓冲区")); + cJSON_AddItemToArray(tips, cJSON_CreateString("info:psram_sufficient")); } } diff --git a/components/ts_webui/web/css/style.css b/components/ts_webui/web/css/style.css index 8459a5f..c892775 100644 --- a/components/ts_webui/web/css/style.css +++ b/components/ts_webui/web/css/style.css @@ -65,7 +65,7 @@ body { .nav { display: flex; gap: 20px; - margin-left: 40px; + margin-left: 0; } .nav-link { @@ -95,21 +95,21 @@ body { padding: 20px; } -/* Footer */ +/* Footer:行高=高度,单行文字在 50px 内由行框自然垂直居中,不依赖像素微调 */ .footer { height: var(--footer-height); + line-height: var(--footer-height); background: transparent; border-top: none; - display: flex; - align-items: center; - justify-content: center; + text-align: center; color: var(--text-light); font-size: 0.9rem; } .footer p { margin: 0; - line-height: 1; + display: inline; + line-height: inherit; } /* Cards */ @@ -984,13 +984,13 @@ button.btn-gray:hover, /* 新增样式 */ /* ============================================================ */ -/* WebSocket 状态指示器 */ +/* WebSocket 状态指示器(位于用户菜单内、root 图标左侧,与 user-menu 的 gap 保持间隔) */ .ws-status { width: 12px; height: 12px; border-radius: 50%; background: #e74c3c; - margin-left: 10px; + flex-shrink: 0; transition: background 0.3s; } @@ -998,10 +998,18 @@ button.btn-gray:hover, background: #2e7d32; } +/* Header separator line */ +.header-separator { + width: 1px; + height: 24px; + background-color: #d0d0d0; + margin: 0 16px; +} + /* 语言切换按钮 */ .lang-switch { - margin-left: 12px; - margin-right: 8px; + margin-left: 16px; + margin-right: 0; position: relative; } @@ -1024,6 +1032,18 @@ button.btn-gray:hover, border-color: var(--primary-color); } +/* 语言切换按钮:无底色、无外框 */ +.lang-btn.btn-service-style { + background: transparent !important; + color: #007bff !important; + border: none !important; +} +.lang-btn.btn-service-style:hover { + background: transparent !important; + color: #007bff !important; + border: none !important; +} + #lang-icon { font-size: 1rem; } @@ -3061,6 +3081,12 @@ button.btn-gray:hover, padding: 5px 12px; font-size: 0.85rem; } +/* 安全页按钮 icon 与文字间距统一(与其他页面一致) */ +.page-security .btn { + display: inline-flex; + align-items: center; + gap: 6px; +} /* HTTPS 证书一行:CSR / 证书 / 删除 等按钮统一宽度,视觉一致(含表格行内按钮) */ .page-security .button-group .btn { min-width: 6rem; @@ -3077,6 +3103,7 @@ button.btn-gray:hover, display: inline-flex; align-items: center; justify-content: center; + gap: 6px; box-sizing: border-box; text-align: center; } @@ -5382,7 +5409,7 @@ button.btn-gray:hover, /* 条件逻辑缩短,避免与冷却时间、立即启用挤在一起 */ #add-rule-modal .form-row.three-col .form-group-logic { - flex: 0 0 90px; + flex: 0 0 120px; min-width: 0; } @@ -7018,7 +7045,8 @@ button.btn-gray:hover, } .data-widgets-empty p { - margin-bottom: 0; + margin: 0 0 4px; + font-size: 0.95em; } /* 组件卡片 */ diff --git a/components/ts_webui/web/index.html b/components/ts_webui/web/index.html index b9a1f47..9b38861 100644 --- a/components/ts_webui/web/index.html +++ b/components/ts_webui/web/index.html @@ -19,6 +19,16 @@ TianshanOS Logo TianshanOS +
+ + +
+
-
-
- - -
+
未登录 - +
@@ -49,7 +50,7 @@
-

加载中...

+

Loading...

@@ -81,7 +82,7 @@

登录 Tia @@ -113,7 +114,7 @@

/sdcard/images
-
加载中...
+
Loading...
已选择: - @@ -133,7 +134,7 @@

内存详细分析

- - + + diff --git a/components/ts_webui/web/js/app.js b/components/ts_webui/web/js/app.js index fc8bd80..8dd1a13 100644 --- a/components/ts_webui/web/js/app.js +++ b/components/ts_webui/web/js/app.js @@ -1,6 +1,7 @@ /** * TianshanOS Web App - Main Application */ +if (typeof window.t === 'undefined') window.t = function(k) { return k; }; // ========================================================================= // 全局状态 @@ -151,6 +152,13 @@ document.addEventListener('DOMContentLoaded', () => { router.register('/security', loadSecurityPage); router.register('/automation', loadAutomationPage); + // 语言切换时重新渲染当前页,使主内容使用新语言;下一帧恢复右上角登录态(避免 translateDOM 覆盖 #user-name) + window.addEventListener('languageChanged', () => { + const loader = router.getCurrentLoader(); + if (loader) loader(); + setTimeout(() => updateAuthUI(), 0); + }); + // 启动 WebSocket setupWebSocket(); @@ -177,18 +185,18 @@ function updateAuthUI() { const level = api.getLevel(); const levelIcon = 'ri-user-line'; // 统一使用人形图标 - loginBtn.textContent = '登出'; + loginBtn.textContent = t('security.logout'); loginBtn.classList.add('btn-service-style'); userName.innerHTML = ` ${username}`; - userName.title = `权限级别: ${level}`; + userName.title = (typeof t === 'function' ? t('ui.permissionLevel') : '权限级别') + ': ' + level; loginBtn.onclick = logout; // 更新导航菜单可见性 router.updateNavVisibility(); } else { - loginBtn.textContent = '登录'; - loginBtn.classList.remove('btn-service-style'); - userName.textContent = '未登录'; + loginBtn.textContent = t('security.login'); + loginBtn.classList.add('btn-service-style'); + userName.textContent = t('ui.notLoggedIn'); userName.title = ''; loginBtn.onclick = showLoginModal; @@ -218,7 +226,7 @@ document.getElementById('login-form')?.addEventListener('submit', async (e) => { // 显示加载状态 submitBtn.disabled = true; - submitBtn.textContent = '登录中...'; + submitBtn.textContent = t('login.loggingIn'); errorEl?.classList.add('hidden'); try { @@ -234,31 +242,31 @@ document.getElementById('login-form')?.addEventListener('submit', async (e) => { } router.navigate(); - showToast(`欢迎, ${username}!`, 'success'); + showToast(t('login.welcomeName', { name: username }), 'success'); } else { // 显示错误信息 if (errorEl) { - errorEl.textContent = result.message || '登录失败'; + errorEl.textContent = result.message || t('login.loginFailed'); errorEl.classList.remove('hidden'); } - showToast(result.message || '登录失败', 'error'); + showToast(result.message || t('login.loginFailed'), 'error'); } } catch (error) { if (errorEl) { - errorEl.textContent = error.message || '网络错误'; + errorEl.textContent = error.message || t('login.networkError'); errorEl.classList.remove('hidden'); } - showToast('登录失败: ' + error.message, 'error'); + showToast(t('login.loginFailed') + ': ' + error.message, 'error'); } finally { submitBtn.disabled = false; - submitBtn.textContent = '登录'; + submitBtn.textContent = t('login.loginButton'); } }); async function logout() { try { await api.logout(); - showToast('已登出', 'info'); + showToast(t('toast.loggedOut'), 'info'); } finally { updateAuthUI(); window.location.hash = '/'; // 重定向到首页 @@ -276,29 +284,29 @@ function showPasswordChangeReminder() { modal.innerHTML = ` `; @@ -316,15 +324,14 @@ async function submitPasswordChange() { const confirmPwd = document.getElementById('change-confirm-pwd').value; const errorEl = document.getElementById('change-pwd-error'); - // 验证 if (newPwd !== confirmPwd) { - errorEl.textContent = '两次输入的新密码不一致'; + errorEl.textContent = typeof t === 'function' ? t('login.passwordMismatch') : '两次输入的新密码不一致'; errorEl.classList.remove('hidden'); return; } if (newPwd.length < 4) { - errorEl.textContent = '新密码至少4个字符'; + errorEl.textContent = typeof t === 'function' ? t('login.passwordMinLength') : '新密码至少4个字符'; errorEl.classList.remove('hidden'); return; } @@ -333,13 +340,13 @@ async function submitPasswordChange() { const result = await api.changePassword(oldPwd, newPwd); if (result.code === 0) { closePasswordChangeModal(); - showToast('密码修改成功!', 'success'); + showToast(t('login.passwordChanged'), 'success'); } else { - errorEl.textContent = result.message || '修改失败'; + errorEl.textContent = result.message || t('toast.saveFailed'); errorEl.classList.remove('hidden'); } } catch (error) { - errorEl.textContent = error.message || '网络错误'; + errorEl.textContent = error.message || t('login.networkError'); errorEl.classList.remove('hidden'); } } @@ -532,22 +539,22 @@ async function loadSystemPage() {
-

资源监控

+

${t('system.resourceMonitor')}

CPU

-
加载中...
+
${t('common.loading')}
-

内存

- +

${t('system.memory')}

+

DRAM:

@@ -564,28 +571,28 @@ async function loadSystemPage() {
-

系统总览

+

${t('system.title')}

- + - +
-

系统信息

-

芯片: -

-

固件: - / -

-

运行: -

+

${t('system.overview')}

+

${t('system.chip')}: -

+

${t('system.firmware')}: - / -

+

${t('system.uptime')}: -

-

-

电源状态

-

输入: - / 内部 -

-

电流: -

-

功率: -

-

保护: - +

${t('system.power')}

+

${t('system.inputVoltage')}: - / ${t('system.internal')} -

+

${t('system.current')}: -

+

${t('system.wattage')}: -

+

${t('system.protection')}: + -

@@ -595,24 +602,24 @@ async function loadSystemPage() {
-

网络 & 时间

- +

${t('system.networkTime')}

+
-

网络连接

-

以太网: -

-

WiFi: -

-

IP: -

+

${t('network.connection')}

+

${t('system.ethernet')}: -

+

${t('system.wifi')}: -

+

${t('system.ipAddress')}: -

-

时间同步

-

当前: -

-

状态: - (-)

-

时区: -

+

${t('system.timeSync')}

+

${t('system.currentTime')}: -

+

${t('system.timeStatus')}: - (-)

+

${t('system.timezone')}: -

- - + +
@@ -624,16 +631,16 @@ async function loadSystemPage() {
-

设备面板

+

${t('system.devicePanel')}

- - - + + +
-
加载中...
+
${t('common.loading')}
@@ -643,41 +650,41 @@ async function loadSystemPage() {
-

风扇控制

+

${t('fan.title')}

- - + +
- 有效温度 + ${t('fan.effectiveTemp')} --°C
- 目标转速 + ${t('fan.targetSpeed')} --%
- 测试温度 + ${t('fan.testTemp')}
- - + +
-
加载中...
+
${t('common.loading')}
@@ -685,15 +692,15 @@ async function loadSystemPage() {
-

LED 控制

+

${t('led.title')}

- - - + + +
-
加载设备中...
+
${t('ledPage.loadingDevices')}
@@ -702,18 +709,18 @@ async function loadSystemPage() {