Browse Source

优化supply.html页面性能,修复操作日志和地区选择功能

Boss3
Default User 2 months ago
parent
commit
f1b92b2afb
  1. 140
      Management.html
  2. 180
      Reject.js
  3. 408
      supply.html

140
Management.html

@ -123,7 +123,7 @@
.supplies-grid { .supplies-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(400px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(min(100%, 350px), 1fr));
gap: 20px; gap: 20px;
} }
@ -139,12 +139,142 @@
transform: translateY(-2px); transform: translateY(-2px);
} }
.supply-card img { .supply-card img,
.supply-card video {
max-width: 100%; max-width: 100%;
height: 200px; height: 180px;
object-fit: cover; object-fit: contain;
border-radius: 4px; border-radius: 4px;
margin-bottom: 10px; 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 { .supply-info {
@ -849,7 +979,7 @@
// 动态计算Y轴最大值,为顶部数字留出空间 // 动态计算Y轴最大值,为顶部数字留出空间
max: function(context) { max: function(context) {
const max = Math.max(...context.chart.data.datasets[0].data); const max = Math.max(...context.chart.data.datasets[0].data);
return max + 1; return max + 2; // 增加Y轴最大高度,为数值标签留出更多空间
} }
}, },
x: { x: {

180
Reject.js

@ -997,7 +997,8 @@ app.post('/api/supplies/create', async (req, res) => {
]; ];
} }
result = await connection.query(insertQuery, insertParams); const queryResult = await connection.query(insertQuery, insertParams);
result = queryResult[0];
} catch (insertError) { } catch (insertError) {
await connection.rollback(); await connection.rollback();
connection.release(); connection.release();
@ -1037,7 +1038,33 @@ app.post('/api/supplies/create', async (req, res) => {
} }
}); });
sendResponse(res, true, { productId: result.insertId }, '货源创建成功'); // 返回完整的货源数据,包括ID和所有字段
sendResponse(res, true, {
id: result.insertId,
productId: productId,
productName,
category: category || '',
freshness: req.body.freshness || '',
costprice: costprice || '',
quantity,
grossWeight,
yolk,
specification,
producting: producting || '',
region,
status: 'published',
supplyStatus: supplyStatus || '',
sourceType: sourceType || '',
description: description || '',
sellerId,
imageUrls: uploadedImageUrls,
created_at: new Date(),
product_contact: productContact,
contact_phone: contactPhone,
autoOfflineTime: req.body.autoOfflineTime,
autoOfflineDays: req.body.autoOfflineDays,
autoOfflineHours: req.body.autoOfflineHours
}, '货源创建成功');
} catch (error) { } catch (error) {
if (connection) { if (connection) {
try { try {
@ -1193,6 +1220,9 @@ app.post('/api/supplies/:id/unpublish', async (req, res) => {
['sold_out', productId] ['sold_out', productId]
); );
// 查询更新后的完整货源数据
const [updatedProduct] = await connection.query('SELECT * FROM products WHERE id = ?', [productId]);
// 提交事务 // 提交事务
await connection.commit(); await connection.commit();
connection.release(); connection.release();
@ -1205,7 +1235,8 @@ app.post('/api/supplies/:id/unpublish', async (req, res) => {
status: 'sold_out' status: 'sold_out'
}); });
sendResponse(res, true, null, '货源下架成功'); // 返回更新后的完整货源数据
sendResponse(res, true, updatedProduct[0], '货源下架成功');
} catch (error) { } catch (error) {
console.error('下架货源失败:', error.message); console.error('下架货源失败:', error.message);
console.error('错误详情:', error); console.error('错误详情:', error);
@ -1298,6 +1329,9 @@ app.post('/api/supplies/:id/delete', async (req, res) => {
['hidden', productId] ['hidden', productId]
); );
// 查询更新后的完整货源数据
const [updatedProduct] = await connection.query('SELECT * FROM products WHERE id = ?', [productId]);
// 提交事务 // 提交事务
await connection.commit(); await connection.commit();
connection.release(); connection.release();
@ -1306,10 +1340,12 @@ app.post('/api/supplies/:id/delete', async (req, res) => {
broadcastMessage({ broadcastMessage({
type: 'supply_update', type: 'supply_update',
supplyId: productId, supplyId: productId,
action: 'update' action: 'update',
status: 'hidden'
}); });
sendResponse(res, true, null, '货源已删除'); // 返回更新后的完整货源数据
sendResponse(res, true, updatedProduct[0], '货源已删除');
} catch (error) { } catch (error) {
console.error('删除货源失败:', error.message); console.error('删除货源失败:', error.message);
console.error('错误详情:', error); console.error('错误详情:', error);
@ -1591,7 +1627,40 @@ app.put('/api/supplies/:id/edit', async (req, res) => {
} }
}); });
sendResponse(res, true, null, '货源编辑成功'); // 查询修改后的完整货源数据(在事务提交前查询,使用同一个连接)
const [updatedProduct] = await connection.query('SELECT * FROM products WHERE id = ?', [productId]);
// 提交事务
await connection.commit();
connection.release();
// 发送WebSocket消息通知所有客户端
broadcastMessage({
type: 'supply_update',
supplyId: productId,
action: 'update',
data: {
id: productId,
productName,
// 不再使用price字段,移除price字段
costprice: costprice || '',
quantity,
grossWeight,
yolk,
specification,
category,
sourceType,
freshness,
producting,
supplyStatus,
description,
region,
imageUrls: uploadedImageUrls.length > 0 ? uploadedImageUrls : imageUrls
}
});
// 返回完整的修改后货源数据
sendResponse(res, true, updatedProduct[0], '货源编辑成功');
} catch (error) { } catch (error) {
console.error('编辑货源失败:', error.message); console.error('编辑货源失败:', error.message);
console.error('错误详情:', error); console.error('错误详情:', error);
@ -1859,7 +1928,106 @@ app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'login.html')); res.sendFile(path.join(__dirname, 'login.html'));
}); });
// 记录货源操作日志API
app.post('/api/supplies/log', async (req, res) => {
try {
const { phoneNumber, projectName, operationEvent, productId, originalData, modifiedData, changedFields, userId } = req.body;
// 验证必填字段
if (!phoneNumber || !projectName || !operationEvent || !productId || !userId) {
return sendResponse(res, false, null, '缺少必填字段');
}
// 1. 查询personnel表获取完整信息
const userLoginConnection = await userLoginPool.getConnection();
// 使用SELECT * 确保获取所有字段,包括company和organization
const [personnelResult] = await userLoginConnection.query(
'SELECT * FROM Personnel WHERE phoneNumber = ?',
[phoneNumber]
);
userLoginConnection.release();
// 打印查询结果,查看是否包含company和organization字段
console.log('查询到的personnel信息:', personnelResult[0]);
// 自动计算变更字段
let calculatedChangedFields = changedFields;
if (operationEvent.includes('编辑') && originalData && modifiedData && !changedFields) {
// 比较两个对象的差异,返回变更的字段列表
const getChangedFields = (oldObj, newObj) => {
if (!oldObj || !newObj) return [];
const changed = [];
const allKeys = new Set([...Object.keys(oldObj), ...Object.keys(newObj)]);
allKeys.forEach(key => {
if (JSON.stringify(oldObj[key]) !== JSON.stringify(newObj[key])) {
changed.push(key);
}
});
return changed;
};
calculatedChangedFields = getChangedFields(originalData, modifiedData);
}
// 2. 准备日志数据
const logData = {
tracompany: '',
tradepartment: '',
traorganization: '',
trarole: projectName || '',
trauserName: '',
traassistant: '', // 暂无协助人信息
userId: userId, // 使用前端传递的正确userId
operationEvent: operationEvent,
operationTime: new Date(),
originalData: originalData ? JSON.stringify(originalData) : null,
modifiedData: modifiedData ? JSON.stringify(modifiedData) : null,
changedFields: calculatedChangedFields ? JSON.stringify(calculatedChangedFields) : null
};
// 如果找到了员工信息,使用真实的员工数据
if (personnelResult.length > 0) {
const personnelInfo = personnelResult[0];
// 检查personnelInfo中的字段名,确保使用正确的字段名
console.log('personnelInfo的所有字段:', Object.keys(personnelInfo));
// 使用正确的字段名:managercompany(公司)、managerdepartment(部门)、organization(组织)
logData.tracompany = personnelInfo.managercompany || personnelInfo.managerCompany || personnelInfo.MANAGERCOMPANY || '';
logData.tradepartment = personnelInfo.managerdepartment || personnelInfo.managerDepartment || personnelInfo.MANAGERDEPARTMENT || '';
logData.traorganization = personnelInfo.organization || personnelInfo.Organization || personnelInfo.ORGANIZATION || personnelInfo.org || '';
logData.trauserName = personnelInfo.name || personnelInfo.Name || personnelInfo.NAME || personnelInfo.alias || personnelInfo.Alias || personnelInfo.ALIAS || '';
}
// 3. 插入日志到informationtra表
const connection = await pool.getConnection();
const [result] = await connection.query(
`INSERT INTO informationtra (
tracompany, tradepartment, traorganization, trarole,
trauserName, traassistant, userId, operationEvent,
operationTime, originalData, modifiedData, changedFields
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
[
logData.tracompany,
logData.tradepartment,
logData.traorganization,
logData.trarole,
logData.trauserName,
logData.traassistant,
logData.userId,
logData.operationEvent,
logData.operationTime,
logData.originalData,
logData.modifiedData,
logData.changedFields
]
);
connection.release();
sendResponse(res, true, { logId: result.insertId }, '日志记录成功');
} catch (error) {
console.error('记录日志失败:', error);
sendResponse(res, false, null, '记录日志失败');
}
});
// 货源管理页面路由 // 货源管理页面路由
app.get('/management', (req, res) => { app.get('/management', (req, res) => {

408
supply.html

@ -2071,16 +2071,16 @@
<input type="text" id="editRegionSearchInput" placeholder="搜索地区" oninput="filterEditRegionOptions()" style="width: 100%; padding: 10px; border: 1px solid #d9d9d9; border-radius: 8px; font-size: 14px; box-sizing: border-box;"> <input type="text" id="editRegionSearchInput" placeholder="搜索地区" oninput="filterEditRegionOptions()" style="width: 100%; padding: 10px; border: 1px solid #d9d9d9; border-radius: 8px; font-size: 14px; box-sizing: border-box;">
<div id="editCurrentRegion" style="margin-top: 10px; padding: 8px 12px; background-color: #f0f8ff; border: 1px solid #91d5ff; border-radius: 4px; font-size: 12px; color: #1890ff;">当前选择: 未选择</div> <div id="editCurrentRegion" style="margin-top: 10px; padding: 8px 12px; background-color: #f0f8ff; border: 1px solid #91d5ff; border-radius: 4px; font-size: 12px; color: #1890ff;">当前选择: 未选择</div>
</div> </div>
<div class="select-body"> <div class="select-body" style="height: 400px;">
<div id="editRegionOptionsList" style="display: flex; gap: 10px; padding: 10px;"> <div id="editRegionOptionsList" style="display: flex; gap: 10px; padding: 10px; height: 100%;">
<!-- 省市选择将通过JavaScript动态生成 --> <!-- 省市选择将通过JavaScript动态生成 -->
<div id="editProvinceList" style="flex: 1; overflow-y: auto;"></div> <div id="editProvinceList" style="flex: 1; overflow-y: auto; height: 350px;"></div>
<div id="editCityList" style="flex: 1; overflow-y: auto;"></div> <div id="editCityList" style="flex: 1; overflow-y: auto; height: 350px;"></div>
</div> </div>
</div> </div>
<div class="select-footer" style="padding: 16px 20px; display: flex; justify-content: space-between; border-top: 1px solid #f0f0f0; background-color: #fafafa;"> <div class="select-footer" style="padding: 16px 20px; display: flex; justify-content: space-between; border-top: 1px solid #f0f0f0; background-color: #fafafa;">
<button onclick="hideEditRegionSelectModal()" style="padding: 8px 20px; background-color: #f5f5f5; color: #666; border: none; border-radius: 6px; cursor: pointer;">取消</button> <button onclick="hideEditRegionSelectModal()" style="padding: 10px 24px; background-color: #f5f5f5; color: #666; border: none; border-radius: 8px; cursor: pointer; font-size: 14px; font-weight: 500; transition: all 0.3s ease;">取消</button>
<button onclick="confirmEditRegionSelection()" style="padding: 8px 20px; background-color: #1677ff; color: white; border: none; border-radius: 6px; cursor: pointer;">确定</button> <button onclick="confirmEditRegionSelection()" style="padding: 10px 24px; background-color: #1677ff; color: white; border: none; border-radius: 8px; cursor: pointer; font-size: 14px; font-weight: 500; transition: all 0.3s ease;">确定</button>
</div> </div>
</div> </div>
</div> </div>
@ -2112,6 +2112,9 @@
} }
}; };
// 操作状态跟踪,防止重复点击
let isOperating = false;
// 编辑相关全局变量 // 编辑相关全局变量
let currentEditSupplyId = null; let currentEditSupplyId = null;
let editSelectedSpec = []; let editSelectedSpec = [];
@ -2172,6 +2175,7 @@
currentUserInfoEl.innerHTML = `当前登录用户:${parsedUserInfo.name} | 用户ID:${userId} | 职位:${parsedUserInfo.projectName}`; currentUserInfoEl.innerHTML = `当前登录用户:${parsedUserInfo.name} | 用户ID:${userId} | 职位:${parsedUserInfo.projectName}`;
} }
// 返回解析后的用户信息
return parsedUserInfo; return parsedUserInfo;
} }
@ -2191,6 +2195,89 @@
// 防止loadSupplies并发执行的标志 // 防止loadSupplies并发执行的标志
let isLoadingSupplies = false; let isLoadingSupplies = false;
// 比较两个对象的差异,返回变更的字段列表
function getChangedFields(oldObj, newObj) {
if (!oldObj || !newObj) return [];
const changedFields = [];
const allKeys = new Set([...Object.keys(oldObj), ...Object.keys(newObj)]);
allKeys.forEach(key => {
const oldValue = oldObj[key];
const newValue = newObj[key];
// 优化比较逻辑,减少不必要的JSON.stringify调用
// 先比较基本类型
if (oldValue !== newValue) {
// 如果基本类型不同,直接添加到变更字段
// 如果是引用类型,再使用JSON.stringify比较
if (typeof oldValue !== 'object' || typeof newValue !== 'object' ||
oldValue === null || newValue === null ||
Array.isArray(oldValue) !== Array.isArray(newValue)) {
changedFields.push(key);
} else {
// 都是对象或都是数组,使用JSON.stringify比较
if (JSON.stringify(oldValue) !== JSON.stringify(newValue)) {
changedFields.push(key);
}
}
}
});
return changedFields;
}
// 记录货源操作日志
async function logSupplyOperation(operationType, productId, originalData = null, modifiedData = null, changedFields = null) {
try {
console.log('开始记录货源操作日志:', operationType, productId);
// 获取当前登录用户信息
const userInfo = checkLogin();
console.log('获取到的用户信息:', userInfo);
if (!userInfo) {
console.log('没有用户信息,跳过日志记录');
return;
}
// 如果没有提供changedFields,自动计算
if (operationType === '修改' && originalData && modifiedData && !changedFields) {
changedFields = getChangedFields(originalData, modifiedData);
}
// 准备日志数据
const logData = {
phoneNumber: userInfo.phoneNumber,
projectName: userInfo.projectName,
userId: userInfo.userId || userInfo.id, // 确保使用正确的userId
operationEvent: `审核系统-${operationType === '创建' ? '创建货源' : operationType === '修改' ? '编辑货源' : operationType === '下架' ? '下架货源' : operationType === '删除' ? '删除货源' : operationType}`,
productId: productId,
originalData: originalData,
modifiedData: modifiedData,
changedFields: changedFields
};
console.log('准备发送的日志数据:', logData);
// 调用后端API记录日志
const response = await fetch('/api/supplies/log', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(logData)
});
const result = await response.json();
console.log('日志API返回结果:', result);
if (!result.success) {
console.warn('记录日志失败:', result.message);
} else {
console.log('日志记录成功:', result.data.logId);
}
} catch (error) {
console.error('记录日志出错:', error);
}
}
// 初始化WebSocket连接 // 初始化WebSocket连接
function initWebSocket() { function initWebSocket() {
// 获取当前页面的协议和主机 // 获取当前页面的协议和主机
@ -2349,23 +2436,27 @@
startAutoOfflineCheck(); startAutoOfflineCheck();
// 启动定期刷新数据,确保label字段变化能实时显示 // 启动定期刷新数据,确保label字段变化能实时显示
// 优化:减少刷新频率,从5秒改为30秒,降低服务器负载
timers.loadSupplies = setInterval(() => { timers.loadSupplies = setInterval(() => {
console.log('定期刷新数据,检查label字段变化');
loadSupplies(); loadSupplies();
}, 5000); // 每5秒刷新一次 }, 30000); // 每30秒刷新一次
// 添加防抖机制,避免短时间内多次调用loadSupplies
let loadSuppliesTimeout;
// 增强WebSocket消息处理,确保所有相关消息都能触发数据刷新 // 增强WebSocket消息处理,确保所有相关消息都能触发数据刷新
console.log('增强WebSocket消息处理');
if (ws) { if (ws) {
// 添加额外的消息监听器,确保所有相关消息都能触发数据刷新 // 添加额外的消息监听器,确保所有相关消息都能触发数据刷新
ws.addEventListener('message', function(event) { ws.addEventListener('message', function(event) {
try { try {
const data = JSON.parse(event.data); const data = JSON.parse(event.data);
console.log('增强的WebSocket消息处理:', data);
// 对于任何类型的WebSocket消息,都重新加载数据,确保label字段实时更新 // 对于任何类型的WebSocket消息,都重新加载数据,确保label字段实时更新
if (data.type !== 'ping' && data.type !== 'pong') { if (data.type !== 'ping' && data.type !== 'pong') {
console.log('收到相关WebSocket消息,刷新数据'); // 使用防抖机制,避免短时间内多次调用loadSupplies
loadSupplies(); clearTimeout(loadSuppliesTimeout);
loadSuppliesTimeout = setTimeout(() => {
loadSupplies();
}, 1000); // 1秒防抖
} }
} catch (error) { } catch (error) {
console.error('解析WebSocket消息失败:', error); console.error('解析WebSocket消息失败:', error);
@ -2439,38 +2530,32 @@
// 根据元素类型设置不同的显示格式 // 根据元素类型设置不同的显示格式
if (element.classList.contains('countdown-badge')) { if (element.classList.contains('countdown-badge')) {
element.innerHTML = `⏰ ${countdownText}`; element.textContent = `⏰ ${countdownText}`;
element.style.background = 'linear-gradient(135deg, #ff6b6b, #ee5a6f)'; element.style.background = 'linear-gradient(135deg, #ff6b6b, #ee5a6f)';
element.style.boxShadow = '0 2px 4px rgba(255, 107, 107, 0.3)'; element.style.boxShadow = '0 2px 4px rgba(255, 107, 107, 0.3)';
} else if (element.classList.contains('countdown')) { } else if (element.classList.contains('countdown')) {
element.innerHTML = `剩余下架时间: ${countdownText}`; element.textContent = `剩余下架时间: ${countdownText}`;
} }
console.log('更新倒计时显示为:', countdownText);
} else { } else {
// 时间到了 // 时间到了
console.log('时间已到,显示已下架');
if (element.classList.contains('countdown-badge')) { if (element.classList.contains('countdown-badge')) {
element.innerHTML = '⏰ 已下架'; element.textContent = '⏰ 已下架';
element.style.background = '#8c8c8c'; element.style.background = '#8c8c8c';
element.style.boxShadow = '0 2px 4px rgba(140, 140, 140, 0.3)'; element.style.boxShadow = '0 2px 4px rgba(140, 140, 140, 0.3)';
} else if (element.classList.contains('countdown')) { } else if (element.classList.contains('countdown')) {
element.innerHTML = '剩余下架时间: 已下架'; element.textContent = '剩余下架时间: 已下架';
} }
} }
} else { } else {
// 没有设置自动下架时间,隐藏倒计时或显示无倒计时 // 没有设置自动下架时间,隐藏倒计时或显示无倒计时
console.log('没有设置自动下架时间,隐藏倒计时');
if (element.classList.contains('countdown-badge')) { if (element.classList.contains('countdown-badge')) {
element.innerHTML = '⏰ 无倒计时'; element.textContent = '⏰ 无倒计时';
element.style.background = '#52c41a'; element.style.background = '#52c41a';
element.style.boxShadow = '0 2px 4px rgba(82, 196, 26, 0.3)'; element.style.boxShadow = '0 2px 4px rgba(82, 196, 26, 0.3)';
} else if (element.classList.contains('countdown')) { } else if (element.classList.contains('countdown')) {
element.innerHTML = '剩余下架时间: 无'; element.textContent = '剩余下架时间: 无';
} }
} }
} else {
console.log('未找到对应的货源,supplyId:', supplyId);
console.log('supplyData.supplies:', supplyData.supplies);
} }
}); });
} }
@ -4074,8 +4159,10 @@
const provinceList = document.getElementById('provinceList'); const provinceList = document.getElementById('provinceList');
const cityList = document.getElementById('cityList'); const cityList = document.getElementById('cityList');
// 使用文档片段减少DOM重绘
const provinceFragment = document.createDocumentFragment();
// 生成省份选项 // 生成省份选项
provinceList.innerHTML = '';
filteredProvinces.forEach(province => { filteredProvinces.forEach(province => {
const option = document.createElement('div'); const option = document.createElement('div');
option.className = 'select-item'; option.className = 'select-item';
@ -4092,15 +4179,21 @@
cityList.scrollTop = 0; cityList.scrollTop = 0;
}, 100); }, 100);
}; };
provinceList.appendChild(option); provinceFragment.appendChild(option);
}); });
// 一次性添加到DOM,减少重绘
provinceList.innerHTML = '';
provinceList.appendChild(provinceFragment);
} }
// 生成城市选项 // 生成城市选项
function generateCityOptions(cities) { function generateCityOptions(cities) {
const cityList = document.getElementById('cityList'); const cityList = document.getElementById('cityList');
cityList.innerHTML = ''; // 使用文档片段减少DOM重绘
const cityFragment = document.createDocumentFragment();
cities.forEach(city => { cities.forEach(city => {
const option = document.createElement('div'); const option = document.createElement('div');
option.className = 'select-item'; option.className = 'select-item';
@ -4122,9 +4215,13 @@
hideRegionSelectModal(); hideRegionSelectModal();
saveFormData(); // 保存选择 saveFormData(); // 保存选择
}; };
cityList.appendChild(option); cityFragment.appendChild(option);
}); });
// 一次性添加到DOM,减少重绘
cityList.innerHTML = '';
cityList.appendChild(cityFragment);
// 使用scrollIntoView滚动到第一个城市 // 使用scrollIntoView滚动到第一个城市
setTimeout(() => { setTimeout(() => {
const firstCity = cityList.querySelector('.select-item'); const firstCity = cityList.querySelector('.select-item');
@ -6282,6 +6379,13 @@
localStorage.removeItem('supplyFormDraft'); localStorage.removeItem('supplyFormDraft');
console.log('创建成功,已清除保存的表单数据'); console.log('创建成功,已清除保存的表单数据');
alert('货源创建成功'); alert('货源创建成功');
// 记录创建货源日志
const userInfo = checkLogin();
if (userInfo) {
await logSupplyOperation('创建', result.data.id, null, result.data);
}
// 重置表单状态,确保下次打开时是空白状态 // 重置表单状态,确保下次打开时是空白状态
resetForm(); resetForm();
hideAddSupplyModal(); hideAddSupplyModal();
@ -6716,19 +6820,40 @@
// 下架货源 // 下架货源
async function unpublishSupply(id) { async function unpublishSupply(id) {
// 防止重复点击
if (isOperating) {
return;
}
if (confirm('确定要下架该货源吗?')) { if (confirm('确定要下架该货源吗?')) {
try { try {
isOperating = true;
// 获取要下架的货源的原始数据
const originalSupply = supplyData.supplies.find(s => String(s.id) === String(id));
if (!originalSupply) {
alert('找不到要下架的货源数据');
return;
}
const response = await fetch(`/api/supplies/${id}/unpublish`, { const response = await fetch(`/api/supplies/${id}/unpublish`, {
method: 'POST' method: 'POST'
}); });
const result = await response.json(); const result = await response.json();
if (result.success) { if (result.success) {
// 记录下架货源日志
const userInfo = checkLogin();
if (userInfo) {
// 传入修改后的数据,包含状态变化
await logSupplyOperation('下架', id, originalSupply, result.data);
}
// 向WebSocket服务器发送消息,通知其他客户端货源已下架 // 向WebSocket服务器发送消息,通知其他客户端货源已下架
sendWebSocketMessage({ sendWebSocketMessage({
type: 'supply_status_change', type: 'supply_status_change',
supplyId: id, supplyId: id,
action: 'unpublish', action: 'unpublish',
status: 'hidden' status: 'sold_out'
}); });
alert('下架成功'); alert('下架成功');
@ -6739,6 +6864,8 @@
} catch (error) { } catch (error) {
console.error('下架失败:', error); console.error('下架失败:', error);
alert('下架失败: 网络错误'); alert('下架失败: 网络错误');
} finally {
isOperating = false;
} }
} }
} }
@ -6746,18 +6873,40 @@
// 显示编辑货源 // 显示编辑货源
// 删除货源 // 删除货源
async function deleteSupply(id) { async function deleteSupply(id) {
// 防止重复点击
if (isOperating) {
return;
}
if (confirm('确定要删除该货源吗?')) { if (confirm('确定要删除该货源吗?')) {
try { try {
isOperating = true;
// 获取要删除的货源的原始数据
const originalSupply = supplyData.supplies.find(s => String(s.id) === String(id));
if (!originalSupply) {
alert('找不到要删除的货源数据');
return;
}
const response = await fetch(`/api/supplies/${id}/delete`, { const response = await fetch(`/api/supplies/${id}/delete`, {
method: 'POST' method: 'POST'
}); });
const result = await response.json(); const result = await response.json();
if (result.success) { if (result.success) {
// 记录删除货源日志
const userInfo = checkLogin();
if (userInfo) {
// 传入修改后的数据,包含状态变化
await logSupplyOperation('删除', id, originalSupply, result.data);
}
// 向WebSocket服务器发送消息,通知其他客户端货源已删除 // 向WebSocket服务器发送消息,通知其他客户端货源已删除
sendWebSocketMessage({ sendWebSocketMessage({
type: 'supply_update', type: 'supply_update',
supplyId: id, supplyId: id,
action: 'delete' action: 'delete',
status: 'hidden'
}); });
alert('删除成功'); alert('删除成功');
@ -6768,6 +6917,8 @@
} catch (error) { } catch (error) {
console.error('删除失败:', error); console.error('删除失败:', error);
alert('删除失败: 网络错误'); alert('删除失败: 网络错误');
} finally {
isOperating = false;
} }
} }
} }
@ -7103,12 +7254,19 @@
// 上架货源 // 上架货源
async function publishSupply(id) { async function publishSupply(id) {
// 防止重复点击
if (isOperating) {
return false;
}
if (!id) { if (!id) {
alert('货源ID不存在'); alert('货源ID不存在');
return; return false;
} }
try { try {
isOperating = true;
const response = await fetch(`/api/supplies/${id}/publish`, { const response = await fetch(`/api/supplies/${id}/publish`, {
method: 'POST' method: 'POST'
}); });
@ -7133,6 +7291,8 @@
console.error('上架失败:', error); console.error('上架失败:', error);
alert('上架失败: ' + error.message); alert('上架失败: ' + error.message);
return false; return false;
} finally {
isOperating = false;
} }
} }
@ -7554,7 +7714,6 @@
// 生成编辑城市选项 // 生成编辑城市选项
function generateEditCityOptions(cities) { function generateEditCityOptions(cities) {
const cityList = document.getElementById('editCityList'); const cityList = document.getElementById('editCityList');
const districtList = document.getElementById('editDistrictList');
cityList.innerHTML = ''; cityList.innerHTML = '';
cities.forEach(city => { cities.forEach(city => {
@ -7568,28 +7727,29 @@
editSelectedCity = city.city; editSelectedCity = city.city;
updateEditRegionDisplay(); updateEditRegionDisplay();
}; };
option.ondblclick = () => {
// 双击城市直接确认整个地区选择
editSelectedCity = city.city;
updateEditRegionDisplay();
// 构建完整的地区字符串
const regionString = `${editSelectedProvince} ${editSelectedCity}`;
// 设置到表单
document.getElementById('editRegionDisplayText').textContent = regionString;
document.getElementById('editRegionValue').value = regionString;
// 隐藏弹窗
hideEditRegionSelectModal();
saveFormData(); // 保存选择
};
cityList.appendChild(option); cityList.appendChild(option);
}); });
}
// 生成编辑区县选项 // 使用scrollIntoView滚动到第一个城市
function generateEditDistrictOptions(districts) { setTimeout(() => {
const districtList = document.getElementById('editDistrictList'); const firstCity = cityList.querySelector('.select-item');
if (firstCity) {
districtList.innerHTML = ''; firstCity.scrollIntoView({ behavior: 'auto', block: 'start' });
districts.forEach(district => {
const option = document.createElement('div');
option.className = 'select-item';
option.textContent = district;
if (district === editSelectedDistrict) {
option.classList.add('selected');
} }
option.onclick = () => { }, 50);
editSelectedDistrict = district;
updateEditRegionDisplay();
};
districtList.appendChild(option);
});
} }
// 更新编辑地区显示 // 更新编辑地区显示
@ -7615,76 +7775,62 @@
editSelectedProvince = ''; editSelectedProvince = '';
editSelectedCity = ''; editSelectedCity = '';
// 先尝试直接查找区县 if (!searchKeyword) {
let foundDistrict = false; // 如果搜索关键词为空,显示所有省份
for (let i = 0; i < allRegionOptions.length; i++) {
const province = allRegionOptions[i];
for (let j = 0; j < province.cities.length; j++) {
const city = province.cities[j];
for (let k = 0; k < city.districts.length; k++) {
const district = city.districts[k];
if (district.toLowerCase().includes(searchKeyword)) {
// 找到匹配的区县,自动填充省市区
editSelectedProvince = province.province;
editSelectedCity = city.city;
editSelectedDistrict = district;
foundDistrict = true;
break;
}
}
if (foundDistrict) break;
}
if (foundDistrict) break;
}
if (foundDistrict) {
// 生成地区选项并选择
generateEditRegionOptions(); generateEditRegionOptions();
updateEditRegionDisplay();
return;
}
// 自动生成城市选项并选择 // 过滤省份
const cityList = document.getElementById('editCityList'); const filteredProvinces = allRegionOptions.filter(province => {
cityList.innerHTML = ''; // 检查省份名称是否匹配
const province = allRegionOptions.find(p => p.province === editSelectedProvince); if (province.province.toLowerCase().includes(searchKeyword)) {
if (province) { return true;
province.cities.forEach(city => { }
const option = document.createElement('div'); // 检查该省份下是否有匹配的城市
option.className = 'select-item'; return province.cities.some(city => {
option.textContent = city.city; return city.city.toLowerCase().includes(searchKeyword);
if (city.city === editSelectedCity) { });
option.classList.add('selected'); });
}
option.onclick = () => {
editSelectedCity = city.city;
editSelectedDistrict = '';
updateEditRegionDisplay();
generateEditDistrictOptions(city.districts);
};
cityList.appendChild(option);
// 如果是选中的城市,生成区县选项 // 生成省份选项
if (city.city === editSelectedCity) { const provinceList = document.getElementById('editProvinceList');
generateEditDistrictOptions(city.districts); provinceList.innerHTML = '';
} filteredProvinces.forEach(province => {
}); const option = document.createElement('div');
option.className = 'select-item';
option.textContent = province.province;
if (province.province === editSelectedProvince) {
option.classList.add('selected');
} }
option.onclick = () => {
editSelectedProvince = province.province;
editSelectedCity = '';
updateEditRegionDisplay();
// 生成城市选项
generateEditCityOptions(province.cities);
// 直接滚动城市列表到顶部
setTimeout(() => {
document.getElementById('editCityList').scrollTop = 0;
}, 100);
};
provinceList.appendChild(option);
});
// 更新区县选项的选中状态 // 自动选择第一个匹配的省份和城市
const districtList = document.getElementById('editDistrictList'); if (filteredProvinces.length > 0) {
const districtOptions = districtList.querySelectorAll('.select-item'); editSelectedProvince = filteredProvinces[0].province;
districtOptions.forEach(option => { // 查找第一个匹配的城市
if (option.textContent === editSelectedDistrict) { const matchedCity = filteredProvinces[0].cities.find(city =>
option.classList.add('selected'); city.city.toLowerCase().includes(searchKeyword)
} );
}); if (matchedCity) {
} else { editSelectedCity = matchedCity.city;
// 否则按原逻辑生成所有省份 }
generateEditRegionOptions(); updateEditRegionDisplay();
// 清空城市和区县选项 generateEditCityOptions(filteredProvinces[0].cities);
document.getElementById('editCityList').innerHTML = '';
document.getElementById('editDistrictList').innerHTML = '';
} }
updateEditRegionDisplay();
} }
// 确认编辑地区选择 // 确认编辑地区选择
@ -7692,11 +7838,17 @@
if (editSelectedProvince && editSelectedCity) { if (editSelectedProvince && editSelectedCity) {
const regionDisplayText = document.getElementById('editRegionDisplayText'); const regionDisplayText = document.getElementById('editRegionDisplayText');
const regionValue = document.getElementById('editRegionValue'); const regionValue = document.getElementById('editRegionValue');
const regionText = `${editSelectedProvince} ${editSelectedCity}`; let regionText = editSelectedProvince;
if (editSelectedCity) {
regionText = `${editSelectedProvince} ${editSelectedCity}`;
}
regionDisplayText.textContent = regionText; regionDisplayText.textContent = regionText;
regionValue.value = regionText; regionValue.value = regionText;
saveFormData(); // 保存选择
hideEditRegionSelectModal();
} else {
alert('请选择完整的地区信息');
} }
hideEditRegionSelectModal();
} }
// 编辑货源类型选择功能 // 编辑货源类型选择功能
@ -8078,12 +8230,27 @@
// 保存编辑货源 // 保存编辑货源
async function saveEditSupply() { async function saveEditSupply() {
// 防止重复点击
if (isOperating) {
return false;
}
if (!currentEditSupplyId) { if (!currentEditSupplyId) {
alert('货源ID不存在'); alert('货源ID不存在');
return false; return false;
} }
// 在编辑操作开始前就获取原始数据,并创建深拷贝,避免引用关系导致原始数据被修改
const originalSupplyTemp = supplyData.supplies.find(s => String(s.id) === String(currentEditSupplyId));
if (!originalSupplyTemp) {
alert('找不到要编辑的货源数据');
return false;
}
// 创建深拷贝,确保原始数据不会被后续操作修改
const originalSupply = JSON.parse(JSON.stringify(originalSupplyTemp));
try { try {
isOperating = true;
// 获取规格和件数数据 // 获取规格和件数数据
const pairs = document.querySelectorAll('#editSpecQuantityPairs .spec-quantity-pair'); const pairs = document.querySelectorAll('#editSpecQuantityPairs .spec-quantity-pair');
const specifications = []; const specifications = [];
@ -8194,6 +8361,13 @@
const result = await response.json(); const result = await response.json();
if (result.success) { if (result.success) {
// 记录编辑货源日志
const userInfo = checkLogin();
if (userInfo) {
// 使用之前保存的原始数据(编辑前的数据)
await logSupplyOperation('修改', currentEditSupplyId, originalSupply, result.data);
}
// 向WebSocket服务器发送消息,通知其他客户端货源已更新 // 向WebSocket服务器发送消息,通知其他客户端货源已更新
sendWebSocketMessage({ sendWebSocketMessage({
type: 'supply_update', type: 'supply_update',
@ -8232,6 +8406,8 @@
console.error('编辑失败:', error); console.error('编辑失败:', error);
alert('编辑失败: 网络错误'); alert('编辑失败: 网络错误');
return false; return false;
} finally {
isOperating = false;
} }
} }

Loading…
Cancel
Save