改进后台
This commit is contained in:
@@ -7,9 +7,8 @@ public record DeviceRegisterRequest
|
|||||||
[Required]
|
[Required]
|
||||||
public string DeviceCode { get; set; } = string.Empty;
|
public string DeviceCode { get; set; } = string.Empty;
|
||||||
|
|
||||||
[Required]
|
|
||||||
[MaxLength(200)]
|
[MaxLength(200)]
|
||||||
public string DeviceName { get; set; } = string.Empty;
|
public string? DeviceName { get; set; }
|
||||||
|
|
||||||
[MaxLength(50)]
|
[MaxLength(50)]
|
||||||
public string? DeviceType { get; set; }
|
public string? DeviceType { get; set; }
|
||||||
@@ -27,6 +26,7 @@ public record DeviceRegisterResponse
|
|||||||
public string? Token { get; set; }
|
public string? Token { get; set; }
|
||||||
public string? Message { get; set; }
|
public string? Message { get; set; }
|
||||||
public int DeviceId { get; set; }
|
public int DeviceId { get; set; }
|
||||||
|
public string? DeviceName { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public record DeviceRefreshTokenRequest
|
public record DeviceRefreshTokenRequest
|
||||||
|
|||||||
@@ -40,11 +40,23 @@ public class DeviceService
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新设备信息
|
// 更新设备信息 (如果提供了则使用,否则保留原值)
|
||||||
|
if (!string.IsNullOrWhiteSpace(request.DeviceName))
|
||||||
|
{
|
||||||
device.DeviceName = request.DeviceName;
|
device.DeviceName = request.DeviceName;
|
||||||
|
}
|
||||||
|
if (!string.IsNullOrWhiteSpace(request.DeviceType))
|
||||||
|
{
|
||||||
device.DeviceType = request.DeviceType;
|
device.DeviceType = request.DeviceType;
|
||||||
|
}
|
||||||
|
if (!string.IsNullOrWhiteSpace(request.OsVersion))
|
||||||
|
{
|
||||||
device.OsVersion = request.OsVersion;
|
device.OsVersion = request.OsVersion;
|
||||||
|
}
|
||||||
|
if (!string.IsNullOrWhiteSpace(request.AppVersion))
|
||||||
|
{
|
||||||
device.AppVersion = request.AppVersion;
|
device.AppVersion = request.AppVersion;
|
||||||
|
}
|
||||||
device.IsActive = true;
|
device.IsActive = true;
|
||||||
device.ActivatedAt = device.ActivatedAt ?? DateTime.UtcNow;
|
device.ActivatedAt = device.ActivatedAt ?? DateTime.UtcNow;
|
||||||
device.LastSeenAt = DateTime.UtcNow;
|
device.LastSeenAt = DateTime.UtcNow;
|
||||||
@@ -60,6 +72,7 @@ public class DeviceService
|
|||||||
Success = true,
|
Success = true,
|
||||||
Token = token,
|
Token = token,
|
||||||
DeviceId = device.Id,
|
DeviceId = device.Id,
|
||||||
|
DeviceName = device.DeviceName,
|
||||||
Message = "注册成功"
|
Message = "注册成功"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,7 +63,39 @@
|
|||||||
<span class="badge bg-secondary">禁用</span>
|
<span class="badge bg-secondary">禁用</span>
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td>@device.LastSeenAt?.ToString("MM-dd HH:mm") ?? "从未"</td>
|
<td>
|
||||||
|
@if (!device.LastSeenAt.HasValue)
|
||||||
|
{
|
||||||
|
<span class="text-muted">等待首次连接</span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var timeDiff = DateTime.UtcNow - device.LastSeenAt.Value;
|
||||||
|
@if (timeDiff.TotalMinutes < 1)
|
||||||
|
{
|
||||||
|
<span class="text-success"><i class="bi bi-circle-fill"></i> 在线</span>
|
||||||
|
}
|
||||||
|
else if (timeDiff.TotalMinutes < 60)
|
||||||
|
{
|
||||||
|
var mins = (int)timeDiff.TotalMinutes;
|
||||||
|
<span class="text-warning">@mins 分钟前</span>
|
||||||
|
}
|
||||||
|
else if (timeDiff.TotalHours < 24)
|
||||||
|
{
|
||||||
|
var hours = (int)timeDiff.TotalHours;
|
||||||
|
<span class="text-info">@hours 小时前</span>
|
||||||
|
}
|
||||||
|
else if (timeDiff.TotalDays < 7)
|
||||||
|
{
|
||||||
|
var days = (int)timeDiff.TotalDays;
|
||||||
|
<span>@days 天前</span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span class="text-muted">@device.LastSeenAt.Value:MM-dd HH:mm</span>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<button class="btn btn-sm btn-outline-primary" @onclick="@(() => EditDevice(device))">
|
<button class="btn btn-sm btn-outline-primary" @onclick="@(() => EditDevice(device))">
|
||||||
<i class="bi bi-pencil"></i>
|
<i class="bi bi-pencil"></i>
|
||||||
|
|||||||
@@ -161,4 +161,4 @@ app.MapRazorComponents<DRS9.Dashboard.Server.Components.App>()
|
|||||||
// WebSocket endpoint
|
// WebSocket endpoint
|
||||||
app.Map("/ws", app => app.UseMiddleware<WebSocketMiddleware>());
|
app.Map("/ws", app => app.UseMiddleware<WebSocketMiddleware>());
|
||||||
|
|
||||||
app.Run();
|
app.Run("http://localhost:5000");
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
"commandName": "Project",
|
"commandName": "Project",
|
||||||
"dotnetRunMessages": true,
|
"dotnetRunMessages": true,
|
||||||
"launchBrowser": false,
|
"launchBrowser": false,
|
||||||
"applicationUrl": "http://localhost:5264",
|
"applicationUrl": "http://localhost:5000",
|
||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
563
src/DRS9.Dashboard.Server/wwwroot/device-client.html
Normal file
563
src/DRS9.Dashboard.Server/wwwroot/device-client.html
Normal file
@@ -0,0 +1,563 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>DRS9 设备端测试客户端</title>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html, body {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
background: #1a1a2e;
|
||||||
|
color: #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 注册界面 */
|
||||||
|
#register-screen {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 100vh;
|
||||||
|
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.register-box {
|
||||||
|
background: rgba(255,255,255,0.1);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
padding: 40px;
|
||||||
|
border-radius: 20px;
|
||||||
|
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
|
||||||
|
text-align: center;
|
||||||
|
max-width: 400px;
|
||||||
|
width: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.register-box h1 {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
font-size: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.register-box input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 15px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: rgba(255,255,255,0.1);
|
||||||
|
color: #fff;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.register-box input::placeholder {
|
||||||
|
color: rgba(255,255,255,0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.register-box button {
|
||||||
|
width: 100%;
|
||||||
|
padding: 15px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: #fff;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.register-box button:hover {
|
||||||
|
transform: scale(1.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 内容显示区 */
|
||||||
|
#content-screen {
|
||||||
|
display: none;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#content-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#content-frame {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border: none;
|
||||||
|
background: #000;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 状态栏 */
|
||||||
|
#status-bar {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background: rgba(0,0,0,0.8);
|
||||||
|
color: #0f0;
|
||||||
|
padding: 8px 15px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-family: monospace;
|
||||||
|
z-index: 9999;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#status-bar.connected {
|
||||||
|
background: rgba(0,100,0,0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
#status-bar.disconnected {
|
||||||
|
background: rgba(100,0,0,0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 控制面板 */
|
||||||
|
#control-panel {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 20px;
|
||||||
|
right: 20px;
|
||||||
|
background: rgba(0,0,0,0.8);
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 10px;
|
||||||
|
z-index: 9999;
|
||||||
|
}
|
||||||
|
|
||||||
|
#control-panel button {
|
||||||
|
padding: 8px 15px;
|
||||||
|
margin: 5px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
background: #444;
|
||||||
|
color: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#control-panel button:hover {
|
||||||
|
background: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 加载动画 */
|
||||||
|
.loading {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 100vh;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading::after {
|
||||||
|
content: '...';
|
||||||
|
animation: dots 1.5s steps(4, end) infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes dots {
|
||||||
|
0%, 20% { content: '.'; }
|
||||||
|
40% { content: '..'; }
|
||||||
|
60%, 100% { content: '...'; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 消息提示 */
|
||||||
|
.message {
|
||||||
|
position: fixed;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
background: rgba(0,0,0,0.9);
|
||||||
|
color: #fff;
|
||||||
|
padding: 30px 50px;
|
||||||
|
border-radius: 15px;
|
||||||
|
font-size: 18px;
|
||||||
|
z-index: 10000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 图片/视频全屏 */
|
||||||
|
#media-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background: #000;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#media-container img,
|
||||||
|
#media-container video {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- 注册界面 -->
|
||||||
|
<div id="register-screen">
|
||||||
|
<div class="register-box">
|
||||||
|
<h1>🖥️ DRS9 设备端</h1>
|
||||||
|
<p style="margin-bottom: 20px; color: rgba(255,255,255,0.7);">
|
||||||
|
请输入设备码以注册此设备
|
||||||
|
</p>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="deviceCode"
|
||||||
|
placeholder="设备码 (如: TEST-DEVICE-001)"
|
||||||
|
autofocus
|
||||||
|
>
|
||||||
|
<button onclick="registerDevice()">注册设备</button>
|
||||||
|
<p id="register-error" style="color: #ff6b6b; margin-top: 15px; display: none;"></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 内容显示区 -->
|
||||||
|
<div id="content-screen">
|
||||||
|
<div id="status-bar" class="disconnected">
|
||||||
|
<span id="status-text">未连接</span>
|
||||||
|
<span id="device-info"></span>
|
||||||
|
</div>
|
||||||
|
<div id="content-container"></div>
|
||||||
|
|
||||||
|
<div id="control-panel">
|
||||||
|
<button onclick="loadContent()">🔄 刷新内容</button>
|
||||||
|
<button onclick="showDeviceInfo()">ℹ️ 设备信息</button>
|
||||||
|
<button onclick="logout()">🚪 退出</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// 配置
|
||||||
|
const API_BASE = 'http://localhost:5000/api';
|
||||||
|
const WS_BASE = 'ws://localhost:5000/ws';
|
||||||
|
|
||||||
|
// 状态
|
||||||
|
let token = localStorage.getItem('drs9_deviceToken');
|
||||||
|
let deviceId = localStorage.getItem('drs9_deviceId');
|
||||||
|
let deviceName = localStorage.getItem('drs9_deviceName');
|
||||||
|
let ws = null;
|
||||||
|
let heartbeatInterval = null;
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
window.onload = function() {
|
||||||
|
if (token) {
|
||||||
|
showContentScreen();
|
||||||
|
loadContent();
|
||||||
|
connectWebSocket();
|
||||||
|
startHeartbeat();
|
||||||
|
} else {
|
||||||
|
showRegisterScreen();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 支持回车键提交
|
||||||
|
document.getElementById('deviceCode').addEventListener('keypress', function(e) {
|
||||||
|
if (e.key === 'Enter') registerDevice();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 显示注册界面
|
||||||
|
function showRegisterScreen() {
|
||||||
|
document.getElementById('register-screen').style.display = 'flex';
|
||||||
|
document.getElementById('content-screen').style.display = 'none';
|
||||||
|
document.getElementById('deviceCode').focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示内容界面
|
||||||
|
function showContentScreen() {
|
||||||
|
document.getElementById('register-screen').style.display = 'none';
|
||||||
|
document.getElementById('content-screen').style.display = 'block';
|
||||||
|
updateDeviceInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册设备
|
||||||
|
async function registerDevice() {
|
||||||
|
const deviceCode = document.getElementById('deviceCode').value.trim();
|
||||||
|
const errorEl = document.getElementById('register-error');
|
||||||
|
|
||||||
|
if (!deviceCode) {
|
||||||
|
errorEl.textContent = '请输入设备码';
|
||||||
|
errorEl.style.display = 'block';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
errorEl.style.display = 'none';
|
||||||
|
console.log('开始注册设备:', deviceCode);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE}/devices/register`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
deviceCode: deviceCode,
|
||||||
|
deviceType: 'Web',
|
||||||
|
osVersion: navigator.userAgent.substring(0, 50) // 限制在50字符以内
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('响应状态:', response.status, response.statusText);
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
console.log('响应数据:', data);
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
token = data.token;
|
||||||
|
deviceId = data.deviceId;
|
||||||
|
deviceName = data.deviceName;
|
||||||
|
|
||||||
|
localStorage.setItem('drs9_deviceToken', token);
|
||||||
|
localStorage.setItem('drs9_deviceId', deviceId);
|
||||||
|
localStorage.setItem('drs9_deviceName', deviceName);
|
||||||
|
|
||||||
|
console.log('注册成功:', { deviceId, deviceName });
|
||||||
|
|
||||||
|
showContentScreen();
|
||||||
|
loadContent();
|
||||||
|
connectWebSocket();
|
||||||
|
startHeartbeat();
|
||||||
|
} else {
|
||||||
|
errorEl.textContent = data.message || '注册失败';
|
||||||
|
errorEl.style.display = 'block';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('注册错误:', error);
|
||||||
|
errorEl.textContent = '网络错误: ' + error.message;
|
||||||
|
errorEl.style.display = 'block';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取并显示内容
|
||||||
|
async function loadContent() {
|
||||||
|
showLoading('正在加载内容...');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE}/devices/content`, {
|
||||||
|
headers: { 'Authorization': `Bearer ${token}` }
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
if (data.data && data.data.length > 0) {
|
||||||
|
displayContent(data.data[0]);
|
||||||
|
} else {
|
||||||
|
showMessage('暂无分配内容<br><small>请在管理后台为设备分配内容</small>');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
showMessage('获取内容失败: ' + (data.message || '未知错误'));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
showMessage('网络错误: ' + error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示内容
|
||||||
|
function displayContent(item) {
|
||||||
|
const container = document.getElementById('content-container');
|
||||||
|
|
||||||
|
const type = item.applicationType;
|
||||||
|
const url = item.contentUrl;
|
||||||
|
const name = item.applicationName;
|
||||||
|
|
||||||
|
console.log('显示内容:', { type, url, name });
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'Dashboard':
|
||||||
|
case 'WebRotator':
|
||||||
|
container.innerHTML = `<iframe id="content-frame" src="${url}" frameborder="0"></iframe>`;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Image':
|
||||||
|
container.innerHTML = `
|
||||||
|
<div id="media-container">
|
||||||
|
<img src="${url}" alt="${name}">
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Video':
|
||||||
|
container.innerHTML = `
|
||||||
|
<div id="media-container">
|
||||||
|
<video src="${url}" autoplay loop muted playsinline></video>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
container.innerHTML = `<iframe id="content-frame" src="${url}" frameborder="0"></iframe>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WebSocket连接
|
||||||
|
function connectWebSocket() {
|
||||||
|
if (ws) {
|
||||||
|
ws.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
ws = new WebSocket(`${WS_BASE}?token=${token}`);
|
||||||
|
|
||||||
|
ws.onopen = function() {
|
||||||
|
console.log('✅ WebSocket 已连接');
|
||||||
|
updateStatus('connected', '已连接');
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onmessage = function(event) {
|
||||||
|
try {
|
||||||
|
const msg = JSON.parse(event.data);
|
||||||
|
console.log('📨 收到消息:', msg);
|
||||||
|
|
||||||
|
switch (msg.type) {
|
||||||
|
case 'content_refresh':
|
||||||
|
console.log('🔄 刷新内容');
|
||||||
|
loadContent();
|
||||||
|
showNotification('内容已刷新');
|
||||||
|
break;
|
||||||
|
case 'app_restart':
|
||||||
|
console.log('🔃 重启应用');
|
||||||
|
showNotification('正在重启...');
|
||||||
|
setTimeout(() => location.reload(), 1000);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.log('❓ 未知消息类型:', msg.type);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('解析消息失败:', e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onerror = function(error) {
|
||||||
|
console.error('❌ WebSocket 错误:', error);
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onclose = function() {
|
||||||
|
console.log('⚠️ WebSocket 断开,5秒后重连...');
|
||||||
|
updateStatus('disconnected', '连接断开 - 重连中...');
|
||||||
|
setTimeout(connectWebSocket, 5000);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 心跳
|
||||||
|
function startHeartbeat() {
|
||||||
|
if (heartbeatInterval) {
|
||||||
|
clearInterval(heartbeatInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
heartbeatInterval = setInterval(async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE}/devices/heartbeat`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Authorization': `Bearer ${token}` }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
console.log('💓 心跳成功');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('💔 心跳失败:', error);
|
||||||
|
}
|
||||||
|
}, 30000); // 30秒心跳
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新状态栏
|
||||||
|
function updateStatus(status, text) {
|
||||||
|
const statusBar = document.getElementById('status-bar');
|
||||||
|
const statusText = document.getElementById('status-text');
|
||||||
|
|
||||||
|
statusBar.className = status;
|
||||||
|
statusText.textContent = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新设备信息
|
||||||
|
function updateDeviceInfo() {
|
||||||
|
document.getElementById('device-info').textContent =
|
||||||
|
`${deviceName || '未知设备'} (ID: ${deviceId || '?'})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示加载
|
||||||
|
function showLoading(text) {
|
||||||
|
document.getElementById('content-container').innerHTML =
|
||||||
|
`<div class="loading">${text || '加载中'}</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示消息
|
||||||
|
function showMessage(html) {
|
||||||
|
document.getElementById('content-container').innerHTML =
|
||||||
|
`<div class="message">${html}</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示通知
|
||||||
|
function showNotification(text) {
|
||||||
|
const notification = document.createElement('div');
|
||||||
|
notification.style.cssText = `
|
||||||
|
position: fixed;
|
||||||
|
top: 60px;
|
||||||
|
right: 20px;
|
||||||
|
background: rgba(0,200,0,0.9);
|
||||||
|
color: #fff;
|
||||||
|
padding: 15px 25px;
|
||||||
|
border-radius: 10px;
|
||||||
|
z-index: 10001;
|
||||||
|
animation: slideIn 0.3s ease;
|
||||||
|
`;
|
||||||
|
notification.textContent = text;
|
||||||
|
document.body.appendChild(notification);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
notification.style.opacity = '0';
|
||||||
|
notification.style.transition = 'opacity 0.3s';
|
||||||
|
setTimeout(() => notification.remove(), 300);
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示设备信息
|
||||||
|
function showDeviceInfo() {
|
||||||
|
alert(`设备信息:\n名称: ${deviceName}\nID: ${deviceId}\n状态: ${ws?.connected ? '已连接' : '未连接'}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 退出登录
|
||||||
|
function logout() {
|
||||||
|
if (confirm('确定要退出吗?')) {
|
||||||
|
if (ws) ws.close();
|
||||||
|
if (heartbeatInterval) clearInterval(heartbeatInterval);
|
||||||
|
|
||||||
|
localStorage.removeItem('drs9_deviceToken');
|
||||||
|
localStorage.removeItem('drs9_deviceId');
|
||||||
|
localStorage.removeItem('drs9_deviceName');
|
||||||
|
|
||||||
|
token = null;
|
||||||
|
deviceId = null;
|
||||||
|
deviceName = null;
|
||||||
|
|
||||||
|
showRegisterScreen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加CSS动画
|
||||||
|
const style = document.createElement('style');
|
||||||
|
style.textContent = `
|
||||||
|
@keyframes slideIn {
|
||||||
|
from { transform: translateX(100%); opacity: 0; }
|
||||||
|
to { transform: translateX(0); opacity: 1; }
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
document.head.appendChild(style);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user