From ac01b1be370e63c9a2b355e3d7460ac524a3e831 Mon Sep 17 00:00:00 2001 From: Default User Date: Wed, 7 Jan 2026 14:45:07 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=BE=9B=E5=BA=94=E9=93=BE?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E7=B3=BB=E7=BB=9F=E4=BB=A3=E7=A0=81=EF=BC=9A?= =?UTF-8?q?=201.=20=E6=B7=BB=E5=8A=A0.gitignore=E6=96=87=E4=BB=B6=EF=BC=8C?= =?UTF-8?q?=E5=BF=BD=E7=95=A5=E4=B8=8D=E5=BF=85=E8=A6=81=E7=9A=84=E6=96=87?= =?UTF-8?q?=E4=BB=B6=202.=20=E4=BF=AE=E6=94=B9Reject.js=E6=96=87=E4=BB=B6?= =?UTF-8?q?=203.=20=E4=BF=AE=E6=94=B9supply.html=E6=96=87=E4=BB=B6?= =?UTF-8?q?=EF=BC=8C=E4=BF=AE=E5=A4=8D=E4=BA=8B=E4=BB=B6=E7=9B=91=E5=90=AC?= =?UTF-8?q?=E5=99=A8=E9=87=8D=E5=A4=8D=E7=BB=91=E5=AE=9A=E3=80=81DOM?= =?UTF-8?q?=E7=A9=BA=E6=8C=87=E9=92=88=E5=92=8C=E6=95=B0=E6=8D=AE=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E8=BD=AC=E6=8D=A2=E9=97=AE=E9=A2=98=204.=20=E5=88=A0?= =?UTF-8?q?=E9=99=A4Review=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 124 +++++++++++++++ Reject.js | 86 ++++++---- Review | 1 - supply.html | 442 +++++++++++++++++++++++++++++++++++++++++++--------- 4 files changed, 551 insertions(+), 102 deletions(-) create mode 100644 .gitignore delete mode 160000 Review diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d354dbf --- /dev/null +++ b/.gitignore @@ -0,0 +1,124 @@ +# Dependencies +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +# Build outputs +dist/ +build/ + +# Environment variables +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# IDE and editor files +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS generated files +Thumbs.db +.DS_Store + +# Log files +logs +*.log + +# Temporary files +tmp/ +temp/ + +# Database files +*.sqlite +*.sqlite3 + +# Coverage directory used by tools like istanbul +coverage/ +*.lcov + +# nyc test coverage +.nyc_output + +# Dependency directories +jspm_packages/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* \ No newline at end of file diff --git a/Reject.js b/Reject.js index 28a2df6..be708d7 100644 --- a/Reject.js +++ b/Reject.js @@ -85,6 +85,47 @@ setInterval(() => { // console.log('发送心跳消息: ping'); }, 30000); +// 自动下架任务 - 每分钟检查一次 +setInterval(async () => { + try { + console.log('检查需要自动下架的商品...'); + const connection = await pool.getConnection(); + + // 查找所有状态为published且autoOfflineTime已过的商品 + const currentTime = new Date(); + const [productsToOffline] = await connection.query( + 'SELECT id FROM products WHERE status = ? AND autoOfflineTime <= ? AND autoOfflineTime IS NOT NULL', + ['published', currentTime] + ); + + if (productsToOffline.length > 0) { + console.log(`发现${productsToOffline.length}个商品需要自动下架`); + + // 批量更新商品状态为sold_out + for (const product of productsToOffline) { + await connection.query( + 'UPDATE products SET status = ?, updated_at = NOW() WHERE id = ?', + ['sold_out', product.id] + ); + + // 发送WebSocket消息通知所有客户端 + broadcastMessage({ + type: 'supply_status_change', + supplyId: product.id, + action: 'unpublish', + status: 'sold_out' + }); + } + + console.log('自动下架任务完成'); + } + + connection.release(); + } catch (error) { + console.error('自动下架任务失败:', error.message); + } +}, 60000); // 每分钟检查一次 + // 配置CORS app.use(cors()); app.use((req, res, next) => { @@ -192,31 +233,31 @@ app.get('/api/supplies', async (req, res) => { let whereClause = ''; let params = []; + // 默认过滤掉status为hidden的货源,实现软删除效果 + whereClause += ` WHERE status != 'hidden'`; + // 添加搜索条件 if (actualSearch) { - whereClause += ` WHERE (p.id LIKE ? OR p.productId LIKE ? OR p.productName LIKE ?)`; + whereClause += ` AND (p.id LIKE ? OR p.productId LIKE ? OR p.productName LIKE ?)`; params.push(`%${actualSearch}%`, `%${actualSearch}%`, `%${actualSearch}%`); } // 添加手机号搜索 if (phoneNumber) { - whereClause += actualSearch ? ' AND' : ' WHERE'; - whereClause += ` u.phoneNumber LIKE ?`; + whereClause += ` AND u.phoneNumber LIKE ?`; params.push(`%${phoneNumber}%`); } // 添加状态筛选 if (status) { - whereClause += (actualSearch || phoneNumber) ? ' AND' : ' WHERE'; - whereClause += ` status = ?`; + whereClause += ` AND status = ?`; params.push(status); } // 添加sellerId筛选,只返回指定用户的货源 const { sellerId } = req.query; if (sellerId) { - whereClause += (actualSearch || phoneNumber || status) ? ' AND' : ' WHERE'; - whereClause += ` p.sellerId = ?`; + whereClause += ` AND p.sellerId = ?`; params.push(sellerId); } @@ -799,17 +840,8 @@ app.post('/api/supplies/create', async (req, res) => { sellerId = 'default_seller'; } - // 检查是否为重复货源 - console.log('检查是否为重复货源...'); - const [existingProducts] = await connection.query( - 'SELECT id FROM products WHERE productName = ? AND specification = ? AND region = ? AND price = ? AND yolk = ?', - [productName, specification, region, price, yolk] - ); - - if (existingProducts.length > 0) { - connection.release(); - return sendResponse(res, false, null, '检测到重复的货源数据,不允许创建'); - } + // 移除重复货源检测限制,允许创建相同货源 + console.log('移除重复货源检测限制,允许创建相同货源...'); // 处理联系人信息 let productContact = ''; @@ -920,18 +952,18 @@ app.post('/api/supplies/create', async (req, res) => { if (columns.length === 0) { // 没有quality字段,不包含quality字段的插入 - insertQuery = 'INSERT INTO products (productId, sellerId, productName, category, price, costprice, quantity, grossWeight, yolk, specification, producting, region, status, supplyStatus, sourceType, description, rejectReason, imageUrls, created_at, audit_time, product_contact, contact_phone, autoOfflineTime, autoOfflineDays, autoOfflineHours) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'; + insertQuery = 'INSERT INTO products (productId, sellerId, productName, category, freshness, price, costprice, quantity, grossWeight, yolk, specification, producting, region, status, supplyStatus, sourceType, description, rejectReason, imageUrls, created_at, audit_time, product_contact, contact_phone, autoOfflineTime, autoOfflineDays, autoOfflineHours) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'; insertParams = [ - productId, productData.sellerId, productName, category || '', price.toString(), costprice || '', quantity, grossWeight, + productId, productData.sellerId, productName, category || '', req.body.freshness || '', price.toString(), costprice || '', quantity, grossWeight, yolk, specification, producting, region, productData.status, productData.supplyStatus, productData.sourceType, productData.description, productData.rejectReason, productData.imageUrls, new Date(), new Date(), productContact, contactPhone, req.body.autoOfflineTime, req.body.autoOfflineDays, req.body.autoOfflineHours // 添加联系人信息和自动下架时间、小时数 ]; } else { // 有quality字段,包含quality字段的插入 - insertQuery = 'INSERT INTO products (productId, sellerId, productName, category, price, costprice, quantity, grossWeight, yolk, specification, producting, quality, region, status, supplyStatus, sourceType, description, rejectReason, imageUrls, created_at, audit_time, product_contact, contact_phone, autoOfflineTime, autoOfflineDays, autoOfflineHours) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'; + insertQuery = 'INSERT INTO products (productId, sellerId, productName, category, freshness, price, costprice, quantity, grossWeight, yolk, specification, producting, quality, region, status, supplyStatus, sourceType, description, rejectReason, imageUrls, created_at, audit_time, product_contact, contact_phone, autoOfflineTime, autoOfflineDays, autoOfflineHours) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'; insertParams = [ - productId, productData.sellerId, productName, category || '', price.toString(), costprice || '', quantity, grossWeight, + productId, productData.sellerId, productName, category || '', req.body.freshness || '', price.toString(), costprice || '', quantity, grossWeight, yolk, specification, producting, quality, region, productData.status, productData.supplyStatus, productData.sourceType, productData.description, productData.rejectReason, productData.imageUrls, new Date(), new Date(), productContact, contactPhone, req.body.autoOfflineTime, req.body.autoOfflineDays, req.body.autoOfflineHours // 添加联系人信息和自动下架时间、小时数 @@ -1233,10 +1265,10 @@ app.post('/api/supplies/:id/delete', async (req, res) => { return sendResponse(res, false, null, '货源不存在'); } - // 删除货源 + // 将货源状态改为hidden,而不是删除 await connection.query( - 'DELETE FROM products WHERE id = ?', - [productId] + 'UPDATE products SET status = ? WHERE id = ?', + ['hidden', productId] ); // 提交事务 @@ -1247,10 +1279,10 @@ app.post('/api/supplies/:id/delete', async (req, res) => { broadcastMessage({ type: 'supply_update', supplyId: productId, - action: 'delete' + action: 'update' }); - sendResponse(res, true, null, '货源删除成功'); + sendResponse(res, true, null, '货源已删除'); } catch (error) { console.error('删除货源失败:', error.message); console.error('错误详情:', error); diff --git a/Review b/Review deleted file mode 160000 index 785502c..0000000 --- a/Review +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 785502c7a13bf341be514c9a73cbd7df2d5c5d40 diff --git a/supply.html b/supply.html index fe53180..5354786 100644 --- a/supply.html +++ b/supply.html @@ -1261,7 +1261,7 @@
3. 敲开鸡蛋后清晰显示蛋清、蛋黄状态,以体现新鲜度;
4. 其他能佐证蛋重、品种的辅助图片。
-
最多上传2张图片
+
最多上传2张图片或视频
@@ -1408,6 +1408,7 @@ @@ -1671,12 +1672,12 @@
+
-
最多上传2张图片
+
最多上传2张图片或视频
-
最多上传2张图片
+
最多上传2张图片或视频
@@ -2539,11 +2540,12 @@ uploadArea.style.backgroundColor = '#fff'; const files = Array.from(e.dataTransfer.files); - // 过滤出图片文件 + // 过滤出图片和视频文件 const imageFiles = files.filter(file => file.type.startsWith('image/')); + const videoFiles = files.filter(file => file.type.startsWith('video/')); - if (imageFiles.length > 0) { - handleDroppedImages(imageFiles); + if (imageFiles.length > 0 || videoFiles.length > 0) { + handleDroppedMedia(imageFiles, videoFiles); } }); @@ -2580,11 +2582,12 @@ editUploadArea.style.padding = '0'; const files = Array.from(e.dataTransfer.files); - // 过滤出图片文件 + // 过滤出图片和视频文件 const imageFiles = files.filter(file => file.type.startsWith('image/')); + const videoFiles = files.filter(file => file.type.startsWith('video/')); - if (imageFiles.length > 0) { - handleEditDroppedImages(imageFiles); + if (imageFiles.length > 0 || videoFiles.length > 0) { + handleEditDroppedMedia(imageFiles, videoFiles); } }); } @@ -2597,9 +2600,10 @@ // 检查是否在创建或编辑货源模态框中(现在使用active类来控制显示) if (createSupplyModal && createSupplyModal.classList.contains('active')) { - // 检查是否有粘贴的图片 + // 检查是否有粘贴的图片和视频 const items = e.clipboardData.items; const imageFiles = []; + const videoFiles = []; for (let i = 0; i < items.length; i++) { if (items[i].type.startsWith('image/')) { @@ -2607,16 +2611,22 @@ if (file) { imageFiles.push(file); } + } else if (items[i].type.startsWith('video/')) { + const file = items[i].getAsFile(); + if (file) { + videoFiles.push(file); + } } } - if (imageFiles.length > 0) { - handleDroppedImages(imageFiles); + if (imageFiles.length > 0 || videoFiles.length > 0) { + handleDroppedMedia(imageFiles, videoFiles); } } else if (editSupplyModal && editSupplyModal.classList.contains('active')) { - // 检查是否有粘贴的图片 + // 检查是否有粘贴的图片和视频 const items = e.clipboardData.items; const imageFiles = []; + const videoFiles = []; for (let i = 0; i < items.length; i++) { if (items[i].type.startsWith('image/')) { @@ -2624,21 +2634,27 @@ if (file) { imageFiles.push(file); } + } else if (items[i].type.startsWith('video/')) { + const file = items[i].getAsFile(); + if (file) { + videoFiles.push(file); + } } } - if (imageFiles.length > 0) { - handleEditDroppedImages(imageFiles); + if (imageFiles.length > 0 || videoFiles.length > 0) { + handleEditDroppedMedia(imageFiles, videoFiles); } } }); } - // 处理拖拽或粘贴的图片 - async function handleDroppedImages(imageFiles) { + // 处理拖拽或粘贴的图片和视频 + async function handleDroppedMedia(imageFiles, videoFiles) { + // 处理图片 for (let i = 0; i < imageFiles.length; i++) { if (supplyData.uploadedImages.length >= 2) { - alert('最多只能上传2张图片'); + alert('最多只能上传2张图片/视频'); break; } @@ -2659,13 +2675,46 @@ reader.readAsDataURL(file); } + + // 处理视频 + for (let i = 0; i < videoFiles.length; i++) { + if (supplyData.uploadedImages.length >= 2) { + alert('最多只能上传2张图片/视频'); + break; + } + + const file = videoFiles[i]; + const reader = new FileReader(); + + reader.onload = function(e) { + try { + const videoUrl = e.target.result; + + supplyData.uploadedImages.push(videoUrl); + renderUploadedImages(); + // 视频上传后自动保存表单数据 + saveFormData(); + } catch (error) { + console.error('视频处理失败:', error); + alert('视频处理失败,请重试'); + } + }; + + reader.onerror = function(error) { + console.error('视频读取失败:', error); + alert('视频读取失败,请重试'); + }; + + reader.readAsDataURL(file); + } } - // 处理编辑页面拖拽或粘贴的图片 - async function handleEditDroppedImages(imageFiles) { + // 处理编辑页面拖拽或粘贴的图片和视频 + async function handleEditDroppedMedia(imageFiles, videoFiles) { + // 处理图片 for (let i = 0; i < imageFiles.length; i++) { if (editCurrentImages.length >= 2) { - alert('最多只能上传2张图片'); + alert('最多只能上传2张图片/视频'); break; } @@ -2684,11 +2733,43 @@ // 更新显示 updateEditImageDisplay(); } catch (error) { - console.error('图片处理失败:', error); - alert('图片处理失败,请重试'); + console.error('媒体处理失败:', error); + alert('媒体处理失败,请重试'); + } + }; + + reader.readAsDataURL(file); + } + + // 处理视频 + for (let i = 0; i < videoFiles.length; i++) { + if (editCurrentImages.length >= 2) { + alert('最多只能上传2张图片/视频'); + break; + } + + const file = videoFiles[i]; + const reader = new FileReader(); + + reader.onload = function(e) { + try { + const videoUrl = e.target.result; + + // 添加到当前编辑图片列表 + editCurrentImages.push(videoUrl); + // 更新显示 + updateEditImageDisplay(); + } catch (error) { + console.error('视频处理失败:', error); + alert('视频处理失败,请重试'); } }; + reader.onerror = function(error) { + console.error('视频读取失败:', error); + alert('视频读取失败,请重试'); + }; + reader.readAsDataURL(file); } } @@ -4423,25 +4504,31 @@ let mediaType = 'image'; // 默认为图片 let mediaPreviewHTML = ''; - if (supply.imageUrls && supply.imageUrls.length > 0) { + // 确保imageUrls是数组 + const imageUrls = Array.isArray(supply.imageUrls) ? supply.imageUrls : + (typeof supply.imageUrls === 'string' ? [supply.imageUrls] : + (supply.imageUrls ? [supply.imageUrls] : [])); + + if (imageUrls.length > 0) { // 获取第一个媒体文件 - firstMediaUrl = supply.imageUrls[0]; + firstMediaUrl = imageUrls[0]; // 检测媒体类型 if (firstMediaUrl.startsWith('data:video/') || - firstMediaUrl.match(/\.(mp4|mov|avi|wmv|flv|webm|mkv)$/i) || - firstMediaUrl.includes('video') || - (supply.imageUrls && supply.imageUrls.some(url => url.startsWith('data:video/') || url.match(/\.(mp4|mov|avi|wmv|flv|webm|mkv)$/i)))) { + firstMediaUrl.match(/\.(mp4|mov|avi|wmv|flv|webm|mkv)$/i)) { mediaType = 'video'; mediaPreviewHTML = ` - -
+
+ +
+
`; } else { mediaType = 'image'; @@ -4450,7 +4537,7 @@ src="${firstMediaUrl}" alt="${supply.productName}" style="width: 100px; height: 100px; object-fit: cover; border-radius: 8px;" - onclick="previewImage('${firstMediaUrl}', ${JSON.stringify(supply.imageUrls || []).replace(/"/g, '"')})" + onclick="previewImage('${firstMediaUrl}', ${JSON.stringify(imageUrls || []).replace(/\"/g, '"')})" > `; } @@ -4787,9 +4874,85 @@ if (productingDisplay) productingDisplay.textContent = supply.producting || '请选择产品包装'; selectedProducting = supply.producting || ''; - // 不复制图片,保持图片列表为空 - supplyData.uploadedImages = []; + // 种类 + const categoryInput = document.getElementById('category'); + if (categoryInput) categoryInput.value = supply.category || ''; + + const categoryDisplay = document.getElementById('categoryDisplayText'); + if (categoryDisplay) categoryDisplay.textContent = supply.category || '请选择种类'; + selectedCategory = supply.category || ''; + + // 新鲜程度 + const freshnessInput = document.getElementById('freshness'); + if (freshnessInput) freshnessInput.value = supply.freshness || ''; + + const freshnessDisplay = document.getElementById('freshnessDisplayText'); + if (freshnessDisplay) freshnessDisplay.textContent = supply.freshness || '请选择新鲜程度'; + selectedFreshness = supply.freshness || ''; + + // 复制图片 + supplyData.uploadedImages = supply.images || supply.imageUrls || supply.imageList || []; renderUploadedImages(); + + // 规格和件数 - 支持中文逗号分隔和英文逗号分隔字符串 + let specifications = []; + let quantities = []; + + // 解析规格(中文逗号分隔字符串) + try { + if (supply.specification) { + if (typeof supply.specification === 'string') { + specifications = supply.specification.split(',').filter(spec => spec.trim()); + } else if (Array.isArray(supply.specification)) { + specifications = supply.specification; + } else { + specifications = [supply.specification]; + } + } else if (supply.spec) { + specifications = [supply.spec]; + } + } catch (e) { + specifications = [supply.specification || supply.spec || '']; + } + + // 解析件数(英文逗号分隔字符串) + try { + if (supply.quantity) { + if (typeof supply.quantity === 'string') { + quantities = supply.quantity.split(',').filter(qty => qty.trim()); + } else if (Array.isArray(supply.quantity)) { + quantities = supply.quantity; + } else { + quantities = [supply.quantity]; + } + } + } catch (e) { + quantities = [supply.quantity || '']; + } + + // 动态生成规格-件数对输入框 + const specQuantityPairs = document.getElementById('specQuantityPairs'); + if (specQuantityPairs) { + // 清空现有对 + specQuantityPairs.innerHTML = ''; + + // 如果没有规格-件数对,添加一个空的 + if (specifications.length === 0) { + addSpecQuantityPair(); + } else { + // 根据规格数量生成相应的规格-件数对 + for (let i = 0; i < specifications.length; i++) { + const pair = document.createElement('div'); + pair.className = 'spec-quantity-pair'; + pair.innerHTML = ` + + + + `; + specQuantityPairs.appendChild(pair); + } + } + } } // 检查是否为重复货源 @@ -5001,6 +5164,22 @@ inStockBtn.style.borderColor = '#d9d9d9'; inStockBtn.style.color = '#666'; } + + // 重置新鲜程度字段 + const freshness = document.getElementById('freshness'); + if (freshness) freshness.value = ''; + + const freshnessDisplayText = document.getElementById('freshnessDisplayText'); + if (freshnessDisplayText) { + freshnessDisplayText.textContent = '请选择新鲜程度'; + } + selectedFreshness = ''; + + // 重置自动下架时间选择框 + const autoOfflineMinutes = document.getElementById('autoOfflineMinutes'); + if (autoOfflineMinutes) { + autoOfflineMinutes.value = '1440'; // 默认24小时 + } } // 显示创建货源模态框 @@ -5171,7 +5350,7 @@ for (let i = 0; i < files.length; i++) { if (editCurrentImages.length >= 2) { - alert('最多只能上传2张图片'); + alert('最多只能上传2张图片或视频'); break; } @@ -5226,7 +5405,7 @@ mediaElement.className = 'upload-image'; let mediaContent; - if (imageUrl.startsWith('data:video/')) { + if (imageUrl.startsWith('data:video/') || imageUrl.match(/\.(mp4|mov|avi|wmv|flv|webm|mkv)$/i)) { // 视频文件 mediaContent = ` @@ -5249,7 +5428,7 @@ function addWatermarkToImage(imageUrl) { return new Promise((resolve, reject) => { // 如果是视频文件,直接返回,不添加水印 - if (imageUrl.startsWith('data:video/')) { + if (imageUrl.startsWith('data:video/') || imageUrl.match(/\.(mp4|mov|avi|wmv|flv|webm|mkv)$/i)) { resolve(imageUrl); return; } @@ -5305,7 +5484,7 @@ for (let i = 0; i < files.length; i++) { if (supplyData.uploadedImages.length >= 2) { - alert('最多只能上传2张图片'); + alert('最多只能上传2张图片/视频'); break; } @@ -5450,6 +5629,7 @@ const regionValue = document.getElementById('regionValue'); const contactId = document.getElementById('contactId'); const category = document.getElementById('category'); + const freshness = document.getElementById('freshness'); const specDisplayText = document.getElementById('specDisplayText'); const regionDisplayText = document.getElementById('regionDisplayText'); const sourceTypeDisplayText = document.getElementById('sourceTypeDisplayText'); @@ -5457,11 +5637,13 @@ const yolkDisplayText = document.getElementById('yolkDisplayText'); const contactIdDisplayText = document.getElementById('contactIdDisplayText'); const categoryDisplayText = document.getElementById('categoryDisplayText'); + const freshnessDisplayText = document.getElementById('freshnessDisplayText'); // 确保所有字段都是安全获取的 const formData = { productName: productName ? productName.value : '', category: category ? category.value : '', + freshness: freshness ? freshness.value : '', price: price ? price.value : '', quantity: quantity ? quantity.value : '', grossWeight: grossWeight ? grossWeight.value : '', @@ -5480,6 +5662,7 @@ yolkDisplay: yolkDisplayText ? yolkDisplayText.textContent : '请选择蛋黄类型', contactIdDisplay: contactIdDisplayText ? contactIdDisplayText.textContent : '请选择联系人', categoryDisplay: categoryDisplayText ? categoryDisplayText.textContent : '请选择种类', + freshnessDisplay: freshnessDisplayText ? freshnessDisplayText.textContent : '请选择新鲜程度', // 深拷贝图片数组,避免引用问题 imageUrls: Array.isArray(supplyData.uploadedImages) ? [...supplyData.uploadedImages] : [], // 保存自定义选择状态 @@ -5491,6 +5674,7 @@ selectedYolk: selectedYolk || '', selectedContactId: selectedContactId || '', selectedCategory: selectedCategory || '', + selectedFreshness: selectedFreshness || '', // 添加时间戳,用于调试和潜在的过期处理 lastSaved: new Date().toISOString() }; @@ -5504,6 +5688,80 @@ } } + // 清除表单记忆数据 + function clearFormMemory() { + try { + // 1. 从localStorage中移除保存的表单数据 + localStorage.removeItem('supplyFormDraft'); + + // 2. 清空上传的图片/视频 + supplyData.uploadedImages = []; + renderUploadedImages(); + + // 3. 重置表单字段 + const productName = document.getElementById('productName'); + const price = document.getElementById('price'); + const quantity = document.getElementById('quantity'); + const grossWeight = document.getElementById('grossWeight'); + const yolk = document.getElementById('yolk'); + const specValue = document.getElementById('specValue'); + const supplyStatus = document.getElementById('supplyStatus'); + const sourceType = document.getElementById('sourceType'); + const description = document.getElementById('description'); + const regionValue = document.getElementById('regionValue'); + const contactId = document.getElementById('contactId'); + const category = document.getElementById('category'); + + // 重置输入框 + [productName, price, quantity, grossWeight, yolk, specValue, supplyStatus, sourceType, description, regionValue, contactId, category].forEach(field => { + if (field) { + field.value = ''; + } + }); + + // 4. 重置下拉框显示文本 + const specDisplayText = document.getElementById('specDisplayText'); + const regionDisplayText = document.getElementById('regionDisplayText'); + const sourceTypeDisplayText = document.getElementById('sourceTypeDisplayText'); + const productNameDisplayText = document.getElementById('productNameDisplayText'); + const yolkDisplayText = document.getElementById('yolkDisplayText'); + const contactIdDisplayText = document.getElementById('contactIdDisplayText'); + const categoryDisplayText = document.getElementById('categoryDisplayText'); + + const displayFields = [ + { field: specDisplayText, text: '请选择规格' }, + { field: regionDisplayText, text: '请选择地区' }, + { field: sourceTypeDisplayText, text: '请选择货源类型' }, + { field: productNameDisplayText, text: '请选择商品名称' }, + { field: yolkDisplayText, text: '请选择蛋黄类型' }, + { field: contactIdDisplayText, text: '请选择联系人' }, + { field: categoryDisplayText, text: '请选择种类' } + ]; + + displayFields.forEach(item => { + if (item.field) { + item.field.textContent = item.text; + } + }); + + // 5. 重置选择状态变量 + selectedSpec = ''; + selectedProvince = ''; + selectedCity = ''; + selectedSourceType = ''; + selectedProductName = ''; + selectedYolk = ''; + selectedContactId = ''; + selectedCategory = ''; + + console.log('表单记忆数据已清除'); + alert('表单记忆已清除'); + } catch (e) { + console.error('清除表单记忆失败:', e); + alert('清除表单记忆失败,请重试'); + } + } + // 加载表单数据 function loadFormData() { try { @@ -5622,6 +5880,17 @@ } } + // 恢复新鲜程度 + if (formData.freshness) { + const freshnessElement = document.getElementById('freshness'); + const freshnessDisplayElement = document.getElementById('freshnessDisplayText'); + if (freshnessElement && freshnessDisplayElement) { + freshnessElement.value = formData.freshness; + freshnessDisplayElement.textContent = formData.freshnessDisplay || '请选择新鲜程度'; + selectedFreshness = formData.selectedFreshness || ''; + } + } + // 恢复图片 if (Array.isArray(formData.imageUrls)) { supplyData.uploadedImages = formData.imageUrls; @@ -5669,11 +5938,12 @@ // 获取自动下架分钟数 const autoOfflineMinutes = document.getElementById('autoOfflineMinutes').value; - // 将分钟转换为小时(用于存储到数据库) - const autoOfflineHours = parseFloat(autoOfflineMinutes) / 60; + // 将分钟转换为小时(用于存储到数据库),确保有默认值 + const minutes = parseFloat(autoOfflineMinutes) || 1440; // 默认24小时(1440分钟) + const autoOfflineHours = minutes / 60; // 计算自动下架时间(当前时间 + 分钟数) const autoOfflineTime = new Date(); - autoOfflineTime.setMinutes(autoOfflineTime.getMinutes() + parseFloat(autoOfflineMinutes)); + autoOfflineTime.setMinutes(autoOfflineTime.getMinutes() + minutes); const formData = { productName: document.getElementById('productName').value, @@ -5712,12 +5982,16 @@ alert('请输入价格'); return; } - if (specifications.length === 0) { - alert('请选择规格'); - return; + // 验证规格和件数 - 确保至少有一个有效的规格-件数对 + let hasValidPair = false; + for (let i = 0; i < specifications.length; i++) { + if (specifications[i].trim() !== '' && quantities[i].trim() !== '') { + hasValidPair = true; + break; + } } - if (quantities.length === 0) { - alert('请输入件数'); + if (!hasValidPair) { + alert('请至少添加一个有效的规格和件数对'); return; } if (!formData.supplyStatus) { @@ -6187,7 +6461,7 @@ // 显示编辑货源 // 删除货源 async function deleteSupply(id) { - if (confirm('确定要删除该货源吗?删除后将无法恢复。')) { + if (confirm('确定要删除该货源吗?')) { try { const response = await fetch(`/api/supplies/${id}/delete`, { method: 'POST' @@ -6404,20 +6678,38 @@ // 显示商品图片 const editUploadImages = document.getElementById('editUploadImages'); editUploadImages.innerHTML = ''; - if (supply.imageUrls && Array.isArray(supply.imageUrls)) { - supply.imageUrls.forEach((imageUrl, index) => { + // 确保imageUrls是数组 + const editImageUrls = Array.isArray(supply.imageUrls) ? supply.imageUrls : + (typeof supply.imageUrls === 'string' ? [supply.imageUrls] : + (supply.imageUrls ? [supply.imageUrls] : [])); + + if (editImageUrls.length > 0) { + editImageUrls.forEach((mediaUrl, index) => { const imageElement = document.createElement('div'); imageElement.className = 'upload-image'; - imageElement.innerHTML = ` - 商品图片 - - `; + + let mediaContent; + if (mediaUrl.startsWith('data:video/') || mediaUrl.match(/\.(mp4|mov|avi|wmv|flv|webm|mkv)$/i)) { + // 视频文件 + mediaContent = ` + + + `; + } else { + // 图片文件 + mediaContent = ` + 商品图片 + + `; + } + + imageElement.innerHTML = mediaContent; editUploadImages.appendChild(imageElement); }); } // 保存当前图片列表到全局变量,用于编辑时使用 - editCurrentImages = supply.imageUrls && Array.isArray(supply.imageUrls) ? [...supply.imageUrls] : []; + editCurrentImages = [...editImageUrls]; // 根据模式修改保存按钮文本和状态 const saveButton = document.querySelector('#editSupplyModal .modal-btn-primary'); @@ -7487,15 +7779,13 @@ // 获取自动下架分钟数 const autoOfflineMinutes = document.getElementById('editAutoOfflineMinutes')?.value; - // 将分钟转换为小时(用于存储到数据库) - const autoOfflineHours = parseFloat(autoOfflineMinutes) / 60; + // 将分钟转换为小时(用于存储到数据库),确保有默认值 + const minutes = parseFloat(autoOfflineMinutes) || 1440; // 默认24小时(1440分钟) + const autoOfflineHours = minutes / 60; // 计算自动下架时间(当前时间 + 分钟数) - let autoOfflineTime = null; - if (autoOfflineMinutes) { - autoOfflineTime = new Date(); - autoOfflineTime.setMinutes(autoOfflineTime.getMinutes() + parseFloat(autoOfflineMinutes)); - autoOfflineTime = autoOfflineTime.toISOString(); - } + const autoOfflineTime = new Date(); + autoOfflineTime.setMinutes(autoOfflineTime.getMinutes() + minutes); + const autoOfflineTimeISO = autoOfflineTime.toISOString(); const formData = { productName: document.getElementById('editProductName').value, @@ -7515,7 +7805,7 @@ contactId: document.getElementById('editContactId').value, freshness: document.getElementById('editFreshness').value, imageUrls: editCurrentImages, // 添加编辑后的图片列表 - autoOfflineTime: autoOfflineTime, // 自动下架时间 + autoOfflineTime: autoOfflineTimeISO, // 自动下架时间 autoOfflineDays: null, // 不再使用天数,设置为null autoOfflineHours: autoOfflineHours // 自动下架小时数(分钟转换而来) }; @@ -7533,12 +7823,16 @@ alert('请输入价格'); return false; } - if (specifications.length === 0) { - alert('请选择规格'); - return false; + // 验证规格和件数 - 确保至少有一个有效的规格-件数对 + let hasValidPair = false; + for (let i = 0; i < specifications.length; i++) { + if (specifications[i].trim() !== '' && quantities[i].trim() !== '') { + hasValidPair = true; + break; + } } - if (quantities.length === 0) { - alert('请输入件数'); + if (!hasValidPair) { + alert('请至少添加一个有效的规格和件数对'); return false; } if (!formData.supplyStatus) {