570 lines
21 KiB
HTML
570 lines
21 KiB
HTML
|
|
{% extends "base.html" %}
|
|||
|
|
|
|||
|
|
{% block title %}卡密管理 - 软件授权管理系统{% endblock %}
|
|||
|
|
|
|||
|
|
{% block page_title %}卡密管理{% endblock %}
|
|||
|
|
|
|||
|
|
{% block page_actions %}
|
|||
|
|
<a href="{{ url_for('web.generate_license') }}" class="btn btn-primary">
|
|||
|
|
<i class="fas fa-plus me-2"></i>
|
|||
|
|
生成卡密
|
|||
|
|
</a>
|
|||
|
|
<a href="{{ url_for('web.import_license') }}" class="btn btn-outline-success">
|
|||
|
|
<i class="fas fa-file-import me-2"></i>
|
|||
|
|
导入卡密
|
|||
|
|
</a>
|
|||
|
|
{% 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>
|
|||
|
|
<option value="3">已禁用</option>
|
|||
|
|
</select>
|
|||
|
|
</div>
|
|||
|
|
<div class="col-md-2">
|
|||
|
|
<select class="form-select" id="search-type">
|
|||
|
|
<option value="">全部类型</option>
|
|||
|
|
<option value="0">试用卡密</option>
|
|||
|
|
<option value="1">正式卡密</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">
|
|||
|
|
<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>卡密</th>
|
|||
|
|
<th>产品</th>
|
|||
|
|
<th>类型</th>
|
|||
|
|
<th>状态</th>
|
|||
|
|
<th>设备信息</th>
|
|||
|
|
<th>激活时间</th>
|
|||
|
|
<th>过期时间</th>
|
|||
|
|
<th>操作</th>
|
|||
|
|
</tr>
|
|||
|
|
</thead>
|
|||
|
|
<tbody id="license-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() {
|
|||
|
|
loadLicenses();
|
|||
|
|
initEventListeners();
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 初始化事件监听器
|
|||
|
|
function initEventListeners() {
|
|||
|
|
// 搜索表单
|
|||
|
|
document.getElementById('search-form').addEventListener('submit', function(e) {
|
|||
|
|
e.preventDefault();
|
|||
|
|
currentPage = 1;
|
|||
|
|
loadLicenses();
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 加载卡密列表
|
|||
|
|
function loadLicenses(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 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);
|
|||
|
|
if (product) params.append('product', product);
|
|||
|
|
|
|||
|
|
apiRequest(`/api/v1/licenses?${params}`)
|
|||
|
|
.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) {
|
|||
|
|
tbody.innerHTML = '<tr><td colspan="8" class="text-center text-muted">暂无数据</td></tr>';
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
tbody.innerHTML = licenses.map(license => `
|
|||
|
|
<tr>
|
|||
|
|
<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);
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取卡密状态文本
|
|||
|
|
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 || '';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
</script>
|
|||
|
|
{% endblock %}
|