feat: add online device management and desktop settings integration

This commit is contained in:
2026-02-19 17:34:41 +08:00
parent 365ada1a4a
commit 19f53875c9
7 changed files with 1070 additions and 48 deletions

View File

@@ -264,6 +264,13 @@ createApp({
has_password: false
}
},
onlineDevices: {
loading: false,
kickingSessionId: '',
items: [],
error: '',
lastLoadedAt: ''
},
// 健康检测
healthCheck: {
@@ -945,11 +952,95 @@ handleDragLeave(e) {
}
},
getOrCreateWebDeviceId() {
const storageKey = 'wanwan_web_device_id_v1';
const existing = localStorage.getItem(storageKey);
if (existing && existing.trim()) {
return existing.trim();
}
const generated = (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function')
? crypto.randomUUID()
: `web-${Date.now()}-${Math.random().toString(16).slice(2)}`;
localStorage.setItem(storageKey, generated);
return generated;
},
buildLoginClientMeta() {
const platform = navigator.platform || '未知平台';
const name = `${navigator.userAgent?.includes('Mobile') ? '移动端网页' : '网页端'} · ${platform || '未知平台'}`;
return {
client_type: navigator.userAgent?.includes('Mobile') ? 'mobile' : 'web',
device_id: this.getOrCreateWebDeviceId(),
device_name: name.slice(0, 120),
platform: String(platform || '').slice(0, 80)
};
},
formatOnlineDeviceType(clientType) {
if (clientType === 'desktop') return '桌面端';
if (clientType === 'mobile') return '移动端';
if (clientType === 'api') return 'API';
return '网页端';
},
async loadOnlineDevices(silent = false) {
if (!silent) {
this.onlineDevices.loading = true;
}
this.onlineDevices.error = '';
try {
const response = await axios.get(`${this.apiBase}/api/user/online-devices`);
if (response.data?.success) {
this.onlineDevices.items = Array.isArray(response.data.devices) ? response.data.devices : [];
this.onlineDevices.lastLoadedAt = new Date().toISOString();
return;
}
this.onlineDevices.error = response.data?.message || '加载在线设备失败';
} catch (error) {
this.onlineDevices.error = error.response?.data?.message || '加载在线设备失败';
} finally {
this.onlineDevices.loading = false;
}
},
async kickOnlineDevice(device) {
const sessionId = String(device?.session_id || '').trim();
if (!sessionId || this.onlineDevices.kickingSessionId) return;
const isCurrent = !!device?.is_current;
const tip = isCurrent
? '确定要下线当前设备吗?下线后需要重新登录。'
: '确定要强制该设备下线吗?';
if (!confirm(tip)) return;
this.onlineDevices.kickingSessionId = sessionId;
try {
const response = await axios.post(`${this.apiBase}/api/user/online-devices/${encodeURIComponent(sessionId)}/kick`);
if (response.data?.success) {
this.showToast('success', '成功', response.data?.message || '设备已下线');
if (response.data?.kicked_current) {
this.handleTokenExpired();
return;
}
await this.loadOnlineDevices(true);
return;
}
this.showToast('error', '失败', response.data?.message || '踢下线失败');
} catch (error) {
this.showToast('error', '失败', error.response?.data?.message || '踢下线失败');
} finally {
this.onlineDevices.kickingSessionId = '';
}
},
async handleLogin() {
this.errorMessage = '';
this.loginLoading = true;
try {
const response = await axios.post(`${this.apiBase}/api/login`, this.loginForm);
const response = await axios.post(`${this.apiBase}/api/login`, {
...this.loginForm,
...this.buildLoginClientMeta()
});
if (response.data.success) {
// token 和 refreshToken 都通过 HttpOnly Cookie 自动管理
@@ -1444,6 +1535,11 @@ handleDragLeave(e) {
localStorage.removeItem('adminTab');
this.showResendVerify = false;
this.resendVerifyEmail = '';
this.onlineDevices.items = [];
this.onlineDevices.kickingSessionId = '';
this.onlineDevices.loading = false;
this.onlineDevices.error = '';
this.onlineDevices.lastLoadedAt = '';
// 停止定期检查
this.stopProfileSync();
@@ -1545,6 +1641,11 @@ handleDragLeave(e) {
localStorage.removeItem('user');
localStorage.removeItem('lastView');
this.stopProfileSync();
this.onlineDevices.items = [];
this.onlineDevices.kickingSessionId = '';
this.onlineDevices.loading = false;
this.onlineDevices.error = '';
this.onlineDevices.lastLoadedAt = '';
},
// 启动token自动刷新定时器
@@ -3918,6 +4019,7 @@ handleDragLeave(e) {
}
break;
case 'settings':
this.loadOnlineDevices();
if (this.user && !this.user.is_admin) {
this.loadDownloadTrafficReport();
}
@@ -4891,6 +4993,9 @@ handleDragLeave(e) {
// 普通用户进入设置页面时加载OSS配置
this.loadOssConfig();
this.loadDownloadTrafficReport();
this.loadOnlineDevices();
} else if (newView === 'settings') {
this.loadOnlineDevices();
}
// 记住最后停留的视图(需合法且已登录)