第一次提交
This commit is contained in:
671
app/web/templates/product/list.html
Normal file
671
app/web/templates/product/list.html
Normal file
@@ -0,0 +1,671 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}产品管理 - 太一软件授权管理系统{% endblock %}
|
||||
|
||||
{% block page_title %}产品管理{% endblock %}
|
||||
|
||||
{% block page_actions %}
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-outline-primary" id="refresh-btn" title="刷新数据">
|
||||
<i class="fas fa-sync-alt me-1"></i>刷新
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" id="auto-refresh-btn" title="切换自动刷新">
|
||||
<i class="fas fa-play me-1"></i><span class="auto-refresh-text">自动刷新</span>
|
||||
</button>
|
||||
<a href="{{ url_for('web.create_product') }}" class="btn btn-primary">
|
||||
<i class="fas fa-plus me-2"></i>
|
||||
创建产品
|
||||
</a>
|
||||
</div>
|
||||
{% 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-4">
|
||||
<label for="search-keyword" class="form-label">关键词搜索</label>
|
||||
<input type="text" class="form-control" id="search-keyword" placeholder="产品名称或ID">
|
||||
</div>
|
||||
<div class="col-md-8 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>图片</th>
|
||||
<th>状态</th>
|
||||
<th>卡密统计</th>
|
||||
<th>设备统计</th>
|
||||
<th>创建时间</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="product-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="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">
|
||||
确定要删除产品 "<strong id="delete-product-name"></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-delete">确定删除</button>
|
||||
</div>
|
||||
</div>
|
||||
</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;
|
||||
let currentKeyword = '';
|
||||
|
||||
// 页面加载完成后初始化
|
||||
// 使用立即执行函数确保在DOM和所有脚本加载完成后执行
|
||||
(function() {
|
||||
function init() {
|
||||
console.log('产品列表页面已加载,开始初始化...');
|
||||
console.log('apiRequest函数是否存在:', typeof apiRequest);
|
||||
if (typeof apiRequest === 'function') {
|
||||
loadProducts();
|
||||
initEventListeners();
|
||||
} else {
|
||||
console.error('apiRequest函数未定义,等待脚本加载...');
|
||||
// 如果apiRequest未定义,等待一段时间后重试
|
||||
setTimeout(function() {
|
||||
if (typeof apiRequest === 'function') {
|
||||
loadProducts();
|
||||
initEventListeners();
|
||||
} else {
|
||||
console.error('apiRequest函数仍未定义,请检查脚本加载顺序');
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
} else {
|
||||
// DOM已经加载完成,直接执行
|
||||
init();
|
||||
}
|
||||
})();
|
||||
|
||||
// 初始化事件监听器
|
||||
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);
|
||||
});
|
||||
|
||||
// 批量删除确认
|
||||
document.getElementById('confirm-batch-delete').addEventListener('click', function() {
|
||||
batchDeleteProducts();
|
||||
});
|
||||
|
||||
// 全选/取消全选
|
||||
document.getElementById('select-all-checkbox').addEventListener('change', function() {
|
||||
const checkboxes = document.querySelectorAll('.product-checkbox');
|
||||
checkboxes.forEach(checkbox => {
|
||||
checkbox.checked = this.checked;
|
||||
});
|
||||
updateBatchButtons();
|
||||
});
|
||||
|
||||
// 批量启用
|
||||
document.getElementById('batch-enable-btn').addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
batchUpdateProductStatus(1);
|
||||
});
|
||||
|
||||
// 批量禁用
|
||||
document.getElementById('batch-disable-btn').addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
batchUpdateProductStatus(0);
|
||||
});
|
||||
|
||||
// 刷新按钮
|
||||
document.getElementById('refresh-btn').addEventListener('click', function() {
|
||||
console.log('手动刷新数据...');
|
||||
loadProducts(currentPage);
|
||||
});
|
||||
|
||||
// 自动刷新按钮
|
||||
document.getElementById('auto-refresh-btn').addEventListener('click', function() {
|
||||
const isEnabled = toggleAutoRefresh(() => loadProducts(currentPage), 30000);
|
||||
const icon = this.querySelector('i');
|
||||
const text = this.querySelector('.auto-refresh-text');
|
||||
|
||||
if (isEnabled) {
|
||||
icon.className = 'fas fa-pause me-1';
|
||||
text.textContent = '停止刷新';
|
||||
this.classList.remove('btn-outline-secondary');
|
||||
this.classList.add('btn-success');
|
||||
showNotification('已开启自动刷新(每30秒)', 'info');
|
||||
} else {
|
||||
icon.className = 'fas fa-play me-1';
|
||||
text.textContent = '自动刷新';
|
||||
this.classList.remove('btn-success');
|
||||
this.classList.add('btn-outline-secondary');
|
||||
showNotification('已关闭自动刷新', 'info');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 加载产品列表
|
||||
function loadProducts(page = 1) {
|
||||
console.log('loadProducts函数被调用,页码:', page);
|
||||
const params = new URLSearchParams({
|
||||
page: page,
|
||||
per_page: 10
|
||||
});
|
||||
|
||||
if (currentKeyword) {
|
||||
params.append('keyword', currentKeyword);
|
||||
}
|
||||
|
||||
const apiUrl = `/api/v1/products?${params}`;
|
||||
console.log('请求API URL:', apiUrl);
|
||||
console.log('准备请求API:', apiUrl);
|
||||
|
||||
// 使用apiRequest函数,它已经处理了加载动画和错误处理
|
||||
apiRequest(apiUrl)
|
||||
.then(data => {
|
||||
if (data && data.success && data.data) {
|
||||
renderProductList(data.data.products);
|
||||
renderPagination(data.data.pagination);
|
||||
} else {
|
||||
renderProductList([]);
|
||||
renderPagination({ pages: 0, page: 1, has_prev: false, has_next: false });
|
||||
if (data && data.message) {
|
||||
showNotification(data.message, 'error');
|
||||
} else {
|
||||
showNotification('加载产品列表失败', 'error');
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Failed to load products:', error);
|
||||
// 确保在任何错误情况下都清除加载状态
|
||||
renderProductList([]);
|
||||
renderPagination({ pages: 0, page: 1, has_prev: false, has_next: false });
|
||||
|
||||
// apiRequest已经处理了401和403错误,这里只处理其他错误
|
||||
if (error.message && !error.message.includes('401') && !error.message.includes('403')) {
|
||||
showNotification('加载产品列表失败: ' + error.message, 'error');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 渲染产品列表
|
||||
function renderProductList(products) {
|
||||
const tbody = document.getElementById('product-list');
|
||||
|
||||
// 防御式编程:确保tbody存在
|
||||
if (!tbody) {
|
||||
console.error('产品列表容器未找到');
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查产品数据是否有效
|
||||
if (!Array.isArray(products) || products.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="9" class="text-center text-muted">暂无数据</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
tbody.innerHTML = products.map(product => `
|
||||
<tr>
|
||||
<td>
|
||||
<input type="checkbox" class="product-checkbox" data-product-id="${escapeHtml(product.product_id)}">
|
||||
</td>
|
||||
<td><code>${escapeHtml(product.product_id)}</code></td>
|
||||
<td>
|
||||
<strong>${escapeHtml(product.product_name)}</strong>
|
||||
${product.latest_version ? `<br><small class="text-muted">最新版本: ${escapeHtml(product.latest_version)}</small>` : ''}
|
||||
</td>
|
||||
<td>${escapeHtml(product.description || '-')}</td>
|
||||
<td>
|
||||
${product.image_path ? `<img src="${escapeHtml(product.image_path)}" alt="产品图片" style="max-width: 50px; max-height: 50px;">` : '-'}
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge ${product.status === 1 ? 'bg-success' : 'bg-secondary'}">
|
||||
${escapeHtml(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);
|
||||
});
|
||||
});
|
||||
|
||||
// 绑定复选框事件
|
||||
document.querySelectorAll('.product-checkbox').forEach(checkbox => {
|
||||
checkbox.addEventListener('change', updateBatchButtons);
|
||||
});
|
||||
|
||||
// 重置全选复选框
|
||||
document.getElementById('select-all-checkbox').checked = false;
|
||||
updateBatchButtons();
|
||||
}
|
||||
|
||||
// 渲染分页
|
||||
function renderPagination(pagination) {
|
||||
const paginationEl = document.getElementById('pagination');
|
||||
|
||||
// 防御式编程:确保分页容器存在
|
||||
if (!paginationEl) {
|
||||
console.error('分页容器未找到');
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查分页数据是否有效
|
||||
if (!pagination || typeof pagination !== 'object' || !Number.isFinite(pagination.pages) || 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) {
|
||||
// 显示加载动画
|
||||
showLoading();
|
||||
|
||||
apiRequest(`/api/v1/products/${productId}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
})
|
||||
.then(response => {
|
||||
// 隐藏加载动画
|
||||
hideLoading();
|
||||
|
||||
if (response.status === 401) {
|
||||
window.location.href = '/login';
|
||||
throw new Error('未授权访问');
|
||||
}
|
||||
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
if (data && data.success) {
|
||||
showNotification('产品删除成功', 'success');
|
||||
loadProducts(currentPage);
|
||||
|
||||
// 关闭模态框
|
||||
const modal = bootstrap.Modal.getInstance(document.getElementById('deleteModal'));
|
||||
modal.hide();
|
||||
} else {
|
||||
showNotification(data.message || '删除失败', 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
// 隐藏加载动画
|
||||
hideLoading();
|
||||
|
||||
console.error('Failed to delete product:', error);
|
||||
showNotification('删除失败: ' + (error.message || '未知错误'), 'error');
|
||||
});
|
||||
}
|
||||
|
||||
// 更新批量操作按钮状态
|
||||
function updateBatchButtons() {
|
||||
const selectedCount = document.querySelectorAll('.product-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('.product-checkbox:checked').length;
|
||||
document.getElementById('batch-delete-count').textContent = selectedCount;
|
||||
|
||||
const modal = new bootstrap.Modal(document.getElementById('batchDeleteModal'));
|
||||
modal.show();
|
||||
}
|
||||
|
||||
// 批量删除产品
|
||||
function batchDeleteProducts() {
|
||||
const selectedCheckboxes = document.querySelectorAll('.product-checkbox:checked');
|
||||
const productIds = Array.from(selectedCheckboxes).map(checkbox => checkbox.dataset.productId);
|
||||
|
||||
// 显示加载动画
|
||||
showLoading();
|
||||
|
||||
apiRequest('/api/v1/products/batch', {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ product_ids: productIds })
|
||||
})
|
||||
.then(response => {
|
||||
// 隐藏加载动画
|
||||
hideLoading();
|
||||
|
||||
if (response.status === 401) {
|
||||
window.location.href = '/login';
|
||||
throw new Error('未授权访问');
|
||||
}
|
||||
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
if (data && data.success) {
|
||||
showNotification(data.message || '批量删除成功', 'success');
|
||||
loadProducts(currentPage);
|
||||
|
||||
// 关闭模态框
|
||||
const modal = bootstrap.Modal.getInstance(document.getElementById('batchDeleteModal'));
|
||||
modal.hide();
|
||||
|
||||
// 重置全选复选框
|
||||
document.getElementById('select-all-checkbox').checked = false;
|
||||
} else {
|
||||
showNotification(data.message || '批量删除失败', 'error');
|
||||
|
||||
// 如果有无法删除的产品,显示详细信息
|
||||
if (data.undeletable_products) {
|
||||
const undeletableInfo = data.undeletable_products.map(p =>
|
||||
`${p.product_name} (${p.license_count}个卡密)`
|
||||
).join(', ');
|
||||
showNotification(`以下产品无法删除: ${undeletableInfo}`, 'error');
|
||||
}
|
||||
|
||||
// 关闭模态框
|
||||
const modal = bootstrap.Modal.getInstance(document.getElementById('batchDeleteModal'));
|
||||
modal.hide();
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
// 隐藏加载动画
|
||||
hideLoading();
|
||||
|
||||
console.error('Failed to batch delete products:', error);
|
||||
showNotification('批量删除失败: ' + (error.message || '未知错误'), 'error');
|
||||
|
||||
// 关闭模态框
|
||||
const modal = bootstrap.Modal.getInstance(document.getElementById('batchDeleteModal'));
|
||||
modal.hide();
|
||||
});
|
||||
}
|
||||
|
||||
// 批量更新产品状态
|
||||
function batchUpdateProductStatus(status) {
|
||||
const selectedCheckboxes = document.querySelectorAll('.product-checkbox:checked');
|
||||
const productIds = Array.from(selectedCheckboxes).map(checkbox => checkbox.dataset.productId);
|
||||
|
||||
if (productIds.length === 0) {
|
||||
showNotification('请至少选择一个产品', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
// 显示加载动画
|
||||
showLoading();
|
||||
|
||||
apiRequest('/api/v1/products/batch/status', {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
product_ids: productIds,
|
||||
status: status
|
||||
})
|
||||
})
|
||||
.then(response => {
|
||||
// 隐藏加载动画
|
||||
hideLoading();
|
||||
|
||||
if (response.status === 401) {
|
||||
window.location.href = '/login';
|
||||
throw new Error('未授权访问');
|
||||
}
|
||||
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
if (data && data.success) {
|
||||
showNotification(data.message || '批量更新状态成功', 'success');
|
||||
loadProducts(currentPage);
|
||||
|
||||
// 重置全选复选框
|
||||
document.getElementById('select-all-checkbox').checked = false;
|
||||
} else {
|
||||
showNotification(data.message || '批量更新状态失败', 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
// 隐藏加载动画
|
||||
hideLoading();
|
||||
|
||||
console.error('Failed to batch update product status:', error);
|
||||
showNotification('批量更新状态失败: ' + (error.message || '未知错误'), 'error');
|
||||
});
|
||||
}
|
||||
|
||||
// 在页面加载完成后为批量删除按钮绑定事件
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const batchDeleteBtn = document.getElementById('batch-delete-btn');
|
||||
if (batchDeleteBtn) {
|
||||
batchDeleteBtn.addEventListener('click', showBatchDeleteModal);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user