Files
realizemultiagent/shared/code/frontend/devices.html
2026-04-02 00:59:42 +08:00

201 lines
8.0 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>设备管理 - 用户认证</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f0f2f5; min-height: 100vh; }
.header { background: #4f46e5; color: #fff; padding: 16px 24px; display: flex; align-items: center; justify-content: space-between; }
.header h1 { font-size: 18px; font-weight: 600; }
.header-right { display: flex; align-items: center; gap: 12px; }
.user-info { font-size: 14px; opacity: 0.9; }
.btn-logout { background: rgba(255,255,255,0.2); color: #fff; border: 1px solid rgba(255,255,255,0.4); border-radius: 6px; padding: 6px 14px; font-size: 13px; cursor: pointer; }
.btn-logout:hover { background: rgba(255,255,255,0.3); }
.main { max-width: 640px; margin: 32px auto; padding: 0 16px; }
.card { background: #fff; border-radius: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); overflow: hidden; margin-bottom: 16px; }
.card-header { padding: 16px 20px; border-bottom: 1px solid #f0f0f0; display: flex; align-items: center; justify-content: space-between; }
.card-header h2 { font-size: 16px; color: #1a1a2e; }
.device-limit { font-size: 12px; color: #888; background: #f5f5f5; padding: 2px 8px; border-radius: 10px; }
.card-body { padding: 8px 0; }
.device-item { display: flex; align-items: center; padding: 14px 20px; border-bottom: 1px solid #f9f9f9; gap: 14px; }
.device-item:last-child { border-bottom: none; }
.device-icon { font-size: 28px; flex-shrink: 0; }
.device-info { flex: 1; min-width: 0; }
.device-name { font-size: 14px; color: #1a1a2e; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.device-meta { font-size: 12px; color: #888; margin-top: 3px; }
.device-current { display: inline-block; background: #dcfce7; color: #16a34a; font-size: 11px; padding: 1px 7px; border-radius: 8px; margin-left: 6px; font-weight: 600; }
.device-actions { flex-shrink: 0; }
.btn-remove { background: #fef2f2; color: #dc2626; border: 1px solid #fca5a5; border-radius: 6px; padding: 5px 12px; font-size: 12px; cursor: pointer; }
.btn-remove:hover { background: #fee2e2; }
.btn-remove:disabled { opacity: 0.5; cursor: not-allowed; }
.empty { text-align: center; padding: 40px 20px; color: #888; font-size: 14px; }
.loading { text-align: center; padding: 40px; color: #888; }
.msg { padding: 10px 14px; border-radius: 8px; font-size: 13px; margin-bottom: 16px; display: none; }
.msg.error { background: #fef2f2; color: #dc2626; border: 1px solid #fca5a5; display: block; }
.msg.success { background: #f0fdf4; color: #16a34a; border: 1px solid #86efac; display: block; }
.user-card { display: flex; align-items: center; gap: 12px; padding: 16px 20px; }
.avatar { width: 44px; height: 44px; border-radius: 50%; background: #e8e7ff; color: #4f46e5; display: flex; align-items: center; justify-content: center; font-size: 18px; font-weight: 700; }
.user-details h3 { font-size: 15px; color: #1a1a2e; }
.user-details p { font-size: 12px; color: #888; margin-top: 2px; }
</style>
</head>
<body>
<div class="header">
<h1>设备管理</h1>
<div class="header-right">
<span class="user-info" id="userPhone"></span>
<button class="btn-logout" id="logoutBtn">退出登录</button>
</div>
</div>
<div class="main">
<div id="msg" class="msg"></div>
<!-- 当前用户信息 -->
<div class="card">
<div class="user-card" id="userCard">
<div class="avatar" id="avatar">?</div>
<div class="user-details">
<h3 id="displayName">加载中...</h3>
<p id="displayPhone"></p>
</div>
</div>
</div>
<!-- 设备列表 -->
<div class="card">
<div class="card-header">
<h2>已登录设备</h2>
<span class="device-limit" id="deviceCount">0 / 3 台</span>
</div>
<div class="card-body" id="deviceList">
<div class="loading" id="loadingTip">加载中...</div>
</div>
</div>
</div>
<script>
const API = 'http://localhost:3000/api/auth';
const token = localStorage.getItem('auth_token');
const user = JSON.parse(localStorage.getItem('auth_user') || '{}');
if (!token) {
alert('未登录,正在跳转到登录页');
location.href = 'login.html';
}
document.getElementById('userPhone').textContent = user.phone || '';
document.getElementById('displayName').textContent = '用户 ' + (user.phone || '未知');
document.getElementById('displayPhone').textContent = user.phone ? '手机号: ' + user.phone : '';
document.getElementById('avatar').textContent = user.phone ? user.phone.slice(-2) : '?';
function showMsg(text, type) {
const el = document.getElementById('msg');
el.textContent = text;
el.className = 'msg ' + type;
el.style.display = 'block';
if (type === 'success') setTimeout(() => { el.style.display = 'none'; }, 3000);
}
function formatTime(dateStr) {
if (!dateStr) return '未知时间';
const d = new Date(dateStr);
return d.toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' });
}
function getDeviceIcon(name) {
const n = (name || '').toLowerCase();
if (n.includes('iphone') || n.includes('ipad')) return '📱';
if (n.includes('android')) return '📱';
if (n.includes('mac') || n.includes('iphone')) return '💻';
if (n.includes('windows')) return '🖥️';
if (n.includes('linux')) return '🖥️';
return '💻';
}
async function loadDevices() {
try {
const res = await fetch(API + '/devices', {
headers: { 'Authorization': 'Bearer ' + token }
});
const data = await res.json();
if (data.code === 0) {
renderDevices(data.devices || []);
} else if (res.status === 401 || data.code === 401) {
logout('登录已过期,请重新登录');
} else {
showMsg(data.message || '加载设备列表失败', 'error');
}
} catch {
showMsg('网络错误,无法加载设备列表', 'error');
}
}
function renderDevices(devices) {
const list = document.getElementById('deviceList');
document.getElementById('deviceCount').textContent = devices.length + ' / 3 台';
if (devices.length === 0) {
list.innerHTML = '<div class="empty">暂无已登录设备</div>';
return;
}
// 当前设备标识(简单用 userId 判断)
const currentId = user.id;
list.innerHTML = devices.map(dev => {
const isCurrent = dev.userId === currentId || dev.isCurrent;
return '<div class="device-item">' +
'<div class="device-icon">' + getDeviceIcon(dev.deviceName) + '</div>' +
'<div class="device-info">' +
'<div class="device-name">' + escapeHtml(dev.deviceName || '未知设备') +
(isCurrent ? '<span class="device-current">当前设备</span>' : '') + '</div>' +
'<div class="device-meta">最后活跃: ' + formatTime(dev.lastActiveAt) + '</div>' +
'</div>' +
'<div class="device-actions">' +
(!isCurrent ? '<button class="btn-remove" onclick="removeDevice(\'' + dev.id + '\')">下线</button>' : '') +
'</div>' +
'</div>';
}).join('');
}
function escapeHtml(str) {
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}
async function removeDevice(deviceId) {
if (!confirm('确定要将该设备强制下线吗?')) return;
try {
const res = await fetch(API + '/devices/' + deviceId, {
method: 'DELETE',
headers: { 'Authorization': 'Bearer ' + token }
});
const data = await res.json();
if (data.code === 0) {
showMsg('设备已下线', 'success');
loadDevices();
} else {
showMsg(data.message || '下线失败', 'error');
}
} catch {
showMsg('网络错误', 'error');
}
}
function logout(reason) {
localStorage.removeItem('auth_token');
localStorage.removeItem('auth_user');
location.href = 'login.html';
}
document.getElementById('logoutBtn').addEventListener('click', () => {
if (confirm('确定要退出登录吗?')) logout();
});
loadDevices();
</script>
</body>
</html>