317 lines
11 KiB
HTML
317 lines
11 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}产品管理 - 软件授权管理系统{% endblock %}
|
|
|
|
{% block page_title %}产品管理{% endblock %}
|
|
|
|
{% block page_actions %}
|
|
<a href="{{ url_for('web.create_product') }}" class="btn btn-primary">
|
|
<i class="fas fa-plus 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-8">
|
|
<input type="text" class="form-control" id="search-keyword" placeholder="搜索产品名称或描述...">
|
|
</div>
|
|
<div class="col-md-4">
|
|
<button type="submit" class="btn btn-outline-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-redo 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>状态</th>
|
|
<th>卡密统计</th>
|
|
<th>设备统计</th>
|
|
<th>创建时间</th>
|
|
<th>操作</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="product-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>
|
|
|
|
<!-- 删除确认模态框 -->
|
|
<div class="modal fade" id="deleteModal" 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">
|
|
<p>确定要删除产品 "<span id="delete-product-name"></span>" 吗?</p>
|
|
<p class="text-danger">注意:删除后无法恢复,且如果产品下有关联的卡密将无法删除。</p>
|
|
</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-delete">确认删除</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block extra_js %}
|
|
<script>
|
|
let currentPage = 1;
|
|
let currentKeyword = '';
|
|
|
|
// 页面加载完成后初始化
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
loadProducts();
|
|
initEventListeners();
|
|
});
|
|
|
|
// 初始化事件监听器
|
|
function initEventListeners() {
|
|
// 搜索表单
|
|
document.getElementById('search-form').addEventListener('submit', function(e) {
|
|
e.preventDefault();
|
|
currentKeyword = document.getElementById('search-keyword').value.trim();
|
|
currentPage = 1;
|
|
loadProducts();
|
|
});
|
|
|
|
// 重置搜索
|
|
document.getElementById('reset-search').addEventListener('click', function() {
|
|
document.getElementById('search-keyword').value = '';
|
|
currentKeyword = '';
|
|
currentPage = 1;
|
|
loadProducts();
|
|
});
|
|
|
|
// 删除确认
|
|
document.getElementById('confirm-delete').addEventListener('click', function() {
|
|
const productId = document.getElementById('confirm-delete').dataset.productId;
|
|
deleteProduct(productId);
|
|
});
|
|
}
|
|
|
|
// 加载产品列表
|
|
function loadProducts(page = 1) {
|
|
const params = new URLSearchParams({
|
|
page: page,
|
|
per_page: 10
|
|
});
|
|
|
|
if (currentKeyword) {
|
|
params.append('keyword', currentKeyword);
|
|
}
|
|
|
|
apiRequest(`/api/v1/products?${params}`)
|
|
.then(data => {
|
|
if (data.success) {
|
|
renderProductList(data.data.products);
|
|
renderPagination(data.data.pagination);
|
|
} else {
|
|
showNotification(data.message || '加载产品列表失败', 'error');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Failed to load products:', error);
|
|
showNotification('加载产品列表失败', 'error');
|
|
});
|
|
}
|
|
|
|
// 渲染产品列表
|
|
function renderProductList(products) {
|
|
const tbody = document.getElementById('product-list');
|
|
|
|
if (products.length === 0) {
|
|
tbody.innerHTML = '<tr><td colspan="8" class="text-center text-muted">暂无数据</td></tr>';
|
|
return;
|
|
}
|
|
|
|
tbody.innerHTML = products.map(product => `
|
|
<tr>
|
|
<td><code>${product.product_id}</code></td>
|
|
<td>
|
|
<strong>${product.product_name}</strong>
|
|
${product.latest_version ? `<br><small class="text-muted">最新版本: ${product.latest_version}</small>` : ''}
|
|
</td>
|
|
<td>${product.description || '-'}</td>
|
|
<td>
|
|
<span class="badge ${product.status === 1 ? 'bg-success' : 'bg-secondary'}">
|
|
${product.status_name}
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<small>
|
|
总计: ${product.total_licenses || 0}<br>
|
|
活跃: <span class="text-success">${product.active_licenses || 0}</span>
|
|
</small>
|
|
</td>
|
|
<td>
|
|
<small>
|
|
在线: <span class="text-success">${product.total_devices || 0}</span>
|
|
</small>
|
|
</td>
|
|
<td>
|
|
<small>${formatDate(product.create_time)}</small>
|
|
</td>
|
|
<td>
|
|
<div class="btn-group btn-group-sm" role="group">
|
|
<a href="/products/${product.product_id}" class="btn btn-outline-primary" title="查看">
|
|
<i class="fas fa-eye"></i>
|
|
</a>
|
|
<a href="/products/${product.product_id}/edit" class="btn btn-outline-secondary" title="编辑">
|
|
<i class="fas fa-edit"></i>
|
|
</a>
|
|
<button type="button" class="btn btn-outline-danger btn-delete"
|
|
data-product-id="${product.product_id}"
|
|
data-product-name="${product.product_name}"
|
|
title="删除">
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
`).join('');
|
|
|
|
// 绑定删除按钮事件
|
|
document.querySelectorAll('.btn-delete').forEach(btn => {
|
|
btn.addEventListener('click', function() {
|
|
const productId = this.dataset.productId;
|
|
const productName = this.dataset.productName;
|
|
showDeleteModal(productId, productName);
|
|
});
|
|
});
|
|
}
|
|
|
|
// 渲染分页
|
|
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;
|
|
loadProducts(page);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// 显示删除确认模态框
|
|
function showDeleteModal(productId, productName) {
|
|
document.getElementById('delete-product-name').textContent = productName;
|
|
document.getElementById('confirm-delete').dataset.productId = productId;
|
|
|
|
const modal = new bootstrap.Modal(document.getElementById('deleteModal'));
|
|
modal.show();
|
|
}
|
|
|
|
// 删除产品
|
|
function deleteProduct(productId) {
|
|
apiRequest(`/api/v1/products/${productId}`, {
|
|
method: 'DELETE'
|
|
})
|
|
.then(data => {
|
|
if (data.success) {
|
|
showNotification('产品删除成功', 'success');
|
|
loadProducts(currentPage);
|
|
|
|
// 关闭模态框
|
|
const modal = bootstrap.Modal.getInstance(document.getElementById('deleteModal'));
|
|
modal.hide();
|
|
} else {
|
|
showNotification(data.message || '删除失败', 'error');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Failed to delete product:', error);
|
|
showNotification('删除失败', 'error');
|
|
});
|
|
}
|
|
</script>
|
|
{% endblock %} |