Kamixitong/app/web/templates/license/list.html

815 lines
30 KiB
HTML
Raw Normal View History

2025-11-11 21:39:12 +08:00
{% extends "base.html" %}
{% block title %}卡密管理 - 软件授权管理系统{% endblock %}
{% block page_title %}卡密管理{% endblock %}
{% block page_actions %}
<a href="{{ url_for('web.generate_license') }}" class="btn btn-primary">
2025-11-12 15:11:05 +08:00
<i class="fas fa-key me-2"></i>
2025-11-11 21:39:12 +08:00
生成卡密
</a>
2025-11-12 15:11:05 +08:00
<a href="{{ url_for('web.import_license') }}" class="btn btn-outline-warning">
2025-11-11 21:39:12 +08:00
<i class="fas fa-file-import me-2"></i>
导入卡密
</a>
2025-11-12 15:11:05 +08:00
<a href="{{ url_for('web.export_license') }}" class="btn btn-outline-success">
<i class="fas fa-file-export me-2"></i>
导出卡密
</a>
2025-11-11 21:39:12 +08:00
{% endblock %}
{% block content %}
2025-11-12 15:11:05 +08:00
<!-- 搜索表单 -->
2025-11-11 21:39:12 +08:00
<div class="card shadow mb-4">
<div class="card-body">
<form id="search-form" class="row g-3">
<div class="col-md-3">
2025-11-12 15:11:05 +08:00
<label for="search-keyword" class="form-label">关键词搜索</label>
<input type="text" class="form-control" id="search-keyword" placeholder="卡密或产品名称">
2025-11-11 21:39:12 +08:00
</div>
<div class="col-md-2">
2025-11-12 15:11:05 +08:00
<label for="search-status" class="form-label">状态</label>
2025-11-11 21:39:12 +08:00
<select class="form-select" id="search-status">
<option value="">全部状态</option>
<option value="0">未激活</option>
<option value="1">已激活</option>
<option value="2">已过期</option>
<option value="3">已禁用</option>
</select>
</div>
<div class="col-md-2">
2025-11-12 15:11:05 +08:00
<label for="search-type" class="form-label">类型</label>
2025-11-11 21:39:12 +08:00
<select class="form-select" id="search-type">
<option value="">全部类型</option>
<option value="1">正式卡密</option>
2025-11-12 15:11:05 +08:00
<option value="2">试用卡密</option>
2025-11-11 21:39:12 +08:00
</select>
</div>
<div class="col-md-3">
2025-11-12 15:11:05 +08:00
<label for="search-product" class="form-label">产品ID</label>
<input type="text" class="form-control" id="search-product" placeholder="产品ID">
2025-11-11 21:39:12 +08:00
</div>
2025-11-12 15:11:05 +08:00
<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>
2025-11-11 21:39:12 +08:00
</div>
</form>
</div>
</div>
2025-11-12 15:11:05 +08:00
<!-- 批量操作栏 -->
<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>
<button type="button" class="btn btn-outline-warning" id="batch-expire-btn">
<i class="fas fa-clock me-1"></i>批量过期
</button>
</div>
</div>
<div class="text-muted small">
<span id="selected-count">已选择 0 项</span>
</div>
</div>
</div>
</div>
2025-11-11 21:39:12 +08:00
<!-- 卡密列表 -->
<div class="card shadow">
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
2025-11-12 15:11:05 +08:00
<thead class="table-light">
2025-11-11 21:39:12 +08:00
<tr>
2025-11-12 15:11:05 +08:00
<th width="50">
<input type="checkbox" id="select-all-checkbox" class="form-check-input">
</th>
2025-11-11 21:39:12 +08:00
<th>卡密</th>
<th>产品</th>
<th>类型</th>
<th>状态</th>
2025-11-12 15:11:05 +08:00
<th>绑定信息</th>
2025-11-11 21:39:12 +08:00
<th>激活时间</th>
<th>过期时间</th>
<th>操作</th>
</tr>
</thead>
<tbody id="license-list">
<tr>
2025-11-12 15:11:05 +08:00
<td colspan="9" class="text-center text-muted">加载中...</td>
2025-11-11 21:39:12 +08:00
</tr>
</tbody>
</table>
</div>
2025-11-12 15:11:05 +08:00
2025-11-11 21:39:12 +08:00
<!-- 分页 -->
2025-11-12 15:11:05 +08:00
<nav aria-label="分页导航">
<ul class="pagination justify-content-center mb-0" id="pagination">
2025-11-11 21:39:12 +08:00
</ul>
</nav>
</div>
</div>
2025-11-12 15:11:05 +08:00
<!-- 批量删除确认模态框 -->
<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>
2025-11-11 21:39:12 +08:00
{% endblock %}
2025-11-15 23:57:05 +08:00
{% block extra_js %}
2025-11-11 21:39:12 +08:00
<script>
let currentPage = 1;
// 页面加载完成后初始化
2025-11-15 23:57:05 +08:00
// 使用立即执行函数确保在DOM和所有脚本加载完成后执行
(function() {
function init() {
console.log('卡密列表页面已加载,开始初始化...');
console.log('apiRequest函数是否存在:', typeof apiRequest);
if (typeof apiRequest === 'function') {
loadLicenses();
initEventListeners();
} else {
console.error('apiRequest函数未定义等待脚本加载...');
setTimeout(function() {
if (typeof apiRequest === 'function') {
loadLicenses();
initEventListeners();
} else {
console.error('apiRequest函数仍未定义请检查脚本加载顺序');
}
}, 100);
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();
2025-11-11 21:39:12 +08:00
// 初始化事件监听器
function initEventListeners() {
// 搜索表单
document.getElementById('search-form').addEventListener('submit', function(e) {
e.preventDefault();
currentPage = 1;
loadLicenses();
});
2025-11-12 15:11:05 +08:00
// 重置搜索
document.getElementById('reset-search').addEventListener('click', function() {
document.getElementById('search-keyword').value = '';
document.getElementById('search-status').value = '';
document.getElementById('search-type').value = '';
document.getElementById('search-product').value = '';
currentPage = 1;
loadLicenses();
});
// 批量删除确认
document.getElementById('confirm-batch-delete').addEventListener('click', function() {
batchDeleteLicenses();
});
// 全选/取消全选
document.getElementById('select-all-checkbox').addEventListener('change', function() {
const checkboxes = document.querySelectorAll('.license-checkbox');
checkboxes.forEach(checkbox => {
checkbox.checked = this.checked;
});
updateBatchButtons();
});
// 批量启用
document.getElementById('batch-enable-btn').addEventListener('click', function(e) {
e.preventDefault();
batchUpdateLicenseStatus(1);
});
// 批量禁用
document.getElementById('batch-disable-btn').addEventListener('click', function(e) {
e.preventDefault();
batchUpdateLicenseStatus(3);
});
// 批量设为过期
document.getElementById('batch-expire-btn').addEventListener('click', function(e) {
e.preventDefault();
batchUpdateLicenseStatus(2);
});
2025-11-11 21:39:12 +08:00
}
// 加载卡密列表
function loadLicenses(page = 1) {
2025-11-15 23:57:05 +08:00
console.log('loadLicenses函数被调用页码:', page);
2025-11-11 21:39:12 +08:00
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 type = document.getElementById('search-type').value;
const product = document.getElementById('search-product').value.trim();
if (keyword) params.append('keyword', keyword);
if (status) params.append('status', status);
if (type) params.append('type', type);
2025-11-17 12:55:58 +08:00
if (product) params.append('product_id', product);
2025-11-11 21:39:12 +08:00
2025-11-15 23:57:05 +08:00
const apiUrl = `/api/v1/licenses?${params}`;
2025-11-17 12:55:58 +08:00
console.log('请求API URL:', apiUrl);
2025-11-15 23:57:05 +08:00
console.log('准备请求API:', apiUrl);
apiRequest(apiUrl)
2025-11-11 21:39:12 +08:00
.then(data => {
if (data.success) {
renderLicenseList(data.data.licenses);
renderPagination(data.data.pagination);
} else {
showNotification(data.message || '加载卡密列表失败', 'error');
}
})
.catch(error => {
console.error('Failed to load licenses:', error);
showNotification('加载卡密列表失败', 'error');
});
}
// 渲染卡密列表
function renderLicenseList(licenses) {
const tbody = document.getElementById('license-list');
if (licenses.length === 0) {
2025-11-12 15:11:05 +08:00
tbody.innerHTML = '<tr><td colspan="9" class="text-center text-muted">暂无数据</td></tr>';
2025-11-11 21:39:12 +08:00
return;
}
tbody.innerHTML = licenses.map(license => `
<tr>
2025-11-12 15:11:05 +08:00
<td>
<input type="checkbox" class="license-checkbox" data-license-key="${license.license_key}">
</td>
2025-11-11 21:39:12 +08:00
<td>
<code>${formatLicenseKey(license.license_key)}</code>
<br><small class="text-muted">${license.license_key}</small>
</td>
<td>
${license.product_name || '-'}
<br><small class="text-muted">${license.product_id || '-'}</small>
</td>
<td>
<span class="badge ${license.type === 1 ? 'bg-primary' : 'bg-warning'}">
${license.type === 1 ? '正式卡密' : '试用卡密'}
</span>
</td>
<td>
<span class="badge ${getLicenseStatusClass(license.status)}">
${getLicenseStatusText(license.status)}
</span>
</td>
<td>
${license.device_info ? `
<small>
${license.device_info.machine_code || '-'}<br>
${license.device_info.ip_address || '-'}
</small>
` : '-'}
</td>
<td>
<small>${license.activate_time ? formatDate(license.activate_time) : '-'}</small>
</td>
<td>
<small>${license.expire_time ? formatDate(license.expire_time) : '-'}</small>
</td>
<td>
<div class="btn-group btn-group-sm" role="group">
<button type="button" class="btn btn-outline-primary btn-detail"
data-license-key="${license.license_key}"
title="详情">
<i class="fas fa-eye"></i>
</button>
${license.status === 1 ? `
<button type="button" class="btn btn-outline-warning btn-disable"
data-license-key="${license.license_key}"
title="禁用">
<i class="fas fa-ban"></i>
</button>
` : license.status === 3 ? `
<button type="button" class="btn btn-outline-success btn-enable"
data-license-key="${license.license_key}"
title="启用">
<i class="fas fa-check"></i>
</button>
` : ''}
<button type="button" class="btn btn-outline-danger btn-delete"
data-license-key="${license.license_key}"
title="删除">
<i class="fas fa-trash"></i>
</button>
</div>
</td>
</tr>
`).join('');
// 绑定事件
document.querySelectorAll('.btn-detail').forEach(btn => {
btn.addEventListener('click', function() {
const licenseKey = this.dataset.licenseKey;
showLicenseDetail(licenseKey);
});
});
document.querySelectorAll('.btn-disable').forEach(btn => {
btn.addEventListener('click', function() {
const licenseKey = this.dataset.licenseKey;
updateLicenseStatus(licenseKey, 3); // 禁用
});
});
document.querySelectorAll('.btn-enable').forEach(btn => {
btn.addEventListener('click', function() {
const licenseKey = this.dataset.licenseKey;
updateLicenseStatus(licenseKey, 1); // 启用
});
});
document.querySelectorAll('.btn-delete').forEach(btn => {
btn.addEventListener('click', function() {
const licenseKey = this.dataset.licenseKey;
deleteLicense(licenseKey);
});
});
2025-11-12 15:11:05 +08:00
// 绑定复选框事件
document.querySelectorAll('.license-checkbox').forEach(checkbox => {
checkbox.addEventListener('change', updateBatchButtons);
});
// 重置全选复选框
document.getElementById('select-all-checkbox').checked = false;
updateBatchButtons();
2025-11-11 21:39:12 +08:00
}
// 获取卡密状态文本
function getLicenseStatusText(status) {
const statusMap = {
0: '未激活',
1: '已激活',
2: '已过期',
3: '已禁用'
};
return statusMap[status] || '未知';
}
// 获取卡密状态样式类
function getLicenseStatusClass(status) {
const classMap = {
0: 'bg-secondary',
1: 'bg-success',
2: 'bg-warning',
3: '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;
loadLicenses(page);
}
});
});
}
// 显示卡密详情
function showLicenseDetail(licenseKey) {
// 根据卡密key获取卡密详情
apiRequest(`/api/v1/licenses?keyword=${licenseKey}`)
.then(data => {
if (data.success && data.data.licenses.length > 0) {
const license = data.data.licenses[0];
// 显示卡密详情弹窗
showLicenseDetailModal(license);
} else {
showNotification('未找到卡密信息', 'error');
}
})
.catch(error => {
console.error('Failed to load license detail:', error);
showNotification('加载卡密详情失败', 'error');
});
}
// 显示卡密详情弹窗
function showLicenseDetailModal(license) {
// 创建模态框HTML
const modalHtml = `
<div class="modal fade" id="licenseDetailModal" tabindex="-1" aria-labelledby="licenseDetailModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="licenseDetailModalLabel">卡密详情</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="row">
<div class="col-md-6">
<table class="table table-borderless">
<tr>
<td class="fw-bold">卡密:</td>
<td><code>${formatLicenseKey(license.license_key)}</code></td>
</tr>
<tr>
<td class="fw-bold">产品:</td>
<td>${license.product_name || '-'}</td>
</tr>
<tr>
<td class="fw-bold">类型:</td>
<td>
<span class="badge ${license.type === 1 ? 'bg-primary' : 'bg-warning'}">
${license.type === 1 ? '正式卡密' : '试用卡密'}
</span>
</td>
</tr>
<tr>
<td class="fw-bold">状态:</td>
<td>
<span class="badge ${getLicenseStatusClass(license.status)}">
${getLicenseStatusText(license.status)}
</span>
</td>
</tr>
<tr>
<td class="fw-bold">有效期:</td>
<td>${license.valid_days === -1 ? '永久' : license.valid_days + '天'}</td>
</tr>
</table>
</div>
<div class="col-md-6">
<table class="table table-borderless">
<tr>
<td class="fw-bold">激活时间:</td>
<td>${license.activate_time || '-'}</td>
</tr>
<tr>
<td class="fw-bold">过期时间:</td>
<td>${license.expire_time || '-'}</td>
</tr>
<tr>
<td class="fw-bold">绑定机器码:</td>
<td>${license.bind_machine_code || '-'}</td>
</tr>
<tr>
<td class="fw-bold">创建时间:</td>
<td>${license.create_time}</td>
</tr>
<tr>
<td class="fw-bold">更新时间:</td>
<td>${license.update_time}</td>
</tr>
</table>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
</div>
</div>
</div>
</div>
`;
// 如果模态框已存在,先移除
const existingModal = document.getElementById('licenseDetailModal');
if (existingModal) {
existingModal.remove();
}
// 添加模态框到页面
document.body.insertAdjacentHTML('beforeend', modalHtml);
// 显示模态框
const modal = new bootstrap.Modal(document.getElementById('licenseDetailModal'));
modal.show();
// 模态框关闭后移除
document.getElementById('licenseDetailModal').addEventListener('hidden.bs.modal', function () {
this.remove();
});
}
// 更新卡密状态
function updateLicenseStatus(licenseKey, status) {
const action = status === 1 ? '启用' : '禁用';
if (!confirm(`确定要${action}卡密 ${licenseKey} 吗?`)) {
return;
}
// 先获取卡密信息以获取license_id
apiRequest(`/api/v1/licenses?keyword=${licenseKey}`)
.then(data => {
if (data.success && data.data.licenses.length > 0) {
const licenseId = data.data.licenses[0].license_id;
// 使用正确的API路由更新卡密状态
return apiRequest(`/api/v1/licenses/${licenseId}`, {
method: 'PUT',
body: JSON.stringify({ status: status })
});
} else {
throw new Error('未找到卡密');
}
})
.then(data => {
if (data.success) {
showNotification(`${action}成功`, 'success');
loadLicenses(currentPage);
} else {
showNotification(data.message || `${action}失败`, 'error');
}
})
.catch(error => {
console.error('Failed to update license status:', error);
showNotification(`${action}失败`, 'error');
});
}
// 删除卡密
function deleteLicense(licenseKey) {
if (!confirm(`确定要删除卡密 ${licenseKey} 吗?`)) {
return;
}
// 首先尝试正常删除
apiRequest(`/api/v1/licenses/${licenseKey}`, {
method: 'DELETE'
})
.then(data => {
if (data.success) {
showNotification('删除成功', 'success');
loadLicenses(currentPage);
} else {
// 检查是否是因为卡密已激活导致的删除失败
if (data.message && data.message.includes('已激活')) {
// 提供更明确的操作指导
if (confirm('该卡密已激活,不能直接删除。\n点击"确定"将强制删除该卡密(包括解绑设备)\n点击"取消"将保留该卡密')) {
// 强制删除卡密
apiRequest(`/api/v1/licenses/${licenseKey}?force=true`, {
method: 'DELETE'
})
.then(forceData => {
if (forceData.success) {
showNotification('卡密已强制删除成功', 'success');
loadLicenses(currentPage);
} else {
showNotification(forceData.message || '强制删除失败', 'error');
}
})
.catch(error => {
console.error('Failed to force delete license:', error);
showNotification('强制删除失败: ' + error.message, 'error');
});
}
} else {
showNotification(data.message || '删除失败', 'error');
}
}
})
.catch(error => {
console.error('Failed to delete license:', error);
showNotification('删除失败: ' + error.message, 'error');
});
}
// 格式化卡密显示为XXXX-XXXX-XXXX-XXXX格式
function formatLicenseKey(licenseKey) {
// 如果已经是XXXX-XXXX-XXXX-XXXX格式直接返回
if (licenseKey && licenseKey.includes('-') && licenseKey.split('-').length === 4) {
const parts = licenseKey.split('-');
if (parts.every(part => part.length === 8)) {
return licenseKey;
}
}
// 否则格式化为XXXX-XXXX-XXXX-XXXX格式
let cleanKey = licenseKey ? licenseKey.replace(/-/g, '').toUpperCase() : '';
// 如果长度不足32位右补0
if (cleanKey.length < 32) {
cleanKey = cleanKey.padEnd(32, '0');
}
// 如果长度超过32位截取前32位
else if (cleanKey.length > 32) {
cleanKey = cleanKey.substring(0, 32);
}
// 格式化为XXXX-XXXX-XXXX-XXXX格式
if (cleanKey.length >= 32) {
return cleanKey.substring(0, 8) + '-' +
cleanKey.substring(8, 16) + '-' +
cleanKey.substring(16, 24) + '-' +
cleanKey.substring(24, 32);
}
// 如果长度不足,按原样返回
return licenseKey || '';
}
2025-11-12 15:11:05 +08:00
// 更新批量操作按钮状态
function updateBatchButtons() {
const selectedCount = document.querySelectorAll('.license-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('.license-checkbox:checked').length;
document.getElementById('batch-delete-count').textContent = selectedCount;
const modal = new bootstrap.Modal(document.getElementById('batchDeleteModal'));
modal.show();
}
// 批量删除卡密
function batchDeleteLicenses() {
const selectedCheckboxes = document.querySelectorAll('.license-checkbox:checked');
const licenseKeys = Array.from(selectedCheckboxes).map(checkbox => checkbox.dataset.licenseKey);
apiRequest('/api/v1/licenses/batch', {
method: 'DELETE',
body: JSON.stringify({ license_keys: licenseKeys })
})
.then(data => {
if (data.success) {
showNotification(data.message || '批量删除成功', 'success');
loadLicenses(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 licenses:', error);
showNotification('批量删除失败', 'error');
// 关闭模态框
const modal = bootstrap.Modal.getInstance(document.getElementById('batchDeleteModal'));
modal.hide();
});
}
// 批量更新卡密状态
function batchUpdateLicenseStatus(status) {
const selectedCheckboxes = document.querySelectorAll('.license-checkbox:checked');
const licenseKeys = Array.from(selectedCheckboxes).map(checkbox => checkbox.dataset.licenseKey);
if (licenseKeys.length === 0) {
showNotification('请至少选择一个卡密', 'warning');
return;
}
apiRequest('/api/v1/licenses/batch/status', {
method: 'PUT',
body: JSON.stringify({
license_keys: licenseKeys,
status: status
})
})
.then(data => {
if (data.success) {
showNotification(data.message || '批量更新状态成功', 'success');
loadLicenses(currentPage);
// 重置全选复选框
document.getElementById('select-all-checkbox').checked = false;
} else {
showNotification(data.message || '批量更新状态失败', 'error');
}
})
.catch(error => {
console.error('Failed to batch update license status:', error);
showNotification('批量更新状态失败', 'error');
});
}
// 在页面加载完成后为批量删除按钮绑定事件
document.addEventListener('DOMContentLoaded', function() {
const batchDeleteBtn = document.getElementById('batch-delete-btn');
if (batchDeleteBtn) {
batchDeleteBtn.addEventListener('click', showBatchDeleteModal);
}
});
2025-11-15 23:57:05 +08:00
</script>
{% endblock %}