feat(ui): 重构应用布局和添加客户端更新功能
All checks were successful
Build Multi-Platform Binaries / build-frontend (push) Successful in 41s
Build Multi-Platform Binaries / build-binaries (amd64, linux, client, true) (push) Successful in 1m31s
Build Multi-Platform Binaries / build-binaries (amd64, darwin, server, false) (push) Successful in 1m38s
Build Multi-Platform Binaries / build-binaries (amd64, windows, client, true) (push) Successful in 1m27s
Build Multi-Platform Binaries / build-binaries (amd64, linux, server, true) (push) Successful in 2m0s
Build Multi-Platform Binaries / build-binaries (amd64, windows, server, true) (push) Successful in 1m42s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, client, true) (push) Successful in 1m13s
Build Multi-Platform Binaries / build-binaries (arm64, darwin, server, false) (push) Successful in 1m48s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, server, true) (push) Successful in 2m10s
Build Multi-Platform Binaries / build-binaries (arm64, linux, client, true) (push) Successful in 1m12s
Build Multi-Platform Binaries / build-binaries (arm64, linux, server, true) (push) Successful in 1m51s
Build Multi-Platform Binaries / build-binaries (arm64, windows, server, false) (push) Successful in 1m29s

- 将侧边栏菜单改为顶部标签页导航设计
- 添加客户端操作系统和架构信息显示
- 实现客户端自动更新检查和应用功能
- 添加底部页脚显示版本和GitHub链接
- 更新主题颜色为紫色渐变风格
- 优化首页和插件页面的UI布局结构
- 修改路由配置将更新页面重命名为设置页面
- 在认证协议中添加客户端平台信息字段
- 重构App.vue中的导航和状态管理逻辑
This commit is contained in:
Flik
2026-01-22 13:59:42 +08:00
parent 7d9ad44856
commit 23fa089608
19 changed files with 687 additions and 524 deletions

View File

@@ -10,12 +10,13 @@ import {
import {
ArrowBackOutline, CreateOutline, TrashOutline,
PushOutline, AddOutline, StorefrontOutline, DocumentTextOutline,
ExtensionPuzzleOutline, SettingsOutline, OpenOutline
ExtensionPuzzleOutline, SettingsOutline, OpenOutline, CloudDownloadOutline, RefreshOutline
} from '@vicons/ionicons5'
import {
getClient, updateClient, deleteClient, pushConfigToClient, disconnectClient, restartClient,
getClientPluginConfig, updateClientPluginConfig,
getStorePlugins, installStorePlugin, getRuleSchemas, startClientPlugin, restartClientPlugin, stopClientPlugin, deleteClientPlugin
getStorePlugins, installStorePlugin, getRuleSchemas, startClientPlugin, restartClientPlugin, stopClientPlugin, deleteClientPlugin,
checkClientUpdate, applyClientUpdate, type UpdateInfo
} from '../api'
import type { ProxyRule, ClientPlugin, ConfigField, StorePluginInfo, RuleSchemasMap } from '../types'
import LogViewer from '../components/LogViewer.vue'
@@ -34,6 +35,13 @@ const nickname = ref('')
const rules = ref<ProxyRule[]>([])
const clientPlugins = ref<ClientPlugin[]>([])
const loading = ref(false)
const clientOs = ref('')
const clientArch = ref('')
// 客户端更新相关
const clientUpdate = ref<UpdateInfo | null>(null)
const checkingUpdate = ref(false)
const updatingClient = ref(false)
// Rule Schemas
const pluginRuleSchemas = ref<RuleSchemasMap>({})
@@ -125,6 +133,8 @@ const loadClient = async () => {
nickname.value = data.nickname || ''
rules.value = data.rules || []
clientPlugins.value = data.plugins || []
clientOs.value = data.os || ''
clientArch.value = data.arch || ''
} catch (e) {
message.error('加载客户端信息失败')
console.error(e)
@@ -133,6 +143,57 @@ const loadClient = async () => {
}
}
// 客户端更新
const handleCheckClientUpdate = async () => {
if (!online.value) {
message.warning('客户端离线,无法检查更新')
return
}
if (!clientOs.value || !clientArch.value) {
message.warning('无法获取客户端平台信息')
return
}
checkingUpdate.value = true
try {
const { data } = await checkClientUpdate(clientOs.value, clientArch.value)
clientUpdate.value = data
if (data.download_url) {
message.success('找到客户端更新: ' + data.latest)
} else {
message.info('已是最新版本或未找到对应平台的更新包')
}
} catch (e: any) {
message.error(e.response?.data || '检查更新失败')
} finally {
checkingUpdate.value = false
}
}
const handleApplyClientUpdate = () => {
if (!clientUpdate.value?.download_url) {
message.error('没有可用的下载链接')
return
}
dialog.warning({
title: '确认更新客户端',
content: `即将更新客户端到 ${clientUpdate.value.latest},更新后客户端将自动重启。确定要继续吗?`,
positiveText: '更新',
negativeText: '取消',
onPositiveClick: async () => {
updatingClient.value = true
try {
await applyClientUpdate(clientId, clientUpdate.value!.download_url)
message.success('更新命令已发送,客户端将自动重启')
clientUpdate.value = null
} catch (e: any) {
message.error(e.response?.data || '更新失败')
} finally {
updatingClient.value = false
}
}
})
}
// Client Rename
const showRenameModal = ref(false)
const renameValue = ref('')
@@ -479,6 +540,32 @@ const handleDeletePlugin = (plugin: ClientPlugin) => {
<n-statistic label="插件数" :value="clientPlugins.length" />
</n-space>
</n-card>
<!-- 客户端更新 -->
<n-card title="客户端更新" bordered size="small">
<template #header-extra>
<n-button size="tiny" :loading="checkingUpdate" @click="handleCheckClientUpdate" :disabled="!online">
<template #icon><n-icon><RefreshOutline /></n-icon></template>
检查
</n-button>
</template>
<div v-if="clientOs && clientArch" style="margin-bottom: 8px; font-size: 12px; color: #666;">
平台: {{ clientOs }}/{{ clientArch }}
</div>
<n-empty v-if="!clientUpdate" description="点击检查更新" size="small" />
<template v-else>
<div v-if="clientUpdate.download_url" style="font-size: 13px;">
<p style="margin: 0 0 8px 0; color: #10b981;">发现新版本 {{ clientUpdate.latest }}</p>
<n-button size="small" type="primary" :loading="updatingClient" @click="handleApplyClientUpdate">
<template #icon><n-icon><CloudDownloadOutline /></n-icon></template>
更新
</n-button>
</div>
<div v-else style="font-size: 13px; color: #666;">
已是最新版本
</div>
</template>
</n-card>
</n-space>
</n-grid-item>