diff --git a/Reject.js b/Reject.js index c086f8c..28a2df6 100644 --- a/Reject.js +++ b/Reject.js @@ -3,10 +3,88 @@ const bodyParser = require('body-parser'); const cors = require('cors'); const mysql = require('mysql2/promise'); const path = require('path'); +const http = require('http'); +const WebSocket = require('ws'); const OssUploader = require('./oss-uploader'); const app = express(); const PORT = 3000; +// 创建HTTP服务器 +const server = http.createServer(app); +// 创建WebSocket服务器 +const wss = new WebSocket.Server({ server }); + +// 客户端连接集合 +const clients = new Set(); + +// WebSocket连接处理 +wss.on('connection', (ws) => { + console.log('WebSocket客户端已连接'); + // 将新连接的客户端添加到集合中 + clients.add(ws); + + // 接收消息处理 + ws.on('message', (message) => { + try { + const data = JSON.parse(message); + console.log('收到WebSocket消息:', data); + + // 处理不同类型的消息 + switch (data.type) { + case 'login': + // 登录消息,存储用户信息 + ws.userId = data.userId; + console.log(`用户 ${data.userId} 已登录`); + break; + case 'pong': + // 心跳响应,无需处理 + break; + default: + console.log('未知消息类型:', data.type); + } + } catch (error) { + console.error('解析WebSocket消息失败:', error); + } + }); + + // 连接关闭处理 + ws.on('close', () => { + console.log('WebSocket客户端已断开连接'); + clients.delete(ws); + }); + + // 连接错误处理 + ws.on('error', (error) => { + console.error('WebSocket连接错误:', error); + }); +}); + +// 广播消息给所有客户端 +function broadcastMessage(message) { + const messageStr = JSON.stringify(message); + clients.forEach((client) => { + if (client.readyState === WebSocket.OPEN) { + client.send(messageStr); + } + }); +} + +// 广播消息给特定用户 +function broadcastToUser(userId, message) { + const messageStr = JSON.stringify(message); + clients.forEach((client) => { + if (client.readyState === WebSocket.OPEN && client.userId === userId) { + client.send(messageStr); + } + }); +} + +// 启动心跳机制,每30秒发送一次ping消息 +setInterval(() => { + broadcastMessage({ type: 'ping' }); + // console.log('发送心跳消息: ping'); +}, 30000); + // 配置CORS app.use(cors()); app.use((req, res, next) => { @@ -873,6 +951,33 @@ app.post('/api/supplies/create', async (req, res) => { await connection.commit(); connection.release(); + // 发送WebSocket消息通知所有客户端 + broadcastMessage({ + type: 'supply_create', + supplyId: result.insertId, + action: 'create', + data: { + id: result.insertId, + productId: productId, + productName, + category: category || '', + price: price.toString(), + costprice: costprice || '', + quantity, + grossWeight, + yolk, + specification, + producting: producting || '', + region, + status: 'published', + supplyStatus: supplyStatus || '', + sourceType: sourceType || '', + description: description || '', + sellerId, + imageUrls: uploadedImageUrls + } + }); + sendResponse(res, true, { productId: result.insertId }, '货源创建成功'); } catch (error) { if (connection) { @@ -1023,9 +1128,9 @@ app.post('/api/supplies/:id/unpublish', async (req, res) => { return sendResponse(res, false, null, '货源不存在'); } - // 更新状态为下架 + // 更新状态为已下架 await connection.query( - 'UPDATE products SET status = ? WHERE id = ?', + 'UPDATE products SET status = ?, updated_at = NOW() WHERE id = ?', ['sold_out', productId] ); @@ -1033,6 +1138,14 @@ app.post('/api/supplies/:id/unpublish', async (req, res) => { await connection.commit(); connection.release(); + // 发送WebSocket消息通知所有客户端 + broadcastMessage({ + type: 'supply_status_change', + supplyId: productId, + action: 'unpublish', + status: 'sold_out' + }); + sendResponse(res, true, null, '货源下架成功'); } catch (error) { console.error('下架货源失败:', error.message); @@ -1052,9 +1165,9 @@ app.post('/api/supplies/:id/publish', async (req, res) => { // 开始事务 await connection.beginTransaction(); - // 检查当前状态 + // 检查当前状态和label const [currentProduct] = await connection.query( - 'SELECT status FROM products WHERE id = ?', + 'SELECT status, label FROM products WHERE id = ?', [productId] ); @@ -1064,6 +1177,13 @@ app.post('/api/supplies/:id/publish', async (req, res) => { return sendResponse(res, false, null, '货源不存在'); } + // 检查是否被锁定 + if (currentProduct[0].label === 1) { + await connection.rollback(); + connection.release(); + return sendResponse(res, false, null, '货源已被锁定,无法上架'); + } + // 更新状态为已发布(直接上架,不需要审核),同时更新updated_at和autoOfflineHours await connection.query( 'UPDATE products SET status = ?, updated_at = NOW() WHERE id = ?', @@ -1074,7 +1194,15 @@ app.post('/api/supplies/:id/publish', async (req, res) => { await connection.commit(); connection.release(); - sendResponse(res, true, null, '货源已成功上架'); + // 发送WebSocket消息通知所有客户端 + broadcastMessage({ + type: 'supply_status_change', + supplyId: productId, + action: 'publish', + status: 'published' + }); + + sendResponse(res, true, null, '货源上架成功'); } catch (error) { console.error('上架货源失败:', error.message); console.error('错误详情:', error); @@ -1115,6 +1243,13 @@ app.post('/api/supplies/:id/delete', async (req, res) => { await connection.commit(); connection.release(); + // 发送WebSocket消息通知所有客户端 + broadcastMessage({ + type: 'supply_update', + supplyId: productId, + action: 'delete' + }); + sendResponse(res, true, null, '货源删除成功'); } catch (error) { console.error('删除货源失败:', error.message); @@ -1155,7 +1290,7 @@ app.put('/api/supplies/:id', async (req, res) => { // 更新状态 await connection.query( - 'UPDATE products SET status = ? WHERE id = ?', + 'UPDATE products SET status = ?, updated_at = NOW() WHERE id = ?', [status, productId] ); @@ -1163,7 +1298,15 @@ app.put('/api/supplies/:id', async (req, res) => { await connection.commit(); connection.release(); - sendResponse(res, true, null, '货源状态更新成功'); + // 发送WebSocket消息通知所有客户端 + broadcastMessage({ + type: 'supply_status_change', + supplyId: productId, + action: 'update_status', + status: status + }); + + sendResponse(res, true, null, '状态更新成功'); } catch (error) { console.error('更新货源状态失败:', error.message); console.error('错误详情:', error); @@ -1183,9 +1326,9 @@ app.put('/api/supplies/:id/edit', async (req, res) => { // 开始事务 await connection.beginTransaction(); - // 检查当前状态 + // 检查当前状态和label const [currentProduct] = await connection.query( - 'SELECT status FROM products WHERE id = ?', + 'SELECT status, label FROM products WHERE id = ?', [productId] ); @@ -1195,6 +1338,13 @@ app.put('/api/supplies/:id/edit', async (req, res) => { return sendResponse(res, false, null, '货源不存在'); } + // 检查是否被锁定 + if (currentProduct[0].label === 1) { + await connection.rollback(); + connection.release(); + return sendResponse(res, false, null, '货源已被锁定,无法编辑'); + } + // 处理联系人信息 let productContact = ''; let contactPhone = ''; @@ -1298,6 +1448,28 @@ app.put('/api/supplies/:id/edit', async (req, res) => { await connection.commit(); connection.release(); + // 发送WebSocket消息通知所有客户端 + broadcastMessage({ + type: 'supply_update', + supplyId: productId, + action: 'update', + data: { + id: productId, + productName, + price: price.toString(), + costprice: costprice || '', + quantity, + grossWeight, + yolk, + specification, + supplyStatus, + description, + region, + producting, + imageUrls: uploadedImageUrls.length > 0 ? uploadedImageUrls : imageUrls + } + }); + sendResponse(res, true, null, '货源编辑成功'); } catch (error) { console.error('编辑货源失败:', error.message); @@ -1392,9 +1564,10 @@ async function startServer() { try { await initDatabase(); - app.listen(PORT, () => { + server.listen(PORT, () => { console.log(`服务器已启动,监听端口 ${PORT}`); console.log(`访问地址: http://localhost:${PORT}`); + console.log(`WebSocket服务已启动,端口: ${PORT}`); }); } catch (error) { console.error('服务器启动失败:', error.message); diff --git a/package.json b/package.json index 74cddf2..b0e33db 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "cors": "^2.8.5", "express": "^5.1.0", "mysql2": "^3.15.3", - "sharp": "^0.34.5" + "sharp": "^0.34.5", + "ws": "^8.19.0" }, "devDependencies": { "chai": "^6.2.1", diff --git a/supply.html b/supply.html index 7a60cd5..51a5c1e 100644 --- a/supply.html +++ b/supply.html @@ -2054,6 +2054,7 @@ // 联系人数据 let contacts = []; + let isLoadingContacts = false; // 标记联系人是否正在加载中 // 登录检查 function checkLogin() { @@ -2090,6 +2091,154 @@ return parsedUserInfo; } + // WebSocket连接管理 + let ws = null; + let wsReconnectAttempts = 0; + const maxReconnectAttempts = 5; + const reconnectDelay = 3000; // 3秒 + + // 保存定时器引用,方便后续清理 + let timers = { + loadSupplies: null, + updateCountdowns: null, + checkAutoOffline: null + }; + + // 防止loadSupplies并发执行的标志 + let isLoadingSupplies = false; + + // 初始化WebSocket连接 + function initWebSocket() { + // 获取当前页面的协议和主机 + const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; + const host = window.location.host; + const wsUrl = `${protocol}//${host}/ws`; + + console.log('正在尝试连接WebSocket服务器:', wsUrl); + + // 关闭现有的WebSocket连接,避免连接泄漏 + if (ws) { + try { + // 关闭码1000表示正常关闭 + ws.close(1000, '重新连接'); + ws = null; + } catch (error) { + console.error('关闭旧WebSocket连接失败:', error); + ws = null; + } + } + + try { + ws = new WebSocket(wsUrl); + + // 连接打开事件 + ws.onopen = function() { + console.log('WebSocket连接已打开'); + wsReconnectAttempts = 0; + // 发送登录用户信息 + const userInfo = JSON.parse(localStorage.getItem('userInfo')); + if (userInfo) { + ws.send(JSON.stringify({ + type: 'login', + userId: userInfo.userId || userInfo.id + })); + } + }; + + // 消息接收事件 + 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); + // 只有在非正常关闭时才重连 + // 正常关闭码1000表示主动关闭,不需要重连 + if (event.code !== 1000) { + attemptReconnect(); + } + }; + + // 连接错误事件 + ws.onerror = function(error) { + console.error('WebSocket错误:', error); + console.error('WebSocket错误详情:', error.message); + // 错误发生后,onclose事件会被触发,由onclose处理重连 + }; + } catch (error) { + console.error('WebSocket连接失败:', error); + attemptReconnect(); + } + } + + // 尝试重新连接 + function attemptReconnect() { + 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); + + switch (data.type) { + case 'supply_update': + // 货源更新通知,重新加载数据 + console.log('收到货源更新通知,重新加载数据'); + loadSupplies(); + break; + case 'supply_lock': + // 货源锁定状态更新 + console.log('收到货源锁定状态更新,重新加载数据'); + loadSupplies(); + break; + case 'supply_status_change': + // 货源状态变更 + console.log('收到货源状态变更,重新加载数据'); + loadSupplies(); + break; + case 'auto_offline': + // 自动下架通知 + console.log('收到自动下架通知,重新加载数据'); + loadSupplies(); + break; + case 'ping': + // 心跳响应 + if (ws && ws.readyState === WebSocket.OPEN) { + ws.send(JSON.stringify({ type: 'pong' })); + } + break; + default: + console.log('未知的WebSocket消息类型:', data.type); + } + } catch (error) { + console.error('解析WebSocket消息失败:', error); + } + } + + // 向WebSocket服务器发送消息 + function sendWebSocketMessage(message) { + if (ws && ws.readyState === WebSocket.OPEN) { + ws.send(JSON.stringify(message)); + } else { + console.warn('WebSocket未连接,无法发送消息'); + } + } + // 初始化 window.onload = function() { // 登录检查 @@ -2098,6 +2247,9 @@ return; } + // 初始化WebSocket连接 + initWebSocket(); + loadSupplies(); loadContacts(); loadFormData(); // 加载保存的表单数据,包括联系人信息 @@ -2111,48 +2263,70 @@ startCountdowns(); // 启动自动检查下架时间 startAutoOfflineCheck(); + + // 启动定期刷新数据,确保label字段变化能实时显示 + timers.loadSupplies = setInterval(loadSupplies, 5000); // 每5秒刷新一次 }; + // 页面卸载时清理资源 + window.addEventListener('beforeunload', function() { + console.log('页面正在卸载,清理资源...'); + + // 关闭WebSocket连接 + if (ws) { + ws.close(1000, '页面卸载'); + ws = null; + } + + // 清除所有定时器 + for (const timerName in timers) { + if (timers[timerName]) { + clearInterval(timers[timerName]); + timers[timerName] = null; + } + } + }); + // 启动倒计时 function startCountdowns() { // 立即更新一次倒计时 updateCountdowns(); // 每秒更新一次倒计时 - setInterval(updateCountdowns, 1000); + timers.updateCountdowns = setInterval(updateCountdowns, 1000); } // 更新倒计时显示 function updateCountdowns() { - console.log('=== 更新倒计时 ==='); + // console.log('=== 更新倒计时 ==='); // 更新所有倒计时元素 const countdownElements = document.querySelectorAll('.countdown-badge, .countdown'); - console.log('找到的倒计时元素数量:', countdownElements.length); + // console.log('找到的倒计时元素数量:', countdownElements.length); countdownElements.forEach(element => { const supplyId = element.dataset.id; - console.log('处理倒计时元素,supplyId:', supplyId); + // console.log('处理倒计时元素,supplyId:', supplyId); // 查找对应的货源 const supply = supplyData.supplies.find(s => String(s.id) === String(supplyId)); if (supply) { - console.log('找到对应的货源:', supply.id, supply.productName); + // console.log('找到对应的货源:', supply.id, supply.productName); // 检查是否有显式设置的自动下架时间 if (supply.autoOfflineHours && supply.autoOfflineHours !== '' && supply.autoOfflineHours !== null) { - console.log('有显式设置的自动下架时间'); + // console.log('有显式设置的自动下架时间'); // 计算下架时间:updated_at + autoOfflineHours(使用毫秒数计算,更精确) const updatedAt = new Date(supply.updated_at || supply.created_at); - console.log('updatedAt:', updatedAt, 'supply.updated_at:', supply.updated_at, 'supply.created_at:', supply.created_at); + // console.log('updatedAt:', updatedAt, 'supply.updated_at:', supply.updated_at, 'supply.created_at:', supply.created_at); const autoOfflineHours = parseFloat(supply.autoOfflineHours); - console.log('autoOfflineHours:', autoOfflineHours, 'supply.autoOfflineHours:', supply.autoOfflineHours); + // console.log('autoOfflineHours:', autoOfflineHours, 'supply.autoOfflineHours:', supply.autoOfflineHours); // 将小时转换为毫秒,使用setTime方法更精确 const autoOfflineMs = autoOfflineHours * 60 * 60 * 1000; const offlineTime = new Date(updatedAt.getTime() + autoOfflineMs); - console.log('offlineTime:', offlineTime); + // console.log('offlineTime:', offlineTime); const now = new Date(); - console.log('当前时间:', now); + // console.log('当前时间:', now); const timeDiff = offlineTime - now; - console.log('时间差:', timeDiff, '毫秒'); + // console.log('时间差:', timeDiff, '毫秒'); if (timeDiff > 0) { // 计算剩余下架时间 @@ -2214,45 +2388,45 @@ // 启动自动检查下架时间 function startAutoOfflineCheck() { // 每30秒检查一次,平衡及时性和服务器负载 - setInterval(checkAutoOffline, 30000); + timers.checkAutoOffline = setInterval(checkAutoOffline, 30000); // 立即检查一次 checkAutoOffline(); } // 检查自动下架时间 async function checkAutoOffline() { - console.log('=== 开始检查自动下架时间 ==='); + // console.log('=== 开始检查自动下架时间 ==='); const allSupplies = supplyData.supplies; const now = new Date(); - console.log('检查的货源总数:', allSupplies.length); + // console.log('检查的货源总数:', allSupplies.length); for (const supply of allSupplies) { - console.log('检查货源:', supply.id, supply.productName, '当前状态:', supply.status); + // console.log('检查货源:', supply.id, supply.productName, '当前状态:', supply.status); // 检查所有状态为published的货源,不管前端显示什么 if (supply.status === 'published') { - console.log('处理上架状态的货源:', supply.id); + // console.log('处理上架状态的货源:', supply.id); // 只有在显式设置了自动下架时间的情况下才应用自动下架逻辑 if (supply.autoOfflineHours && supply.autoOfflineHours !== '' && supply.autoOfflineHours !== null) { - console.log('有显式设置的自动下架时间'); + // console.log('有显式设置的自动下架时间'); // 计算下架时间:updated_at + autoOfflineHours(使用毫秒数计算,更精确) const updatedAt = new Date(supply.updated_at || supply.created_at); const autoOfflineHours = parseFloat(supply.autoOfflineHours); // 只转换,不设置默认值 - console.log('autoOfflineHours:', autoOfflineHours); + // console.log('autoOfflineHours:', autoOfflineHours); // 将小时转换为毫秒,使用setTime方法更精确 const autoOfflineMs = autoOfflineHours * 60 * 60 * 1000; const offlineTime = new Date(updatedAt.getTime() + autoOfflineMs); - console.log('计算的下架时间:', offlineTime, '当前时间:', now); + // console.log('计算的下架时间:', offlineTime, '当前时间:', now); if (now >= offlineTime) { // 时间到了,修改状态 - console.log('时间到了,调用updateSupplyStatus:', supply.id, '从published改为sold_out'); + // console.log('时间到了,调用updateSupplyStatus:', supply.id, '从published改为sold_out'); await updateSupplyStatus(supply.id, 'sold_out'); } } else { - console.log('没有显式设置自动下架时间,跳过自动下架检查'); + // console.log('没有显式设置自动下架时间,跳过自动下架检查'); } } } @@ -2271,6 +2445,14 @@ }); if (response.ok) { + // 向WebSocket服务器发送消息,通知其他客户端货源状态已更新 + sendWebSocketMessage({ + type: 'supply_status_change', + supplyId: supplyId, + action: 'update_status', + status: status + }); + // 重新加载货源列表 loadSupplies(); console.log(`货源 ${supplyId} 状态已更新为 ${status}`); @@ -2975,8 +3157,23 @@ contactIdSelectModal.classList.add('active'); // 重置搜索输入 document.getElementById('contactIdSearchInput').value = ''; - filteredContactIdOptions = [...contacts]; - generateContactIdOptions(); + + // 检查contacts数组是否为空,如果为空则尝试重新加载 + if (contacts.length === 0) { + console.log('contacts数组为空,尝试重新加载...'); + loadContacts().then(() => { + filteredContactIdOptions = [...contacts]; + generateContactIdOptions(); + }).catch(error => { + console.error('重新加载联系人失败:', error); + // 即使加载失败,也要确保UI更新 + filteredContactIdOptions = [...contacts]; + generateContactIdOptions(); + }); + } else { + filteredContactIdOptions = [...contacts]; + generateContactIdOptions(); + } } // 隐藏联系人选择弹窗 @@ -3841,15 +4038,25 @@ // 加载联系人数据 async function loadContacts() { + // 防止并发执行 + if (isLoadingContacts) { + console.log('loadContacts已在执行中,跳过当前请求'); + return Promise.resolve(); + } + + isLoadingContacts = true; + try { + console.log('开始加载联系人数据...'); const response = await fetch('/api/contacts'); if (!response.ok) { - throw new Error('服务器响应异常'); + throw new Error(`服务器响应异常: ${response.status} ${response.statusText}`); } const result = await response.json(); + console.log('联系人API返回结果:', result); // 确保contacts是一个数组 contacts = result.data || []; - console.log('联系人数据加载成功:', contacts); + console.log('联系人数据加载成功,共', contacts.length, '个联系人:', contacts); // 保存到本地缓存,添加时间戳和版本号 const contactsCache = { @@ -3872,7 +4079,7 @@ const cacheExpiry = 7 * 24 * 60 * 60 * 1000; if (Date.now() - contactsCache.timestamp < cacheExpiry) { contacts = contactsCache.data || []; - console.log('从本地缓存加载联系人数据:', contacts); + console.log('从本地缓存加载联系人数据,共', contacts.length, '个联系人:', contacts); updateContactSelects(); return; } else { @@ -3885,6 +4092,9 @@ // 出错且无有效缓存时,将contacts设为空数组 contacts = []; + } finally { + // 无论成功还是失败,都要重置加载状态 + isLoadingContacts = false; } } @@ -3922,6 +4132,14 @@ // 加载货源列表 async function loadSupplies() { + // 防止并发执行 + if (isLoadingSupplies) { + console.log('loadSupplies已在执行中,跳过当前请求'); + return; + } + + isLoadingSupplies = true; + try { // 获取当前登录用户信息 const userInfo = checkLogin(); @@ -3962,6 +4180,9 @@ } } catch (error) { console.error('加载货源失败:', error); + } finally { + // 无论成功还是失败,都要重置加载状态 + isLoadingSupplies = false; } } @@ -5512,6 +5733,15 @@ // 重置表单状态,确保下次打开时是空白状态 resetForm(); hideAddSupplyModal(); + + // 向WebSocket服务器发送消息,通知其他客户端有新的货源创建 + sendWebSocketMessage({ + type: 'supply_create', + supplyId: result.data.id, + action: 'create', + data: result.data + }); + loadSupplies(); } else { // 创建失败,保持保存的数据 @@ -5890,6 +6120,14 @@ }); const result = await response.json(); if (result.success) { + // 向WebSocket服务器发送消息,通知其他客户端货源已下架 + sendWebSocketMessage({ + type: 'supply_status_change', + supplyId: id, + action: 'unpublish', + status: 'hidden' + }); + alert('下架成功'); loadSupplies(); } else { @@ -5912,6 +6150,13 @@ }); const result = await response.json(); if (result.success) { + // 向WebSocket服务器发送消息,通知其他客户端货源已删除 + sendWebSocketMessage({ + type: 'supply_update', + supplyId: id, + action: 'delete' + }); + alert('删除成功'); loadSupplies(); } else { @@ -5972,6 +6217,9 @@ return; } + // 检查是否被锁定 + const isLocked = supply.label === 1; + currentEditSupplyId = id; // 保存发布模式状态 window.currentEditPublishMode = publishMode; @@ -6122,18 +6370,68 @@ // 保存当前图片列表到全局变量,用于编辑时使用 editCurrentImages = supply.imageUrls && Array.isArray(supply.imageUrls) ? [...supply.imageUrls] : []; - // 根据模式修改保存按钮文本 + // 根据模式修改保存按钮文本和状态 const saveButton = document.querySelector('#editSupplyModal .modal-btn-primary'); if (saveButton) { - if (publishMode) { - saveButton.textContent = '上架'; - saveButton.onclick = publishSupplyAfterEdit; + if (isLocked) { + // 锁定状态:按钮变灰色,不可点击 + saveButton.textContent = publishMode ? '已锁定' : '已锁定'; + saveButton.className = 'btn-disabled'; + saveButton.onclick = null; } else { - saveButton.textContent = '保存'; - saveButton.onclick = saveEditSupply; + // 未锁定状态:正常功能 + if (publishMode) { + saveButton.textContent = '上架'; + saveButton.className = 'btn-primary'; + saveButton.onclick = publishSupplyAfterEdit; + } else { + saveButton.textContent = '保存'; + saveButton.className = 'btn-primary'; + saveButton.onclick = saveEditSupply; + } } } + // 设置表单元素的只读状态 + const formElements = document.querySelectorAll('#editSupplyModal input, #editSupplyModal select, #editSupplyModal textarea, #editSupplyModal .modal-select-btn, #editSupplyModal .add-quantity-btn, #editSupplyModal .remove-quantity-btn, #editSupplyModal .delete-image-btn'); + formElements.forEach(element => { + if (isLocked) { + // 锁定状态:设置为只读或禁用 + if (element.tagName === 'INPUT' || element.tagName === 'SELECT' || element.tagName === 'TEXTAREA') { + element.readOnly = true; + element.style.opacity = '0.6'; + element.style.cursor = 'not-allowed'; + } else { + // 按钮元素 + element.disabled = true; + element.className = 'btn-disabled'; + element.style.cursor = 'not-allowed'; + } + } else { + // 未锁定状态:恢复正常 + if (element.tagName === 'INPUT' || element.tagName === 'SELECT' || element.tagName === 'TEXTAREA') { + // 只有规格输入框是只读的,其他可以编辑 + if (element.className.includes('spec-value')) { + element.readOnly = true; + } else { + element.readOnly = false; + } + element.style.opacity = '1'; + element.style.cursor = 'default'; + } else { + // 按钮元素 + element.disabled = false; + // 恢复按钮原有样式 + if (element.className.includes('modal-select-btn')) { + element.className = 'modal-select-btn'; + } else if (element.className.includes('add-quantity-btn')) { + element.className = 'add-quantity-btn'; + } + element.style.cursor = 'pointer'; + } + } + }); + // 显示编辑模态框 document.getElementById('editSupplyModal').classList.add('active'); // 隐藏body滚动条,避免双滚动条 @@ -6186,6 +6484,14 @@ const result = await response.json(); if (result.success) { + // 向WebSocket服务器发送消息,通知其他客户端货源已上架 + sendWebSocketMessage({ + type: 'supply_status_change', + supplyId: id, + action: 'publish', + status: 'published' + }); + alert('上架成功'); loadSupplies(); } else { @@ -7003,8 +7309,23 @@ const contactIdSelectModal = document.getElementById('editContactIdSelectModal'); contactIdSelectModal.classList.add('active'); document.getElementById('editContactIdSearchInput').value = ''; - editFilteredContacts = [...contacts]; - generateEditContactIdOptions(); + + // 检查contacts数组是否为空,如果为空则尝试重新加载 + if (contacts.length === 0) { + console.log('contacts数组为空,尝试重新加载...'); + loadContacts().then(() => { + editFilteredContacts = [...contacts]; + generateEditContactIdOptions(); + }).catch(error => { + console.error('重新加载联系人失败:', error); + // 即使加载失败,也要确保UI更新 + editFilteredContacts = [...contacts]; + generateEditContactIdOptions(); + }); + } else { + editFilteredContacts = [...contacts]; + generateEditContactIdOptions(); + } } // 隐藏编辑联系人选择弹窗 @@ -7203,6 +7524,14 @@ const result = await response.json(); if (result.success) { + // 向WebSocket服务器发送消息,通知其他客户端货源已更新 + sendWebSocketMessage({ + type: 'supply_update', + supplyId: currentEditSupplyId, + action: 'update', + data: result.data + }); + // 如果是直接编辑,显示成功消息;如果是编辑后上架,不显示,由上架函数处理 if (!window.currentEditPublishMode) { alert('编辑成功');