diff --git a/web/src/components/InlineLogPanel.vue b/web/src/components/InlineLogPanel.vue index 50383fc..9905f31 100644 --- a/web/src/components/InlineLogPanel.vue +++ b/web/src/components/InlineLogPanel.vue @@ -134,9 +134,9 @@ onUnmounted(() => { diff --git a/web/src/views/ClientView.vue b/web/src/views/ClientView.vue index 34ebddf..f8fe6cc 100644 --- a/web/src/views/ClientView.vue +++ b/web/src/views/ClientView.vue @@ -15,7 +15,8 @@ import { getClient, updateClient, deleteClient, pushConfigToClient, disconnectClient, restartClient, getClientPluginConfig, updateClientPluginConfig, getStorePlugins, installStorePlugin, getRuleSchemas, startClientPlugin, restartClientPlugin, stopClientPlugin, deleteClientPlugin, - checkClientUpdate, applyClientUpdate, getClientSystemStats, type UpdateInfo, type SystemStats + checkClientUpdate, applyClientUpdate, getClientSystemStats, getVersionInfo, + type UpdateInfo, type SystemStats } from '../api' import type { ProxyRule, ClientPlugin, ConfigField, StorePluginInfo, RuleSchemasMap } from '../types' import LogViewer from '../components/LogViewer.vue' @@ -42,6 +43,7 @@ const clientVersion = ref('') // 客户端更新相关 const clientUpdate = ref(null) const updatingClient = ref(false) +const serverVersion = ref('') // 系统状态相关 const systemStats = ref(null) @@ -94,6 +96,61 @@ const getExtraFields = (type: string): ConfigField[] => { return schema?.extra_fields || [] } +// 加载服务端版本 +const loadServerVersion = async () => { + try { + const { data } = await getVersionInfo() + serverVersion.value = data.version || '' + } catch (e) { + console.error('Failed to load server version', e) + } +} + +// 版本比较函数:返回 -1 (v1 < v2), 0 (v1 == v2), 1 (v1 > v2) +const compareVersions = (v1: string, v2: string): number => { + const normalize = (v: string) => v.replace(/^v/, '').split('.').map(n => parseInt(n, 10) || 0) + const parts1 = normalize(v1) + const parts2 = normalize(v2) + const len = Math.max(parts1.length, parts2.length) + for (let i = 0; i < len; i++) { + const p1 = parts1[i] || 0 + const p2 = parts2[i] || 0 + if (p1 < p2) return -1 + if (p1 > p2) return 1 + } + return 0 +} + +// 判断客户端是否需要更新 +// 逻辑:如果客户端最新版>=服务端版本,则目标版本为服务端版本;否则为客户端最新版 +const needsUpdate = (): boolean => { + if (!clientUpdate.value?.latest || !clientVersion.value) return false + const latestClientVer = clientUpdate.value.latest + const currentClientVer = clientVersion.value + const serverVer = serverVersion.value + + // 确定目标版本 + let targetVersion = latestClientVer + if (serverVer && compareVersions(latestClientVer, serverVer) >= 0) { + targetVersion = serverVer + } + + // 比较当前客户端版本和目标版本 + return compareVersions(currentClientVer, targetVersion) < 0 +} + +// 获取目标更新版本 +const getTargetVersion = (): string => { + if (!clientUpdate.value?.latest) return '' + const latestClientVer = clientUpdate.value.latest + const serverVer = serverVersion.value + + if (serverVer && compareVersions(latestClientVer, serverVer) >= 0) { + return serverVer + } + return latestClientVer +} + // Actions const loadClient = async () => { loading.value = true @@ -441,6 +498,7 @@ const pollTimer = ref(null) onMounted(() => { loadRuleSchemas() + loadServerVersion() loadClient() // 启动自动轮询,每 5 秒刷新一次 pollTimer.value = window.setInterval(() => { @@ -557,10 +615,10 @@ const handleDeletePlugin = (plugin: ClientPlugin) => { 客户端版本 {{ clientVersion || '-' }} - - 可更新 + + 可更新 → {{ getTargetVersion() }} - + 最新版本 @@ -853,7 +911,7 @@ const handleDeletePlugin = (plugin: ClientPlugin) => {