Kamixitong/app/web/templates/version/list.html
2025-11-19 22:49:24 +08:00

568 lines
20 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 page_actions %}
<a href="{{ url_for('web.create_version') }}" 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-4">
<label for="search-keyword" class="form-label">关键词搜索</label>
<input type="text" class="form-control" id="search-keyword" placeholder="版本号">
</div>
<div class="col-md-3">
<label for="search-product" class="form-label">产品</label>
<select class="form-select" id="search-product">
<option value="">全部产品</option>
{% for product in products %}
<option value="{{ product.product_id }}">{{ product.product_name }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-3">
<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>
</select>
</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-publish-btn">
<i class="fas fa-paper-plane me-1"></i>批量发布
</button>
<button type="button" class="btn btn-outline-secondary" id="batch-unpublish-btn">
<i class="fas fa-undo 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>版本号</th>
<th>产品</th>
<th>描述</th>
<th>文件大小</th>
<th>状态</th>
<th>发布时间</th>
<th>操作</th>
</tr>
</thead>
<tbody id="version-list">
<tr>
<td colspan="8" 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);
if (typeof apiRequest === 'function') {
loadVersions();
initEventListeners();
} else {
console.error('apiRequest函数未定义等待脚本加载...');
setTimeout(function() {
if (typeof apiRequest === 'function') {
loadVersions();
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;
loadVersions();
});
// 重置搜索
document.getElementById('reset-search').addEventListener('click', function() {
document.getElementById('search-keyword').value = '';
document.getElementById('search-product').value = '';
document.getElementById('search-status').value = '';
currentPage = 1;
loadVersions();
});
// 批量删除确认
document.getElementById('confirm-batch-delete').addEventListener('click', function() {
batchDeleteVersions();
});
// 全选/取消全选
document.getElementById('select-all-checkbox').addEventListener('change', function() {
const checkboxes = document.querySelectorAll('.version-checkbox');
checkboxes.forEach(checkbox => {
checkbox.checked = this.checked;
});
updateBatchButtons();
});
// 批量发布
document.getElementById('batch-publish-btn').addEventListener('click', function(e) {
e.preventDefault();
batchUpdateVersionStatus(1);
});
// 批量取消发布
document.getElementById('batch-unpublish-btn').addEventListener('click', function(e) {
e.preventDefault();
batchUpdateVersionStatus(0);
});
}
// 加载版本列表
function loadVersions(page = 1) {
console.log('loadVersions函数被调用页码:', page);
const params = new URLSearchParams({
page: page,
per_page: 10
});
// 添加搜索参数
const keyword = document.getElementById('search-keyword').value.trim();
const product = document.getElementById('search-product').value;
const status = document.getElementById('search-status').value;
if (keyword) params.append('keyword', keyword);
if (product) params.append('product_id', product);
if (status) params.append('publish_status', status);
const apiUrl = `/api/v1/versions?${params}`;
console.log('请求API URL:', apiUrl);
console.log('准备请求API:', apiUrl);
apiRequest(apiUrl)
.then(data => {
if (data.success) {
renderVersionList(data.data.versions);
renderPagination(data.data.pagination);
} else {
showNotification(data.message || '加载版本列表失败', 'error');
}
})
.catch(error => {
console.error('Failed to load versions:', error);
showNotification('加载版本列表失败', 'error');
});
}
// 渲染版本列表
function renderVersionList(versions) {
const tbody = document.getElementById('version-list');
if (versions.length === 0) {
tbody.innerHTML = '<tr><td colspan="8" class="text-center text-muted">暂无数据</td></tr>';
return;
}
tbody.innerHTML = versions.map(version => `
<tr>
<td>
<input type="checkbox" class="version-checkbox" data-version-id="${version.version_id}">
</td>
<td>
<strong>${version.version_num}</strong>
<br><small class="text-muted">${version.platform || '-'}</small>
</td>
<td>
${version.product_name || '-'}
<br><small class="text-muted">${version.product_id || '-'}</small>
</td>
<td>${version.description || '-'}</td>
<td>${version.file_size ? formatFileSize(version.file_size) : '-'}</td>
<td>
<span class="badge ${version.publish_status === 1 ? 'bg-success' : 'bg-secondary'}">
${version.publish_status_name || (version.publish_status === 1 ? '已发布' : '未发布')}
</span>
</td>
<td>
<small>${version.create_time ? formatDate(version.create_time) : '-'}</small>
</td>
<td>
<div class="btn-group btn-group-sm" role="group">
<a href="/versions/${version.version_id}" class="btn btn-outline-primary" title="查看">
<i class="fas fa-eye"></i>
</a>
<a href="/versions/${version.version_id}/edit" class="btn btn-outline-secondary" title="编辑">
<i class="fas fa-edit"></i>
</a>
${version.publish_status === 0 || version.publish_status === 2 ? `
<button type="button" class="btn btn-outline-success btn-publish"
data-version-id="${version.version_id}"
title="发布">
<i class="fas fa-paper-plane"></i>
</button>
${version.publish_status === 0 ? `
<button type="button" class="btn btn-outline-danger btn-delete"
data-version-id="${version.version_id}"
title="删除">
<i class="fas fa-trash"></i>
</button>
` : ''}
` : `
<button type="button" class="btn btn-outline-warning btn-unpublish"
data-version-id="${version.version_id}"
title="取消发布">
<i class="fas fa-undo"></i>
</button>
`}
</div>
</td>
</tr>
`).join('');
// 绑定事件
document.querySelectorAll('.btn-publish').forEach(btn => {
btn.addEventListener('click', function() {
const versionId = this.dataset.versionId;
updateVersionStatus(versionId, 1); // 发布
});
});
document.querySelectorAll('.btn-unpublish').forEach(btn => {
btn.addEventListener('click', function() {
const versionId = this.dataset.versionId;
updateVersionStatus(versionId, 0); // 取消发布
});
});
document.querySelectorAll('.btn-delete').forEach(btn => {
btn.addEventListener('click', function() {
const versionId = this.dataset.versionId;
deleteVersion(versionId);
});
});
// 绑定复选框事件
document.querySelectorAll('.version-checkbox').forEach(checkbox => {
checkbox.addEventListener('change', updateBatchButtons);
});
// 重置全选复选框
document.getElementById('select-all-checkbox').checked = false;
updateBatchButtons();
}
// 渲染分页
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;
loadVersions(page);
}
});
});
}
// 更新版本状态
function updateVersionStatus(versionId, status) {
const action = status === 1 ? '发布' : '取消发布';
if (!confirm(`确定要${action}该版本吗?`)) {
return;
}
apiRequest(`/api/v1/versions/${versionId}/status`, {
method: 'PUT',
body: JSON.stringify({ status: status })
})
.then(data => {
if (data.success) {
showNotification(`${action}成功`, 'success');
loadVersions(currentPage);
} else {
showNotification(data.message || `${action}失败`, 'error');
}
})
.catch(error => {
console.error('Failed to update version status:', error);
showNotification(`${action}失败`, 'error');
});
}
// 删除版本
function deleteVersion(versionId) {
if (!confirm('确定要删除该版本吗?')) {
return;
}
apiRequest(`/api/v1/versions/${versionId}`, {
method: 'DELETE'
})
.then(data => {
if (data.success) {
showNotification('删除成功', 'success');
loadVersions(currentPage);
} else {
showNotification(data.message || '删除失败', 'error');
}
})
.catch(error => {
console.error('Failed to delete version:', error);
if (error.message && error.message.includes('HTTP error! status: 400')) {
showNotification('删除失败:该版本可能已发布或正在被设备使用,请先取消发布并确保无设备使用后再删除', 'error');
} else {
showNotification('删除失败:' + (error.message || '未知错误'), 'error');
}
});
}
// 更新批量操作按钮状态
function updateBatchButtons() {
const selectedCount = document.querySelectorAll('.version-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('.version-checkbox:checked').length;
document.getElementById('batch-delete-count').textContent = selectedCount;
const modal = new bootstrap.Modal(document.getElementById('batchDeleteModal'));
modal.show();
}
// 批量删除版本
function batchDeleteVersions() {
const selectedCheckboxes = document.querySelectorAll('.version-checkbox:checked');
const versionIds = Array.from(selectedCheckboxes).map(checkbox => parseInt(checkbox.dataset.versionId));
apiRequest('/api/v1/versions/batch', {
method: 'DELETE',
body: JSON.stringify({ version_ids: versionIds })
})
.then(data => {
if (data.success) {
showNotification(data.message || '批量删除成功', 'success');
loadVersions(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 versions:', error);
showNotification('批量删除失败', 'error');
// 关闭模态框
const modal = bootstrap.Modal.getInstance(document.getElementById('batchDeleteModal'));
modal.hide();
});
}
// 批量更新版本状态
function batchUpdateVersionStatus(status) {
const selectedCheckboxes = document.querySelectorAll('.version-checkbox:checked');
const versionIds = Array.from(selectedCheckboxes).map(checkbox => parseInt(checkbox.dataset.versionId));
if (versionIds.length === 0) {
showNotification('请至少选择一个版本', 'warning');
return;
}
apiRequest('/api/v1/versions/batch/status', {
method: 'PUT',
body: JSON.stringify({
version_ids: versionIds,
status: status
})
})
.then(data => {
if (data.success) {
showNotification(data.message || '批量更新状态成功', 'success');
loadVersions(currentPage);
// 重置全选复选框
document.getElementById('select-all-checkbox').checked = false;
} else {
showNotification(data.message || '批量更新状态失败', 'error');
}
})
.catch(error => {
console.error('Failed to batch update version status:', error);
showNotification('批量更新状态失败', 'error');
});
}
// 在页面加载完成后为批量删除按钮绑定事件
document.addEventListener('DOMContentLoaded', function() {
const batchDeleteBtn = document.getElementById('batch-delete-btn');
if (batchDeleteBtn) {
batchDeleteBtn.addEventListener('click', showBatchDeleteModal);
}
});
</script>
{% endblock %}