384 lines
13 KiB
HTML
384 lines
13 KiB
HTML
{% extends "base.html" %}
|
||
|
||
{% block title %}设备管理 - 软件授权管理系统{% endblock %}
|
||
|
||
{% block page_title %}设备管理{% endblock %}
|
||
|
||
{% block content %}
|
||
<!-- 搜索和筛选 -->
|
||
<div class="card shadow mb-4">
|
||
<div class="card-body">
|
||
<form id="search-form" class="row g-3">
|
||
<div class="col-md-3">
|
||
<input type="text" class="form-control" id="search-keyword" placeholder="搜索设备信息...">
|
||
</div>
|
||
<div class="col-md-2">
|
||
<select class="form-select" id="search-status">
|
||
<option value="">全部状态</option>
|
||
<option value="0">离线</option>
|
||
<option value="1">在线</option>
|
||
<option value="2">禁用</option>
|
||
</select>
|
||
</div>
|
||
<div class="col-md-3">
|
||
<input type="text" class="form-control" id="search-product" placeholder="产品ID或名称...">
|
||
</div>
|
||
<div class="col-md-2">
|
||
<input type="text" class="form-control" id="search-license" placeholder="卡密...">
|
||
</div>
|
||
<div class="col-md-2">
|
||
<button type="submit" class="btn btn-outline-primary">
|
||
<i class="fas fa-search me-2"></i>
|
||
搜索
|
||
</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 设备列表 -->
|
||
<div class="card shadow">
|
||
<div class="card-body">
|
||
<div class="table-responsive">
|
||
<table class="table table-hover">
|
||
<thead>
|
||
<tr>
|
||
<th>设备ID</th>
|
||
<th>产品/卡密</th>
|
||
<th>机器码</th>
|
||
<th>IP地址</th>
|
||
<th>状态</th>
|
||
<th>激活时间</th>
|
||
<th>最后在线</th>
|
||
<th>操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="device-list">
|
||
<tr>
|
||
<td colspan="8" class="text-center text-muted">
|
||
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
||
加载中...
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<!-- 分页 -->
|
||
<nav aria-label="设备列表分页">
|
||
<ul class="pagination justify-content-center" id="pagination">
|
||
<!-- 分页将通过JavaScript动态生成 -->
|
||
</ul>
|
||
</nav>
|
||
</div>
|
||
</div>
|
||
{% endblock %}
|
||
|
||
{% block extra_js %}
|
||
<script>
|
||
let currentPage = 1;
|
||
|
||
// 页面加载完成后初始化
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
// 从URL参数中读取筛选条件
|
||
const urlParams = new URLSearchParams(window.location.search);
|
||
if (urlParams.has('product_id')) {
|
||
document.getElementById('search-product').value = urlParams.get('product_id');
|
||
}
|
||
if (urlParams.has('software_version')) {
|
||
// 这里可以添加版本筛选,但当前UI没有对应的输入框
|
||
}
|
||
if (urlParams.has('status')) {
|
||
document.getElementById('search-status').value = urlParams.get('status');
|
||
}
|
||
|
||
loadDevices();
|
||
initEventListeners();
|
||
});
|
||
|
||
// 初始化事件监听器
|
||
function initEventListeners() {
|
||
// 搜索表单
|
||
document.getElementById('search-form').addEventListener('submit', function(e) {
|
||
e.preventDefault();
|
||
currentPage = 1;
|
||
loadDevices();
|
||
});
|
||
}
|
||
|
||
// 加载设备列表
|
||
function loadDevices(page = 1) {
|
||
const params = new URLSearchParams({
|
||
page: page,
|
||
per_page: 10
|
||
});
|
||
|
||
// 添加搜索参数
|
||
const keyword = document.getElementById('search-keyword').value.trim();
|
||
const status = document.getElementById('search-status').value;
|
||
const product = document.getElementById('search-product').value.trim();
|
||
const license = document.getElementById('search-license').value.trim();
|
||
|
||
// 从URL参数中读取额外的筛选条件
|
||
const urlParams = new URLSearchParams(window.location.search);
|
||
const productId = urlParams.get('product_id');
|
||
const softwareVersion = urlParams.get('software_version');
|
||
|
||
if (keyword) params.append('keyword', keyword);
|
||
if (status) params.append('status', status);
|
||
if (product) params.append('product', product);
|
||
if (license) params.append('license', license);
|
||
if (productId) params.append('product_id', productId);
|
||
if (softwareVersion) params.append('software_version', softwareVersion);
|
||
|
||
apiRequest(`/api/v1/devices?${params}`)
|
||
.then(data => {
|
||
if (data.success) {
|
||
renderDeviceList(data.data.devices);
|
||
renderPagination(data.data.pagination);
|
||
} else {
|
||
showNotification(data.message || '加载设备列表失败', 'error');
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('Failed to load devices:', error);
|
||
showNotification('加载设备列表失败', 'error');
|
||
});
|
||
}
|
||
|
||
// 渲染设备列表
|
||
function renderDeviceList(devices) {
|
||
const tbody = document.getElementById('device-list');
|
||
|
||
if (devices.length === 0) {
|
||
tbody.innerHTML = '<tr><td colspan="8" class="text-center text-muted">暂无数据</td></tr>';
|
||
return;
|
||
}
|
||
|
||
tbody.innerHTML = devices.map(device => `
|
||
<tr>
|
||
<td>
|
||
<code>${String(device.device_id).substring(0, 8)}...</code>
|
||
<br><small class="text-muted">${device.device_id}</small>
|
||
</td>
|
||
<td>
|
||
${device.product_name || '-'}
|
||
<br><small class="text-muted">${device.license_key ? device.license_key.substring(0, 16) + '...' : '-'}</small>
|
||
</td>
|
||
<td>
|
||
<code>${device.machine_code || '-'}</code>
|
||
</td>
|
||
<td>
|
||
-
|
||
</td>
|
||
<td>
|
||
<span class="badge ${getDeviceStatusClass(device.status)}">
|
||
${getDeviceStatusText(device.status)}
|
||
</span>
|
||
</td>
|
||
<td>
|
||
<small>${device.activate_time ? formatDate(device.activate_time) : '-'}</small>
|
||
</td>
|
||
<td>
|
||
<small>${device.last_verify_time ? formatDate(device.last_verify_time) : '-'}</small>
|
||
</td>
|
||
<td>
|
||
<div class="btn-group btn-group-sm" role="group">
|
||
<button type="button" class="btn btn-outline-primary btn-detail"
|
||
data-device-id="${device.device_id}"
|
||
title="详情">
|
||
<i class="fas fa-eye"></i>
|
||
</button>
|
||
${device.status === 1 ? `
|
||
<button type="button" class="btn btn-outline-warning btn-disable"
|
||
data-device-id="${device.device_id}"
|
||
title="禁用">
|
||
<i class="fas fa-ban"></i>
|
||
</button>
|
||
` : device.status === 2 ? `
|
||
<button type="button" class="btn btn-outline-success btn-enable"
|
||
data-device-id="${device.device_id}"
|
||
title="启用">
|
||
<i class="fas fa-check"></i>
|
||
</button>
|
||
` : ''}
|
||
<button type="button" class="btn btn-outline-danger btn-delete"
|
||
data-device-id="${device.device_id}"
|
||
title="删除">
|
||
<i class="fas fa-trash"></i>
|
||
</button>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
`).join('');
|
||
|
||
// 绑定事件
|
||
document.querySelectorAll('.btn-detail').forEach(btn => {
|
||
btn.addEventListener('click', function() {
|
||
const deviceId = this.dataset.deviceId;
|
||
showDeviceDetail(deviceId);
|
||
});
|
||
});
|
||
|
||
document.querySelectorAll('.btn-disable').forEach(btn => {
|
||
btn.addEventListener('click', function() {
|
||
const deviceId = this.dataset.deviceId;
|
||
updateDeviceStatus(deviceId, 2); // 禁用
|
||
});
|
||
});
|
||
|
||
document.querySelectorAll('.btn-enable').forEach(btn => {
|
||
btn.addEventListener('click', function() {
|
||
const deviceId = this.dataset.deviceId;
|
||
updateDeviceStatus(deviceId, 1); // 启用
|
||
});
|
||
});
|
||
|
||
document.querySelectorAll('.btn-delete').forEach(btn => {
|
||
btn.addEventListener('click', function() {
|
||
const deviceId = this.dataset.deviceId;
|
||
deleteDevice(deviceId);
|
||
});
|
||
});
|
||
}
|
||
|
||
// 获取设备状态文本
|
||
function getDeviceStatusText(status) {
|
||
const statusMap = {
|
||
0: '离线',
|
||
1: '在线',
|
||
2: '禁用'
|
||
};
|
||
return statusMap[status] || '未知';
|
||
}
|
||
|
||
// 获取设备状态样式类
|
||
function getDeviceStatusClass(status) {
|
||
const classMap = {
|
||
0: 'bg-secondary',
|
||
1: 'bg-success',
|
||
2: 'bg-danger'
|
||
};
|
||
return classMap[status] || 'bg-secondary';
|
||
}
|
||
|
||
// 渲染分页
|
||
function renderPagination(pagination) {
|
||
const paginationEl = document.getElementById('pagination');
|
||
|
||
if (pagination.pages <= 1) {
|
||
paginationEl.innerHTML = '';
|
||
return;
|
||
}
|
||
|
||
let html = '';
|
||
|
||
// 上一页
|
||
if (pagination.has_prev) {
|
||
html += `<li class="page-item">
|
||
<a class="page-link" href="#" data-page="${pagination.page - 1}">上一页</a>
|
||
</li>`;
|
||
}
|
||
|
||
// 页码
|
||
const startPage = Math.max(1, pagination.page - 2);
|
||
const endPage = Math.min(pagination.pages, pagination.page + 2);
|
||
|
||
if (startPage > 1) {
|
||
html += `<li class="page-item"><a class="page-link" href="#" data-page="1">1</a></li>`;
|
||
if (startPage > 2) {
|
||
html += `<li class="page-item disabled"><span class="page-link">...</span></li>`;
|
||
}
|
||
}
|
||
|
||
for (let i = startPage; i <= endPage; i++) {
|
||
html += `<li class="page-item ${i === pagination.page ? 'active' : ''}">
|
||
<a class="page-link" href="#" data-page="${i}">${i}</a>
|
||
</li>`;
|
||
}
|
||
|
||
if (endPage < pagination.pages) {
|
||
if (endPage < pagination.pages - 1) {
|
||
html += `<li class="page-item disabled"><span class="page-link">...</span></li>`;
|
||
}
|
||
html += `<li class="page-item"><a class="page-link" href="#" data-page="${pagination.pages}">${pagination.pages}</a></li>`;
|
||
}
|
||
|
||
// 下一页
|
||
if (pagination.has_next) {
|
||
html += `<li class="page-item">
|
||
<a class="page-link" href="#" data-page="${pagination.page + 1}">下一页</a>
|
||
</li>`;
|
||
}
|
||
|
||
paginationEl.innerHTML = html;
|
||
|
||
// 绑定分页点击事件
|
||
paginationEl.querySelectorAll('.page-link').forEach(link => {
|
||
link.addEventListener('click', function(e) {
|
||
e.preventDefault();
|
||
const page = parseInt(this.dataset.page);
|
||
if (page && page !== currentPage) {
|
||
currentPage = page;
|
||
loadDevices(page);
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
// 显示设备详情
|
||
function showDeviceDetail(deviceId) {
|
||
// 这里可以实现详情弹窗或跳转到详情页
|
||
showNotification('功能开发中...', 'info');
|
||
}
|
||
|
||
// 更新设备状态
|
||
function updateDeviceStatus(deviceId, status) {
|
||
const action = status === 1 ? '启用' : '禁用';
|
||
|
||
if (!confirm(`确定要${action}设备 ${deviceId} 吗?`)) {
|
||
return;
|
||
}
|
||
|
||
apiRequest(`/api/v1/devices/${deviceId}/status`, {
|
||
method: 'PUT',
|
||
body: JSON.stringify({ status: status })
|
||
})
|
||
.then(data => {
|
||
if (data.success) {
|
||
showNotification(`${action}成功`, 'success');
|
||
loadDevices(currentPage);
|
||
} else {
|
||
showNotification(data.message || `${action}失败`, 'error');
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('Failed to update device status:', error);
|
||
showNotification(`${action}失败`, 'error');
|
||
});
|
||
}
|
||
|
||
// 删除设备
|
||
function deleteDevice(deviceId) {
|
||
if (!confirm(`确定要删除设备 ${deviceId} 吗?`)) {
|
||
return;
|
||
}
|
||
|
||
apiRequest(`/api/v1/devices/${deviceId}`, {
|
||
method: 'DELETE'
|
||
})
|
||
.then(data => {
|
||
if (data.success) {
|
||
showNotification('删除成功', 'success');
|
||
loadDevices(currentPage);
|
||
} else {
|
||
showNotification(data.message || '删除失败', 'error');
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('Failed to delete device:', error);
|
||
showNotification('删除失败', 'error');
|
||
});
|
||
}
|
||
</script>
|
||
{% endblock %} |