Kamixitong/app/web/templates/user/product_detail.html

508 lines
22 KiB
HTML
Raw Normal View History

2025-11-19 22:49:24 +08:00
{% extends "user/base.html" %}
{% block title %}产品详情 - {{ config.SITE_NAME or '软件授权管理系统' }}{% endblock %}
{% block extra_css %}
<style>
.product-detail-image {
max-height: 400px;
object-fit: cover;
border-radius: 10px;
}
.version-table th {
background-color: #f8f9fa;
}
.download-section {
background: #f8f9fa;
border-radius: 10px;
padding: 2rem;
}
.feature-list li {
margin-bottom: 0.5rem;
}
</style>
{% endblock %}
{% block content %}
<div class="container py-4">
<div class="row">
<div class="col-12">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{{ url_for('user.user_index') }}">首页</a></li>
<li class="breadcrumb-item"><a href="{{ url_for('user.user_products') }}">产品中心</a></li>
<li class="breadcrumb-item active" aria-current="page">产品详情</li>
</ol>
</nav>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="card shadow-sm">
<div class="card-body">
<div class="row">
<div class="col-lg-6 mb-4 mb-lg-0">
2025-11-22 16:48:45 +08:00
<img src="/static/images/product-default.png"
2025-11-19 22:49:24 +08:00
class="img-fluid product-detail-image"
alt="产品图片" id="productImage">
</div>
<div class="col-lg-6">
<h1 class="mb-3" id="productName">产品名称</h1>
<p class="lead" id="productDescription">产品描述信息,这里是产品的详细介绍内容。</p>
<div class="row mb-3">
<div class="col-sm-4"><strong>产品版本:</strong></div>
<div class="col-sm-8" id="productVersion">v1.0.0</div>
</div>
<div class="row mb-3">
<div class="col-sm-4"><strong>发布状态:</strong></div>
<div class="col-sm-8">
<span class="badge bg-success" id="productStatus">已发布</span>
</div>
</div>
<div class="row mb-3">
<div class="col-sm-4"><strong>分类:</strong></div>
<div class="col-sm-8" id="productCategory">办公软件</div>
</div>
<div class="row mb-4">
<div class="col-sm-4"><strong>更新时间:</strong></div>
<div class="col-sm-8" id="updateTime">2023-10-01 10:30:00</div>
</div>
<div class="download-section">
<h4 class="mb-3">
<i class="fas fa-download me-2"></i>下载产品
</h4>
<div class="input-group mb-3">
<input type="text" class="form-control"
id="licenseKey"
placeholder="请输入卡密">
<button class="btn btn-outline-secondary"
type="button"
id="verifyLicenseBtn">
验证
</button>
</div>
<div id="downloadSection" style="display: none;">
<div class="alert alert-success">
<i class="fas fa-check-circle me-2"></i>
卡密验证成功!
</div>
<button class="btn btn-success w-100" id="downloadBtn">
<i class="fas fa-download me-2"></i>
立即下载
</button>
</div>
<div class="mt-3 text-center">
<a href="{{ url_for('user.user_license_purchase') }}" class="btn btn-link">
<i class="fas fa-shopping-cart me-1"></i>购买卡密
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
2025-12-12 11:35:14 +08:00
<div class="row mt-4" id="featuresSection" style="display: none;">
2025-11-19 22:49:24 +08:00
<div class="col-12">
<div class="card shadow-sm">
<div class="card-header">
<h5 class="mb-0">产品功能特性</h5>
</div>
<div class="card-body">
2025-12-12 11:35:14 +08:00
<ul class="feature-list" id="featuresList">
<!-- 功能特性将通过JavaScript动态加载 -->
2025-11-19 22:49:24 +08:00
</ul>
</div>
</div>
</div>
</div>
<div class="row mt-4">
<div class="col-12">
<div class="card shadow-sm">
<div class="card-header">
<h5 class="mb-0">版本历史</h5>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-striped version-table">
<thead>
<tr>
<th>版本</th>
<th>发布日期</th>
<th>更新内容</th>
<th>操作</th>
</tr>
</thead>
<tbody id="versionHistory">
<tr>
<td>v1.0.0</td>
<td>2023-10-01</td>
<td>初始版本发布</td>
<td>
<button class="btn btn-sm btn-outline-primary">
<i class="fas fa-download me-1"></i>下载
</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
// 产品详情页面JavaScript
document.addEventListener('DOMContentLoaded', function() {
console.log('产品详情页面加载完成');
// 从URL中获取产品ID
const urlPath = window.location.pathname;
const productId = urlPath.split('/').pop();
// 加载产品详情
loadProductDetail(productId);
// 验证卡密按钮事件
document.getElementById('verifyLicenseBtn').addEventListener('click', function() {
const licenseKey = document.getElementById('licenseKey').value.trim();
if (!licenseKey) {
showNotification('请输入卡密', 'warning');
return;
}
// 显示加载状态
showLoading();
// 调用API验证卡密
apiRequest('/user/licenses/verify', {
method: 'POST',
body: JSON.stringify({ license_key: licenseKey })
})
.then(data => {
if (data.success) {
// 验证成功,显示下载按钮
document.getElementById('downloadSection').style.display = 'block';
showNotification('卡密验证成功!', 'success');
} else {
showNotification('卡密验证失败: ' + data.message, 'error');
}
})
.catch(error => {
showNotification('验证卡密失败,请稍后重试', 'error');
console.error('验证卡密失败:', error);
})
.finally(() => {
hideLoading();
});
});
// 下载按钮事件
document.getElementById('downloadBtn').addEventListener('click', function() {
showNotification('开始下载...', 'info');
// 这里应该触发实际的下载逻辑
});
});
// 加载产品详情
function loadProductDetail(productId) {
if (!productId) {
showNotification('产品ID无效', 'error');
return;
}
// 显示加载状态
showLoading();
// 调用API获取产品详情
apiRequest(`/api/v1/user/products/${productId}`)
.then(data => {
if (data.success) {
renderProductDetail(data.data);
} else {
showNotification('加载产品详情失败: ' + data.message, 'error');
}
})
.catch(error => {
showNotification('加载产品详情失败,请稍后重试', 'error');
console.error('加载产品详情失败:', error);
})
.finally(() => {
hideLoading();
});
}
2025-12-12 11:35:14 +08:00
// 转义HTML特殊字符
function escapeHtml(text) {
if (!text) return '';
const map = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#039;'
};
return text.replace(/[&<>'"]/g, function(m) { return map[m]; });
}
2025-11-19 22:49:24 +08:00
// 渲染产品详情
function renderProductDetail(product) {
// 更新页面内容
document.getElementById('productName').textContent = product.product_name;
document.getElementById('productDescription').textContent = product.description || '暂无描述';
document.getElementById('productVersion').textContent = product.latest_version || '1.0.0';
document.getElementById('productStatus').textContent = product.status_name;
document.getElementById('productStatus').className = `badge ${product.status === 1 ? 'bg-success' : 'bg-secondary'}`;
document.getElementById('updateTime').textContent = product.update_time;
2025-11-22 16:48:45 +08:00
// 更新产品图片
const productImage = document.getElementById('productImage');
if (product.image_url) {
productImage.src = product.image_url;
} else {
productImage.src = '/static/images/product-default.png';
}
2025-12-12 11:35:14 +08:00
// 更新功能特性
const featuresList = document.getElementById('featuresList');
const featuresSection = document.getElementById('featuresSection');
if (product.features) {
// 将功能特性按行分割并渲染
const features = product.features.split('\n').filter(f => f.trim() !== '');
if (features.length > 0) {
let featuresHtml = '';
features.forEach(feature => {
featuresHtml += `<li><i class="fas fa-check-circle text-success me-2"></i>${escapeHtml(feature)}</li>`;
});
featuresList.innerHTML = featuresHtml;
featuresSection.style.display = 'block';
} else {
featuresSection.style.display = 'none';
}
} else {
featuresSection.style.display = 'none';
}
2025-11-19 22:49:24 +08:00
// 更新版本历史
const versionHistory = document.getElementById('versionHistory');
if (product.recent_updates && product.recent_updates.length > 0) {
let versionHtml = '';
product.recent_updates.forEach(update => {
2025-12-12 11:35:14 +08:00
// 安全地处理下载URL
const downloadUrl = update.download_url ? escapeHtml(update.download_url) : '';
console.log('Rendering version update:', update);
2025-11-19 22:49:24 +08:00
versionHtml += `
<tr>
2025-12-12 11:35:14 +08:00
<td>${escapeHtml(update.version_num)}</td>
2025-11-19 22:49:24 +08:00
<td>${formatDate(update.update_time)}</td>
2025-12-12 11:35:14 +08:00
<td>${escapeHtml(update.update_log || '无更新说明')}</td>
2025-11-19 22:49:24 +08:00
<td>
2025-12-12 11:35:14 +08:00
<button class="btn btn-sm btn-outline-primary download-version-btn"
data-product="${escapeHtml(product.product_id)}"
data-version="${escapeHtml(update.version_num)}"
data-url="${downloadUrl}">
2025-11-19 22:49:24 +08:00
<i class="fas fa-download me-1"></i>下载
</button>
</td>
</tr>
`;
});
versionHistory.innerHTML = versionHtml;
2025-12-12 11:35:14 +08:00
// 为每个下载按钮添加点击事件
document.querySelectorAll('.download-version-btn').forEach(button => {
button.addEventListener('click', function() {
const productId = this.getAttribute('data-product');
const versionNum = this.getAttribute('data-version');
const downloadUrl = this.getAttribute('data-url');
console.log('Download button clicked:', {productId, versionNum, downloadUrl});
downloadVersion(productId, versionNum, downloadUrl);
});
});
}
}
// 添加下载版本的函数
function downloadVersion(productId, versionNum, downloadUrl) {
console.log('Download version called:', {productId, versionNum, downloadUrl});
showLoading();
// 如果有直接的下载URL直接处理
if (downloadUrl && downloadUrl.trim() !== '') {
try {
console.log('Processing direct download URL:', downloadUrl);
// 检查URL是否是本地上传的文件相对路径
if (downloadUrl.startsWith('/')) {
// 本地文件,直接下载
window.open(downloadUrl, '_blank');
showNotification('开始下载...', 'info');
} else {
// 外部链接,询问用户是否跳转
if (confirm(`此版本是外部链接,将跳转到:${downloadUrl}\n是否继续`)) {
window.open(downloadUrl, '_blank');
}
}
} catch (e) {
console.error('下载处理出错:', e);
showNotification('下载处理出错,请稍后重试', 'error');
}
hideLoading();
return;
}
// 如果没有直接的下载URL检查下载权限
console.log('Checking download permissions for product:', productId);
apiRequest(`/api/v1/user/downloads/check?product_id=${productId}`)
.then(data => {
console.log('Download permission check result:', data);
if (data.success) {
const downloadInfo = data.data;
console.log('Download info:', downloadInfo);
// 如果有下载链接,直接处理
if (downloadInfo.download_url && downloadInfo.download_url.trim() !== '') {
try {
console.log('Processing download URL from API:', downloadInfo.download_url);
// 检查URL是否是本地上传的文件相对路径
if (downloadInfo.download_url.startsWith('/')) {
// 本地文件,直接下载
window.open(downloadInfo.download_url, '_blank');
showNotification('开始下载...', 'info');
} else {
// 外部链接,询问用户是否跳转
if (confirm(`此版本是外部链接,将跳转到:${downloadInfo.download_url}\n是否继续`)) {
window.open(downloadInfo.download_url, '_blank');
}
}
} catch (e) {
console.error('下载处理出错:', e);
showNotification('下载处理出错,请稍后重试', 'error');
}
} else {
console.log('No download URL available in API response');
// 检查是否是付费产品
if (downloadInfo.is_paid) {
// 显示卡密输入模态框
showLicenseVerificationModal(productId, versionNum, downloadInfo.download_url);
} else {
showNotification('暂无下载链接', 'warning');
}
}
} else {
showNotification('检查下载权限失败: ' + data.message, 'error');
}
})
.catch(error => {
console.error('检查下载权限失败:', error);
showNotification('检查下载权限失败,请稍后重试', 'error');
})
.finally(() => {
hideLoading();
});
}
// 显示卡密验证模态框
function showLicenseVerificationModal(productId, versionNum, downloadUrl) {
// 创建模态框HTML
const modalHtml = `
<div class="modal fade" id="licenseVerifyModal" tabindex="-1" aria-labelledby="licenseVerifyModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="licenseVerifyModalLabel">卡密验证</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label for="licenseKeyInput" class="form-label">请输入您的卡密</label>
<input type="text" class="form-control" id="licenseKeyInput" placeholder="请输入卡密">
<div class="form-text">版本: ${versionNum}</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary" id="verifyLicenseBtn">验证并下载</button>
</div>
</div>
</div>
</div>
`;
// 添加到页面中
if (!document.getElementById('licenseVerifyModal')) {
document.body.insertAdjacentHTML('beforeend', modalHtml);
2025-11-19 22:49:24 +08:00
}
2025-12-12 11:35:14 +08:00
// 显示模态框
const modal = new bootstrap.Modal(document.getElementById('licenseVerifyModal'));
modal.show();
// 绑定验证按钮事件
document.getElementById('verifyLicenseBtn').onclick = function() {
const licenseKey = document.getElementById('licenseKeyInput').value.trim();
if (!licenseKey) {
showNotification('请输入卡密', 'warning');
return;
}
verifyAndDownload(productId, licenseKey, downloadUrl);
};
}
// 验证卡密并下载
function verifyAndDownload(productId, licenseKey, downloadUrl) {
showLoading();
apiRequest(`/api/v1/user/licenses/verify?license_key=${encodeURIComponent(licenseKey)}&product_id=${productId}`)
.then(data => {
if (data.success) {
// 验证成功,开始下载
if (data.data.download_url) {
window.open(data.data.download_url, '_blank');
showNotification('验证成功,开始下载...', 'success');
// 关闭模态框
const modalElement = document.getElementById('licenseVerifyModal');
if (modalElement) {
const modal = bootstrap.Modal.getInstance(modalElement);
if (modal) {
modal.hide();
}
}
} else {
showNotification('暂无下载链接', 'warning');
}
} else {
showNotification('卡密验证失败: ' + data.message, 'error');
}
})
.catch(error => {
showNotification('卡密验证失败,请稍后重试', 'error');
console.error('卡密验证失败:', error);
})
.finally(() => {
hideLoading();
});
2025-11-19 22:49:24 +08:00
}
</script>
{% endblock %}