From aad8fa98487a4544aabd879080708d8d05daeca2 Mon Sep 17 00:00:00 2001 From: kevin Date: Fri, 10 Jul 2026 13:53:49 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=94=A8=E6=88=B7=E4=B8=AA=E4=BA=BA?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E5=B1=95=E7=A4=BA=E5=BD=93=E5=89=8D=20VPN=20?= =?UTF-8?q?=E8=BF=9E=E6=8E=A5=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - handler/vpn.go: 新增 GetMyVpnConnections,返回当前用户的在线 VPN 连接列表(IP/连接时间)及 max_conns_per_user 上限 - router.go: 注册 GET /api/me/vpn/connections(普通用户可访问) - ProfileView.vue: 新增"我的 VPN 连接"区块,含 n/max 徽章、 IPv4/IPv6/连接时间表格,底部提示异常连接请修改密码 - zh.ts/en.ts: 新增 myVpnConnections/noConnections/ abnormalConnectionHint 国际化 --- frontend/src/locales/en.ts | 3 ++ frontend/src/locales/zh.ts | 3 ++ frontend/src/views/ProfileView.vue | 50 ++++++++++++++++++++++++++++++ internal/handler/vpn.go | 36 +++++++++++++++++++++ internal/router/router.go | 1 + 5 files changed, 93 insertions(+) diff --git a/frontend/src/locales/en.ts b/frontend/src/locales/en.ts index 37b8880..cf923aa 100644 --- a/frontend/src/locales/en.ts +++ b/frontend/src/locales/en.ts @@ -59,6 +59,9 @@ export default { enterOldAndNewPassword: 'Please enter current and new password', passwordsDoNotMatch: 'The two passwords do not match', passwordChangeFailed: 'Failed to change password', + myVpnConnections: 'My VPN Connections', + noConnections: 'No active connections', + abnormalConnectionHint: 'If you notice any abnormal connections, please change your password to disconnect all devices.', }, about: { title: 'About LmVPN', diff --git a/frontend/src/locales/zh.ts b/frontend/src/locales/zh.ts index 7ac506c..eab44ed 100644 --- a/frontend/src/locales/zh.ts +++ b/frontend/src/locales/zh.ts @@ -59,6 +59,9 @@ export default { enterOldAndNewPassword: '请填写原密码和新密码', passwordsDoNotMatch: '两次输入的新密码不一致', passwordChangeFailed: '密码修改失败', + myVpnConnections: '我的 VPN 连接', + noConnections: '暂无在线连接', + abnormalConnectionHint: '如发现异常连接,请修改密码以断开所有设备', }, about: { title: '关于 LmVPN', diff --git a/frontend/src/views/ProfileView.vue b/frontend/src/views/ProfileView.vue index 3269821..1192413 100644 --- a/frontend/src/views/ProfileView.vue +++ b/frontend/src/views/ProfileView.vue @@ -8,8 +8,29 @@ const authStore = useAuthStore() const router = useRouter() const { t } = useI18n() +interface VpnConnection { + ip: string + ip6?: string + connected_at: string +} +const vpnConnections = ref([]) +const maxConns = ref(30) + +async function fetchVpnConnections() { + try { + const res = await fetch('/api/me/vpn/connections', { + headers: { Authorization: `Bearer ${authStore.token}` }, + }) + if (!res.ok) return + const data = await res.json() + vpnConnections.value = data.connections || [] + maxConns.value = data.max_conns_per_user || 30 + } catch {} +} + onMounted(async () => { await authStore.fetchUser() + fetchVpnConnections() }) const showPasswordModal = ref(false) @@ -73,6 +94,35 @@ async function handleChangePassword() { +
+
+

{{ t('profile.myVpnConnections') }}

+ + {{ vpnConnections.length }} / {{ maxConns }} + +
+ + + + + + + + + + + + + + + + + + +
{{ t('vpn.ipv4') }}{{ t('vpn.ipv6') }}{{ t('vpn.connectTime') }}
{{ t('profile.noConnections') }}
{{ c.ip }}{{ c.ip6 || '-' }}{{ c.connected_at }}
+

{{ t('profile.abnormalConnectionHint') }}

+
+