524 lines
17 KiB
HTML
524 lines
17 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>设备状态API测试</title>
|
||
<style>
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
body {
|
||
font-family: 'Microsoft YaHei', sans-serif;
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
min-height: 100vh;
|
||
padding: 20px;
|
||
}
|
||
|
||
.container {
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
background: white;
|
||
border-radius: 15px;
|
||
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
|
||
overflow: hidden;
|
||
}
|
||
|
||
.header {
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
color: white;
|
||
padding: 30px;
|
||
text-align: center;
|
||
}
|
||
|
||
.header h1 {
|
||
font-size: 2.5em;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.header p {
|
||
font-size: 1.1em;
|
||
opacity: 0.9;
|
||
}
|
||
|
||
.controls {
|
||
padding: 30px;
|
||
background: #f8f9fa;
|
||
border-bottom: 1px solid #e9ecef;
|
||
}
|
||
|
||
.control-group {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.control-group label {
|
||
display: block;
|
||
margin-bottom: 8px;
|
||
font-weight: bold;
|
||
color: #495057;
|
||
}
|
||
|
||
.control-group input {
|
||
width: 100%;
|
||
padding: 12px;
|
||
border: 2px solid #e9ecef;
|
||
border-radius: 8px;
|
||
font-size: 14px;
|
||
transition: border-color 0.3s;
|
||
}
|
||
|
||
.control-group input:focus {
|
||
outline: none;
|
||
border-color: #667eea;
|
||
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
||
}
|
||
|
||
.btn {
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
color: white;
|
||
padding: 12px 30px;
|
||
border: none;
|
||
border-radius: 8px;
|
||
font-size: 16px;
|
||
cursor: pointer;
|
||
transition: transform 0.2s, box-shadow 0.2s;
|
||
}
|
||
|
||
.btn:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
|
||
}
|
||
|
||
.btn:disabled {
|
||
opacity: 0.6;
|
||
cursor: not-allowed;
|
||
transform: none;
|
||
}
|
||
|
||
.status {
|
||
padding: 15px 30px;
|
||
margin: 0;
|
||
color: white;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.status.loading {
|
||
background: #17a2b8;
|
||
}
|
||
|
||
.status.success {
|
||
background: #28a745;
|
||
}
|
||
|
||
.status.error {
|
||
background: #dc3545;
|
||
}
|
||
|
||
.results {
|
||
padding: 30px;
|
||
}
|
||
|
||
.data-card {
|
||
background: #f8f9fa;
|
||
border-radius: 10px;
|
||
padding: 20px;
|
||
margin-bottom: 20px;
|
||
border-left: 4px solid #667eea;
|
||
}
|
||
|
||
.data-card h3 {
|
||
color: #495057;
|
||
margin-bottom: 15px;
|
||
font-size: 1.2em;
|
||
}
|
||
|
||
.data-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||
gap: 15px;
|
||
}
|
||
|
||
.data-item {
|
||
background: white;
|
||
padding: 15px;
|
||
border-radius: 8px;
|
||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||
}
|
||
|
||
.data-item .label {
|
||
font-weight: bold;
|
||
color: #667eea;
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
.data-item .value {
|
||
color: #495057;
|
||
font-size: 1.1em;
|
||
}
|
||
|
||
.json-viewer {
|
||
background: #2d3748;
|
||
color: #e2e8f0;
|
||
padding: 20px;
|
||
border-radius: 8px;
|
||
font-family: 'Courier New', monospace;
|
||
overflow-x: auto;
|
||
max-height: 400px;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.copy-btn {
|
||
float: right;
|
||
background: #4a5568;
|
||
color: white;
|
||
border: none;
|
||
padding: 5px 10px;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.copy-btn:hover {
|
||
background: #2d3748;
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.container {
|
||
margin: 10px;
|
||
border-radius: 10px;
|
||
}
|
||
|
||
.header {
|
||
padding: 20px;
|
||
}
|
||
|
||
.header h1 {
|
||
font-size: 2em;
|
||
}
|
||
|
||
.controls, .results {
|
||
padding: 20px;
|
||
}
|
||
|
||
.data-grid {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<div class="header">
|
||
<h1>🚁 设备状态API测试</h1>
|
||
<p>测试无人机设备状态查询接口</p>
|
||
</div>
|
||
|
||
<div class="controls">
|
||
<div class="control-group">
|
||
<label for="requestMode">请求模式:</label>
|
||
<select id="requestMode" onchange="toggleRequestMode()" style="width: 100%; padding: 12px; border: 2px solid #e9ecef; border-radius: 8px; font-size: 14px;">
|
||
<option value="direct">直接请求 (需要后端支持CORS)</option>
|
||
<option value="proxy">代理模式 (通过当前域名代理)</option>
|
||
<option value="no-cors">No-CORS模式 (获取状态码)</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="control-group">
|
||
<label for="apiUrl">API地址:</label>
|
||
<input type="text" id="apiUrl" value="http://27.11.11.30:6791/api/v1/qb3/devices/battle-unit/11010100/status">
|
||
</div>
|
||
|
||
<div class="control-group">
|
||
<label for="authorization">Authorization (Bearer Token):</label>
|
||
<input type="text" id="authorization" placeholder="请输入Bearer Token">
|
||
</div>
|
||
|
||
<div class="control-group">
|
||
<label for="xAuthToken">X-Auth-Token:</label>
|
||
<input type="text" id="xAuthToken" value="eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9">
|
||
</div>
|
||
|
||
<div class="control-group">
|
||
<label for="tenantId">Tenant-Id:</label>
|
||
<input type="text" id="tenantId" value="325">
|
||
</div>
|
||
|
||
<button class="btn" onclick="fetchDeviceStatus()">📡 获取设备状态</button>
|
||
<button class="btn" onclick="refreshTokens()" style="margin-left: 10px; background: linear-gradient(135deg, #28a745 0%, #20c997 100%);">🔄 刷新认证信息</button>
|
||
</div>
|
||
|
||
<div id="status" class="status" style="display: none;"></div>
|
||
|
||
<div class="results">
|
||
<div id="parsed-data" style="display: none;">
|
||
<div class="data-card">
|
||
<h3>📊 设备基本信息</h3>
|
||
<div class="data-grid" id="basic-info">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="data-card">
|
||
<h3>📍 位置信息</h3>
|
||
<div class="data-grid" id="location-info">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="data-card">
|
||
<h3>✈️ 飞行状态</h3>
|
||
<div class="data-grid" id="flight-info">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="data-card">
|
||
<h3>📋 原始JSON数据
|
||
<button class="copy-btn" onclick="copyToClipboard()">📋 复制</button>
|
||
</h3>
|
||
<div class="json-viewer" id="json-output">
|
||
暂无数据,请点击"获取设备状态"按钮
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
let currentData = null;
|
||
|
||
async function fetchDeviceStatus() {
|
||
const statusEl = document.getElementById('status');
|
||
const jsonOutputEl = document.getElementById('json-output');
|
||
const parsedDataEl = document.getElementById('parsed-data');
|
||
const btnEl = document.querySelector('.btn');
|
||
|
||
// 获取输入值
|
||
const apiUrl = document.getElementById('apiUrl').value.trim();
|
||
const authorization = document.getElementById('authorization').value.trim();
|
||
const xAuthToken = document.getElementById('xAuthToken').value.trim();
|
||
const tenantId = document.getElementById('tenantId').value.trim();
|
||
|
||
if (!apiUrl) {
|
||
showStatus('error', '❌ 请输入API地址');
|
||
return;
|
||
}
|
||
|
||
// 显示加载状态
|
||
showStatus('loading', '🔄 正在获取设备状态...');
|
||
btnEl.disabled = true;
|
||
parsedDataEl.style.display = 'none';
|
||
|
||
try {
|
||
// 构建请求头
|
||
const headers = {
|
||
'Content-Type': 'application/json',
|
||
};
|
||
|
||
if (authorization) {
|
||
headers['Authorization'] = authorization.startsWith('Bearer ') ? authorization : `Bearer ${authorization}`;
|
||
}
|
||
if (xAuthToken) {
|
||
headers['x-auth-token'] = xAuthToken;
|
||
}
|
||
if (tenantId) {
|
||
headers['Tenant-Id'] = tenantId;
|
||
}
|
||
|
||
console.log('发送请求:', { url: apiUrl, headers });
|
||
|
||
// 发送请求
|
||
const response = await fetch(apiUrl, {
|
||
method: 'GET',
|
||
headers: headers,
|
||
mode: 'cors', // 如果遇到跨域问题,可能需要后端支持
|
||
});
|
||
|
||
const responseText = await response.text();
|
||
console.log('响应状态:', response.status);
|
||
console.log('响应文本:', responseText);
|
||
|
||
if (!response.ok) {
|
||
throw new Error(`HTTP ${response.status}: ${response.statusText}\n${responseText}`);
|
||
}
|
||
|
||
// 解析JSON
|
||
let data;
|
||
try {
|
||
data = JSON.parse(responseText);
|
||
} catch (parseError) {
|
||
throw new Error(`JSON解析失败: ${parseError.message}\n原始响应: ${responseText}`);
|
||
}
|
||
|
||
currentData = data;
|
||
|
||
// 显示成功状态
|
||
showStatus('success', '✅ 数据获取成功!');
|
||
|
||
// 显示格式化的JSON
|
||
jsonOutputEl.innerHTML = `<pre>${JSON.stringify(data, null, 2)}</pre>`;
|
||
|
||
// 解析并显示结构化数据
|
||
if (data.data) {
|
||
parseAndDisplayData(data.data);
|
||
parsedDataEl.style.display = 'block';
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('请求失败:', error);
|
||
showStatus('error', `❌ 请求失败: ${error.message}`);
|
||
jsonOutputEl.innerHTML = `<pre style="color: #ff6b6b;">错误信息: ${error.message}</pre>`;
|
||
} finally {
|
||
btnEl.disabled = false;
|
||
}
|
||
}
|
||
|
||
function showStatus(type, message) {
|
||
const statusEl = document.getElementById('status');
|
||
statusEl.className = `status ${type}`;
|
||
statusEl.textContent = message;
|
||
statusEl.style.display = 'block';
|
||
|
||
// 3秒后自动隐藏状态
|
||
setTimeout(() => {
|
||
statusEl.style.display = 'none';
|
||
}, 3000);
|
||
}
|
||
|
||
function parseAndDisplayData(data) {
|
||
// 基本信息
|
||
const basicInfo = document.getElementById('basic-info');
|
||
basicInfo.innerHTML = `
|
||
<div class="data-item">
|
||
<div class="label">设备ID</div>
|
||
<div class="value">${data.id || 'N/A'}</div>
|
||
</div>
|
||
<div class="data-item">
|
||
<div class="label">作战单元代码</div>
|
||
<div class="value">${data.battle_unit_code || 'N/A'}</div>
|
||
</div>
|
||
<div class="data-item">
|
||
<div class="label">平台类型</div>
|
||
<div class="value">${data.platform_type || 'N/A'}</div>
|
||
</div>
|
||
<div class="data-item">
|
||
<div class="label">租户ID</div>
|
||
<div class="value">${data.tenant_id || 'N/A'}</div>
|
||
</div>
|
||
`;
|
||
|
||
// 位置信息
|
||
const locationInfo = document.getElementById('location-info');
|
||
locationInfo.innerHTML = `
|
||
<div class="data-item">
|
||
<div class="label">纬度</div>
|
||
<div class="value">${data.altitude ? data.altitude.toFixed(6) : 'N/A'}°</div>
|
||
</div>
|
||
<div class="data-item">
|
||
<div class="label">经度</div>
|
||
<div class="value">${data.latitude ? data.latitude.toFixed(6) : 'N/A'}°</div>
|
||
</div>
|
||
<div class="data-item">
|
||
<div class="label">海拔高度</div>
|
||
<div class="value">${data.longitude ? data.longitude.toFixed(2) : 'N/A'}m</div>
|
||
</div>
|
||
<div class="data-item">
|
||
<div class="label">飞行方向</div>
|
||
<div class="value">${data.trk_direction ? data.trk_direction.toFixed(2) : 'N/A'}°</div>
|
||
</div>
|
||
`;
|
||
|
||
// 飞行状态
|
||
const flightInfo = document.getElementById('flight-info');
|
||
flightInfo.innerHTML = `
|
||
<div class="data-item">
|
||
<div class="label">飞行速度</div>
|
||
<div class="value">${data.velocity ? data.velocity.toFixed(2) : 'N/A'} m/s</div>
|
||
</div>
|
||
<div class="data-item">
|
||
<div class="label">上报时间</div>
|
||
<div class="value">${data.report_time ? new Date(data.report_time).toLocaleString() : 'N/A'}</div>
|
||
</div>
|
||
<div class="data-item">
|
||
<div class="label">设备SN</div>
|
||
<div class="value">${data.sn || 'N/A'}</div>
|
||
</div>
|
||
<div class="data-item">
|
||
<div class="label">创建时间</div>
|
||
<div class="value">${data.create_time || 'N/A'}</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
function copyToClipboard() {
|
||
if (currentData) {
|
||
const jsonText = JSON.stringify(currentData, null, 2);
|
||
navigator.clipboard.writeText(jsonText).then(() => {
|
||
showStatus('success', '📋 JSON数据已复制到剪贴板');
|
||
}).catch(err => {
|
||
console.error('复制失败:', err);
|
||
showStatus('error', '❌ 复制失败,请手动选择文本复制');
|
||
});
|
||
}
|
||
}
|
||
|
||
// 从localStorage获取token的函数
|
||
function getAuthToken() {
|
||
return localStorage.getItem('x-auth-token') || localStorage.getItem('ws-auth-token') || '';
|
||
}
|
||
|
||
function getAuthTokenNew() {
|
||
return localStorage.getItem('Authorization') || '';
|
||
}
|
||
|
||
function getTenantId() {
|
||
return localStorage.getItem('tenantId') || localStorage.getItem('tenant_id') || '';
|
||
}
|
||
|
||
// 页面加载时自动填充认证信息
|
||
window.onload = function() {
|
||
// 从localStorage动态获取token
|
||
const xAuthToken = getAuthToken();
|
||
const authToken = getAuthTokenNew();
|
||
const tenantId = getTenantId();
|
||
|
||
if (xAuthToken) {
|
||
document.getElementById('xAuthToken').value = xAuthToken;
|
||
}
|
||
|
||
if (authToken) {
|
||
document.getElementById('authorization').value = authToken;
|
||
}
|
||
|
||
if (tenantId) {
|
||
document.getElementById('tenantId').value = tenantId;
|
||
}
|
||
|
||
// 显示获取到的token信息
|
||
if (xAuthToken || authToken) {
|
||
showStatus('success', `✅ 已自动获取认证信息 - x-auth-token: ${xAuthToken ? '已获取' : '未找到'}, Authorization: ${authToken ? '已获取' : '未找到'}`);
|
||
} else {
|
||
showStatus('error', '⚠️ 未找到认证信息,请先登录系统或手动输入token');
|
||
}
|
||
};
|
||
|
||
// 添加刷新token按钮功能
|
||
function refreshTokens() {
|
||
const xAuthToken = getAuthToken();
|
||
const authToken = getAuthTokenNew();
|
||
const tenantId = getTenantId();
|
||
|
||
document.getElementById('xAuthToken').value = xAuthToken || '';
|
||
document.getElementById('authorization').value = authToken || '';
|
||
document.getElementById('tenantId').value = tenantId || '';
|
||
|
||
if (xAuthToken || authToken) {
|
||
showStatus('success', '🔄 认证信息已刷新');
|
||
} else {
|
||
showStatus('error', '⚠️ 仍未找到认证信息');
|
||
}
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|