Files
2026-01-11 18:14:42 +08:00

492 lines
23 KiB
HTML
Raw Permalink 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.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>数据统计 - 商家后台</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css">
<style>
.filter-btn.active {
background-color: #0d6efd;
color: white;
border-color: #0d6efd;
}
</style>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container-fluid">
<a class="navbar-brand" href="/merchant/dashboard.html">商家后台</a>
<div class="navbar-nav ms-auto">
<a class="nav-link" href="/merchant/dashboard.html">首页</a>
<a class="nav-link" href="/merchant/products.html">商品管理</a>
<a class="nav-link" href="/merchant/orders.html">订单管理</a>
<a class="nav-link" href="/merchant/inventory.html">库存管理</a>
<a class="nav-link active" href="/merchant/statistics.html">数据统计</a>
<a class="nav-link" href="/merchant/announcements.html">公告管理</a>
<a class="nav-link" href="/merchant/messages.html">留言管理</a>
<a class="nav-link" href="/merchant/reviews.html">评价管理</a>
<a class="nav-link" href="/merchant/users.html">用户管理</a>
</div>
</div>
</nav>
<div class="container mt-4 mb-5">
<h2 class="mb-4"><i class="bi bi-graph-up"></i> 数据统计</h2>
<div class="mb-4">
<div class="btn-group" role="group">
<button class="btn btn-outline-primary filter-btn" onclick="loadStatistics('day')" id="filter-day">今日</button>
<button class="btn btn-outline-primary filter-btn" onclick="loadStatistics('week')" id="filter-week">本周</button>
<button class="btn btn-outline-primary filter-btn" onclick="loadStatistics('month')" id="filter-month">本月</button>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-4">
<div class="card shadow-sm">
<div class="card-header bg-primary text-white">
<h5 class="mb-0"><i class="bi bi-cart-check"></i> 销售统计</h5>
</div>
<div class="card-body" id="salesStats">
<p class="text-muted">请选择时间段</p>
</div>
</div>
</div>
<div class="col-md-6 mb-4">
<div class="card shadow-sm">
<div class="card-header bg-success text-white">
<h5 class="mb-0"><i class="bi bi-currency-yen"></i> 营收统计</h5>
</div>
<div class="card-body" id="revenueStats">
<p class="text-muted">请选择时间段</p>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-4">
<div class="card shadow-sm">
<div class="card-header bg-info text-white">
<h5 class="mb-0"><i class="bi bi-people"></i> 活跃用户</h5>
</div>
<div class="card-body" id="activeUsersStats">
<p class="text-muted">加载中...</p>
</div>
</div>
</div>
<div class="col-md-6 mb-4">
<div class="card shadow-sm">
<div class="card-header bg-warning text-white">
<h5 class="mb-0"><i class="bi bi-star-fill"></i> 粘性用户</h5>
</div>
<div class="card-body" id="stickyUsersStats">
<p class="text-muted">加载中...</p>
</div>
</div>
</div>
</div>
<div class="card shadow-sm">
<div class="card-header bg-white">
<h5 class="mb-0"><i class="bi bi-fire"></i> 热销商品</h5>
</div>
<div class="card-body">
<div id="hotProducts"></div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="/static/js/sidebar-replacer.js"></script>
<script src="/static/js/ui-enhancements.js"></script>
<script>
// 使用Session浏览器自动发送Cookie
// 检查登录状态
async function checkLogin() {
try {
const response = await fetch('/merchant/profile', {
credentials: 'include'
});
const data = await response.json();
if (data.code !== 200 || !data.data) {
window.location.href = '/merchant/login.html';
return false;
}
return true;
} catch (error) {
console.error('Error:', error);
window.location.href = '/merchant/login.html';
return false;
}
}
let currentPeriod = 'day';
// 页面加载
checkLogin().then(isLoggedIn => {
if (isLoggedIn) {
loadStatistics('day');
loadHotProducts();
loadActiveUsers();
loadStickyUsers();
// 激活"今日"按钮
document.getElementById('filter-day').classList.add('active');
}
});
// 加载统计数据
async function loadStatistics(period) {
currentPeriod = period;
// 更新按钮激活状态
document.querySelectorAll('.filter-btn').forEach(btn => {
btn.classList.remove('active');
});
const activeBtn = document.getElementById('filter-' + period);
if (activeBtn) activeBtn.classList.add('active');
try {
// 加载销售统计
const salesResponse = await fetch(`/merchant/statistics/sales?period=${period}`, {
credentials: 'include'
});
const salesData = await salesResponse.json();
if (salesData.code === 200 && salesData.data) {
const stats = salesData.data;
const totalAmount = parseFloat(stats.totalAmount || 0).toFixed(2);
const avgAmount = parseFloat(stats.avgOrderAmount || 0).toFixed(2);
document.getElementById('salesStats').innerHTML = `
<div class="mb-3">
<div class="d-flex justify-content-between align-items-center mb-2">
<span class="text-muted">总订单数</span>
<strong class="fs-4">${stats.totalOrders || 0}</strong>
</div>
<div class="d-flex justify-content-between align-items-center mb-2">
<span class="text-muted">总销售额</span>
<strong class="fs-4 text-primary">¥${totalAmount}</strong>
</div>
<div class="d-flex justify-content-between align-items-center">
<span class="text-muted">平均订单金额</span>
<strong>¥${avgAmount}</strong>
</div>
</div>
`;
} else {
document.getElementById('salesStats').innerHTML = '<p class="text-muted">加载失败:' + (salesData.message || '未知错误') + '</p>';
}
// 加载营收统计
const revenueResponse = await fetch(`/merchant/statistics/revenue?period=${period}`, {
credentials: 'include'
});
const revenueData = await revenueResponse.json();
if (revenueData.code === 200 && revenueData.data) {
const stats = revenueData.data;
const totalRevenue = parseFloat(stats.totalRevenue || 0).toFixed(2);
const totalCost = parseFloat(stats.totalCost || 0).toFixed(2);
const netProfit = parseFloat(stats.netProfit || 0).toFixed(2);
document.getElementById('revenueStats').innerHTML = `
<div class="mb-3">
<div class="d-flex justify-content-between align-items-center mb-2">
<span class="text-muted">总收入</span>
<strong class="fs-4 text-success">¥${totalRevenue}</strong>
</div>
<div class="d-flex justify-content-between align-items-center mb-2">
<span class="text-muted">总成本</span>
<strong>¥${totalCost}</strong>
</div>
<div class="d-flex justify-content-between align-items-center">
<span class="text-muted">净利润</span>
<strong class="fs-5 text-primary">¥${netProfit}</strong>
</div>
</div>
`;
} else {
document.getElementById('revenueStats').innerHTML = '<p class="text-muted">加载失败:' + (revenueData.message || '未知错误') + '</p>';
}
} catch (error) {
console.error('Error:', error);
document.getElementById('salesStats').innerHTML = '<p class="text-danger">加载失败,请刷新重试</p>';
document.getElementById('revenueStats').innerHTML = '<p class="text-danger">加载失败,请刷新重试</p>';
}
}
// 加载热销商品
async function loadHotProducts() {
try {
const response = await fetch('/merchant/statistics/hot-products?limit=10', {
credentials: 'include'
});
const data = await response.json();
const container = document.getElementById('hotProducts');
if (data.code === 200 && data.data) {
const products = data.data || [];
if (products.length === 0) {
container.innerHTML = '<p class="text-muted">暂无数据</p>';
return;
}
container.innerHTML = `
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead class="table-light">
<tr>
<th>排名</th>
<th>商品名称</th>
<th>销量</th>
<th>销售额</th>
</tr>
</thead>
<tbody>
${products.map((product, index) => `
<tr>
<td>
<span class="badge ${index < 3 ? 'bg-warning' : 'bg-secondary'}">${index + 1}</span>
</td>
<td><strong>${product.productName || '未知商品'}</strong></td>
<td><span class="badge bg-info">${product.salesCount || 0}</span></td>
<td><strong class="text-primary">¥${parseFloat(product.salesAmount || 0).toFixed(2)}</strong></td>
</tr>
`).join('')}
</tbody>
</table>
</div>
`;
} else {
container.innerHTML = '<p class="text-muted">加载失败</p>';
}
} catch (error) {
console.error('Error:', error);
document.getElementById('hotProducts').innerHTML = '<p class="text-muted">加载失败</p>';
}
}
// 加载活跃用户统计
async function loadActiveUsers() {
try {
const response = await fetch('/merchant/statistics/active-users?days=30', {
credentials: 'include'
});
const data = await response.json();
const container = document.getElementById('activeUsersStats');
if (data.code === 200 && data.data) {
const users = data.data || [];
const count = users.length;
container.innerHTML = `
<div class="mb-3">
<div class="d-flex justify-content-between align-items-center mb-2">
<span class="text-muted">活跃用户数</span>
<strong class="fs-4 text-info">${count}</strong>
</div>
<div class="text-muted small mb-2">最近30天有浏览行为的用户</div>
${count > 0 ? `
<button class="btn btn-sm btn-outline-info w-100" onclick="showActiveUsersModal()">
<i class="bi bi-eye"></i> 查看详情
</button>
` : ''}
</div>
`;
} else {
container.innerHTML = '<p class="text-muted">加载失败</p>';
}
} catch (error) {
console.error('Error:', error);
document.getElementById('activeUsersStats').innerHTML = '<p class="text-muted">加载失败</p>';
}
}
// 加载粘性用户统计
async function loadStickyUsers() {
try {
const response = await fetch('/merchant/statistics/sticky-users?days=30&minOrders=5', {
credentials: 'include'
});
const data = await response.json();
const container = document.getElementById('stickyUsersStats');
if (data.code === 200 && data.data) {
const users = data.data || [];
const count = users.length;
const totalAmount = users.reduce((sum, user) => sum + parseFloat(user.totalAmount || 0), 0).toFixed(2);
container.innerHTML = `
<div class="mb-3">
<div class="d-flex justify-content-between align-items-center mb-2">
<span class="text-muted">粘性用户数</span>
<strong class="fs-4 text-warning">${count}</strong>
</div>
<div class="d-flex justify-content-between align-items-center mb-2">
<span class="text-muted">总消费金额</span>
<strong>¥${totalAmount}</strong>
</div>
<div class="text-muted small mb-2">最近30天下单5次以上</div>
${count > 0 ? `
<button class="btn btn-sm btn-outline-warning w-100" onclick="showStickyUsersModal()">
<i class="bi bi-eye"></i> 查看详情
</button>
` : ''}
</div>
`;
} else {
container.innerHTML = '<p class="text-muted">加载失败</p>';
}
} catch (error) {
console.error('Error:', error);
document.getElementById('stickyUsersStats').innerHTML = '<p class="text-muted">加载失败</p>';
}
}
// 显示活跃用户列表模态框
async function showActiveUsersModal() {
try {
const response = await fetch('/merchant/statistics/active-users?days=30', {
credentials: 'include'
});
const data = await response.json();
if (data.code === 200 && data.data) {
const users = data.data || [];
let modalContent = `
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>用户名</th>
<th>姓名</th>
<th>浏览次数</th>
<th>最后浏览时间</th>
</tr>
</thead>
<tbody>
`;
users.forEach(user => {
const lastViewTime = user.lastViewTime ? new Date(user.lastViewTime).toLocaleString('zh-CN') : '-';
modalContent += `
<tr>
<td>${user.username || '-'}</td>
<td>${user.name || '-'}</td>
<td><span class="badge bg-info">${user.viewCount || 0}</span></td>
<td>${lastViewTime}</td>
</tr>
`;
});
modalContent += `
</tbody>
</table>
</div>
`;
showModal('活跃用户列表', modalContent);
}
} catch (error) {
console.error('Error:', error);
alert('加载活跃用户列表失败');
}
}
// 显示粘性用户列表模态框
async function showStickyUsersModal() {
try {
const response = await fetch('/merchant/statistics/sticky-users?days=30&minOrders=5', {
credentials: 'include'
});
const data = await response.json();
if (data.code === 200 && data.data) {
const users = data.data || [];
let modalContent = `
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>用户名</th>
<th>姓名</th>
<th>订单数</th>
<th>总消费金额</th>
</tr>
</thead>
<tbody>
`;
users.forEach(user => {
const totalAmount = parseFloat(user.totalAmount || 0).toFixed(2);
modalContent += `
<tr>
<td>${user.username || '-'}</td>
<td>${user.name || '-'}</td>
<td><span class="badge bg-warning">${user.orderCount || 0}</span></td>
<td><strong class="text-primary">¥${totalAmount}</strong></td>
</tr>
`;
});
modalContent += `
</tbody>
</table>
</div>
`;
showModal('粘性用户列表', modalContent);
}
} catch (error) {
console.error('Error:', error);
alert('加载粘性用户列表失败');
}
}
// 显示模态框的通用函数
function showModal(title, content) {
const modalHtml = `
<div class="modal fade" id="userListModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">${title}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
${content}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
</div>
</div>
</div>
</div>
`;
// 移除旧的模态框
const oldModal = document.getElementById('userListModal');
if (oldModal) {
oldModal.remove();
}
// 添加新的模态框
document.body.insertAdjacentHTML('beforeend', modalHtml);
const modal = new bootstrap.Modal(document.getElementById('userListModal'));
modal.show();
// 模态框关闭后移除
document.getElementById('userListModal').addEventListener('hidden.bs.modal', function() {
this.remove();
});
}
</script>
</body>
</html>