第一次提交
This commit is contained in:
402
app/web/templates/version/create.html
Normal file
402
app/web/templates/version/create.html
Normal file
@@ -0,0 +1,402 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}发布版本 - 软件授权管理系统{% endblock %}
|
||||
|
||||
{% block page_title %}发布版本{% endblock %}
|
||||
|
||||
{% block page_actions %}
|
||||
<a href="{{ url_for('web.versions') }}" class="btn btn-outline-secondary">
|
||||
<i class="fas fa-arrow-left me-2"></i>
|
||||
返回列表
|
||||
</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-lg-8">
|
||||
<div class="card shadow">
|
||||
<div class="card-body">
|
||||
<form id="version-form" enctype="multipart/form-data">
|
||||
<div class="mb-3">
|
||||
<label for="product_id" class="form-label">选择产品 *</label>
|
||||
<select class="form-select" id="product_id" name="product_id" required>
|
||||
<option value="">请选择产品</option>
|
||||
{% for product in products %}
|
||||
<option value="{{ product.product_id }}" {% if request.args.get('product_id') == product.product_id %}selected{% endif %}>
|
||||
{{ product.product_name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="version_num" class="form-label">版本号 *</label>
|
||||
<input type="text" class="form-control" id="version_num" name="version_num" required>
|
||||
<div class="form-text">版本号格式建议使用 x.x.x 格式,如 1.0.0</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="platform" class="form-label">平台</label>
|
||||
<input type="text" class="form-control" id="platform" name="platform" placeholder="如: Windows, macOS, Linux">
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="description" class="form-label">版本描述</label>
|
||||
<textarea class="form-control" id="description" name="description" rows="3"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="update_log" class="form-label">更新日志</label>
|
||||
<textarea class="form-control" id="update_log" name="update_log" rows="5"></textarea>
|
||||
</div>
|
||||
|
||||
<!-- 文件提供方式选择 -->
|
||||
<div class="mb-3">
|
||||
<label class="form-label">文件提供方式 *</label>
|
||||
<ul class="nav nav-tabs" id="fileSourceTabs" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link active" id="upload-tab" data-bs-toggle="tab" data-bs-target="#upload-content" type="button" role="tab">上传文件</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="link-tab" data-bs-toggle="tab" data-bs-target="#link-content" type="button" role="tab">文件链接</button>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="tab-content border border-top-0 p-3" id="fileSourceTabsContent">
|
||||
<!-- 文件上传区域 -->
|
||||
<div class="tab-pane fade show active" id="upload-content" role="tabpanel">
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<button type="button" class="btn btn-outline-primary btn-sm" id="upload-file-btn">
|
||||
<i class="fas fa-upload me-1"></i>选择文件
|
||||
</button>
|
||||
<input type="file" id="version_file" name="version_file" accept=".zip,.rar,.7z,.exe,.dmg,.pkg" class="d-none">
|
||||
<div id="upload-status" class="text-muted">未选择文件</div>
|
||||
</div>
|
||||
<div id="file-info" class="small text-muted d-none">
|
||||
<div>文件名: <span id="file-name"></span></div>
|
||||
<div>文件大小: <span id="file-size"></span></div>
|
||||
<div>文件哈希: <span id="file-hash"></span></div>
|
||||
<input type="hidden" id="file_url" name="file_url">
|
||||
<input type="hidden" id="file_hash" name="file_hash">
|
||||
</div>
|
||||
<div class="form-text">支持常见的压缩包和安装包格式</div>
|
||||
</div>
|
||||
|
||||
<!-- 文件链接区域 -->
|
||||
<div class="tab-pane fade" id="link-content" role="tabpanel">
|
||||
<div class="mb-3">
|
||||
<label for="file_link" class="form-label">文件下载链接</label>
|
||||
<input type="url" class="form-control" id="file_link" name="download_url" placeholder="https://example.com/software.zip">
|
||||
<div class="form-text">请输入完整的文件下载链接</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="link_file_name" class="form-label">文件名(可选)</label>
|
||||
<input type="text" class="form-control" id="link_file_name" name="link_file_name" placeholder="software.zip">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3 form-check">
|
||||
<input type="checkbox" class="form-check-input" id="publish_now" name="publish_now">
|
||||
<label class="form-check-label" for="publish_now">立即发布</label>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary" id="submit-btn">
|
||||
<i class="fas fa-save me-2"></i>
|
||||
<span id="submit-text">创建版本</span>
|
||||
</button>
|
||||
<a href="{{ url_for('web.versions') }}" class="btn btn-secondary">取消</a>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-4">
|
||||
<div class="card shadow">
|
||||
<div class="card-header">
|
||||
<h6 class="mb-0">发布说明</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<ul class="list-unstyled">
|
||||
<li class="mb-2">
|
||||
<i class="fas fa-info-circle text-primary me-2"></i>
|
||||
<small>必须选择一个产品才能创建版本</small>
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
<i class="fas fa-info-circle text-primary me-2"></i>
|
||||
<small>版本号建议使用语义化版本格式</small>
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
<i class="fas fa-info-circle text-primary me-2"></i>
|
||||
<small>可以先创建版本再上传文件,或直接上传文件</small>
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
<i class="fas fa-info-circle text-primary me-2"></i>
|
||||
<small>立即发布选项会直接将版本设为可用状态</small>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
<script>
|
||||
// 页面加载完成后初始化
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
initEventListeners();
|
||||
});
|
||||
|
||||
// 初始化事件监听器
|
||||
function initEventListeners() {
|
||||
// 表单提交
|
||||
document.getElementById('version-form').addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
createVersion();
|
||||
});
|
||||
|
||||
// 文件选择按钮点击事件
|
||||
document.getElementById('upload-file-btn').addEventListener('click', function() {
|
||||
document.getElementById('version_file').click();
|
||||
});
|
||||
|
||||
// 文件选择变化事件
|
||||
document.getElementById('version_file').addEventListener('change', function(e) {
|
||||
if (e.target.files.length > 0) {
|
||||
uploadFile(e.target.files[0]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 上传文件
|
||||
function uploadFile(file) {
|
||||
const uploadBtn = document.getElementById('upload-file-btn');
|
||||
const uploadStatus = document.getElementById('upload-status');
|
||||
|
||||
// 显示上传状态
|
||||
uploadBtn.disabled = true;
|
||||
uploadStatus.textContent = '上传中...';
|
||||
|
||||
// 创建FormData对象
|
||||
const formData = new FormData();
|
||||
formData.append('version_file', file);
|
||||
|
||||
// 发送上传请求
|
||||
const uploadUrl = '/api/v1/versions/upload';
|
||||
|
||||
apiRequest(uploadUrl, {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(data => {
|
||||
console.log('Upload response data:', data);
|
||||
if (data.success) {
|
||||
// 显示文件信息
|
||||
document.getElementById('file-name').textContent = data.data.file_name;
|
||||
document.getElementById('file-size').textContent = formatFileSize(data.data.file_size);
|
||||
document.getElementById('file-hash').textContent = data.data.file_hash;
|
||||
document.getElementById('file_url').value = data.data.file_url;
|
||||
document.getElementById('file_hash').value = data.data.file_hash;
|
||||
|
||||
// 显示文件信息区域
|
||||
document.getElementById('file-info').classList.remove('d-none');
|
||||
uploadStatus.textContent = '上传成功';
|
||||
} else {
|
||||
uploadStatus.textContent = data.message || '上传失败';
|
||||
showNotification(data.message || '文件上传失败', 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('文件上传失败:', error);
|
||||
uploadStatus.textContent = '上传失败';
|
||||
showNotification('文件上传失败,请稍后重试: ' + error.message, 'error');
|
||||
})
|
||||
.finally(() => {
|
||||
// 恢复按钮状态
|
||||
uploadBtn.disabled = false;
|
||||
});
|
||||
}
|
||||
|
||||
// 格式化文件大小
|
||||
function formatFileSize(bytes) {
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
const k = 1024;
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||
}
|
||||
|
||||
// 创建版本
|
||||
function createVersion() {
|
||||
const submitBtn = document.getElementById('submit-btn');
|
||||
const submitText = document.getElementById('submit-text');
|
||||
|
||||
// 获取表单数据
|
||||
const formData = new FormData();
|
||||
formData.append('product_id', document.getElementById('product_id').value);
|
||||
formData.append('version_num', document.getElementById('version_num').value.trim());
|
||||
|
||||
const platform = document.getElementById('platform').value.trim();
|
||||
if (platform) {
|
||||
formData.append('platform', platform);
|
||||
}
|
||||
|
||||
const description = document.getElementById('description').value.trim();
|
||||
if (description) {
|
||||
formData.append('description', description);
|
||||
}
|
||||
|
||||
const updateLog = document.getElementById('update_log').value.trim();
|
||||
if (updateLog) {
|
||||
formData.append('update_log', updateLog);
|
||||
}
|
||||
|
||||
// 检查当前选择的文件提供方式
|
||||
const activeTab = document.querySelector('.nav-link.active').id;
|
||||
|
||||
if (activeTab === 'upload-tab') {
|
||||
// 上传文件方式
|
||||
const fileUrl = document.getElementById('file_url').value;
|
||||
if (fileUrl) {
|
||||
formData.append('download_url', fileUrl);
|
||||
}
|
||||
|
||||
const fileHash = document.getElementById('file_hash').value;
|
||||
if (fileHash) {
|
||||
formData.append('file_hash', fileHash);
|
||||
}
|
||||
} else if (activeTab === 'link-tab') {
|
||||
// 文件链接方式
|
||||
const downloadUrl = document.getElementById('file_link').value.trim();
|
||||
console.log('Link tab selected, downloadUrl:', downloadUrl);
|
||||
if (downloadUrl) {
|
||||
formData.append('download_url', downloadUrl);
|
||||
console.log('Added download_url to formData:', downloadUrl);
|
||||
|
||||
// 如果提供了文件名,则使用提供的文件名
|
||||
const linkFileName = document.getElementById('link_file_name').value.trim();
|
||||
console.log('Link file name:', linkFileName);
|
||||
if (linkFileName) {
|
||||
// 生成一个虚拟的文件哈希(基于文件名)
|
||||
const fileHash = generateSimpleHash(linkFileName);
|
||||
formData.append('file_hash', fileHash);
|
||||
console.log('Generated file hash:', fileHash);
|
||||
} else {
|
||||
// 如果没有提供文件名但有URL,基于URL生成一个简单的哈希
|
||||
const fileHash = generateSimpleHash(downloadUrl);
|
||||
formData.append('file_hash', fileHash);
|
||||
console.log('Generated file hash from URL:', fileHash);
|
||||
}
|
||||
} else {
|
||||
console.log('No download URL provided');
|
||||
}
|
||||
}
|
||||
|
||||
// 额外检查:确保无论哪个tab被激活,只要download_url元素中有值就添加到formData
|
||||
// 这是为了防止tab切换状态不一致的问题
|
||||
if (!formData.has('download_url')) {
|
||||
// 首先检查链接输入框
|
||||
const downloadUrlInput = document.getElementById('file_link');
|
||||
if (downloadUrlInput && downloadUrlInput.value.trim()) {
|
||||
formData.append('download_url', downloadUrlInput.value.trim());
|
||||
console.log('Fallback: Added download_url from file_link input:', downloadUrlInput.value.trim());
|
||||
}
|
||||
// 如果链接输入框没有值,检查上传文件的URL
|
||||
else {
|
||||
const fileUrlInput = document.getElementById('file_url');
|
||||
if (fileUrlInput && fileUrlInput.value.trim()) {
|
||||
formData.append('download_url', fileUrlInput.value.trim());
|
||||
console.log('Fallback: Added download_url from file_url input:', fileUrlInput.value.trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 调试:打印所有表单数据
|
||||
console.log('FormData contents:');
|
||||
for (let pair of formData.entries()) {
|
||||
console.log(pair[0] + ': ' + pair[1]);
|
||||
}
|
||||
|
||||
const publishNow = document.getElementById('publish_now').checked;
|
||||
formData.append('publish_now', publishNow);
|
||||
|
||||
// 基础验证
|
||||
if (!document.getElementById('product_id').value) {
|
||||
showNotification('请选择产品', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!document.getElementById('version_num').value.trim()) {
|
||||
showNotification('请输入版本号', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
// 验证文件提供方式
|
||||
if (activeTab === 'upload-tab') {
|
||||
// 上传方式不需要强制要求文件,但如果有文件则使用
|
||||
} else if (activeTab === 'link-tab') {
|
||||
const downloadUrl = document.getElementById('file_link').value.trim();
|
||||
if (downloadUrl && !isValidUrl(downloadUrl)) {
|
||||
showNotification('请输入有效的文件链接', 'warning');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 显示加载状态
|
||||
submitBtn.disabled = true;
|
||||
submitText.textContent = '创建中...';
|
||||
|
||||
// 发送请求
|
||||
showLoading();
|
||||
const createUrl = '/api/v1/versions';
|
||||
|
||||
apiRequest(createUrl, {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
showNotification('版本创建成功', 'success');
|
||||
// 延迟跳转到版本列表
|
||||
setTimeout(() => {
|
||||
window.location.href = '/versions';
|
||||
}, 1500);
|
||||
} else {
|
||||
showNotification(data.message || '创建失败', 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
hideLoading();
|
||||
console.error('Failed to create version:', error);
|
||||
showNotification('网络错误,请稍后重试', 'error');
|
||||
})
|
||||
.finally(() => {
|
||||
// 恢复按钮状态
|
||||
submitBtn.disabled = false;
|
||||
submitText.textContent = '创建版本';
|
||||
});
|
||||
}
|
||||
|
||||
// 简单的哈希生成函数
|
||||
function generateSimpleHash(str) {
|
||||
let hash = 0;
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const char = str.charCodeAt(i);
|
||||
hash = ((hash << 5) - hash) + char;
|
||||
hash = hash & hash; // 转换为32位整数
|
||||
}
|
||||
return Math.abs(hash).toString(16);
|
||||
}
|
||||
|
||||
// 验证URL格式
|
||||
function isValidUrl(string) {
|
||||
try {
|
||||
new URL(string);
|
||||
return true;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
252
app/web/templates/version/detail.html
Normal file
252
app/web/templates/version/detail.html
Normal file
@@ -0,0 +1,252 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}版本详情 - 软件授权管理系统{% endblock %}
|
||||
|
||||
{% block page_title %}版本详情{% endblock %}
|
||||
|
||||
{% block page_actions %}
|
||||
<a href="{{ url_for('web.versions') }}" class="btn btn-outline-secondary">
|
||||
<i class="fas fa-arrow-left me-2"></i>
|
||||
返回列表
|
||||
</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-lg-8">
|
||||
<div class="card shadow">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">版本信息</h5>
|
||||
<table class="table table-borderless">
|
||||
<tr>
|
||||
<td class="fw-bold">版本ID:</td>
|
||||
<td>{{ version.version_id }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="fw-bold">产品:</td>
|
||||
<td>{{ version.product.product_name if version.product else '-' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="fw-bold">版本号:</td>
|
||||
<td>{{ version.version_num }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="fw-bold">平台:</td>
|
||||
<td>{{ version.platform or '-' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="fw-bold">版本描述:</td>
|
||||
<td>{{ version.description or '-' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="fw-bold">更新日志:</td>
|
||||
<td>{{ version.update_log or '-' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="fw-bold">下载URL:</td>
|
||||
<td>
|
||||
{% if version.download_url %}
|
||||
<a href="{{ version.download_url }}" target="_blank">{{ version.download_url }}</a>
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="fw-bold">文件哈希:</td>
|
||||
<td>{{ version.file_hash or '-' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="fw-bold">最低卡密版本:</td>
|
||||
<td>{{ version.min_license_version or '-' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="fw-bold">强制更新:</td>
|
||||
<td>{{ '是' if version.force_update == 1 else '否' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="fw-bold">下载状态:</td>
|
||||
<td>
|
||||
<span class="badge {{ 'bg-success' if version.download_status == 1 else 'bg-secondary' }}">
|
||||
{{ '启用' if version.download_status == 1 else '禁用' }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="fw-bold">发布状态:</td>
|
||||
<td>
|
||||
<span class="badge {{ 'bg-success' if version.publish_status == 1 else 'bg-secondary' }}">
|
||||
{{ version.publish_status_name }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="fw-bold">创建时间:</td>
|
||||
<td>{{ version.create_time.strftime('%Y-%m-%d %H:%M:%S') if version.create_time else '-' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="fw-bold">更新时间:</td>
|
||||
<td>{{ version.update_time.strftime('%Y-%m-%d %H:%M:%S') if version.update_time else '-' }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-4">
|
||||
<div class="card shadow">
|
||||
<div class="card-header">
|
||||
<h6 class="mb-0">统计信息</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
下载次数
|
||||
<span class="badge bg-primary rounded-pill">{{ version.get_download_count() }}</span>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
活跃设备数
|
||||
<a href="{{ url_for('web.devices') }}?product_id={{ version.product_id }}&software_version={{ version.version_num }}&status=1" class="badge bg-success rounded-pill text-decoration-none">{{ version.get_active_device_count() }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card shadow mt-4">
|
||||
<div class="card-header">
|
||||
<h6 class="mb-0">操作</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<a href="{{ url_for('web.edit_version', version_id=version.version_id) }}" class="btn btn-primary w-100 mb-2">
|
||||
<i class="fas fa-edit me-2"></i>编辑版本
|
||||
</a>
|
||||
|
||||
{% if version.publish_status == 0 or version.publish_status == 2 %}
|
||||
<button type="button" class="btn btn-success w-100 mb-2" id="publish-btn">
|
||||
<i class="fas fa-paper-plane me-2"></i>发布版本
|
||||
</button>
|
||||
{% elif version.publish_status == 1 %}
|
||||
<button type="button" class="btn btn-warning w-100 mb-2" id="unpublish-btn">
|
||||
<i class="fas fa-undo me-2"></i>取消发布
|
||||
</button>
|
||||
{% endif %}
|
||||
|
||||
{% if version.publish_status == 0 or version.publish_status == 2 %}
|
||||
<button type="button" class="btn btn-danger w-100" id="delete-btn">
|
||||
<i class="fas fa-trash me-2"></i>删除版本
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
<script>
|
||||
// 页面加载完成后初始化
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
initEventListeners();
|
||||
});
|
||||
|
||||
// 初始化事件监听器
|
||||
function initEventListeners() {
|
||||
// 发布按钮点击事件
|
||||
document.getElementById('publish-btn')?.addEventListener('click', function() {
|
||||
updateVersionStatus(1);
|
||||
});
|
||||
|
||||
// 取消发布按钮点击事件
|
||||
document.getElementById('unpublish-btn')?.addEventListener('click', function() {
|
||||
updateVersionStatus(0);
|
||||
});
|
||||
|
||||
// 删除按钮点击事件
|
||||
document.getElementById('delete-btn')?.addEventListener('click', function() {
|
||||
deleteVersion();
|
||||
});
|
||||
}
|
||||
|
||||
// 更新版本状态
|
||||
function updateVersionStatus(status) {
|
||||
const action = status === 1 ? '发布' : '取消发布';
|
||||
|
||||
if (!confirm(`确定要${action}该版本吗?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
showLoading();
|
||||
fetch(`/api/v1/versions/{{ version.version_id }}/status`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ status: status }),
|
||||
credentials: 'same-origin'
|
||||
})
|
||||
.then(response => {
|
||||
hideLoading();
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
} else {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
})
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
showNotification(`${action}成功`, 'success');
|
||||
// 延迟刷新页面
|
||||
setTimeout(() => {
|
||||
location.reload();
|
||||
}, 1500);
|
||||
} else {
|
||||
showNotification(data.message || `${action}失败`, 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
hideLoading();
|
||||
console.error('Failed to update version status:', error);
|
||||
showNotification(`${action}失败`, 'error');
|
||||
});
|
||||
}
|
||||
|
||||
// 删除版本
|
||||
function deleteVersion() {
|
||||
if (!confirm('确定要删除该版本吗?此操作不可恢复!')) {
|
||||
return;
|
||||
}
|
||||
|
||||
showLoading();
|
||||
fetch(`/api/v1/versions/{{ version.version_id }}`, {
|
||||
method: 'DELETE',
|
||||
credentials: 'same-origin'
|
||||
})
|
||||
.then(response => {
|
||||
hideLoading();
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
} else {
|
||||
return response.json().then(data => {
|
||||
throw new Error(data.message || `HTTP error! status: ${response.status}`);
|
||||
});
|
||||
}
|
||||
})
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
showNotification('删除成功', 'success');
|
||||
// 延迟跳转到版本列表
|
||||
setTimeout(() => {
|
||||
window.location.href = '/versions';
|
||||
}, 1500);
|
||||
} else {
|
||||
showNotification(data.message || '删除失败', 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
hideLoading();
|
||||
console.error('Failed to delete version:', error);
|
||||
showNotification('删除失败:' + error.message, 'error');
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
226
app/web/templates/version/edit.html
Normal file
226
app/web/templates/version/edit.html
Normal file
@@ -0,0 +1,226 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}编辑版本 - 软件授权管理系统{% endblock %}
|
||||
|
||||
{% block page_title %}编辑版本{% endblock %}
|
||||
|
||||
{% block page_actions %}
|
||||
<a href="{{ url_for('web.version_detail', version_id=version.version_id) }}" class="btn btn-outline-secondary">
|
||||
<i class="fas fa-arrow-left me-2"></i>
|
||||
返回详情
|
||||
</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-lg-8">
|
||||
<div class="card shadow">
|
||||
<div class="card-body">
|
||||
<form id="version-form">
|
||||
<div class="mb-3">
|
||||
<label for="product_id" class="form-label">产品</label>
|
||||
<select class="form-select" id="product_id" name="product_id" {% if version.publish_status == 1 %}disabled{% endif %}>
|
||||
<option value="">请选择产品</option>
|
||||
{% for product in products %}
|
||||
<option value="{{ product.product_id }}" {% if product.product_id == version.product_id %}selected{% endif %}>
|
||||
{{ product.product_name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
{% if version.publish_status == 1 %}
|
||||
<div class="form-text text-warning">已发布的版本不能更改产品</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="version_num" class="form-label">版本号</label>
|
||||
<input type="text" class="form-control" id="version_num" name="version_num" value="{{ version.version_num }}" {% if version.publish_status == 1 %}readonly{% endif %}>
|
||||
{% if version.publish_status == 1 %}
|
||||
<div class="form-text text-warning">已发布的版本不能更改版本号</div>
|
||||
{% else %}
|
||||
<div class="form-text">版本号格式建议使用 x.x.x 格式,如 1.0.0</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="platform" class="form-label">平台</label>
|
||||
<input type="text" class="form-control" id="platform" name="platform" value="{{ version.platform or '' }}" placeholder="如: Windows, macOS, Linux">
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="description" class="form-label">版本描述</label>
|
||||
<textarea class="form-control" id="description" name="description" rows="3">{{ version.description or '' }}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="update_log" class="form-label">更新日志</label>
|
||||
<textarea class="form-control" id="update_log" name="update_log" rows="5">{{ version.update_log or '' }}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="min_license_version" class="form-label">最低卡密版本</label>
|
||||
<input type="text" class="form-control" id="min_license_version" name="min_license_version" value="{{ version.min_license_version or '' }}" placeholder="如: 1.0.0">
|
||||
<div class="form-text">设置使用此版本所需的最低卡密版本</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="force_update" class="form-label">强制更新</label>
|
||||
<select class="form-select" id="force_update" name="force_update">
|
||||
<option value="0" {% if version.force_update == 0 %}selected{% endif %}>否</option>
|
||||
<option value="1" {% if version.force_update == 1 %}selected{% endif %}>是</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="download_status" class="form-label">下载状态</label>
|
||||
<select class="form-select" id="download_status" name="download_status">
|
||||
<option value="0" {% if version.download_status == 0 %}selected{% endif %}>禁用</option>
|
||||
<option value="1" {% if version.download_status == 1 %}selected{% endif %}>启用</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary" id="submit-btn">
|
||||
<i class="fas fa-save me-2"></i>
|
||||
<span id="submit-text">保存更改</span>
|
||||
</button>
|
||||
<a href="{{ url_for('web.version_detail', version_id=version.version_id) }}" class="btn btn-secondary">取消</a>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-4">
|
||||
<div class="card shadow">
|
||||
<div class="card-header">
|
||||
<h6 class="mb-0">版本状态</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<ul class="list-unstyled">
|
||||
<li class="mb-2">
|
||||
<strong>当前状态:</strong>
|
||||
<span class="badge {{ 'bg-success' if version.publish_status == 1 else 'bg-secondary' }}">
|
||||
{{ version.publish_status_name }}
|
||||
</span>
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
<strong>版本ID:</strong> {{ version.version_id }}
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
<strong>创建时间:</strong> {{ version.create_time.strftime('%Y-%m-%d %H:%M:%S') if version.create_time else '-' }}
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
<strong>更新时间:</strong> {{ version.update_time.strftime('%Y-%m-%d %H:%M:%S') if version.update_time else '-' }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card shadow mt-4">
|
||||
<div class="card-header">
|
||||
<h6 class="mb-0">编辑说明</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<ul class="list-unstyled">
|
||||
<li class="mb-2">
|
||||
<i class="fas fa-info-circle text-primary me-2"></i>
|
||||
<small>已发布的版本只能修改描述信息</small>
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
<i class="fas fa-info-circle text-primary me-2"></i>
|
||||
<small>未发布或已回滚的版本可以修改所有信息</small>
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
<i class="fas fa-info-circle text-primary me-2"></i>
|
||||
<small>版本号和产品只能在未发布状态下修改</small>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
<script>
|
||||
// 页面加载完成后初始化
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
initEventListeners();
|
||||
});
|
||||
|
||||
// 初始化事件监听器
|
||||
function initEventListeners() {
|
||||
// 表单提交
|
||||
document.getElementById('version-form').addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
updateVersion();
|
||||
});
|
||||
}
|
||||
|
||||
// 更新版本
|
||||
function updateVersion() {
|
||||
const submitBtn = document.getElementById('submit-btn');
|
||||
const submitText = document.getElementById('submit-text');
|
||||
|
||||
// 获取表单数据
|
||||
const data = {
|
||||
product_id: document.getElementById('product_id').value,
|
||||
version_num: document.getElementById('version_num').value.trim(),
|
||||
platform: document.getElementById('platform').value.trim(),
|
||||
description: document.getElementById('description').value.trim(),
|
||||
update_log: document.getElementById('update_log').value.trim(),
|
||||
min_license_version: document.getElementById('min_license_version').value.trim(),
|
||||
force_update: parseInt(document.getElementById('force_update').value),
|
||||
download_status: parseInt(document.getElementById('download_status').value)
|
||||
};
|
||||
|
||||
// 显示加载状态
|
||||
submitBtn.disabled = true;
|
||||
submitText.textContent = '保存中...';
|
||||
|
||||
// 发送请求
|
||||
showLoading();
|
||||
fetch(`/api/v1/versions/{{ version.version_id }}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
credentials: 'same-origin'
|
||||
})
|
||||
.then(response => {
|
||||
hideLoading();
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
} else {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
})
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
showNotification('版本信息更新成功', 'success');
|
||||
// 延迟跳转到版本详情
|
||||
setTimeout(() => {
|
||||
window.location.href = '/versions/{{ version.version_id }}';
|
||||
}, 1500);
|
||||
} else {
|
||||
showNotification(data.message || '更新失败', 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
hideLoading();
|
||||
console.error('Failed to update version:', error);
|
||||
showNotification('网络错误,请稍后重试', 'error');
|
||||
})
|
||||
.finally(() => {
|
||||
// 恢复按钮状态
|
||||
submitBtn.disabled = false;
|
||||
submitText.textContent = '保存更改';
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
568
app/web/templates/version/list.html
Normal file
568
app/web/templates/version/list.html
Normal file
@@ -0,0 +1,568 @@
|
||||
{% 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 %}
|
||||
Reference in New Issue
Block a user