diff --git a/src-tauri/src/commands/config.rs b/src-tauri/src/commands/config.rs index 2eb6a8e..93845f8 100644 --- a/src-tauri/src/commands/config.rs +++ b/src-tauri/src/commands/config.rs @@ -192,7 +192,7 @@ fn standalone_archive_ext() -> &'static str { } /// standalone 安装目录 -fn standalone_install_dir() -> Option { +pub(crate) fn standalone_install_dir() -> Option { #[cfg(target_os = "windows")] { // Inno Setup PrivilegesRequired=lowest 默认安装到 %LOCALAPPDATA%\Programs @@ -207,7 +207,7 @@ fn standalone_install_dir() -> Option { } /// 所有可能的 standalone 安装位置(用于检测和卸载) -fn all_standalone_dirs() -> Vec { +pub(crate) fn all_standalone_dirs() -> Vec { let mut dirs = Vec::new(); #[cfg(target_os = "windows")] { @@ -1194,7 +1194,22 @@ async fn get_local_version() -> Option { #[cfg(target_os = "windows")] { + // 优先从活跃 CLI 路径读取版本(与 macOS 逻辑一致) + if let Some(cli_path) = crate::utils::resolve_openclaw_cli_path() { + let cli_pb = PathBuf::from(&cli_path); + let resolved = std::fs::canonicalize(&cli_pb).unwrap_or_else(|_| cli_pb.clone()); + if let Some(ver) = read_version_from_installation(&resolved) + .or_else(|| read_version_from_installation(&cli_pb)) + { + return Some(ver); + } + } + for sa_dir in all_standalone_dirs() { + // 仅当 CLI 二进制实际存在时才读取版本,避免残留文件误判为已安装 + if !sa_dir.join("openclaw.cmd").exists() { + continue; + } let version_file = sa_dir.join("VERSION"); if let Ok(content) = fs::read_to_string(&version_file) { for line in content.lines() { @@ -1222,21 +1237,87 @@ async fn get_local_version() -> Option { } if let Ok(appdata) = std::env::var("APPDATA") { - let cli_is_zh = crate::utils::resolve_openclaw_cli_path() - .map(|p| crate::utils::classify_cli_source(&p) == "npm-zh") - .unwrap_or(false); - let pkgs: &[&str] = if cli_is_zh { - &["@qingchencloud/openclaw-zh", "openclaw"] + let npm_bin = PathBuf::from(&appdata).join("npm"); + let shim_path = npm_bin.join("openclaw.cmd"); + // 仅当 npm 全局 CLI shim 存在时才读取版本 + if !shim_path.exists() { + // npm 全局无 CLI shim,跳过 } else { - &["openclaw", "@qingchencloud/openclaw-zh"] - }; - for pkg in pkgs { - let pkg_json = PathBuf::from(&appdata) - .join("npm") - .join("node_modules") - .join(pkg) - .join("package.json"); - if let Ok(content) = fs::read_to_string(&pkg_json) { + // 读 .cmd 内容判断活跃包,而非依赖 classify_cli_source(路径无法区分) + let is_zh = detect_source_from_cmd_shim(&shim_path) + .map(|s| s == "chinese") + .unwrap_or(false); + let pkgs: &[&str] = if is_zh { + &["@qingchencloud/openclaw-zh", "openclaw"] + } else { + &["openclaw", "@qingchencloud/openclaw-zh"] + }; + for pkg in pkgs { + let pkg_json = npm_bin.join("node_modules").join(pkg).join("package.json"); + if let Ok(content) = fs::read_to_string(&pkg_json) { + if let Some(ver) = serde_json::from_str::(&content) + .ok() + .and_then(|v| v.get("version")?.as_str().map(String::from)) + { + return Some(ver); + } + } + } + } + } + } + + // Linux: 参照 macOS/Windows 实现,完整检测链 + #[cfg(target_os = "linux")] + { + // 1. 活跃 CLI 优先 + if let Some(cli_path) = crate::utils::resolve_openclaw_cli_path() { + let cli_pb = PathBuf::from(&cli_path); + let resolved = std::fs::canonicalize(&cli_pb).unwrap_or_else(|_| cli_pb.clone()); + if let Some(ver) = read_version_from_installation(&resolved) + .or_else(|| read_version_from_installation(&cli_pb)) + { + return Some(ver); + } + } + // 2. standalone 目录 + for sa_dir in all_standalone_dirs() { + if !sa_dir.join("openclaw").exists() { + continue; + } + let version_file = sa_dir.join("VERSION"); + if let Ok(content) = fs::read_to_string(&version_file) { + for line in content.lines() { + if let Some(ver) = line.strip_prefix("openclaw_version=") { + let ver = ver.trim(); + if !ver.is_empty() { + return Some(ver.to_string()); + } + } + } + } + let sa_pkg = sa_dir + .join("node_modules") + .join("@qingchencloud") + .join("openclaw-zh") + .join("package.json"); + if let Ok(content) = fs::read_to_string(&sa_pkg) { + if let Some(ver) = serde_json::from_str::(&content) + .ok() + .and_then(|v| v.get("version")?.as_str().map(String::from)) + { + return Some(ver); + } + } + } + // 3. symlink -> package.json + if let Ok(target) = fs::read_link("/usr/local/bin/openclaw") { + let pkg_json = PathBuf::from("/usr/local/bin") + .join(&target) + .parent() + .map(|p| p.join("package.json")); + if let Some(ref pkg_path) = pkg_json { + if let Ok(content) = fs::read_to_string(pkg_path) { if let Some(ver) = serde_json::from_str::(&content) .ok() .and_then(|v| v.get("version")?.as_str().map(String::from)) @@ -1298,9 +1379,27 @@ async fn get_latest_version_for(source: &str) -> Option { .map(String::from) } +/// 从 Windows .cmd shim 文件内容判断实际关联的 npm 包来源 +/// npm 生成的 shim 末尾引用实际 JS 入口,据此区分官方版与汉化版 +#[cfg(target_os = "windows")] +fn detect_source_from_cmd_shim(cmd_path: &std::path::Path) -> Option { + let content = std::fs::read_to_string(cmd_path).ok()?; + let lower = content.to_lowercase(); + // 汉化版标记:@qingchencloud 或 openclaw-zh + if lower.contains("openclaw-zh") || lower.contains("@qingchencloud") { + return Some("chinese".into()); + } + // 确认是 npm shim(含 node_modules 引用)→ 官方版 + if lower.contains("node_modules") { + return Some("official".into()); + } + // standalone 的 .cmd 可能不含 node_modules(自定义脚本),由 classify 处理 + None +} + /// 检测当前安装的是官方版还是汉化版 -/// macOS: 优先检查 homebrew symlink,fallback 到 npm list -/// Windows: 优先检查 npm 全局目录下的 package.json,避免调用 npm list 阻塞 +/// macOS: 优先检查 symlink 指向的实际路径 +/// Windows: 读取 .cmd shim 内容判断实际关联的包 /// Linux: 直接用 npm list fn detect_installed_source() -> String { // macOS: 检查 openclaw bin 的 symlink 指向 @@ -1332,49 +1431,73 @@ fn detect_installed_source() -> String { return "chinese".into(); } } - "official".into() + "unknown".into() } - // Windows: 优先通过 CLI 路径判断,fallback 到文件系统检测 + // Windows: 通过活跃 CLI 的 .cmd shim 内容判断来源 + // npm 生成的 .cmd shim 最后一行包含实际 JS 入口路径,例如: + // "%dp0%\node_modules\openclaw\bin\openclaw.js" → 官方版 + // "%dp0%\node_modules\@qingchencloud\openclaw-zh\..." → 汉化版 + // 读取内容即可一锤定音,不依赖文件系统扫描(避免残留目录误判) #[cfg(target_os = "windows")] { - // 优先通过实际 CLI 路径判断 if let Some(cli_path) = crate::utils::resolve_openclaw_cli_path() { let source = crate::utils::classify_cli_source(&cli_path); - if source == "npm-zh" { + // 路径本身能确定的情况(standalone 目录、npm-zh 路径含 openclaw-zh) + if source == "npm-zh" || source == "standalone" { return "chinese".into(); } - if source == "standalone" { - return "official".into(); - } - // npm-official/npm-global: Windows .cmd shim 路径不含包名,需继续检查文件系统 - } - // 检查所有可能的 standalone 安装目录 - for sa_dir in all_standalone_dirs() { - let sa_zh = sa_dir - .join("node_modules") - .join("@qingchencloud") - .join("openclaw-zh"); - if sa_zh.exists() { - return "chinese".into(); + // npm-official / npm-global / unknown: 路径不含包名,读 .cmd 内容判断 + if let Some(shim_source) = detect_source_from_cmd_shim(std::path::Path::new(&cli_path)) + { + return shim_source; } } - // 检查 npm 全局目录 - if let Some(appdata) = std::env::var_os("APPDATA") { - let zh_dir = PathBuf::from(&appdata) - .join("npm") - .join("node_modules") - .join("@qingchencloud") - .join("openclaw-zh"); - if zh_dir.exists() { - return "chinese".into(); + // 无活跃 CLI 时的兜底:仅检查 npm 全局目录中实际存在的 shim + if let Ok(appdata) = std::env::var("APPDATA") { + let shim = PathBuf::from(&appdata).join("npm").join("openclaw.cmd"); + if let Some(s) = detect_source_from_cmd_shim(&shim) { + return s; } } - // 默认返回官方版 - "official".into() + // 确实无法判断 + "unknown".into() } - // 所有平台通用: npm list 检测 + // Linux: 参照 macOS 实现,完整检测链 #[cfg(not(any(target_os = "macos", target_os = "windows")))] { + // 1. 活跃 CLI 路径分类(与 macOS 一致) + if let Some(cli_path) = crate::utils::resolve_openclaw_cli_path() { + let resolved = std::fs::canonicalize(&cli_path) + .ok() + .unwrap_or_else(|| PathBuf::from(&cli_path)); + let source = crate::utils::classify_cli_source(&resolved.to_string_lossy()); + if source == "npm-zh" || source == "standalone" { + return "chinese".into(); + } + if source == "npm-official" || source == "npm-global" { + return "official".into(); + } + } + // 2. 检查 symlink 指向(/usr/local/bin/openclaw, ~/bin/openclaw) + let home = dirs::home_dir().unwrap_or_default(); + for link in &[ + PathBuf::from("/usr/local/bin/openclaw"), + home.join("bin").join("openclaw"), + ] { + if let Ok(target) = std::fs::read_link(link) { + if target.to_string_lossy().contains("openclaw-zh") { + return "chinese".into(); + } + return "official".into(); + } + } + // 3. standalone 目录检测 + for sa_dir in all_standalone_dirs() { + if sa_dir.join("openclaw").exists() || sa_dir.join("VERSION").exists() { + return "chinese".into(); + } + } + // 4. npm list 兜底 if let Ok(o) = npm_command() .args(["list", "-g", "@qingchencloud/openclaw-zh", "--depth=0"]) .output() @@ -1383,7 +1506,7 @@ fn detect_installed_source() -> String { return "chinese".into(); } } - "official".into() + "unknown".into() } } @@ -1391,14 +1514,23 @@ fn detect_installed_source() -> String { pub async fn get_version_info() -> Result { let current = get_local_version().await; let mut source = detect_installed_source(); - // 兜底:版本号含 -zh 则一定是汉化版(文件系统检测可能误判) + // 兜底:版本号含 -zh 则一定是汉化版 if let Some(ref ver) = current { if ver.contains("-zh") && source != "chinese" { source = "chinese".to_string(); } } - let latest = get_latest_version_for(&source).await; - let recommended = recommended_version_for(&source); + // unknown 来源不查询 latest/recommended(无法确定对应哪个 npm 包) + let latest = if source == "unknown" { + None + } else { + get_latest_version_for(&source).await + }; + let recommended = if source == "unknown" { + None + } else { + recommended_version_for(&source) + }; let update_available = match (¤t, &recommended) { (Some(c), Some(r)) => recommended_is_newer(r, c), (None, Some(_)) => true, @@ -1546,8 +1678,16 @@ fn read_version_from_installation(cli_path: &std::path::Path) -> Option } } } + // 根据 CLI 路径判断来源,决定 package.json 检查顺序 + // 避免残留的另一来源包被优先读取 + let cli_source = crate::utils::classify_cli_source(&cli_path.to_string_lossy()); + let pkg_names: &[&str] = if cli_source == "npm-zh" || cli_source == "standalone" { + &["@qingchencloud/openclaw-zh", "openclaw"] + } else { + &["openclaw", "@qingchencloud/openclaw-zh"] + }; // 尝试从 package.json 读取 - for pkg_name in &["@qingchencloud/openclaw-zh", "openclaw"] { + for pkg_name in pkg_names { let pkg_json = dir.join("node_modules").join(pkg_name).join("package.json"); if let Ok(content) = std::fs::read_to_string(&pkg_json) { if let Some(ver) = serde_json::from_str::(&content) @@ -1560,7 +1700,7 @@ fn read_version_from_installation(cli_path: &std::path::Path) -> Option } // npm shim 情况:向上查找 node_modules if let Some(parent) = dir.parent() { - for pkg_name in &["@qingchencloud/openclaw-zh", "openclaw"] { + for pkg_name in pkg_names { let pkg_json = parent .join("node_modules") .join(pkg_name) @@ -2010,6 +2150,14 @@ async fn try_standalone_install( ]) .creation_flags(0x08000000) .status(); + // 同步更新当前进程的 PATH 环境变量,使后续 resolve_openclaw_cli_path() + // 和 build_enhanced_path() 能立即发现 standalone 安装的 CLI, + // 无需重启应用(注册表写入仅对新进程生效) + // SAFETY: 在 Tauri 命令处理器中单次调用,此时无其他线程并发读写 PATH。 + // enhanced_path 使用独立的 RwLock 缓存,不受影响。 + unsafe { + std::env::set_var("PATH", format!("{};{}", current_path, install_str)); + } let _ = app.emit("upgrade-log", format!("已添加到 PATH: {install_str}")); } } @@ -2621,6 +2769,18 @@ async fn upgrade_openclaw_inner( if need_uninstall_old { let _ = app.emit("upgrade-log", format!("清理旧版本 ({old_pkg})...")); let _ = npm_command().args(["uninstall", "-g", old_pkg]).output(); + + // 清理 standalone 安装目录(不论从 standalone 切走还是切到 standalone, + // npm 路径已经安装了新 CLI,standalone 残留会干扰源检测) + for sa_dir in all_standalone_dirs() { + if sa_dir.exists() { + let _ = app.emit( + "upgrade-log", + format!("清理 standalone 残留: {}", sa_dir.display()), + ); + let _ = std::fs::remove_dir_all(&sa_dir); + } + } } // 切换源后重装 Gateway 服务 diff --git a/src-tauri/src/commands/mod.rs b/src-tauri/src/commands/mod.rs index 1ea43b8..913f5e8 100644 --- a/src-tauri/src/commands/mod.rs +++ b/src-tauri/src/commands/mod.rs @@ -258,6 +258,10 @@ fn build_enhanced_path() -> String { if let Ok(prefix) = std::env::var("NPM_CONFIG_PREFIX") { extra.push(format!("{}/bin", prefix)); } + // standalone 安装目录(集中管理,避免多处硬编码) + for sa_dir in config::all_standalone_dirs() { + extra.push(sa_dir.to_string_lossy().into_owned()); + } // 扫描 nvm 实际安装的版本目录(兼容无 current 符号链接的情况) // 按版本号倒序排列,确保最新版优先(修复 #143:v20 排在 v24 前面) let nvm_versions = home.join(".nvm/versions/node"); @@ -326,6 +330,10 @@ fn build_enhanced_path() -> String { if let Ok(prefix) = std::env::var("NPM_CONFIG_PREFIX") { extra.push(format!("{}/bin", prefix)); } + // standalone 安装目录(集中管理,避免多处硬编码) + for sa_dir in config::all_standalone_dirs() { + extra.push(sa_dir.to_string_lossy().into_owned()); + } // NVM_DIR 环境变量(用户可能自定义了 nvm 安装目录) // 按版本号倒序排列,确保最新版优先(修复 #143:v20 排在 v24 前面) let nvm_dir = std::env::var("NVM_DIR") @@ -539,6 +547,14 @@ fn build_enhanced_path() -> String { extra.push(format!(r"{}\npm", appdata)); } + // 6.5 standalone 安装目录(集中管理,避免多处硬编码) + // standalone 安装后通过注册表写入用户 PATH,但当前进程的 PATH 环境变量不会 + // 实时更新,需要显式添加到 enhanced_path 以确保 resolve_openclaw_cli_path() + // 能找到 standalone 安装的 openclaw.cmd + for sa_dir in config::all_standalone_dirs() { + extra.push(sa_dir.to_string_lossy().into_owned()); + } + // 7. 系统默认 Node.js 安装路径(优先级最低) extra.push(format!(r"{}\nodejs", pf)); extra.push(format!(r"{}\nodejs", pf86)); diff --git a/src-tauri/src/commands/service.rs b/src-tauri/src/commands/service.rs index 8dc5fa6..e4e9aff 100644 --- a/src-tauri/src/commands/service.rs +++ b/src-tauri/src/commands/service.rs @@ -329,10 +329,11 @@ mod platform { fn common_cli_candidates() -> Vec { let mut candidates = Vec::new(); - if let Some(home) = dirs::home_dir() { - candidates.push(home.join(".openclaw-bin").join("openclaw")); + // standalone 安装目录(集中管理,避免多处硬编码) + for sa_dir in crate::commands::config::all_standalone_dirs() { + candidates.push(sa_dir.join("openclaw")); } - candidates.push(PathBuf::from("/opt/openclaw/openclaw")); + // Homebrew 路径(非 standalone,保留) candidates.push(PathBuf::from("/opt/homebrew/bin/openclaw")); candidates.push(PathBuf::from("/usr/local/bin/openclaw")); candidates @@ -849,23 +850,9 @@ mod platform { fn candidate_cli_paths() -> Vec { let mut candidates = Vec::new(); - // standalone 安装目录(优先检测,覆盖所有可能位置) - if let Ok(localappdata) = env::var("LOCALAPPDATA") { - // Inno Setup PrivilegesRequired=lowest 默认路径 - candidates.push( - Path::new(&localappdata) - .join("Programs") - .join("OpenClaw") - .join("openclaw.cmd"), - ); - candidates.push( - Path::new(&localappdata) - .join("OpenClaw") - .join("openclaw.cmd"), - ); - } - if let Ok(pf) = env::var("ProgramFiles") { - candidates.push(Path::new(&pf).join("OpenClaw").join("openclaw.cmd")); + // standalone 安装目录(集中管理,避免多处硬编码) + for sa_dir in crate::commands::config::all_standalone_dirs() { + candidates.push(sa_dir.join("openclaw.cmd")); } if let Ok(appdata) = env::var("APPDATA") { @@ -1224,6 +1211,10 @@ mod platform { .join("openclaw"), ); } + // standalone 安装目录(集中管理,避免多处硬编码) + for sa_dir in crate::commands::config::all_standalone_dirs() { + candidates.push(sa_dir.join("openclaw")); + } candidates.push(PathBuf::from("/usr/local/bin/openclaw")); candidates.push(PathBuf::from("/usr/bin/openclaw")); for segment in crate::commands::enhanced_path().split(':') { diff --git a/src-tauri/src/utils.rs b/src-tauri/src/utils.rs index da083b3..5065b7e 100644 --- a/src-tauri/src/utils.rs +++ b/src-tauri/src/utils.rs @@ -38,11 +38,14 @@ fn find_openclaw_cmd() -> Option { #[cfg(not(target_os = "windows"))] fn common_non_windows_cli_candidates() -> Vec { let mut candidates = Vec::new(); + // standalone 安装目录(集中管理,避免多处硬编码) + for sa_dir in crate::commands::config::all_standalone_dirs() { + candidates.push(sa_dir.join("openclaw")); + } + // 其他标准路径 if let Some(home) = dirs::home_dir() { - candidates.push(home.join(".openclaw-bin").join("openclaw")); candidates.push(home.join(".local").join("bin").join("openclaw")); } - candidates.push(std::path::PathBuf::from("/opt/openclaw/openclaw")); candidates.push(std::path::PathBuf::from("/opt/homebrew/bin/openclaw")); candidates.push(std::path::PathBuf::from("/usr/local/bin/openclaw")); candidates.push(std::path::PathBuf::from("/usr/bin/openclaw")); diff --git a/src/locales/modules/about.js b/src/locales/modules/about.js index d58956b..5455fe6 100644 --- a/src/locales/modules/about.js +++ b/src/locales/modules/about.js @@ -14,6 +14,7 @@ export default { checkingUpdate: _('检查更新中...', 'Checking for updates...', '檢查更新中...', '更新を確認中...', '업데이트 확인 중...', 'Đang kiểm tra cập nhật...', 'Verificando actualizaciones...', 'Verificando atualizações...', 'Проверка обновлений...', 'Vérification des mises à jour...', 'Suche nach Updates...'), official: _('原版', 'Official', '', '公式', '공식', 'Chính thức', 'Oficial', 'Oficial', 'Официальный', 'Officiel', 'Offiziell'), chinese: _('汉化版', 'Chinese', '漢化版', '中国語版', '중국어판'), + unknownSource: _('未知来源', 'Unknown Source', '未知來源', '不明なソース', '알 수 없는 출처'), policyAhead: _('检测到你本地安装的是高于推荐稳定版的 {current},可能存在接口、事件或配置兼容性问题。建议回退到 {recommended};如果你要继续使用高版本,请自行验证兼容性并关注 issue / release。', 'Your local installation {current} is ahead of the recommended stable version. There may be API, event, or config compatibility issues. Consider rolling back to {recommended}; if you want to keep the newer version, verify compatibility yourself and watch issues/releases.', '檢測到你本地安裝的是高於推薦穩定版的 {current},可能存在介面、事件或設定相容性問題。建議回退到 {recommended};如果你要繼續使用高版本,請自行驗證相容性並關注 issue / release。'), policyDefault: _('当前面板默认只保证推荐稳定版的兼容性;如果你要尝试其他版本或预览版,请自行验证兼容性。若希望面板尽快支持最新版特性,欢迎提交 issue 告诉我们。', 'This panel only guarantees compatibility with the recommended stable version. If you want to try other versions or previews, verify compatibility yourself. Submit an issue if you want us to support the latest version sooner.', '目前面板預設只保證推薦穩定版的相容性;如果你要尝試其他版本或預覽版,請自行驗證相容性。若希望面板尽快支援最新版特性,欢迎提交 issue 告诉我們。'), notInstalled: _('未安装', 'Not installed', '未安裝', '未インストール', '미설치', 'Chưa cài đặt', 'No instalado', 'Não instalado', 'Не установлен', 'Non installé', 'Nicht installiert'), diff --git a/src/locales/modules/dashboard.js b/src/locales/modules/dashboard.js index 2c8cf9c..3c1a023 100644 --- a/src/locales/modules/dashboard.js +++ b/src/locales/modules/dashboard.js @@ -9,6 +9,7 @@ export default { versionLabel: _('版本', 'Version', '', 'バージョン', '버전', 'Phiên bản', 'Versión', 'Versão', 'Версия'), versionOfficial: _('官方', 'Official', '', '公式', '공식'), versionChinese: _('汉化', 'Chinese', '漢化', '中国語版', '중국어판'), + versionUnknownSource: _('未知来源', 'Unknown Source', '未知來源', '不明なソース', '알 수 없는 출처'), versionUnknown: _('版本信息未获取', 'Version info unavailable', '版本資訊未取得', 'バージョン情報未取得', '버전 정보 없음'), versionAhead: _('当前版本高于推荐稳定版 {version},可能不稳定', 'Current version is ahead of recommended stable {version}, may be unstable', '目前版本高於推薦穩定版 {version},可能不穩定', '現在のバージョンは推奨安定版 {version} より新しく、不安定な可能性があります', '현재 버전이 권장 안정 버전 {version}보다 높아 불안정할 수 있습니다'), versionStable: _('稳定版 {version}', 'Stable {version}', '穩定版 {version}', '安定版 {version}', '안정 버전 {version}'), diff --git a/src/pages/about.js b/src/pages/about.js index 6750377..f785242 100644 --- a/src/pages/about.js +++ b/src/pages/about.js @@ -83,7 +83,7 @@ async function loadData(page) { checkHotUpdate(cards, panelVersion) const isInstalled = !!version.current - const sourceLabel = version.source === 'official' ? t('about.official') : t('about.chinese') + const sourceLabel = version.source === 'official' ? t('about.official') : version.source === 'chinese' ? t('about.chinese') : t('about.unknownSource') const btnSm = 'padding:2px 8px;font-size:var(--font-size-xs)' const hasRecommended = !!version.recommended const aheadOfRecommended = isInstalled && hasRecommended && !!version.ahead_of_recommended @@ -250,18 +250,18 @@ async function showVersionPicker(page, currentVersion) { if (!targetVer || targetVer === '') { hintEl.textContent = ''; confirmBtn.disabled = true; return } const targetTag = select.selectedIndex === 0 ? t('about.tagRecommended') : t('about.tagNeedTest') - const sameSource = targetSource === (currentVersion.source === 'official' ? 'official' : 'chinese') + const sameSource = targetSource === currentVersion.source if (!isInstalled) { confirmBtn.textContent = t('about.btnInstall') - hintEl.textContent = t('about.hintInstall', { source: targetSource === 'official' ? t('about.official') : t('about.chinese'), ver: targetVer, tag: targetTag }) + hintEl.textContent = t('about.hintInstall', { source: targetSource === 'official' ? t('about.official') : targetSource === 'chinese' ? t('about.chinese') : t('about.unknownSource'), ver: targetVer, tag: targetTag }) confirmBtn.disabled = false return } if (!sameSource) { confirmBtn.textContent = t('about.btnSwitch') - hintEl.innerHTML = `${t('about.hintCurrent')}: ${currentVersion.source === 'official' ? t('about.official') : t('about.chinese')} ${currentVersion.current}${targetSource === 'official' ? t('about.official') : t('about.chinese')} ${targetVer}${targetTag}` + hintEl.innerHTML = `${t('about.hintCurrent')}: ${currentVersion.source === 'official' ? t('about.official') : currentVersion.source === 'chinese' ? t('about.chinese') : t('about.unknownSource')} ${currentVersion.current}${targetSource === 'official' ? t('about.official') : targetSource === 'chinese' ? t('about.chinese') : t('about.unknownSource')} ${targetVer}${targetTag}` confirmBtn.disabled = false return } @@ -310,7 +310,7 @@ async function showVersionPicker(page, currentVersion) { const versions = showNightly ? allVersions : (stable.length > 0 ? stable : allVersions) const nightlyCount = allVersions.length - stable.length select.innerHTML = versions.map((v, idx) => { - const isCurrent = isInstalled && v === currentVersion.current && source === (currentVersion.source === 'official' ? 'official' : 'chinese') + const isCurrent = isInstalled && v === currentVersion.current && source === currentVersion.source return `` }).join('') // nightly 切换提示 diff --git a/src/pages/dashboard.js b/src/pages/dashboard.js index 111ab40..ba23c9f 100644 --- a/src/pages/dashboard.js +++ b/src/pages/dashboard.js @@ -186,7 +186,7 @@ function renderStatCards(page, services, version, agents, config) {
- ${t('dashboard.versionLabel')} · ${version.source === 'official' ? t('dashboard.versionOfficial') : t('dashboard.versionChinese')} + ${t('dashboard.versionLabel')} · ${version.source === 'official' ? t('dashboard.versionOfficial') : version.source === 'chinese' ? t('dashboard.versionChinese') : t('dashboard.versionUnknownSource')}
${version.current || t('common.unknown')}
${versionMeta}