Kamixitong/app/web/templates/device/list.html
2025-11-22 16:48:45 +08:00

627 lines
22 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% 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">
<label for="search-keyword" class="form-label">关键词搜索</label>
<input type="text" class="form-control" id="search-keyword" placeholder="设备ID或机器码">
</div>
<div class="col-md-2">
<label for="search-status" class="form-label">状态</label>
<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">
<label for="search-product" class="form-label">产品</label>
<input type="text" class="form-control" id="search-product" placeholder="产品ID">
</div>
<div class="col-md-2">
<label for="search-license" class="form-label">卡密</label>
<input type="text" class="form-control" id="search-license" placeholder="卡密">
</div>
<div class="col-md-2 d-flex align-items-end">
<div class="btn-group" role="group">
<button type="submit" class="btn btn-primary">
<i class="fas fa-search me-2"></i>搜索
</button>
<button type="button" class="btn btn-outline-secondary" id="reset-search">
<i class="fas fa-undo me-2"></i>重置
</button>
</div>
</div>
</form>
</div>
</div>
<!-- 批量操作栏 -->
<div class="card shadow mb-4">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<button type="button" class="btn btn-danger btn-sm" id="batch-delete-btn" style="display: none;">
<i class="fas fa-trash me-1"></i>批量删除
</button>
<div class="btn-group btn-group-sm" id="batch-status-btn" style="display: none;">
<button type="button" class="btn btn-outline-success" id="batch-enable-btn">
<i class="fas fa-check-circle me-1"></i>批量启用
</button>
<button type="button" class="btn btn-outline-secondary" id="batch-disable-btn">
<i class="fas fa-ban me-1"></i>批量禁用
</button>
</div>
</div>
<div class="text-muted small">
<span id="selected-count">已选择 0 项</span>
</div>
</div>
</div>
</div>
<!-- 设备列表 -->
<div class="card shadow">
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead class="table-light">
<tr>
<th width="50">
<input type="checkbox" id="select-all-checkbox" class="form-check-input">
</th>
<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="9" class="text-center text-muted">加载中...</td>
</tr>
</tbody>
</table>
</div>
<!-- 分页 -->
<nav aria-label="分页导航">
<ul class="pagination justify-content-center mb-0" id="pagination">
</ul>
</nav>
</div>
</div>
<!-- 批量删除确认模态框 -->
<div class="modal fade" id="batchDeleteModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">确认批量删除</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
确定要删除选中的 <strong id="batch-delete-count"></strong> 个设备吗?此操作不可恢复。
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="button" class="btn btn-danger" id="confirm-batch-delete">确定删除</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
let currentPage = 1;
// 页面加载完成后初始化
// 使用立即执行函数确保在DOM和所有脚本加载完成后执行
(function() {
function init() {
console.log('设备列表页面已加载,开始初始化...');
console.log('apiRequest函数是否存在:', typeof apiRequest);
// 从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');
}
if (typeof apiRequest === 'function') {
loadDevices();
initEventListeners();
} else {
console.error('apiRequest函数未定义等待脚本加载...');
setTimeout(function() {
if (typeof apiRequest === 'function') {
loadDevices();
initEventListeners();
} else {
console.error('apiRequest函数仍未定义请检查脚本加载顺序');
}
}, 100);
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();
// 初始化事件监听器
function initEventListeners() {
// 搜索表单
document.getElementById('search-form').addEventListener('submit', function(e) {
e.preventDefault();
currentPage = 1;
loadDevices();
});
// 重置搜索
document.getElementById('reset-search').addEventListener('click', function() {
document.getElementById('search-keyword').value = '';
document.getElementById('search-status').value = '';
document.getElementById('search-product').value = '';
document.getElementById('search-license').value = '';
currentPage = 1;
loadDevices();
});
// 批量删除确认
document.getElementById('confirm-batch-delete').addEventListener('click', function() {
batchDeleteDevices();
});
// 全选/取消全选
document.getElementById('select-all-checkbox').addEventListener('change', function() {
const checkboxes = document.querySelectorAll('.device-checkbox');
checkboxes.forEach(checkbox => {
checkbox.checked = this.checked;
});
updateBatchButtons();
});
// 批量启用
document.getElementById('batch-enable-btn').addEventListener('click', function(e) {
e.preventDefault();
batchUpdateDeviceStatus(1);
});
// 批量禁用
document.getElementById('batch-disable-btn').addEventListener('click', function(e) {
e.preventDefault();
batchUpdateDeviceStatus(0);
});
}
// 加载设备列表
function loadDevices(page = 1) {
console.log('loadDevices函数被调用页码:', page);
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);
const apiUrl = `/api/v1/devices?${params}`;
console.log('请求API URL:', apiUrl);
console.log('准备请求API:', apiUrl);
apiRequest(apiUrl)
.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="9" class="text-center text-muted">暂无数据</td></tr>';
return;
}
tbody.innerHTML = devices.map(device => `
<tr>
<td>
<input type="checkbox" class="device-checkbox" data-device-id="${device.device_id}">
</td>
<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>
${device.ip_address || '-'}
</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 === 0 ? `
<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, 0); // 禁用
});
});
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);
});
});
// 绑定复选框事件
document.querySelectorAll('.device-checkbox').forEach(checkbox => {
checkbox.addEventListener('change', updateBatchButtons);
});
// 重置全选复选框
document.getElementById('select-all-checkbox').checked = false;
updateBatchButtons();
}
// 获取设备状态文本
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) {
let action;
if (status === 0) {
action = '禁用';
} else if (status === 1) {
action = '启用';
} else if (status === 2) {
action = '设为离线';
} else {
action = '更新状态';
}
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');
});
}
// 更新批量操作按钮状态
function updateBatchButtons() {
const selectedCount = document.querySelectorAll('.device-checkbox:checked').length;
const batchDeleteBtn = document.getElementById('batch-delete-btn');
const batchStatusBtn = document.getElementById('batch-status-btn');
if (selectedCount > 0) {
batchDeleteBtn.style.display = 'inline-block';
batchStatusBtn.style.display = 'inline-block';
} else {
batchDeleteBtn.style.display = 'none';
batchStatusBtn.style.display = 'none';
}
// 更新选中数量显示
document.getElementById('selected-count').textContent = `已选择 ${selectedCount}`;
}
// 显示批量删除确认模态框
function showBatchDeleteModal() {
const selectedCount = document.querySelectorAll('.device-checkbox:checked').length;
document.getElementById('batch-delete-count').textContent = selectedCount;
const modal = new bootstrap.Modal(document.getElementById('batchDeleteModal'));
modal.show();
}
// 批量删除设备
function batchDeleteDevices() {
const selectedCheckboxes = document.querySelectorAll('.device-checkbox:checked');
const deviceIds = Array.from(selectedCheckboxes).map(checkbox => parseInt(checkbox.dataset.deviceId));
apiRequest('/api/v1/devices/batch', {
method: 'DELETE',
body: JSON.stringify({ device_ids: deviceIds })
})
.then(data => {
if (data.success) {
showNotification(data.message || '批量删除成功', 'success');
loadDevices(currentPage);
// 关闭模态框
const modal = bootstrap.Modal.getInstance(document.getElementById('batchDeleteModal'));
modal.hide();
// 重置全选复选框
document.getElementById('select-all-checkbox').checked = false;
} else {
showNotification(data.message || '批量删除失败', 'error');
// 关闭模态框
const modal = bootstrap.Modal.getInstance(document.getElementById('batchDeleteModal'));
modal.hide();
}
})
.catch(error => {
console.error('Failed to batch delete devices:', error);
showNotification('批量删除失败', 'error');
// 关闭模态框
const modal = bootstrap.Modal.getInstance(document.getElementById('batchDeleteModal'));
modal.hide();
});
}
// 批量更新设备状态
function batchUpdateDeviceStatus(status) {
const selectedCheckboxes = document.querySelectorAll('.device-checkbox:checked');
const deviceIds = Array.from(selectedCheckboxes).map(checkbox => parseInt(checkbox.dataset.deviceId));
if (deviceIds.length === 0) {
showNotification('请至少选择一个设备', 'warning');
return;
}
apiRequest('/api/v1/devices/batch/status', {
method: 'PUT',
body: JSON.stringify({
device_ids: deviceIds,
status: status
})
})
.then(data => {
if (data.success) {
showNotification(data.message || '批量更新状态成功', 'success');
loadDevices(currentPage);
// 重置全选复选框
document.getElementById('select-all-checkbox').checked = false;
} else {
showNotification(data.message || '批量更新状态失败', 'error');
}
})
.catch(error => {
console.error('Failed to batch update device status:', error);
showNotification('批量更新状态失败', 'error');
});
}
// 在页面加载完成后为批量删除按钮绑定事件
document.addEventListener('DOMContentLoaded', function() {
const batchDeleteBtn = document.getElementById('batch-delete-btn');
if (batchDeleteBtn) {
batchDeleteBtn.addEventListener('click', showBatchDeleteModal);
}
});
</script>
{% endblock %}