You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

2158 lines
81 KiB

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>管理货源页面</title>
<script src="js/chart.js"></script>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
margin: 0;
padding: 0;
background-color: #f5f5f5;
color: #333;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.title-bar {
background-color: #1677ff;
color: white;
padding: 15px 20px;
border-radius: 8px;
margin-bottom: 20px;
display: flex;
justify-content: space-between;
align-items: center;
}
.title-bar h1 {
margin: 0;
font-size: 24px;
font-weight: 500;
}
.chart-section {
background-color: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
margin-bottom: 20px;
}
.chart-container {
position: relative;
height: 400px;
margin-bottom: 20px;
}
.controls {
display: flex;
gap: 10px;
margin-bottom: 20px;
flex-wrap: wrap;
}
.controls button {
padding: 8px 16px;
background-color: #1677ff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
.controls button:hover {
background-color: #4096ff;
}
.controls button.active {
background-color: #0958d9;
}
.stats-info {
background-color: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
margin-bottom: 20px;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
}
.stat-card {
background-color: #f0f5ff;
padding: 20px;
border-radius: 8px;
text-align: center;
}
.stat-number {
font-size: 32px;
font-weight: bold;
color: #1677ff;
}
.stat-label {
font-size: 14px;
color: #666;
margin-top: 5px;
}
.supplies-section {
background-color: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.supplies-section h3 {
margin-top: 0;
margin-bottom: 20px;
}
.supplies-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(min(100%, 350px), 1fr));
gap: 20px;
}
.supply-card {
border: 1px solid #e8e8e8;
border-radius: 8px;
padding: 15px;
transition: all 0.3s;
}
.supply-card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
transform: translateY(-2px);
}
.supply-card img,
.supply-card video {
max-width: 100%;
height: 180px;
object-fit: contain;
border-radius: 4px;
margin-bottom: 10px;
background-color: #f5f5f5;
}
/* 响应式设计:调整小屏幕设备上的样式 */
@media (max-width: 768px) {
.container {
padding: 10px;
}
.title-bar {
padding: 12px 15px;
flex-direction: column;
align-items: flex-start;
gap: 10px;
}
.title-bar h1 {
font-size: 20px;
}
.title-bar-actions {
flex-wrap: wrap;
gap: 6px;
width: 100%;
}
.title-bar-actions button {
padding: 6px 12px;
font-size: 13px;
}
.title-bar-actions input[type="date"] {
padding: 4px 8px;
font-size: 13px;
}
.chart-container {
height: 300px;
}
.stats-grid {
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 15px;
}
.stat-number {
font-size: 28px;
}
.stat-label {
font-size: 13px;
}
.supplies-grid {
gap: 15px;
}
.supply-card {
padding: 12px;
}
.supply-card img,
.supply-card video {
height: 150px;
}
.supply-title {
font-size: 15px;
margin-bottom: 8px;
}
.supply-info {
font-size: 13px;
}
}
@media (max-width: 480px) {
.title-bar h1 {
font-size: 18px;
}
.title-bar-actions {
gap: 4px;
}
.title-bar-actions button {
padding: 5px 10px;
font-size: 12px;
}
.title-bar-actions input[type="date"] {
padding: 3px 6px;
font-size: 12px;
}
.chart-container {
height: 250px;
}
.stats-grid {
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
gap: 12px;
}
.stat-number {
font-size: 24px;
}
.stat-label {
font-size: 12px;
}
.supplies-grid {
grid-template-columns: 1fr;
gap: 12px;
}
.supply-card img,
.supply-card video {
height: 140px;
}
.supply-title {
font-size: 14px;
}
.supply-info {
font-size: 12px;
}
}
.supply-info {
font-size: 14px;
}
.supply-info p {
margin: 5px 0;
}
.supply-title {
font-weight: bold;
font-size: 16px;
margin-bottom: 10px;
}
.supply-status {
display: inline-block;
padding: 2px 8px;
border-radius: 10px;
font-size: 12px;
margin-top: 5px;
}
.status-pending_review { background-color: #fff2cc; color: #d68910; }
.status-published { background-color: #d4edda; color: #155724; }
.status-sold_out { background-color: #f8d7da; color: #721c24; }
.loading {
text-align: center;
padding: 40px;
color: #666;
}
/* 标题栏操作按钮样式 */
.title-bar-actions {
display: flex;
gap: 8px;
align-items: center;
}
.title-bar-actions button {
padding: 8px 16px;
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 20px;
background-color: rgba(255, 255, 255, 0.1);
color: white;
cursor: pointer;
font-size: 14px;
transition: all 0.3s ease;
font-weight: 500;
}
.title-bar-actions button:hover {
background-color: rgba(255, 255, 255, 0.2);
border-color: rgba(255, 255, 255, 0.5);
transform: translateY(-1px);
}
.title-bar-actions button.active {
background-color: white;
color: #1677ff;
border-color: white;
box-shadow: 0 2px 8px rgba(22, 119, 255, 0.3);
}
/* 日期输入框样式 */
.title-bar-actions input[type="date"] {
padding: 6px 12px;
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 20px;
background-color: rgba(255, 255, 255, 0.1);
color: white;
font-size: 14px;
transition: all 0.3s ease;
}
.title-bar-actions input[type="date"]::-webkit-calendar-picker-indicator {
filter: invert(1);
}
.title-bar-actions input[type="date"]:hover {
background-color: rgba(255, 255, 255, 0.2);
border-color: rgba(255, 255, 255, 0.5);
}
/* 自定义按钮样式 */
#customBtn {
background-color: rgba(255, 255, 255, 0.2);
border-color: rgba(255, 255, 255, 0.4);
}
#customBtn:hover {
background-color: rgba(255, 255, 255, 0.3);
border-color: rgba(255, 255, 255, 0.6);
}
</style>
<!-- 图片预览样式 -->
<style>
/* 图片预览模态框 */
.preview-modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.9);
z-index: 1000;
justify-content: center;
align-items: center;
}
.preview-modal.active {
display: flex;
}
.preview-content {
max-width: 90%;
max-height: 90%;
position: relative;
}
.preview-content img,
.preview-content video {
max-width: 100%;
max-height: 80vh;
object-fit: contain;
border-radius: 4px;
transition: transform 0.2s ease;
}
/* 可缩放的图片容器 */
.preview-media {
position: relative;
display: flex;
justify-content: center;
align-items: center;
max-width: 100%;
max-height: 80vh;
overflow: visible;
}
/* 可缩放的图片样式 */
.preview-media img {
cursor: grab;
transform-origin: center center;
user-select: none;
}
.preview-media img:active {
cursor: grabbing;
}
/* 缩放控制按钮 */
.zoom-controls {
position: absolute;
bottom: -40px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 10px;
background-color: rgba(0, 0, 0, 0.5);
padding: 8px 16px;
border-radius: 20px;
}
.zoom-btn {
background-color: rgba(255, 255, 255, 0.2);
color: white;
border: none;
border-radius: 50%;
width: 30px;
height: 30px;
font-size: 16px;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
transition: all 0.3s ease;
}
.zoom-btn:hover {
background-color: rgba(255, 255, 255, 0.4);
}
.zoom-value {
color: white;
font-size: 14px;
align-self: center;
min-width: 50px;
text-align: center;
}
/* 关闭按钮 */
.close-preview {
position: fixed;
top: 20px;
right: 20px;
background-color: rgba(0, 0, 0, 0.5);
color: white;
border: none;
font-size: 24px;
cursor: pointer;
width: 36px;
height: 36px;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
transition: all 0.3s ease;
z-index: 1001; /* 确保关闭按钮在最上层 */
}
.close-preview:hover {
background-color: rgba(0, 0, 0, 0.8);
transform: scale(1.1);
}
/* 点击图片时的光标样式 */
.supply-card img,
.supply-card video {
cursor: pointer;
}
/* 回到顶部按钮样式 */
.back-to-top {
position: fixed;
bottom: 30px;
right: 30px;
width: 40px;
height: 40px;
border-radius: 50%;
background-color: rgba(153, 153, 153, 0.7); /* 浅灰色透明背景 */
color: #333;
border: none;
cursor: pointer;
z-index: 1000;
transition: all 0.3s ease;
display: flex;
justify-content: center;
align-items: center;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.back-to-top-content {
display: flex;
align-items: center;
justify-content: center;
text-align: center;
}
.back-to-top-text {
font-size: 12px;
font-weight: bold;
line-height: 1;
}
.back-to-top:hover {
background-color: rgba(102, 102, 102, 0.9); /* 加深颜色 */
transform: translateY(-3px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}
.back-to-top:active {
transform: translateY(0);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
/* 货源卡片优化样式 */
.supply-media {
width: 100%;
height: 250px;
object-fit: contain;
border-radius: 4px;
margin-bottom: 10px;
cursor: pointer;
background-color: #f5f5f5;
}
.supply-card {
border: 1px solid #e8e8e8;
border-radius: 8px;
padding: 15px;
transition: all 0.3s ease;
width: 100%;
box-sizing: border-box;
}
.supply-card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
transform: translateY(-2px);
}
.supply-header {
margin-bottom: 15px;
}
.supply-title {
font-weight: bold;
font-size: 16px;
margin: 10px 0;
}
.supply-meta {
margin: 5px 0 10px 0;
font-size: 14px;
line-height: 1.4;
color: #666;
font-weight: 500;
}
.supply-info {
font-size: 14px;
}
.supply-info p {
margin: 5px 0;
}
.spec-section {
margin: 10px 0;
}
.spec-item {
background-color: #f5f5f5;
padding: 8px 12px;
border-radius: 4px;
margin: 5px 0;
font-size: 13px;
display: block;
word-break: break-all;
}
.description-section {
margin: 10px 0;
font-size: 14px;
background-color: #f5f5f5;
padding: 10px 12px;
border-radius: 4px;
}
.description-section strong {
display: block;
margin-bottom: 5px;
}
.log-section {
margin: 10px 0;
}
.log-section h4 {
margin: 0 0 10px 0;
font-size: 14px;
font-weight: bold;
color: #333;
padding-bottom: 5px;
border-bottom: 2px solid #ff4d4f;
}
.log-item {
margin: 8px 0;
padding: 8px 12px;
background-color: #f9f0f0;
border-radius: 4px;
font-size: 13px;
line-height: 1.4;
}
.log-badge {
display: inline-block;
background-color: #ff4d4f;
color: white;
font-size: 10px;
font-weight: bold;
padding: 2px 6px;
border-radius: 3px;
margin-right: 8px;
}
.supply-status {
display: inline-block;
padding: 2px 8px;
border-radius: 10px;
font-size: 12px;
margin-top: 5px;
}
.empty-state {
text-align: center;
color: #666;
grid-column: 1 / -1;
}
</style>
</head>
<body>
<!-- 图片预览模态框 -->
<div id="previewModal" class="preview-modal">
<div class="preview-content">
<button id="closePreview" class="close-preview">&times;</button>
<div id="previewMedia"></div>
</div>
</div>
<!-- 回到顶部按钮 -->
<button id="backToTop" class="back-to-top" onclick="scrollToSuppliesSection()" style="display: none;">
<div class="back-to-top-content">
<span class="back-to-top-text">Top</span>
</div>
</button>
<div class="container">
<div class="title-bar">
<div style="display: flex; align-items: center; gap: 15px;">
<button id="backBtn" style="background-color: rgba(255, 255, 255, 0.2); color: white; border: none; border-radius: 4px; padding: 8px 16px; cursor: pointer; font-size: 14px;">返回</button>
<h1 style="margin: 0;">管理货源页面</h1>
</div>
<div class="title-bar-actions">
<button id="todayBtn" class="active">今日</button>
<button id="yesterdayBtn">昨天</button>
<button id="beforeYesterdayBtn">前天</button>
<button id="weekBtn">本周</button>
<button id="monthBtn">本月</button>
<button id="lastMonthBtn">上个月</button>
<button id="allBtn">全部</button>
<div style="display: inline-flex; align-items: center; gap: 5px; margin-left: 10px;">
<input type="date" id="startDate" style="padding: 6px; border: 1px solid #ccc; border-radius: 4px; font-size: 14px;">
<span style="color: white;">-</span>
<input type="date" id="endDate" style="padding: 6px; border: 1px solid #ccc; border-radius: 4px; font-size: 14px;">
<button id="customBtn" style="padding: 6px 12px; background-color: rgba(255, 255, 255, 0.2); color: white; border: 1px solid white; border-radius: 4px; cursor: pointer; font-size: 14px;">自定义</button>
</div>
</div>
</div>
<div class="stats-info">
<div class="stats-grid">
<div class="stat-card">
<div class="stat-number" id="totalSupplies">0</div>
<div class="stat-label">总货源数</div>
</div>
<div class="stat-card">
<div class="stat-number" id="totalUsers">0</div>
<div class="stat-label">活跃用户数</div>
</div>
<div class="stat-card">
<div class="stat-number" id="avgPerUser">0</div>
<div class="stat-label">人均创建数</div>
</div>
</div>
</div>
<div class="chart-section">
<h3>用户创建货源统计</h3>
<div class="chart-container">
<canvas id="suppliesChart"></canvas>
</div>
</div>
<div class="supplies-section" id="suppliesSection" style="display: block;">
<h3 id="suppliesTitle">用户货源详情</h3>
<div class="supplies-grid" id="suppliesGrid">
<!-- 货源卡片将动态生成 -->
</div>
</div>
</div>
<script>
// 全局变量
let currentChart = null;
let currentFilter = 'today';
let suppliesData = [];
let usersData = [];
let chartData = [];
// 防抖动函数
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// 防重复点击标志
let isFiltering = false;
// 货源详情相关变量
let currentSellerId = null; // 当前正在显示的卖家ID
// 分页相关变量
let currentPage = 1;
const pageSize = 20;
let totalSupplies = 0;
let hasMoreData = true;
let isLoading = false;
// WebSocket相关变量
let ws = null;
let wsReconnectAttempts = 0;
const maxReconnectAttempts = 10; // 增加最大重连次数
const reconnectDelay = 5000; // 增加重连延迟,避免频繁重连
let wsUrl = '';
let wsConnecting = false; // 防止重复连接
// 缓存相关变量
const CACHE_KEY = 'management_stats_cache';
const CACHE_EXPIRY = 5 * 60 * 1000; // 缓存5分钟
const MAX_CACHE_SIZE = 50; // 最大缓存条目数
// 定时刷新相关
const REFRESH_INTERVAL = 30 * 1000; // 每30秒自动刷新一次
// DOM元素
const backBtn = document.getElementById('backBtn');
const todayBtn = document.getElementById('todayBtn');
const yesterdayBtn = document.getElementById('yesterdayBtn');
const beforeYesterdayBtn = document.getElementById('beforeYesterdayBtn');
const weekBtn = document.getElementById('weekBtn');
const monthBtn = document.getElementById('monthBtn');
const lastMonthBtn = document.getElementById('lastMonthBtn');
const allBtn = document.getElementById('allBtn');
const startDateInput = document.getElementById('startDate');
const endDateInput = document.getElementById('endDate');
const customBtn = document.getElementById('customBtn');
const totalSuppliesEl = document.getElementById('totalSupplies');
const totalUsersEl = document.getElementById('totalUsers');
const avgPerUserEl = document.getElementById('avgPerUser');
const suppliesChartEl = document.getElementById('suppliesChart');
const suppliesSection = document.getElementById('suppliesSection');
const suppliesTitle = document.getElementById('suppliesTitle');
const suppliesGrid = document.getElementById('suppliesGrid');
// 图片预览相关DOM元素
const previewModal = document.getElementById('previewModal');
const closePreview = document.getElementById('closePreview');
const previewMedia = document.getElementById('previewMedia');
// 初始化
document.addEventListener('DOMContentLoaded', function() {
// 检查登录状态和角色
checkLoginAndRole();
initEventListeners();
initWebSocket();
loadStats(currentFilter);
// 设置定时刷新
setInterval(() => {
if (currentFilter === 'custom') {
// 如果是自定义筛选,获取当前日期输入值
const startDate = startDateInput.value;
const endDate = endDateInput.value;
loadStats('custom', startDate, endDate);
} else {
loadStats(currentFilter);
}
}, REFRESH_INTERVAL);
});
// 页面卸载时清理资源
window.addEventListener('beforeunload', () => {
// 关闭WebSocket连接
if (ws && ws.readyState === WebSocket.OPEN) {
try {
ws.close(1000, '页面关闭');
} catch (error) {
console.error('关闭WebSocket连接失败:', error);
}
}
});
// 初始化WebSocket连接
function initWebSocket() {
// 防止重复连接
if (wsConnecting) {
console.log('WebSocket连接正在建立中,请勿重复调用');
return;
}
// 检查是否已经有活跃连接
if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) {
console.log('WebSocket连接已存在或正在建立中,无需重新连接');
return;
}
try {
wsConnecting = true;
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
wsUrl = `${protocol}//${window.location.host}`;
console.log('正在尝试连接WebSocket服务器:', wsUrl);
// 关闭现有的WebSocket连接,避免连接泄漏
if (ws) {
try {
ws.close(1000, '重新连接');
} catch (error) {
console.error('关闭旧WebSocket连接失败:', error);
} finally {
ws = null;
}
}
// 创建新的WebSocket连接
ws = new WebSocket(wsUrl);
// 设置WebSocket事件监听器
ws.onopen = function(event) {
console.log('WebSocket连接已打开');
wsReconnectAttempts = 0; // 重置重连计数器
wsConnecting = false;
};
ws.onmessage = function(event) {
console.log('收到WebSocket消息:', event.data);
try {
handleWebSocketMessage(event.data);
} catch (error) {
console.error('处理WebSocket消息失败:', error);
}
};
ws.onclose = function(event) {
console.log('WebSocket连接已关闭:', event.code, event.reason);
wsConnecting = false;
ws = null;
// 尝试重新连接,最多重试maxReconnectAttempts次
if (wsReconnectAttempts < maxReconnectAttempts) {
wsReconnectAttempts++;
console.log(`尝试重新连接WebSocket (${wsReconnectAttempts}/${maxReconnectAttempts})...`);
setTimeout(initWebSocket, reconnectDelay);
} else {
console.error('WebSocket重连失败,已达到最大重试次数');
}
};
ws.onerror = function(error) {
console.error('WebSocket错误:', error);
console.error('WebSocket错误详情:', error.message);
wsConnecting = false;
};
} catch (error) {
console.error('WebSocket连接失败:', error);
wsConnecting = false;
ws = null;
// 尝试重新连接
if (wsReconnectAttempts < maxReconnectAttempts) {
wsReconnectAttempts++;
console.log(`尝试重新连接WebSocket (${wsReconnectAttempts}/${maxReconnectAttempts})...`);
setTimeout(initWebSocket, reconnectDelay);
} else {
console.error('WebSocket重连失败,已达到最大重试次数');
}
}
}
// 处理WebSocket消息
function handleWebSocketMessage(message) {
try {
const data = JSON.parse(message);
console.log('处理WebSocket消息:', data);
// 只有当消息类型与货源相关时,才重新加载数据
if (data.type && (data.type === 'supply_created' || data.type === 'supply_updated' || data.type === 'supply_deleted' || data.type === 'supply_status_change')) {
console.log('收到货源相关WebSocket消息,刷新数据:', data.type);
// 保存当前滚动位置
const scrollPosition = window.pageYOffset || document.documentElement.scrollTop;
// 重新加载统计数据
if (currentFilter === 'custom') {
// 如果是自定义筛选,获取当前日期输入值
const startDate = startDateInput.value;
const endDate = endDateInput.value;
loadStats('custom', startDate, endDate);
} else {
loadStats(currentFilter);
}
// 如果正在显示货源详情,重新加载货源数据
if (currentSellerId) {
console.log('正在显示货源详情,重新加载数据');
showSuppliesBySeller(currentSellerId);
}
// 恢复滚动位置
window.scrollTo(0, scrollPosition);
} else {
console.warn('收到未知格式的WebSocket消息,不刷新数据:', data.type);
}
} catch (error) {
console.error('解析WebSocket消息失败:', error);
}
}
// 向WebSocket服务器发送消息
function sendWebSocketMessage(message) {
if (ws && ws.readyState === WebSocket.OPEN) {
try {
ws.send(typeof message === 'string' ? message : JSON.stringify(message));
} catch (error) {
console.error('发送WebSocket消息失败:', error);
}
} else {
console.warn('WebSocket未连接,无法发送消息');
}
}
// 检查登录状态和角色
function checkLoginAndRole() {
const userInfoStr = localStorage.getItem('userInfo');
const token = localStorage.getItem('token');
if (!userInfoStr || !token) {
// 未登录,跳转到登录页面
window.location.href = 'login.html';
return;
}
try {
const userInfo = JSON.parse(userInfoStr);
// 检查是否为管理员角色
if (userInfo.projectName !== '管理员') {
// 不是管理员,根据角色跳转到相应页面
if (userInfo.projectName === '采购员') {
window.location.href = 'supply.html';
} else {
window.location.href = 'SupplierReview.html';
}
return;
}
} catch (e) {
console.error('解析用户信息失败:', e);
// 解析失败,跳转到登录页面
window.location.href = 'login.html';
return;
}
}
// 缩放相关变量
let currentScale = 1;
let minScale = 0.5;
let maxScale = 5;
let isDragging = false;
let startX = 0;
let startY = 0;
let translateX = 0;
let translateY = 0;
let currentMedia = null;
// 显示媒体预览
function showPreview(mediaUrl) {
// 重置缩放相关变量
currentScale = 1;
translateX = 0;
translateY = 0;
isDragging = false;
currentMedia = null;
// 清空预览内容
previewMedia.innerHTML = '';
// 检查是图片还是视频
const isVideo = mediaUrl.startsWith('data:video/') || mediaUrl.match(/\.(mp4|mov|avi|wmv|flv|webm|mkv)$/i);
if (isVideo) {
// 视频预览
const video = document.createElement('video');
video.src = mediaUrl;
video.controls = true;
previewMedia.appendChild(video);
currentMedia = video;
} else {
// 图片预览
const img = document.createElement('img');
img.src = mediaUrl;
previewMedia.appendChild(img);
currentMedia = img;
// 添加缩放控制按钮
const zoomControls = document.createElement('div');
zoomControls.className = 'zoom-controls';
zoomControls.innerHTML = `
<button class="zoom-btn" id="zoomOutBtn" title="缩小">−</button>
<span class="zoom-value" id="zoomValue">100%</span>
<button class="zoom-btn" id="zoomInBtn" title="放大">+</button>
<button class="zoom-btn" id="resetZoomBtn" title="重置">⟲</button>
`;
previewMedia.appendChild(zoomControls);
// 等待图片加载完成后添加事件监听器
img.onload = function() {
addZoomEvents(img);
addDragEvents(img);
setupZoomControls();
};
}
// 显示预览模态框
previewModal.classList.add('active');
}
// 添加缩放事件
function addZoomEvents(img) {
// 鼠标滚轮缩放
img.addEventListener('wheel', function(e) {
e.preventDefault();
// 计算缩放比例
const delta = e.deltaY > 0 ? -0.1 : 0.1;
const newScale = Math.max(minScale, Math.min(maxScale, currentScale + delta));
// 计算鼠标在图片上的相对位置
const rect = img.getBoundingClientRect();
const mouseX = e.clientX - rect.left;
const mouseY = e.clientY - rect.top;
// 计算缩放中心点
const centerX = mouseX / rect.width;
const centerY = mouseY / rect.height;
// 计算新的偏移量,使缩放中心点保持在鼠标位置
const oldScale = currentScale;
currentScale = newScale;
translateX += (mouseX - rect.width * centerX) * (1 - currentScale / oldScale);
translateY += (mouseY - rect.height * centerY) * (1 - currentScale / oldScale);
// 应用变换
applyTransform(img);
updateZoomValue();
});
}
// 添加拖拽事件
function addDragEvents(img) {
// 鼠标按下开始拖拽
img.addEventListener('mousedown', function(e) {
if (currentScale === 1) return; // 只有在缩放后才能拖拽
isDragging = true;
startX = e.clientX - translateX;
startY = e.clientY - translateY;
});
// 鼠标移动时拖拽
document.addEventListener('mousemove', function(e) {
if (!isDragging) return;
translateX = e.clientX - startX;
translateY = e.clientY - startY;
applyTransform(img);
});
// 鼠标释放结束拖拽
document.addEventListener('mouseup', function() {
isDragging = false;
});
// 鼠标离开窗口结束拖拽
document.addEventListener('mouseleave', function() {
isDragging = false;
});
}
// 设置缩放控制按钮
function setupZoomControls() {
const zoomInBtn = document.getElementById('zoomInBtn');
const zoomOutBtn = document.getElementById('zoomOutBtn');
const resetZoomBtn = document.getElementById('resetZoomBtn');
// 放大按钮
zoomInBtn.addEventListener('click', function() {
if (currentScale < maxScale) {
currentScale += 0.1;
applyTransform(currentMedia);
updateZoomValue();
}
});
// 缩小按钮
zoomOutBtn.addEventListener('click', function() {
if (currentScale > minScale) {
currentScale -= 0.1;
applyTransform(currentMedia);
updateZoomValue();
}
});
// 重置按钮
resetZoomBtn.addEventListener('click', function() {
currentScale = 1;
translateX = 0;
translateY = 0;
applyTransform(currentMedia);
updateZoomValue();
});
}
// 应用变换
function applyTransform(media) {
if (!media || media.tagName === 'VIDEO') return;
media.style.transform = `translate(${translateX}px, ${translateY}px) scale(${currentScale})`;
}
// 更新缩放值显示
function updateZoomValue() {
const zoomValue = document.getElementById('zoomValue');
if (zoomValue) {
zoomValue.textContent = `${Math.round(currentScale * 100)}%`;
}
}
// 关闭媒体预览
function closePreviewModal() {
previewModal.classList.remove('active');
// 清空预览内容,释放资源
previewMedia.innerHTML = '';
currentMedia = null;
}
// 初始化事件监听器
function initEventListeners() {
// 返回按钮点击事件
backBtn.addEventListener('click', () => {
window.location.href = 'SupplierReview.html';
});
// 时间筛选按钮
todayBtn.addEventListener('click', () => setFilter('today'));
yesterdayBtn.addEventListener('click', () => setFilter('yesterday'));
beforeYesterdayBtn.addEventListener('click', () => setFilter('beforeYesterday'));
weekBtn.addEventListener('click', () => setFilter('week'));
monthBtn.addEventListener('click', () => setFilter('month'));
lastMonthBtn.addEventListener('click', () => setFilter('lastMonth'));
allBtn.addEventListener('click', () => setFilter('all'));
// 自定义时间筛选按钮
customBtn.addEventListener('click', applyCustomFilter);
// 日期输入框改变时,自动清除预设筛选的active状态
startDateInput.addEventListener('change', () => {
if (startDateInput.value || endDateInput.value) {
[todayBtn, yesterdayBtn, beforeYesterdayBtn, weekBtn, monthBtn, lastMonthBtn, allBtn].forEach(btn => btn.classList.remove('active'));
}
});
endDateInput.addEventListener('change', () => {
if (startDateInput.value || endDateInput.value) {
[todayBtn, yesterdayBtn, beforeYesterdayBtn, weekBtn, monthBtn, lastMonthBtn, allBtn].forEach(btn => btn.classList.remove('active'));
}
});
// 图片预览相关事件
closePreview.addEventListener('click', closePreviewModal);
// 点击模态框背景关闭预览
previewModal.addEventListener('click', (e) => {
if (e.target === previewModal) {
closePreviewModal();
}
});
// 按ESC键关闭预览
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && previewModal.classList.contains('active')) {
closePreviewModal();
}
});
// 为预览模态框添加鼠标滚轮事件监听器,阻止页面滚动
previewModal.addEventListener('wheel', (e) => {
// 只在模态框激活时阻止滚动
if (previewModal.classList.contains('active')) {
e.preventDefault();
e.stopPropagation();
}
});
// 滚动事件,用于显示/隐藏回到顶部按钮
window.addEventListener('scroll', () => {
const backToTopBtn = document.getElementById('backToTop');
// 当滚动距离超过300px时显示按钮
if (window.pageYOffset > 300) {
backToTopBtn.style.display = 'flex';
} else {
backToTopBtn.style.display = 'none';
}
});
}
// 回到supplies-section顶部
function scrollToSuppliesSection() {
const suppliesSection = document.getElementById('suppliesSection');
suppliesSection.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
// 设置筛选条件
function setFilter(filter) {
// 防重复点击检查
if (isFiltering) return;
isFiltering = true;
currentFilter = filter;
// 重置当前卖家ID,确保切换时间筛选时显示所有货源
currentSellerId = null;
// 重置分页参数
currentPage = 1;
hasMoreData = true;
suppliesData = [];
// 更新按钮状态
[todayBtn, yesterdayBtn, beforeYesterdayBtn, weekBtn, monthBtn, lastMonthBtn, allBtn].forEach(btn => btn.classList.remove('active'));
document.getElementById(filter + 'Btn').classList.add('active');
// 清空自定义日期输入
startDateInput.value = '';
endDateInput.value = '';
// 重新加载数据
loadStats(filter).finally(() => {
isFiltering = false;
});
}
// 应用自定义时间筛选
function applyCustomFilter() {
// 防重复点击检查
if (isFiltering) return;
isFiltering = true;
const startDate = startDateInput.value;
const endDate = endDateInput.value;
if (!startDate && !endDate) {
// 没有选择日期,默认显示全部
setFilter('all');
return;
}
// 更新当前筛选条件为自定义
currentFilter = 'custom';
// 重置当前卖家ID,确保切换时间筛选时显示所有货源
currentSellerId = null;
// 重置分页参数
currentPage = 1;
hasMoreData = true;
suppliesData = [];
// 清除预设筛选的active状态
[todayBtn, yesterdayBtn, beforeYesterdayBtn, weekBtn, monthBtn, lastMonthBtn, allBtn].forEach(btn => btn.classList.remove('active'));
// 重新加载数据,传递自定义日期范围
loadStats('custom', startDate, endDate).finally(() => {
isFiltering = false;
});
}
// 从缓存中获取数据
function getCachedData(filter) {
try {
const cached = localStorage.getItem(CACHE_KEY);
if (cached) {
const cache = JSON.parse(cached);
if (cache[filter] && (Date.now() - cache[filter].timestamp < CACHE_EXPIRY)) {
console.log('使用缓存数据:', filter);
return cache[filter].data;
}
}
} catch (error) {
console.error('读取缓存失败:', error);
}
return null;
}
// 保存数据到缓存
function saveCachedData(filter, data) {
try {
const cached = localStorage.getItem(CACHE_KEY);
let cache = cached ? JSON.parse(cached) : {};
// 添加新缓存
cache[filter] = {
data: data,
timestamp: Date.now()
};
// 管理缓存大小,移除最旧的缓存条目
const cacheEntries = Object.entries(cache);
if (cacheEntries.length > MAX_CACHE_SIZE) {
// 按时间戳排序,移除最旧的
cacheEntries.sort((a, b) => a[1].timestamp - b[1].timestamp);
const entriesToRemove = cacheEntries.slice(0, cacheEntries.length - MAX_CACHE_SIZE);
entriesToRemove.forEach(([key]) => {
delete cache[key];
});
}
localStorage.setItem(CACHE_KEY, JSON.stringify(cache));
console.log('保存数据到缓存:', filter);
} catch (error) {
console.error('保存缓存失败:', error);
}
}
// 加载统计数据
async function loadStats(filter, startDate = '', endDate = '') {
try {
// 先尝试从缓存获取数据,提高响应速度
const cacheKey = filter === 'custom' ? `custom_${startDate}_${endDate}` : filter;
const cachedData = getCachedData(cacheKey);
if (cachedData) {
// 使用缓存数据更新UI,提高响应速度
updateStatsInfo(cachedData.stats);
renderChart(cachedData.chartData);
chartData = cachedData.chartData;
suppliesData = cachedData.suppliesData;
usersData = cachedData.usersData;
// 只有在当前没有显示特定卖家的货源时才显示当天所有货源
if (!currentSellerId) {
showAllSupplies();
}
}
// 构建API请求URL,包含自定义日期参数
let url = `/api/admin/stats/supplies?filter=${filter}`;
if (filter === 'custom') {
if (startDate) url += `&startDate=${startDate}`;
if (endDate) url += `&endDate=${endDate}`;
}
const response = await fetch(url, {
method: 'GET',
timeout: 10000,
headers: {
'Content-Type': 'application/json'
}
});
if (!response.ok) {
throw new Error(`HTTP错误! 状态: ${response.status}`);
}
const data = await response.json();
if (data.success) {
// 更新统计信息
updateStatsInfo(data.data.stats);
// 渲染图表并保存chartData
renderChart(data.data.chartData);
chartData = data.data.chartData;
// 保存到全局变量
suppliesData = data.data.suppliesData;
usersData = data.data.usersData;
// 保存到缓存,自定义日期使用特殊的缓存键
saveCachedData(cacheKey, data.data);
// 只有在当前没有显示特定卖家的货源时才显示当天所有货源
if (!currentSellerId) {
showAllSupplies();
}
} else {
console.error('加载统计数据失败:', data.message);
// 已经尝试过使用缓存数据,这里可以不再处理
}
} catch (error) {
console.error('加载统计数据出错:', error);
// 尝试使用缓存数据
const cacheKey = filter === 'custom' ? `custom_${startDate}_${endDate}` : filter;
const cachedData = getCachedData(cacheKey);
if (cachedData) {
updateStatsInfo(cachedData.stats);
renderChart(cachedData.chartData);
chartData = cachedData.chartData;
suppliesData = cachedData.suppliesData;
usersData = cachedData.usersData;
// 只有在当前没有显示特定卖家的货源时才显示当天所有货源
if (!currentSellerId) {
showAllSupplies();
}
}
}
}
// 更新统计信息卡片
function updateStatsInfo(stats) {
totalSuppliesEl.textContent = stats.totalSupplies;
totalUsersEl.textContent = stats.totalUsers;
avgPerUserEl.textContent = Math.round(stats.avgPerUser);
}
// 渲染图表
function renderChart(chartData) {
// 销毁现有图表
if (currentChart) {
currentChart.destroy();
}
// 准备图表数据
const labels = chartData.map(item => item.nickName || item.sellerId);
const counts = chartData.map(item => item.count);
// 创建新图表
currentChart = new Chart(suppliesChartEl, {
type: 'bar',
data: {
labels: labels,
datasets: [{
label: '创建货源数量',
data: counts,
backgroundColor: 'rgba(22, 119, 255, 0.6)',
borderColor: 'rgba(22, 119, 255, 1)',
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
ticks: {
stepSize: 1
},
// 动态计算Y轴最大值,为顶部数字留出空间
max: function(context) {
const max = Math.max(...context.chart.data.datasets[0].data);
return max + 2; // 增加Y轴最大高度,为数值标签留出更多空间
}
},
x: {
ticks: {
autoSkip: true,
maxRotation: 0,
minRotation: 0
}
}
},
plugins: {
tooltip: {
callbacks: {
title: function(context) {
const index = context[0].dataIndex;
return `${chartData[index].nickName || chartData[index].sellerId}`;
},
label: function(context) {
return `创建数量: ${context.parsed.y}`;
}
}
},
legend: {
display: false
}
},
onClick: function(event, elements) {
if (elements.length > 0) {
const index = elements[0].index;
const sellerId = chartData[index].sellerId;
showSuppliesBySeller(sellerId);
}
}
}
});
}
// 添加Chart.js自定义插件用于显示数据标签
Chart.register({
id: 'customDataLabels',
afterDraw: function(chart) {
const ctx = chart.ctx;
chart.data.datasets.forEach(function(dataset, datasetIndex) {
const meta = chart.getDatasetMeta(datasetIndex);
if (meta.hidden) return;
meta.data.forEach(function(element, index) {
// 获取柱子的位置
const x = element.x;
const y = element.y; // 柱子顶部的Y坐标
const value = dataset.data[index];
// 设置文本样式
ctx.fillStyle = '#333';
ctx.font = 'bold 14px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'bottom';
// 在柱子顶部上方5像素处绘制数值
ctx.fillText(value, x, y - 5);
});
});
}
});
// 显示当天所有货源
async function showAllSupplies(reset = true) {
// 保存当前滚动位置和货源列表高度
const scrollPosition = window.pageYOffset || document.documentElement.scrollTop;
const originalHeight = suppliesGrid.clientHeight;
// 清空当前正在显示的卖家ID
currentSellerId = null;
// 重置分页参数
if (reset) {
currentPage = 1;
hasMoreData = true;
suppliesData = [];
}
// 构建标题
let title = '当天所有货源';
// 根据当前筛选条件更新标题
switch (currentFilter) {
case 'today':
title = '今日所有货源';
break;
case 'yesterday':
title = '昨日所有货源';
break;
case 'beforeYesterday':
title = '前日所有货源';
break;
case 'week':
title = '本周所有货源';
break;
case 'month':
title = '本月所有货源';
break;
case 'lastMonth':
title = '上月所有货源';
break;
case 'all':
title = '全部货源';
break;
case 'custom':
const startDate = startDateInput.value;
const endDate = endDateInput.value;
if (startDate && endDate) {
title = `${startDate}${endDate} 所有货源`;
} else if (startDate) {
title = `${startDate} 至今所有货源`;
} else if (endDate) {
title = `截至 ${endDate} 所有货源`;
}
break;
}
// 只有在标题变化时才更新,减少不必要的DOM操作
if (suppliesTitle.textContent !== title) {
suppliesTitle.textContent = title;
}
// 只有在suppliesSection的display属性不是block时才设置,避免不必要的布局重排
if (suppliesSection.style.display !== 'block') {
suppliesSection.style.display = 'block';
}
// 如果已经没有更多数据或正在加载,直接返回
if (!hasMoreData || isLoading) {
return;
}
isLoading = true;
try {
// 直接从API获取所有货源数据
let url = `/api/admin/supplies?filter=${currentFilter}&page=${currentPage}&pageSize=${pageSize}`;
if (currentFilter === 'custom') {
const startDate = startDateInput.value;
const endDate = endDateInput.value;
if (startDate) url += `&startDate=${startDate}`;
if (endDate) url += `&endDate=${endDate}`;
}
console.log('showAllSupplies - 正在请求API:', url);
const response = await fetch(url, {
method: 'GET',
timeout: 10000, // 添加10秒超时设置
headers: {
'Content-Type': 'application/json'
}
});
// 检查响应状态
if (!response.ok) {
throw new Error(`HTTP错误! 状态: ${response.status}`);
}
const data = await response.json();
console.log('showAllSupplies - API返回数据:', data);
// 仅在数据成功返回且有变化时才更新DOM,减少视觉闪烁
if (data.success) {
console.log('showAllSupplies - 数据请求成功,supplies数量:', data.data.supplies ? data.data.supplies.length : 0);
// 更新分页信息
if (data.data.pagination) {
totalSupplies = data.data.pagination.total;
hasMoreData = currentPage < data.data.pagination.totalPages;
}
// 添加新数据
if (data.data.supplies && data.data.supplies.length > 0) {
suppliesData = reset ? data.data.supplies : [...suppliesData, ...data.data.supplies];
renderSupplies(suppliesData);
currentPage++;
} else if (reset) {
// 显示空状态
console.log('showAllSupplies - 没有找到货源数据');
suppliesGrid.innerHTML = '<p style="text-align: center; color: #666; grid-column: 1 / -1;">暂无货源数据</p>';
}
// 添加加载更多按钮或提示
if (hasMoreData) {
addLoadMoreButton();
} else if (suppliesData.length > 0) {
addNoMoreDataHint();
}
} else {
console.error('加载货源数据失败:', data.message);
// 只在当前不是错误状态时才显示错误信息
if (!suppliesGrid.innerHTML.includes('加载失败') && !suppliesGrid.innerHTML.includes('网络异常')) {
suppliesGrid.innerHTML = '<p style="text-align: center; color: #666; grid-column: 1 / -1;">加载货源数据失败,请稍后重试</p>';
}
}
} catch (error) {
console.error('加载货源数据出错:', error);
// 只在当前不是错误状态时才显示错误信息
if (!suppliesGrid.innerHTML.includes('加载失败') && !suppliesGrid.innerHTML.includes('网络异常')) {
suppliesGrid.innerHTML = '<p style="text-align: center; color: #666; grid-column: 1 / -1;">网络异常,请检查网络连接后重试</p>';
}
} finally {
isLoading = false;
// 恢复滚动位置
window.scrollTo(0, scrollPosition);
}
}
// 添加加载更多按钮
function addLoadMoreButton() {
// 检查是否已经有加载更多按钮
if (document.getElementById('loadMoreBtn')) {
return;
}
const loadMoreBtn = document.createElement('div');
loadMoreBtn.id = 'loadMoreBtn';
loadMoreBtn.style.cssText = `
text-align: center;
padding: 15px;
grid-column: 1 / -1;
`;
loadMoreBtn.innerHTML = `
<button style="
padding: 10px 20px;
background-color: #1677ff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
" onclick="loadMoreSupplies()">
加载更多
</button>
`;
suppliesGrid.appendChild(loadMoreBtn);
}
// 添加没有更多数据的提示
function addNoMoreDataHint() {
// 移除加载更多按钮
const loadMoreBtn = document.getElementById('loadMoreBtn');
if (loadMoreBtn) {
loadMoreBtn.remove();
}
// 检查是否已经有提示
if (document.getElementById('noMoreDataHint')) {
return;
}
const hint = document.createElement('div');
hint.id = 'noMoreDataHint';
hint.style.cssText = `
text-align: center;
padding: 15px;
color: #999;
grid-column: 1 / -1;
`;
hint.textContent = '没有更多数据了';
suppliesGrid.appendChild(hint);
}
// 加载更多货源
function loadMoreSupplies() {
showAllSupplies(false);
}
// 显示指定卖家的货源
async function showSuppliesBySeller(sellerId) {
// 保存当前正在显示的卖家ID
currentSellerId = sellerId;
// 获取当前筛选条件和日期范围
let startDate = '';
let endDate = '';
let filter = currentFilter;
// 如果是自定义筛选,获取日期输入值
if (filter === 'custom') {
startDate = startDateInput.value;
endDate = endDateInput.value;
}
// 构建API请求URL,包含自定义日期参数
let url = `/api/admin/supplies?sellerId=${sellerId}&filter=${filter}`;
if (filter === 'custom') {
if (startDate) url += `&startDate=${startDate}`;
if (endDate) url += `&endDate=${endDate}`;
}
// 显示加载状态
suppliesGrid.innerHTML = '<p style="text-align: center; color: #666; grid-column: 1 / -1;">加载中...</p>';
try {
const response = await fetch(url, {
method: 'GET',
timeout: 10000, // 添加10秒超时设置
headers: {
'Content-Type': 'application/json'
}
});
// 检查响应状态
if (!response.ok) {
throw new Error(`HTTP错误! 状态: ${response.status}`);
}
const data = await response.json();
if (data.success) {
// 获取创建人的姓名
let creatorName = '';
// 从chartData中查找对应的nickName
const chartItem = chartData.find(item => item.sellerId === sellerId);
if (chartItem && chartItem.nickName) {
creatorName = chartItem.nickName;
}
// 如果chartData中没有,从返回的supplies中获取
else if (data.data.supplies && data.data.supplies.length > 0 && data.data.supplies[0].nickName) {
creatorName = data.data.supplies[0].nickName;
}
// 构建标题
let title = `${sellerId} 创建的货源`;
if (creatorName) {
title = `${creatorName} (${sellerId}) 创建的货源`;
}
// 添加"查看全部货源"按钮
suppliesTitle.innerHTML = `
${title}
<button id="showAllBtn" style="
margin-left: 10px;
padding: 4px 12px;
background-color: #1677ff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
font-weight: normal;
">查看全部货源</button>
`;
// 为"查看全部货源"按钮添加点击事件
document.getElementById('showAllBtn').addEventListener('click', showAllSupplies);
renderSupplies(data.data.supplies);
// 只有在suppliesSection的display属性不是block时才设置,避免不必要的布局重排
if (suppliesSection.style.display !== 'block') {
suppliesSection.style.display = 'block';
// 只有在第一次显示suppliesSection时才滚动到该区域
suppliesSection.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
} else {
console.error('加载货源数据失败:', data.message);
suppliesGrid.innerHTML = '<p style="text-align: center; color: #666; grid-column: 1 / -1;">加载货源数据失败,请稍后重试</p>';
// 只有在suppliesSection的display属性不是block时才设置,避免不必要的布局重排
if (suppliesSection.style.display !== 'block') {
suppliesSection.style.display = 'block';
}
}
} catch (error) {
console.error('加载货源数据出错:', error);
suppliesGrid.innerHTML = '<p style="text-align: center; color: #666; grid-column: 1 / -1;">网络异常,请检查网络连接后重试</p>';
// 只有在suppliesSection的display属性不是block时才设置,避免不必要的布局重排
if (suppliesSection.style.display !== 'block') {
suppliesSection.style.display = 'block';
}
}
}
// 渲染货源卡片
function renderSupplies(supplies) {
// 不过滤任何状态的货源,显示所有货源
const filteredSupplies = supplies;
if (filteredSupplies.length === 0) {
suppliesGrid.innerHTML = '<p class="empty-state">暂无货源数据</p>';
return;
}
// 保存当前滚动位置和容器高度
const scrollPosition = window.pageYOffset || document.documentElement.scrollTop;
const containerHeight = suppliesGrid.clientHeight;
// 设置容器固定高度,防止清空内容时页面高度变化
suppliesGrid.style.height = `${containerHeight}px`;
suppliesGrid.style.overflow = 'hidden';
// 创建DocumentFragment,用于构建DOM结构,减少页面重排
const fragment = document.createDocumentFragment();
filteredSupplies.forEach((supply) => {
const card = document.createElement('div');
card.className = 'supply-card';
card.dataset.productId = supply.productId; // 添加产品ID作为数据属性
card.innerHTML = '';
// 解析媒体URL
let mediaUrl = '';
try {
// 使用更快的JSON.parse替代复杂的字符串处理
const imageUrls = JSON.parse(supply.imageUrls || '[]');
if (imageUrls && Array.isArray(imageUrls) && imageUrls.length > 0) {
mediaUrl = imageUrls[0];
}
} catch (e) {
// 解析失败时不打印错误,避免性能影响
}
// 状态文本映射,避免switch语句
const statusMap = {
'published': '已发布',
'sold_out': '已下架',
'hidden': '已隐藏'
};
const statusClass = `status-${supply.status}`;
const statusText = statusMap[supply.status] || '待审核';
// 检查是否为视频文件(使用更简单的正则表达式)
let mediaElement = '';
if (mediaUrl) {
const isVideo = /\.(mp4|mov|avi|wmv|flv|webm|mkv)$/i.test(mediaUrl) || mediaUrl.startsWith('data:video/');
if (isVideo) {
// 视频文件
mediaElement = `<video src="${mediaUrl}" alt="${supply.productName}" controls class="supply-media"></video>`;
} else {
// 图片文件
mediaElement = `<img src="${mediaUrl}" alt="${supply.productName}" class="supply-media" onclick="showPreview('${mediaUrl}')">`;
}
}
// 第一行展示:种类|蛋黄|货源类型|产品包装|新鲜程度
const firstLineParts = [];
if (supply.category) firstLineParts.push(supply.category);
if (supply.yolk) firstLineParts.push(supply.yolk);
if (supply.sourceType) firstLineParts.push(supply.sourceType);
if (supply.producting) firstLineParts.push(supply.producting);
if (supply.freshness) firstLineParts.push(supply.freshness);
const firstLineHTML = firstLineParts.length > 0 ?
`<p class="supply-meta">${firstLineParts.join(' | ')}</p>` : '';
// 构建详细信息HTML
const detailsParts = [];
// 产品ID
if (supply.productId) {
detailsParts.push(`<p><strong>产品ID:</strong> ${supply.productId}</p>`);
}
// 规格+件数+采购价+销售价组合展示
const specifications = [];
if (supply.specification) {
if (typeof supply.specification === 'string') {
// 规格可能用逗号或中文逗号分隔
specifications.push(...supply.specification.split(/[,,]/).filter(spec => spec.trim()));
} else if (Array.isArray(supply.specification)) {
specifications.push(...supply.specification);
} else {
specifications.push(String(supply.specification));
}
}
// 处理数量、采购价、销售价
const quantities = Array.isArray(supply.quantity) ? supply.quantity :
(typeof supply.quantity === 'string' ? supply.quantity.split(',').filter(qty => qty.trim()) :
(supply.quantity ? [String(supply.quantity)] : []));
const costprices = Array.isArray(supply.costprice) ? supply.costprice :
(typeof supply.costprice === 'string' ? supply.costprice.split(',').filter(cp => cp.trim()) :
(supply.costprice ? [String(supply.costprice)] : []));
const prices = Array.isArray(supply.price) ? supply.price :
(typeof supply.price === 'string' ? supply.price.split(',').filter(p => p.trim()) :
(supply.price ? [String(supply.price)] : []));
// 计算最大长度,确保每个规格都有对应的数据
const maxLength = Math.max(specifications.length, quantities.length, costprices.length, prices.length);
// 生成组合展示HTML
if (maxLength > 0) {
const specDetails = [];
for (let i = 0; i < maxLength; i++) {
const spec = specifications[i] || '';
const quantity = quantities[i] || '';
const costprice = costprices[i] || '';
const price = prices[i] || '';
// 只显示有数据的行
if (spec || quantity || costprice || price) {
const parts = [];
if (spec) parts.push(`规格${i+1}: ${spec}`);
if (quantity) parts.push(`件数: ${quantity}`);
if (costprice) parts.push(`采购价: ¥${costprice}`);
if (price) parts.push(`销售价: ¥${price}`);
if (parts.length > 0) {
specDetails.push(`<div class="spec-item">${parts.join(' - ')}</div>`);
}
}
}
if (specDetails.length > 0) {
detailsParts.push(`<div class="spec-section">${specDetails.join('')}</div>`);
}
}
// 商品地区
if (supply.region) {
detailsParts.push(`<p><strong>商品地区:</strong> ${supply.region}</p>`);
}
// 商品联系人和联系电话
if (supply.product_contact || supply.contact_phone) {
let contactText = '<strong>商品联系人:</strong> ';
if (supply.product_contact) {
contactText += supply.product_contact;
}
if (supply.contact_phone) {
if (supply.product_contact) {
contactText += ` ${supply.contact_phone}`;
} else {
contactText += supply.contact_phone;
}
}
detailsParts.push(`<p>${contactText}</p>`);
}
// 创建时间
detailsParts.push(`<p><strong>创建时间:</strong> ${new Date(supply.created_at).toLocaleString()}</p>`);
// 修改时间
if (supply.updated_at) {
detailsParts.push(`<p><strong>修改时间:</strong> ${new Date(supply.updated_at).toLocaleString()}</p>`);
}
// 货源描述(限制显示长度)
if (supply.description) {
const shortDesc = supply.description.length > 50 ? `${supply.description.substring(0, 50)}...` : supply.description;
detailsParts.push(`<div class="description-section"><strong>货源描述:</strong><div>${shortDesc}</div></div>`);
}
// 价格变更日志
let logHTML = `<div class="log-section"><h4>价格变更日志</h4>`;
try {
let logs = [];
let hasLogs = false;
// 检查product_log是否有值
if (supply.product_log != null && supply.product_log != undefined && supply.product_log != '') {
// 有值,处理日志
if (typeof supply.product_log === 'string') {
// 清理JSON字符串中的特殊字符
const cleanLogStr = supply.product_log.replace(/\s+/g, ' ').trim();
// 检查是否为JSON格式
if (cleanLogStr.startsWith('[') && cleanLogStr.endsWith(']')) {
try {
logs = JSON.parse(cleanLogStr);
} catch (parseError) {
// 解析失败,作为单个日志处理
logs = [supply.product_log];
}
} else {
// 不是JSON数组,作为单个日志处理
logs = [supply.product_log];
}
} else if (Array.isArray(supply.product_log)) {
logs = supply.product_log;
} else {
// 其他类型,转换为字符串数组
logs = [String(supply.product_log)];
}
hasLogs = logs.length > 0;
}
// 显示日志
if (hasLogs) {
logs.forEach((log, index) => {
logHTML += `<div class="log-item"><span class="log-badge">日志${index + 1}</span><span>${log}</span></div>`;
});
} else {
// 没有日志,显示提示
logHTML += `<div class="log-item"><span>暂无价格变更日志</span></div>`;
}
} catch (error) {
// 如果解析失败,直接显示日志内容
logHTML += `<div class="log-item"><span>${supply.product_log || '暂无价格变更日志'}</span></div>`;
}
logHTML += `</div>`;
detailsParts.push(logHTML);
// 状态
detailsParts.push(`<span class="supply-status ${statusClass}">${statusText}</span>`);
// 构建完整卡片HTML
card.innerHTML = `
<div class="supply-header">
${mediaElement}
<div class="supply-title">${supply.productName}</div>
${firstLineHTML}
</div>
<div class="supply-info">
${detailsParts.join('')}
</div>
`;
// 添加到DocumentFragment
fragment.appendChild(card);
});
// 一次性替换旧内容,减少页面重排
suppliesGrid.innerHTML = '';
suppliesGrid.appendChild(fragment);
// 恢复容器高度和滚动位置
suppliesGrid.style.height = '';
suppliesGrid.style.overflow = '';
window.scrollTo(0, scrollPosition);
}
</script>
</body>
</html>