Kamixitong/app/web/templates/version/create.html
2025-12-12 11:35:14 +08:00

402 lines
17 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.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 %}