2025-11-19 22:49:24 +08:00
|
|
|
{% extends "user/base.html" %}
|
|
|
|
|
|
|
|
|
|
{% block title %}产品中心 - {{ config.SITE_NAME or '软件授权管理系统' }}{% endblock %}
|
|
|
|
|
|
|
|
|
|
{% block extra_css %}
|
|
|
|
|
<style>
|
|
|
|
|
.product-card {
|
|
|
|
|
transition: transform 0.3s ease;
|
|
|
|
|
border: none;
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.product-card:hover {
|
|
|
|
|
transform: translateY(-5px);
|
|
|
|
|
box-shadow: 0 5px 20px rgba(0,0,0,0.1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.product-image {
|
|
|
|
|
height: 200px;
|
|
|
|
|
object-fit: cover;
|
|
|
|
|
border-top-left-radius: 10px;
|
|
|
|
|
border-top-right-radius: 10px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.search-filters {
|
|
|
|
|
background: #f8f9fa;
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
padding: 1.5rem;
|
|
|
|
|
margin-bottom: 2rem;
|
|
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|
{% endblock %}
|
|
|
|
|
|
|
|
|
|
{% block content %}
|
|
|
|
|
<div class="container py-4">
|
|
|
|
|
<div class="row">
|
|
|
|
|
<div class="col-12">
|
|
|
|
|
<h1 class="mb-4">产品中心</h1>
|
|
|
|
|
|
|
|
|
|
<!-- 搜索和筛选区 -->
|
|
|
|
|
<div class="search-filters">
|
|
|
|
|
<div class="row g-3">
|
|
|
|
|
<div class="col-md-4">
|
|
|
|
|
<input type="text" class="form-control" id="searchInput" placeholder="搜索产品名称...">
|
|
|
|
|
</div>
|
|
|
|
|
<div class="col-md-3">
|
|
|
|
|
<select class="form-select" id="statusFilter">
|
|
|
|
|
<option value="">所有状态</option>
|
|
|
|
|
<option value="1">已发布</option>
|
|
|
|
|
<option value="0">未发布</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
2025-11-22 16:48:45 +08:00
|
|
|
<div class="col-md-3">
|
2025-11-19 22:49:24 +08:00
|
|
|
<button class="btn btn-primary w-100" id="searchBtn">
|
|
|
|
|
<i class="fas fa-search me-1"></i>搜索
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 产品列表 -->
|
|
|
|
|
<div class="row" id="productsContainer">
|
|
|
|
|
<!-- 产品项将通过JavaScript动态加载 -->
|
|
|
|
|
<div class="col-12 text-center py-5">
|
|
|
|
|
<div class="spinner-border text-primary" role="status">
|
|
|
|
|
<span class="visually-hidden">加载中...</span>
|
|
|
|
|
</div>
|
|
|
|
|
<p class="mt-2">正在加载产品列表...</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 分页 -->
|
|
|
|
|
<nav aria-label="产品分页" class="mt-4">
|
|
|
|
|
<ul class="pagination justify-content-center" id="pagination">
|
|
|
|
|
<!-- 分页按钮将通过JavaScript动态生成 -->
|
|
|
|
|
</ul>
|
|
|
|
|
</nav>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
{% endblock %}
|
|
|
|
|
|
|
|
|
|
{% block extra_js %}
|
|
|
|
|
<script>
|
|
|
|
|
// 产品列表页面JavaScript
|
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
|
|
|
console.log('产品列表页面加载完成');
|
|
|
|
|
|
|
|
|
|
// 初始化产品列表
|
|
|
|
|
loadProducts();
|
|
|
|
|
|
|
|
|
|
// 搜索按钮事件
|
|
|
|
|
document.getElementById('searchBtn').addEventListener('click', function() {
|
|
|
|
|
loadProducts();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 回车搜索
|
|
|
|
|
document.getElementById('searchInput').addEventListener('keypress', function(e) {
|
|
|
|
|
if (e.key === 'Enter') {
|
|
|
|
|
loadProducts();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 加载产品列表
|
|
|
|
|
function loadProducts(page = 1) {
|
|
|
|
|
const searchInput = document.getElementById('searchInput').value;
|
|
|
|
|
const statusFilter = document.getElementById('statusFilter').value;
|
|
|
|
|
|
|
|
|
|
// 构建查询参数
|
|
|
|
|
const params = new URLSearchParams();
|
|
|
|
|
params.append('page', page);
|
|
|
|
|
if (searchInput) params.append('keyword', searchInput);
|
|
|
|
|
if (statusFilter) params.append('status', statusFilter);
|
|
|
|
|
|
|
|
|
|
// 显示加载状态
|
|
|
|
|
document.getElementById('productsContainer').innerHTML = `
|
|
|
|
|
<div class="col-12 text-center py-5">
|
|
|
|
|
<div class="spinner-border text-primary" role="status">
|
|
|
|
|
<span class="visually-hidden">加载中...</span>
|
|
|
|
|
</div>
|
|
|
|
|
<p class="mt-2">正在加载产品列表...</p>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
// 调用API获取产品列表
|
|
|
|
|
apiRequest(`/api/v1/user/products?${params.toString()}`)
|
|
|
|
|
.then(data => {
|
|
|
|
|
if (data.success) {
|
|
|
|
|
renderProducts(data.data.products, data.data.pagination);
|
|
|
|
|
} else {
|
|
|
|
|
showNotification('加载产品列表失败: ' + data.message, 'error');
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.catch(error => {
|
|
|
|
|
showNotification('加载产品列表失败,请稍后重试', 'error');
|
|
|
|
|
console.error('加载产品列表失败:', error);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 渲染产品列表
|
|
|
|
|
function renderProducts(products, pagination) {
|
|
|
|
|
const container = document.getElementById('productsContainer');
|
|
|
|
|
|
|
|
|
|
if (products.length === 0) {
|
|
|
|
|
container.innerHTML = `
|
|
|
|
|
<div class="col-12 text-center py-5">
|
|
|
|
|
<i class="fas fa-box-open fa-3x text-muted mb-3"></i>
|
|
|
|
|
<h4 class="text-muted">暂无产品</h4>
|
|
|
|
|
<p class="text-muted">当前没有符合条件的产品</p>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
|
|
|
|
document.getElementById('pagination').innerHTML = '';
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 生成产品卡片HTML
|
|
|
|
|
let productsHtml = '';
|
|
|
|
|
products.forEach(product => {
|
2025-11-22 16:48:45 +08:00
|
|
|
// 处理图片路径,如果没有图片则使用默认图片
|
|
|
|
|
const imagePath = product.image_path || '/static/images/product-default.png';
|
2025-11-19 22:49:24 +08:00
|
|
|
productsHtml += `
|
|
|
|
|
<div class="col-lg-4 col-md-6 mb-4">
|
|
|
|
|
<div class="card product-card h-100">
|
2025-11-22 16:48:45 +08:00
|
|
|
<img src="${imagePath}"
|
2025-11-19 22:49:24 +08:00
|
|
|
class="card-img-top product-image"
|
|
|
|
|
alt="${product.product_name}">
|
|
|
|
|
<div class="card-body d-flex flex-column">
|
|
|
|
|
<h5 class="card-title">${product.product_name}</h5>
|
|
|
|
|
<p class="card-text flex-grow-1">${product.description || '暂无描述'}</p>
|
|
|
|
|
<div class="mt-auto">
|
|
|
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
|
|
|
<span class="text-muted">版本: ${product.latest_version || '1.0.0'}</span>
|
|
|
|
|
<span class="badge ${product.status === 1 ? 'bg-success' : 'bg-secondary'}">
|
|
|
|
|
${product.status === 1 ? '已发布' : '未发布'}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="mt-3">
|
|
|
|
|
<a href="/index/products/${product.product_id}" class="btn btn-primary w-100">
|
|
|
|
|
<i class="fas fa-info-circle me-1"></i>查看详情
|
|
|
|
|
</a>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
container.innerHTML = productsHtml;
|
|
|
|
|
|
|
|
|
|
// 渲染分页
|
|
|
|
|
renderPagination(pagination);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 渲染分页
|
|
|
|
|
function renderPagination(pagination) {
|
|
|
|
|
const paginationContainer = document.getElementById('pagination');
|
|
|
|
|
|
|
|
|
|
if (pagination.total_pages <= 1) {
|
|
|
|
|
paginationContainer.innerHTML = '';
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let paginationHtml = '';
|
|
|
|
|
|
|
|
|
|
// 上一页
|
|
|
|
|
if (pagination.current_page > 1) {
|
|
|
|
|
paginationHtml += `
|
|
|
|
|
<li class="page-item">
|
|
|
|
|
<a class="page-link" href="#" onclick="loadProducts(${pagination.current_page - 1}); return false;">
|
|
|
|
|
<i class="fas fa-chevron-left"></i>
|
|
|
|
|
</a>
|
|
|
|
|
</li>
|
|
|
|
|
`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 页码
|
|
|
|
|
for (let i = Math.max(1, pagination.current_page - 2);
|
|
|
|
|
i <= Math.min(pagination.total_pages, pagination.current_page + 2);
|
|
|
|
|
i++) {
|
|
|
|
|
paginationHtml += `
|
|
|
|
|
<li class="page-item ${i === pagination.current_page ? 'active' : ''}">
|
|
|
|
|
<a class="page-link" href="#" onclick="loadProducts(${i}); return false;">${i}</a>
|
|
|
|
|
</li>
|
|
|
|
|
`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 下一页
|
|
|
|
|
if (pagination.current_page < pagination.total_pages) {
|
|
|
|
|
paginationHtml += `
|
|
|
|
|
<li class="page-item">
|
|
|
|
|
<a class="page-link" href="#" onclick="loadProducts(${pagination.current_page + 1}); return false;">
|
|
|
|
|
<i class="fas fa-chevron-right"></i>
|
|
|
|
|
</a>
|
|
|
|
|
</li>
|
|
|
|
|
`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
paginationContainer.innerHTML = paginationHtml;
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
{% endblock %}
|