第一次提交
This commit is contained in:
255
app/web/templates/user/products.html
Normal file
255
app/web/templates/user/products.html
Normal file
@@ -0,0 +1,255 @@
|
||||
{% 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>
|
||||
<div class="col-md-3">
|
||||
<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 = '';
|
||||
if (!Array.isArray(products) || 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;
|
||||
}
|
||||
products.forEach(product => {
|
||||
// 处理图片路径,如果没有图片则使用默认图片
|
||||
const imagePath = product.image_path || '/static/images/product-default.png';
|
||||
productsHtml += `
|
||||
<div class="col-lg-4 col-md-6 mb-4">
|
||||
<div class="card product-card h-100">
|
||||
<img src="${imagePath}"
|
||||
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 %}
|
||||
Reference in New Issue
Block a user