From 64179ca614d96dc6f18dc9fd3bc9125792bc98d5 Mon Sep 17 00:00:00 2001 From: Trae AI Date: Wed, 31 Dec 2025 11:43:06 +0800 Subject: [PATCH 1/6] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=A7=E5=93=81?= =?UTF-8?q?=E5=8C=85=E8=A3=85=E5=8A=9F=E8=83=BD=E5=92=8C=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?SQL=E6=8F=92=E5=85=A5=E8=AF=AD=E5=8F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Reject.js | 33 +++++-- package-lock.json | 4 +- supply-manager.js | 7 ++ supply.html | 242 +++++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 262 insertions(+), 24 deletions(-) diff --git a/Reject.js b/Reject.js index 7c3abed..948d720 100644 --- a/Reject.js +++ b/Reject.js @@ -726,7 +726,7 @@ app.post('/api/supplies/create', async (req, res) => { let connection; try { connection = await pool.getConnection(); - const { productName, price, quantity, grossWeight, yolk, specification, quality, region, imageUrls, sellerId, supplyStatus, description, sourceType, contactId, category } = req.body; + const { productName, price, quantity, grossWeight, yolk, specification, quality, region, imageUrls, sellerId, supplyStatus, description, sourceType, contactId, category, producting } = req.body; // 开始事务 await connection.beginTransaction(); @@ -813,6 +813,7 @@ app.post('/api/supplies/create', async (req, res) => { yolk, specification, quality, + producting: producting || '', // 添加产品包装 region, status: 'published', // 直接上架,而不是审核中 supplyStatus: supplyStatus || '', // 预售/现货 @@ -835,19 +836,19 @@ app.post('/api/supplies/create', async (req, res) => { if (columns.length === 0) { // 没有quality字段,不包含quality字段的插入 - insertQuery = 'INSERT INTO products (productId, sellerId, productName, category, price, quantity, grossWeight, yolk, specification, region, status, supplyStatus, sourceType, description, rejectReason, imageUrls, created_at, audit_time, product_contact, contact_phone) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'; + insertQuery = 'INSERT INTO products (productId, sellerId, productName, category, price, quantity, grossWeight, yolk, specification, producting, region, status, supplyStatus, sourceType, description, rejectReason, imageUrls, created_at, audit_time, product_contact, contact_phone) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'; insertParams = [ productId, productData.sellerId, productName, category || '', price.toString(), parseInt(quantity), grossWeight, - yolk, specification, region, productData.status, productData.supplyStatus, productData.sourceType, + yolk, specification, producting, region, productData.status, productData.supplyStatus, productData.sourceType, productData.description, productData.rejectReason, productData.imageUrls, new Date(), new Date(), productContact, contactPhone // 添加联系人信息 ]; } else { // 有quality字段,包含quality字段的插入 - insertQuery = 'INSERT INTO products (productId, sellerId, productName, category, price, quantity, grossWeight, yolk, specification, quality, region, status, supplyStatus, sourceType, description, rejectReason, imageUrls, created_at, audit_time, product_contact, contact_phone) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'; + insertQuery = 'INSERT INTO products (productId, sellerId, productName, category, price, quantity, grossWeight, yolk, specification, quality, producting, region, status, supplyStatus, sourceType, description, rejectReason, imageUrls, created_at, audit_time, product_contact, contact_phone) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'; insertParams = [ productId, productData.sellerId, productName, category || '', price.toString(), parseInt(quantity), grossWeight, - yolk, specification, quality, region, productData.status, productData.supplyStatus, + yolk, specification, quality, producting, region, productData.status, productData.supplyStatus, productData.sourceType, productData.description, productData.rejectReason, productData.imageUrls, new Date(), new Date(), productContact, contactPhone // 添加联系人信息 ]; @@ -1053,7 +1054,7 @@ app.put('/api/supplies/:id/edit', async (req, res) => { try { const connection = await pool.getConnection(); const productId = req.params.id; - const { productName, price, quantity, grossWeight, yolk, specification, supplyStatus, description, region, contactId } = req.body; + const { productName, price, quantity, grossWeight, yolk, specification, supplyStatus, description, region, contactId, producting } = req.body; // 开始事务 await connection.beginTransaction(); @@ -1093,14 +1094,14 @@ app.put('/api/supplies/:id/edit', async (req, res) => { UPDATE products SET productName = ?, price = ?, quantity = ?, grossWeight = ?, yolk = ?, specification = ?, supplyStatus = ?, description = ?, region = ?, - product_contact = ?, contact_phone = ? + producting = ?, product_contact = ?, contact_phone = ? WHERE id = ? `; await connection.query(updateQuery, [ productName, price.toString(), parseInt(quantity), grossWeight, yolk, specification, supplyStatus, description, region, - productContact, contactPhone, productId + producting, productContact, contactPhone, productId ]); // 提交事务 @@ -1316,6 +1317,22 @@ async function ensureDatabaseSchema() { console.log('audit_time字段添加成功'); } + // 检查表是否有producting字段 + console.log('检查表products是否有producting字段...'); + const [productingColumns] = await connection.query( + 'SHOW COLUMNS FROM `products` LIKE ?', + ['producting'] + ); + console.log('检查表字段结果:', productingColumns.length > 0 ? '已存在' : '不存在'); + + if (productingColumns.length === 0) { + console.log('添加producting字段到products表...'); + await connection.query( + 'ALTER TABLE `products` ADD COLUMN producting VARCHAR(255) COMMENT "产品包装"' + ); + console.log('producting字段添加成功'); + } + // 检查audit_logs表是否存在,如果不存在则创建 console.log('检查audit_logs表是否存在...'); const [tables] = await connection.query( diff --git a/package-lock.json b/package-lock.json index 8871825..a42c8c5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "Review", + "name": "Review2", "lockfileVersion": 3, "requires": true, "packages": { @@ -52,6 +52,7 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -1295,6 +1296,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", diff --git a/supply-manager.js b/supply-manager.js index 0747554..a22c956 100644 --- a/supply-manager.js +++ b/supply-manager.js @@ -34,6 +34,13 @@ class SupplyManager { editSpecSearchKeyword: '', filteredSpecOptions: [], filteredEditSpecOptions: [], + productingOptions: ['1*360枚新包装', '1*360枚旧包新拖', '1*360枚旧包旧拖', '1*420枚新包装', '1*480枚新包装', '30枚蛋托散装', '360枚散托'], + productingSearchKeyword: '', + editProductingSearchKeyword: '', + filteredProductingOptions: [], + filteredEditProductingOptions: [], + showProductingSelectModal: false, + currentProductingMode: 'create', showRegionSelectModal: false, currentRegionMode: 'create', regionOptions: [ diff --git a/supply.html b/supply.html index efe4faf..79c7dff 100644 --- a/supply.html +++ b/supply.html @@ -988,9 +988,52 @@
暂无已上架的货源
+
+ + + +
+
+
+

选择产品包装

+ +
+ +
+
+ +
+
+
- +
+ + +
+
+
+

选择产品包装

+ +
+ +
+
+ +
+
+ +
+
@@ -1105,6 +1148,15 @@
+ +
+ +
+ 请选择产品包装 + + +
+
@@ -1139,7 +1191,7 @@
-
+ @@ -1426,6 +1478,16 @@
+ +
+ +
+ 请选择产品包装 + + +
+
+
@@ -1459,7 +1521,7 @@
-
+ @@ -1923,6 +1985,145 @@ } hideSpecSelectModal(); } + + // 产品包装相关变量 + const allProductingOptions = ['1*360枚新包装', '1*360枚旧包新拖', '1*360枚旧包旧拖', '1*420枚新包装', '1*480枚新包装', '30枚蛋托散装', '360枚散托']; + let filteredProductingOptions = []; + let selectedProducting = ''; + + // 显示产品包装选择弹窗 + function showProductingSelectModal() { + const productingSelectModal = document.getElementById('productingSelectModal'); + productingSelectModal.classList.add('active'); + // 重置搜索输入 + document.getElementById('productingSearchInput').value = ''; + filteredProductingOptions = [...allProductingOptions]; + generateProductingOptions(); + } + + // 隐藏产品包装选择弹窗 + function hideProductingSelectModal() { + const productingSelectModal = document.getElementById('productingSelectModal'); + productingSelectModal.classList.remove('active'); + } + + // 生成产品包装选项 + function generateProductingOptions() { + const productingOptionsList = document.getElementById('productingOptionsList'); + productingOptionsList.innerHTML = ''; + + filteredProductingOptions.forEach(producting => { + const option = document.createElement('div'); + option.className = 'select-item'; + option.textContent = producting; + option.onclick = () => { + // 移除所有选项的选中状态 + document.querySelectorAll('.select-item').forEach(item => { + item.classList.remove('selected'); + }); + // 添加当前选项的选中状态 + option.classList.add('selected'); + selectedProducting = producting; + }; + option.ondblclick = () => { + // 双击直接确认选择 + document.getElementById('productingDisplayText').textContent = producting; + document.getElementById('productingValue').value = producting; + hideProductingSelectModal(); + }; + productingOptionsList.appendChild(option); + }); + } + + // 过滤产品包装选项 + function filterProductingOptions() { + const searchInput = document.getElementById('productingSearchInput'); + const searchKeyword = searchInput.value.toLowerCase(); + + filteredProductingOptions = allProductingOptions.filter(producting => { + return producting.toLowerCase().includes(searchKeyword); + }); + + generateProductingOptions(); + } + + // 确认产品包装选择 + function confirmProductingSelection() { + if (selectedProducting) { + document.getElementById('productingDisplayText').textContent = selectedProducting; + document.getElementById('productingValue').value = selectedProducting; + } + hideProductingSelectModal(); + } + + // 编辑产品包装相关变量 + let editFilteredProductingOptions = []; + let editSelectedProducting = ''; + + // 显示编辑产品包装选择弹窗 + function showEditProductingSelectModal() { + const editProductingSelectModal = document.getElementById('editProductingSelectModal'); + editProductingSelectModal.classList.add('active'); + // 重置搜索输入 + document.getElementById('editProductingSearchInput').value = ''; + editFilteredProductingOptions = [...allProductingOptions]; + generateEditProductingOptions(); + } + + // 隐藏编辑产品包装选择弹窗 + function hideEditProductingSelectModal() { + const editProductingSelectModal = document.getElementById('editProductingSelectModal'); + editProductingSelectModal.classList.remove('active'); + } + + // 生成编辑产品包装选项 + function generateEditProductingOptions() { + const editProductingOptionsList = document.getElementById('editProductingOptionsList'); + editProductingOptionsList.innerHTML = ''; + + editFilteredProductingOptions.forEach(producting => { + const option = document.createElement('div'); + option.className = 'select-item'; + option.textContent = producting; + option.onclick = () => { + // 移除所有选项的选中状态 + document.querySelectorAll('.select-item').forEach(item => { + item.classList.remove('selected'); + }); + // 添加当前选项的选中状态 + option.classList.add('selected'); + editSelectedProducting = producting; + }; + option.ondblclick = () => { + // 双击直接确认选择 + document.getElementById('editProductingDisplayText').textContent = producting; + document.getElementById('editProductingValue').value = producting; + hideEditProductingSelectModal(); + }; + editProductingOptionsList.appendChild(option); + }); + } + + // 过滤编辑产品包装选项 + function filterEditProductingOptions() { + const searchInput = document.getElementById('editProductingSearchInput'); + const searchKeyword = searchInput.value.toLowerCase(); + + editFilteredProductingOptions = allProductingOptions.filter(producting => { + return producting.toLowerCase().includes(searchKeyword); + }); + + generateEditProductingOptions(); + } + + // 确认编辑产品包装选择 + function confirmEditProductingSelection() { + if (editSelectedProducting) { + document.getElementById('editProductingDisplayText').textContent = editSelectedProducting; + document.getElementById('editProductingValue').value = editSelectedProducting; + } + hideEditProductingSelectModal(); + } // 显示货源类型选择弹窗 function showSourceTypeSelectModal() { @@ -3289,7 +3490,7 @@
货源状态: ${supply.supplyStatus || '未设置'}
货源描述: ${supply.description || '无'}
件数: ${supply.quantity || '0'}件
-
斤重: ${supply.grossWeight || ''}斤
+
地区: ${supply.region || '未设置'}
价格: ¥${supply.price || '0'}
创建时间: ${formatDate(supply.created_at)}
@@ -3441,7 +3642,7 @@ const productName = document.getElementById('productName'); const price = document.getElementById('price'); const quantity = document.getElementById('quantity'); - const grossWeight = document.getElementById('grossWeight'); + const grossWeight = null; // 斤重功能已隐藏 const yolk = document.getElementById('yolk'); const specValue = document.getElementById('specValue'); const supplyStatus = document.getElementById('supplyStatus'); @@ -3456,7 +3657,7 @@ if (productName) productName.value = formData.productName || ''; if (price) price.value = formData.price || ''; if (quantity) quantity.value = formData.quantity || ''; - if (grossWeight) grossWeight.value = formData.grossWeight || ''; + // 斤重功能已隐藏 if (yolk) yolk.value = formData.yolk || ''; if (specValue) specValue.value = formData.specification || ''; if (supplyStatus) supplyStatus.value = formData.supplyStatus || ''; @@ -3674,7 +3875,7 @@ // 设置表单自动保存 function setupAutoSave() { // 为所有输入字段添加输入事件监听 - const formFields = ['productName', 'price', 'quantity', 'grossWeight', 'yolk', 'specValue', 'supplyStatus', 'description', 'regionValue', 'contactId', 'sourceType']; + const formFields = ['productName', 'price', 'quantity', 'grossWeight', 'yolk', 'specValue', 'supplyStatus', 'description', 'regionValue', 'contactId', 'sourceType', 'productingValue']; formFields.forEach(fieldId => { const element = document.getElementById(fieldId); @@ -3704,7 +3905,8 @@ 'sourceTypeDisplayText', 'productNameDisplayText', 'yolkDisplayText', - 'contactIdDisplayText' + 'contactIdDisplayText', + 'productingDisplayText' ]; customDisplays.forEach(displayId => { @@ -3744,7 +3946,7 @@ const productName = document.getElementById('productName'); const price = document.getElementById('price'); const quantity = document.getElementById('quantity'); - const grossWeight = document.getElementById('grossWeight'); + const grossWeight = null; // 斤重功能已隐藏 const yolk = document.getElementById('yolk'); const specValue = document.getElementById('specValue'); const supplyStatus = document.getElementById('supplyStatus'); @@ -3760,6 +3962,8 @@ const yolkDisplayText = document.getElementById('yolkDisplayText'); const contactIdDisplayText = document.getElementById('contactIdDisplayText'); const categoryDisplayText = document.getElementById('categoryDisplayText'); + const productingValue = document.getElementById('productingValue'); + const productingDisplayText = document.getElementById('productingDisplayText'); // 确保所有字段都是安全获取的 const formData = { @@ -3767,7 +3971,7 @@ category: category ? category.value : '', price: price ? price.value : '', quantity: quantity ? quantity.value : '', - grossWeight: grossWeight ? grossWeight.value : '', + grossWeight: '', // 斤重功能已隐藏 yolk: yolk ? yolk.value : '', specification: specValue ? specValue.value : '', specificationDisplay: specDisplayText ? specDisplayText.textContent : '请选择规格', @@ -3777,6 +3981,8 @@ region: regionValue ? regionValue.value : '', regionDisplay: regionDisplayText ? regionDisplayText.textContent : '请选择地区', contactId: contactId ? contactId.value : '', + producting: productingValue ? productingValue.value : '', + productingDisplay: productingDisplayText ? productingDisplayText.textContent : '请选择产品包装', // 保存其他自定义下拉框显示文本 sourceTypeDisplay: sourceTypeDisplayText ? sourceTypeDisplayText.textContent : '请选择货源类型', productNameDisplay: productNameDisplayText ? productNameDisplayText.textContent : '请选择商品名称', @@ -3819,7 +4025,7 @@ category: document.getElementById('category').value, price: document.getElementById('price').value, quantity: document.getElementById('quantity').value, - grossWeight: document.getElementById('grossWeight').value, + grossWeight: '', // 斤重功能已隐藏 yolk: document.getElementById('yolk').value, specification: document.getElementById('specValue').value, supplyStatus: document.getElementById('supplyStatus').value, @@ -3827,6 +4033,7 @@ description: document.getElementById('description').value, region: document.getElementById('regionValue').value, contactId: document.getElementById('contactId').value, + producting: document.getElementById('productingValue').value, imageUrls: supplyData.uploadedImages, sellerId: userInfo.userId // 添加当前登录用户的userId作为sellerId }; @@ -4365,7 +4572,11 @@ document.getElementById('editRegionValue').value = supply.region || ''; document.getElementById('editPrice').value = supply.price || ''; document.getElementById('editQuantity').value = supply.quantity || ''; - document.getElementById('editGrossWeight').value = supply.grossWeight || ''; + // 斤重功能已隐藏 + + // 产品包装 + document.getElementById('editProductingDisplayText').textContent = supply.producting || '请选择产品包装'; + document.getElementById('editProductingValue').value = supply.producting || ''; // 显示商品图片 const editUploadImages = document.getElementById('editUploadImages'); @@ -5030,14 +5241,15 @@ category: document.getElementById('editCategory').value, price: document.getElementById('editPrice').value, quantity: document.getElementById('editQuantity').value, - grossWeight: document.getElementById('editGrossWeight').value, + grossWeight: '', // 斤重功能已隐藏 yolk: document.getElementById('editYolk').value, specification: document.getElementById('editSpecValue').value, supplyStatus: document.getElementById('editSupplyStatus').value, description: document.getElementById('editDescription').value, region: document.getElementById('editRegionValue').value, - contactId: document.getElementById('editContactId').value - }; + contactId: document.getElementById('editContactId').value, + producting: document.getElementById('editProductingValue').value + }; // 验证表单 if (!formData.productName) { From 3f46a1fe62e792a6741ec301d38f865b38c03200 Mon Sep 17 00:00:00 2001 From: Trae AI Date: Wed, 31 Dec 2025 12:25:33 +0800 Subject: [PATCH 2/6] =?UTF-8?q?=E5=90=88=E5=B9=B6=E6=8F=90=E4=BA=A4=2002f1?= =?UTF-8?q?fba:=20=E6=9B=B4=E6=96=B0=E8=B4=A7=E6=BA=90=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E5=8A=9F=E8=83=BD=E5=92=8C=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supply.html | 238 +++------------------------------------------------- 1 file changed, 13 insertions(+), 225 deletions(-) diff --git a/supply.html b/supply.html index a993530..395d679 100644 --- a/supply.html +++ b/supply.html @@ -993,52 +993,9 @@
暂无已上架的货源
-
- - - -
-
-
-

选择产品包装

- -
- -
-
- -
-
- -
-
- - -
-
-
-

选择产品包装

- -
- -
-
- -
-
-
-
+
@@ -1163,15 +1120,6 @@
- -
- -
- 请选择产品包装 - - -
-
@@ -1509,16 +1457,6 @@
- -
- -
- 请选择产品包装 - - -
-
-
@@ -2030,145 +1968,6 @@ } hideSpecSelectModal(); } - - // 产品包装相关变量 - const allProductingOptions = ['1*360枚新包装', '1*360枚旧包新拖', '1*360枚旧包旧拖', '1*420枚新包装', '1*480枚新包装', '30枚蛋托散装', '360枚散托']; - let filteredProductingOptions = []; - let selectedProducting = ''; - - // 显示产品包装选择弹窗 - function showProductingSelectModal() { - const productingSelectModal = document.getElementById('productingSelectModal'); - productingSelectModal.classList.add('active'); - // 重置搜索输入 - document.getElementById('productingSearchInput').value = ''; - filteredProductingOptions = [...allProductingOptions]; - generateProductingOptions(); - } - - // 隐藏产品包装选择弹窗 - function hideProductingSelectModal() { - const productingSelectModal = document.getElementById('productingSelectModal'); - productingSelectModal.classList.remove('active'); - } - - // 生成产品包装选项 - function generateProductingOptions() { - const productingOptionsList = document.getElementById('productingOptionsList'); - productingOptionsList.innerHTML = ''; - - filteredProductingOptions.forEach(producting => { - const option = document.createElement('div'); - option.className = 'select-item'; - option.textContent = producting; - option.onclick = () => { - // 移除所有选项的选中状态 - document.querySelectorAll('.select-item').forEach(item => { - item.classList.remove('selected'); - }); - // 添加当前选项的选中状态 - option.classList.add('selected'); - selectedProducting = producting; - }; - option.ondblclick = () => { - // 双击直接确认选择 - document.getElementById('productingDisplayText').textContent = producting; - document.getElementById('productingValue').value = producting; - hideProductingSelectModal(); - }; - productingOptionsList.appendChild(option); - }); - } - - // 过滤产品包装选项 - function filterProductingOptions() { - const searchInput = document.getElementById('productingSearchInput'); - const searchKeyword = searchInput.value.toLowerCase(); - - filteredProductingOptions = allProductingOptions.filter(producting => { - return producting.toLowerCase().includes(searchKeyword); - }); - - generateProductingOptions(); - } - - // 确认产品包装选择 - function confirmProductingSelection() { - if (selectedProducting) { - document.getElementById('productingDisplayText').textContent = selectedProducting; - document.getElementById('productingValue').value = selectedProducting; - } - hideProductingSelectModal(); - } - - // 编辑产品包装相关变量 - let editFilteredProductingOptions = []; - let editSelectedProducting = ''; - - // 显示编辑产品包装选择弹窗 - function showEditProductingSelectModal() { - const editProductingSelectModal = document.getElementById('editProductingSelectModal'); - editProductingSelectModal.classList.add('active'); - // 重置搜索输入 - document.getElementById('editProductingSearchInput').value = ''; - editFilteredProductingOptions = [...allProductingOptions]; - generateEditProductingOptions(); - } - - // 隐藏编辑产品包装选择弹窗 - function hideEditProductingSelectModal() { - const editProductingSelectModal = document.getElementById('editProductingSelectModal'); - editProductingSelectModal.classList.remove('active'); - } - - // 生成编辑产品包装选项 - function generateEditProductingOptions() { - const editProductingOptionsList = document.getElementById('editProductingOptionsList'); - editProductingOptionsList.innerHTML = ''; - - editFilteredProductingOptions.forEach(producting => { - const option = document.createElement('div'); - option.className = 'select-item'; - option.textContent = producting; - option.onclick = () => { - // 移除所有选项的选中状态 - document.querySelectorAll('.select-item').forEach(item => { - item.classList.remove('selected'); - }); - // 添加当前选项的选中状态 - option.classList.add('selected'); - editSelectedProducting = producting; - }; - option.ondblclick = () => { - // 双击直接确认选择 - document.getElementById('editProductingDisplayText').textContent = producting; - document.getElementById('editProductingValue').value = producting; - hideEditProductingSelectModal(); - }; - editProductingOptionsList.appendChild(option); - }); - } - - // 过滤编辑产品包装选项 - function filterEditProductingOptions() { - const searchInput = document.getElementById('editProductingSearchInput'); - const searchKeyword = searchInput.value.toLowerCase(); - - editFilteredProductingOptions = allProductingOptions.filter(producting => { - return producting.toLowerCase().includes(searchKeyword); - }); - - generateEditProductingOptions(); - } - - // 确认编辑产品包装选择 - function confirmEditProductingSelection() { - if (editSelectedProducting) { - document.getElementById('editProductingDisplayText').textContent = editSelectedProducting; - document.getElementById('editProductingValue').value = editSelectedProducting; - } - hideEditProductingSelectModal(); - } // 显示货源类型选择弹窗 function showSourceTypeSelectModal() { @@ -3489,7 +3288,7 @@
货源状态: ${supply.supplyStatus || '未设置'}
货源描述: ${supply.description || '无'}
件数: ${supply.quantity || '0'}件
- +
斤重: ${supply.grossWeight || ''}斤
地区: ${supply.region || '未设置'}
价格: ¥${supply.price || '0'}
创建时间: ${formatDate(supply.created_at)}
@@ -3640,7 +3439,7 @@ const productName = document.getElementById('productName'); const price = document.getElementById('price'); const quantity = document.getElementById('quantity'); - const grossWeight = null; // 斤重功能已隐藏 + const grossWeight = document.getElementById('grossWeight'); const yolk = document.getElementById('yolk'); const specValue = document.getElementById('specValue'); const supplyStatus = document.getElementById('supplyStatus'); @@ -3655,7 +3454,7 @@ if (productName) productName.value = formData.productName || ''; if (price) price.value = formData.price || ''; if (quantity) quantity.value = formData.quantity || ''; - // 斤重功能已隐藏 + if (grossWeight) grossWeight.value = formData.grossWeight || ''; if (yolk) yolk.value = formData.yolk || ''; if (specValue) specValue.value = formData.specification || ''; if (supplyStatus) supplyStatus.value = formData.supplyStatus || ''; @@ -3871,7 +3670,7 @@ // 设置表单自动保存 function setupAutoSave() { // 为所有输入字段添加输入事件监听 - const formFields = ['productName', 'price', 'quantity', 'grossWeight', 'yolk', 'specValue', 'supplyStatus', 'description', 'regionValue', 'contactId', 'sourceType', 'productingValue']; + const formFields = ['productName', 'price', 'quantity', 'grossWeight', 'yolk', 'specValue', 'supplyStatus', 'description', 'regionValue', 'contactId', 'sourceType']; formFields.forEach(fieldId => { const element = document.getElementById(fieldId); @@ -3901,8 +3700,7 @@ 'sourceTypeDisplayText', 'productNameDisplayText', 'yolkDisplayText', - 'contactIdDisplayText', - 'productingDisplayText' + 'contactIdDisplayText' ]; customDisplays.forEach(displayId => { @@ -3942,7 +3740,7 @@ const productName = document.getElementById('productName'); const price = document.getElementById('price'); const quantity = document.getElementById('quantity'); - const grossWeight = null; // 斤重功能已隐藏 + const grossWeight = document.getElementById('grossWeight'); const yolk = document.getElementById('yolk'); const specValue = document.getElementById('specValue'); const supplyStatus = document.getElementById('supplyStatus'); @@ -3958,8 +3756,6 @@ const yolkDisplayText = document.getElementById('yolkDisplayText'); const contactIdDisplayText = document.getElementById('contactIdDisplayText'); const categoryDisplayText = document.getElementById('categoryDisplayText'); - const productingValue = document.getElementById('productingValue'); - const productingDisplayText = document.getElementById('productingDisplayText'); // 确保所有字段都是安全获取的 const formData = { @@ -3967,7 +3763,7 @@ category: category ? category.value : '', price: price ? price.value : '', quantity: quantity ? quantity.value : '', - grossWeight: '', // 斤重功能已隐藏 + grossWeight: grossWeight ? grossWeight.value : '', yolk: yolk ? yolk.value : '', specification: specValue ? specValue.value : '', specificationDisplay: specDisplayText ? specDisplayText.textContent : '请选择规格', @@ -3977,8 +3773,6 @@ region: regionValue ? regionValue.value : '', regionDisplay: regionDisplayText ? regionDisplayText.textContent : '请选择地区', contactId: contactId ? contactId.value : '', - producting: productingValue ? productingValue.value : '', - productingDisplay: productingDisplayText ? productingDisplayText.textContent : '请选择产品包装', // 保存其他自定义下拉框显示文本 sourceTypeDisplay: sourceTypeDisplayText ? sourceTypeDisplayText.textContent : '请选择货源类型', productNameDisplay: productNameDisplayText ? productNameDisplayText.textContent : '请选择商品名称', @@ -4028,7 +3822,7 @@ category: document.getElementById('category').value, price: document.getElementById('price').value, quantity: document.getElementById('quantity').value, - grossWeight: '', // 斤重功能已隐藏 + grossWeight: document.getElementById('grossWeight').value, yolk: document.getElementById('yolk').value, specification: document.getElementById('specValue').value, supplyStatus: document.getElementById('supplyStatus').value, @@ -4036,7 +3830,6 @@ description: document.getElementById('description').value, region: document.getElementById('regionValue').value, contactId: document.getElementById('contactId').value, - producting: document.getElementById('productingValue').value, imageUrls: supplyData.uploadedImages, sellerId: userInfo.userId // 添加当前登录用户的userId作为sellerId }; @@ -4587,11 +4380,7 @@ document.getElementById('editRegionValue').value = supply.region || ''; document.getElementById('editPrice').value = supply.price || ''; document.getElementById('editQuantity').value = supply.quantity || ''; - // 斤重功能已隐藏 - - // 产品包装 - document.getElementById('editProductingDisplayText').textContent = supply.producting || '请选择产品包装'; - document.getElementById('editProductingValue').value = supply.producting || ''; + document.getElementById('editGrossWeight').value = supply.grossWeight || ''; // 显示商品图片 const editUploadImages = document.getElementById('editUploadImages'); @@ -5256,15 +5045,14 @@ category: document.getElementById('editCategory').value, price: document.getElementById('editPrice').value, quantity: document.getElementById('editQuantity').value, - grossWeight: '', // 斤重功能已隐藏 + grossWeight: document.getElementById('editGrossWeight').value, yolk: document.getElementById('editYolk').value, specification: document.getElementById('editSpecValue').value, supplyStatus: document.getElementById('editSupplyStatus').value, description: document.getElementById('editDescription').value, region: document.getElementById('editRegionValue').value, - contactId: document.getElementById('editContactId').value, - producting: document.getElementById('editProductingValue').value - }; + contactId: document.getElementById('editContactId').value + }; // 验证表单 if (!formData.productName) { From 2d962a799775d02e2dbaeba271768c4eecc2cbf7 Mon Sep 17 00:00:00 2001 From: Trae AI Date: Wed, 31 Dec 2025 14:12:48 +0800 Subject: [PATCH 3/6] =?UTF-8?q?feat:=20=E5=AE=8C=E6=88=90=E4=BA=A7?= =?UTF-8?q?=E5=93=81=E5=8C=85=E8=A3=85=E5=8A=9F=E8=83=BD=E5=BC=80=E5=8F=91?= =?UTF-8?q?=E5=B9=B6=E4=BF=AE=E5=A4=8DSQL=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- node_modules/.package-lock.json | 4 +- tatus | 410 ++++++++++++++++++++++++++++++++ 2 files changed, 413 insertions(+), 1 deletion(-) create mode 100644 tatus diff --git a/node_modules/.package-lock.json b/node_modules/.package-lock.json index 95a1426..ee13f04 100644 --- a/node_modules/.package-lock.json +++ b/node_modules/.package-lock.json @@ -1,5 +1,5 @@ { - "name": "Review", + "name": "Review2", "lockfileVersion": 3, "requires": true, "packages": { @@ -34,6 +34,7 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -830,6 +831,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", diff --git a/tatus b/tatus new file mode 100644 index 0000000..2a6a1dc --- /dev/null +++ b/tatus @@ -0,0 +1,410 @@ + + SSUUMMMMAARRYY OOFF LLEESSSS CCOOMMMMAANNDDSS + + Commands marked with * may be preceded by a number, _N. + Notes in parentheses indicate the behavior if _N is given. + A key preceded by a caret indicates the Ctrl key; thus ^K is ctrl-K. + + h H Display this help. + q :q Q :Q ZZ Exit. + --------------------------------------------------------------------------- + + MMOOVVIINNGG + + e ^E j ^N CR * Forward one line (or _N lines). + y ^Y k ^K ^P * Backward one line (or _N lines). + ESC-j * Forward one file line (or _N file lines). + ESC-k * Backward one file line (or _N file lines). + f ^F ^V SPACE * Forward one window (or _N lines). + b ^B ESC-v * Backward one window (or _N lines). + z * Forward one window (and set window to _N). + w * Backward one window (and set window to _N). + ESC-SPACE * Forward one window, but don't stop at end-of-file. + ESC-b * Backward one window, but don't stop at beginning-of-file. + d ^D * Forward one half-window (and set half-window to _N). + u ^U * Backward one half-window (and set half-window to _N). + ESC-) RightArrow * Right one half screen width (or _N positions). + ESC-( LeftArrow * Left one half screen width (or _N positions). + ESC-} ^RightArrow Right to last column displayed. + ESC-{ ^LeftArrow Left to first column. + F Forward forever; like "tail -f". + ESC-F Like F but stop when search pattern is found. + r ^R ^L Repaint screen. + R Repaint screen, discarding buffered input. + --------------------------------------------------- + Default "window" is the screen height. + Default "half-window" is half of the screen height. + --------------------------------------------------------------------------- + + SSEEAARRCCHHIINNGG + + /_p_a_t_t_e_r_n * Search forward for (_N-th) matching line. + ?_p_a_t_t_e_r_n * Search backward for (_N-th) matching line. + n * Repeat previous search (for _N-th occurrence). + N * Repeat previous search in reverse direction. + ESC-n * Repeat previous search, spanning files. + ESC-N * Repeat previous search, reverse dir. & spanning files. + ^O^N ^On * Search forward for (_N-th) OSC8 hyperlink. + ^O^P ^Op * Search backward for (_N-th) OSC8 hyperlink. + ^O^L ^Ol Jump to the currently selected OSC8 hyperlink. + ESC-u Undo (toggle) search highlighting. + ESC-U Clear search highlighting. + &_p_a_t_t_e_r_n * Display only matching lines. + --------------------------------------------------- + Search is case-sensitive unless changed with -i or -I. + A search pattern may begin with one or more of: + ^N or ! Search for NON-matching lines. + ^E or * Search multiple files (pass thru END OF FILE). + ^F or @ Start search at FIRST file (for /) or last file (for ?). + ^K Highlight matches, but don't move (KEEP position). + ^R Don't use REGULAR EXPRESSIONS. + ^S _n Search for match in _n-th parenthesized subpattern. + ^W WRAP search if no match found. + ^L Enter next character literally into pattern. + --------------------------------------------------------------------------- + + JJUUMMPPIINNGG + + g < ESC-< * Go to first line in file (or line _N). + G > ESC-> * Go to last line in file (or line _N). + p % * Go to beginning of file (or _N percent into file). + t * Go to the (_N-th) next tag. + T * Go to the (_N-th) previous tag. + { ( [ * Find close bracket } ) ]. + } ) ] * Find open bracket { ( [. + ESC-^F _<_c_1_> _<_c_2_> * Find close bracket _<_c_2_>. + ESC-^B _<_c_1_> _<_c_2_> * Find open bracket _<_c_1_>. + --------------------------------------------------- + Each "find close bracket" command goes forward to the close bracket + matching the (_N-th) open bracket in the top line. + Each "find open bracket" command goes backward to the open bracket + matching the (_N-th) close bracket in the bottom line. + + m_<_l_e_t_t_e_r_> Mark the current top line with . + M_<_l_e_t_t_e_r_> Mark the current bottom line with . + '_<_l_e_t_t_e_r_> Go to a previously marked position. + '' Go to the previous position. + ^X^X Same as '. + ESC-m_<_l_e_t_t_e_r_> Clear a mark. + --------------------------------------------------- + A mark is any upper-case or lower-case letter. + Certain marks are predefined: + ^ means beginning of the file + $ means end of the file + --------------------------------------------------------------------------- + + CCHHAANNGGIINNGG FFIILLEESS + + :e [_f_i_l_e] Examine a new file. + ^X^V Same as :e. + :n * Examine the (_N-th) next file from the command line. + :p * Examine the (_N-th) previous file from the command line. + :x * Examine the first (or _N-th) file from the command line. + ^O^O Open the currently selected OSC8 hyperlink. + :d Delete the current file from the command line list. + = ^G :f Print current file name. + --------------------------------------------------------------------------- + + MMIISSCCEELLLLAANNEEOOUUSS CCOOMMMMAANNDDSS + + -_<_f_l_a_g_> Toggle a command line option [see OPTIONS below]. + --_<_n_a_m_e_> Toggle a command line option, by name. + __<_f_l_a_g_> Display the setting of a command line option. + ___<_n_a_m_e_> Display the setting of an option, by name. + +_c_m_d Execute the less cmd each time a new file is examined. + + !_c_o_m_m_a_n_d Execute the shell command with $SHELL. + #_c_o_m_m_a_n_d Execute the shell command, expanded like a prompt. + |XX_c_o_m_m_a_n_d Pipe file between current pos & mark XX to shell command. + s _f_i_l_e Save input to a file. + v Edit the current file with $VISUAL or $EDITOR. + V Print version number of "less". + --------------------------------------------------------------------------- + + OOPPTTIIOONNSS + + Most options may be changed either on the command line, + or from within less by using the - or -- command. + Options may be given in one of two forms: either a single + character preceded by a -, or a name preceded by --. + + -? ........ --help + Display help (from command line). + -a ........ --search-skip-screen + Search skips current screen. + -A ........ --SEARCH-SKIP-SCREEN + Search starts just after target line. + -b [_N] .... --buffers=[_N] + Number of buffers. + -B ........ --auto-buffers + Don't automatically allocate buffers for pipes. + -c ........ --clear-screen + Repaint by clearing rather than scrolling. + -d ........ --dumb + Dumb terminal. + -D xx_c_o_l_o_r . --color=xx_c_o_l_o_r + Set screen colors. + -e -E .... --quit-at-eof --QUIT-AT-EOF + Quit at end of file. + -f ........ --force + Force open non-regular files. + -F ........ --quit-if-one-screen + Quit if entire file fits on first screen. + -g ........ --hilite-search + Highlight only last match for searches. + -G ........ --HILITE-SEARCH + Don't highlight any matches for searches. + -h [_N] .... --max-back-scroll=[_N] + Backward scroll limit. + -i ........ --ignore-case + Ignore case in searches that do not contain uppercase. + -I ........ --IGNORE-CASE + Ignore case in all searches. + -j [_N] .... --jump-target=[_N] + Screen position of target lines. + -J ........ --status-column + Display a status column at left edge of screen. + -k _f_i_l_e ... --lesskey-file=_f_i_l_e + Use a compiled lesskey file. + -K ........ --quit-on-intr + Exit less in response to ctrl-C. + -L ........ --no-lessopen + Ignore the LESSOPEN environment variable. + -m -M .... --long-prompt --LONG-PROMPT + Set prompt style. + -n ......... --line-numbers + Suppress line numbers in prompts and messages. + -N ......... --LINE-NUMBERS + Display line number at start of each line. + -o [_f_i_l_e] .. --log-file=[_f_i_l_e] + Copy to log file (standard input only). + -O [_f_i_l_e] .. --LOG-FILE=[_f_i_l_e] + Copy to log file (unconditionally overwrite). + -p _p_a_t_t_e_r_n . --pattern=[_p_a_t_t_e_r_n] + Start at pattern (from command line). + -P [_p_r_o_m_p_t] --prompt=[_p_r_o_m_p_t] + Define new prompt. + -q -Q .... --quiet --QUIET --silent --SILENT + Quiet the terminal bell. + -r -R .... --raw-control-chars --RAW-CONTROL-CHARS + Output "raw" control characters. + -s ........ --squeeze-blank-lines + Squeeze multiple blank lines. + -S ........ --chop-long-lines + Chop (truncate) long lines rather than wrapping. + -t _t_a_g .... --tag=[_t_a_g] + Find a tag. + -T [_t_a_g_s_f_i_l_e] --tag-file=[_t_a_g_s_f_i_l_e] + Use an alternate tags file. + -u -U .... --underline-special --UNDERLINE-SPECIAL + Change handling of backspaces, tabs and carriage returns. + -V ........ --version + Display the version number of "less". + -w ........ --hilite-unread + Highlight first new line after forward-screen. + -W ........ --HILITE-UNREAD + Highlight first new line after any forward movement. + -x [_N[,...]] --tabs=[_N[,...]] + Set tab stops. + -X ........ --no-init + Don't use termcap init/deinit strings. + -y [_N] .... --max-forw-scroll=[_N] + Forward scroll limit. + -z [_N] .... --window=[_N] + Set size of window. + -" [_c[_c]] . --quotes=[_c[_c]] + Set shell quote characters. + -~ ........ --tilde + Don't display tildes after end of file. + -# [_N] .... --shift=[_N] + Set horizontal scroll amount (0 = one half screen width). + + --exit-follow-on-close + Exit F command on a pipe when writer closes pipe. + --file-size + Automatically determine the size of the input file. + --follow-name + The F command changes files if the input file is renamed. + --form-feed + Stop scrolling when a form feed character is reached. + --header=[_L[,_C[,_N]]] + Use _L lines (starting at line _N) and _C columns as headers. + --incsearch + Search file as each pattern character is typed in. + --intr=[_C] + Use _C instead of ^X to interrupt a read. + --lesskey-context=_t_e_x_t + Use lesskey source file contents. + --lesskey-src=_f_i_l_e + Use a lesskey source file. + --line-num-width=[_N] + Set the width of the -N line number field to _N characters. + --match-shift=[_N] + Show at least _N characters to the left of a search match. + --modelines=[_N] + Read _N lines from the input file and look for vim modelines. + --mouse + Enable mouse input. + --no-edit-warn + Don't warn when using v command on a file opened via LESSOPEN. + --no-keypad + Don't send termcap keypad init/deinit strings. + --no-histdups + Remove duplicates from command history. + --no-number-headers + Don't give line numbers to header lines. + --no-paste + Ignore pasted input. + --no-search-header-lines + Searches do not include header lines. + --no-search-header-columns + Searches do not include header columns. + --no-search-headers + Searches do not include header lines or columns. + --no-vbell + Disable the terminal's visual bell. + --redraw-on-quit + Redraw final screen when quitting. + --rscroll=[_C] + Set the character used to mark truncated lines. + --save-marks + Retain marks across invocations of less. + --search-options=[EFKNRW-] + Set default options for every search. + --show-preproc-errors + Display a message if preprocessor exits with an error status. + --proc-backspace + Process backspaces for bold/underline. + --PROC-BACKSPACE + Treat backspaces as control characters. + --proc-return + Delete carriage returns before newline. + --PROC-RETURN + Treat carriage returns as control characters. + --proc-tab + Expand tabs to spaces. + --PROC-TAB + Treat tabs as control characters. + --status-col-width=[_N] + Set the width of the -J status column to _N characters. + --status-line + Highlight or color the entire line containing a mark. + --use-backslash + Subsequent options use backslash as escape char. + --use-color + Enables colored text. + --wheel-lines=[_N] + Each click of the mouse wheel moves _N lines. + --wordwrap + Wrap lines at spaces. + + + --------------------------------------------------------------------------- + + LLIINNEE EEDDIITTIINNGG + + These keys can be used to edit text being entered + on the "command line" at the bottom of the screen. + + RightArrow ..................... ESC-l ... Move cursor right one character. + LeftArrow ...................... ESC-h ... Move cursor left one character. + ctrl-RightArrow ESC-RightArrow ESC-w ... Move cursor right one word. + ctrl-LeftArrow ESC-LeftArrow ESC-b ... Move cursor left one word. + HOME ........................... ESC-0 ... Move cursor to start of line. + END ............................ ESC-$ ... Move cursor to end of line. + BACKSPACE ................................ Delete char to left of cursor. + DELETE ......................... ESC-x ... Delete char under cursor. + ctrl-BACKSPACE ESC-BACKSPACE ........... Delete word to left of cursor. + ctrl-DELETE .... ESC-DELETE .... ESC-X ... Delete word under cursor. + ctrl-U ......... ESC (MS-DOS only) ....... Delete entire line. + UpArrow ........................ ESC-k ... Retrieve previous command line. + DownArrow ...................... ESC-j ... Retrieve next command line. + TAB ...................................... Complete filename & cycle. + SHIFT-TAB ...................... ESC-TAB Complete filename & reverse cycle. + ctrl-L ................................... Complete filename, list all. + + SSUUMMMMAARRYY OOFF LLEESSSS CCOOMMMMAANNDDSS + + Commands marked with * may be preceded by a number, _N. + Notes in parentheses indicate the behavior if _N is given. + A key preceded by a caret indicates the Ctrl key; thus ^K is ctrl-K. + + h H Display this help. + q :q Q :Q ZZ Exit. + --------------------------------------------------------------------------- + + MMOOVVIINNGG + + e ^E j ^N CR * Forward one line (or _N lines). + y ^Y k ^K ^P * Backward one line (or _N lines). + ESC-j * Forward one file line (or _N file lines). + ESC-k * Backward one file line (or _N file lines). + f ^F ^V SPACE * Forward one window (or _N lines). + b ^B ESC-v * Backward one window (or _N lines). + z * Forward one window (and set window to _N). + w * Backward one window (and set window to _N). + ESC-SPACE * Forward one window, but don't stop at end-of-file. + ESC-b * Backward one window, but don't stop at beginning-of-file. + d ^D * Forward one half-window (and set half-window to _N). + u ^U * Backward one half-window (and set half-window to _N). + ESC-) RightArrow * Right one half screen width (or _N positions). + ESC-( LeftArrow * Left one half screen width (or _N positions). + ESC-} ^RightArrow Right to last column displayed. + ESC-{ ^LeftArrow Left to first column. + F Forward forever; like "tail -f". + ESC-F Like F but stop when search pattern is found. + r ^R ^L Repaint screen. + R Repaint screen, discarding buffered input. + --------------------------------------------------- + Default "window" is the screen height. + Default "half-window" is half of the screen height. + --------------------------------------------------------------------------- + + SSEEAARRCCHHIINNGG + + /_p_a_t_t_e_r_n * Search forward for (_N-th) matching line. + ?_p_a_t_t_e_r_n * Search backward for (_N-th) matching line. + n * Repeat previous search (for _N-th occurrence). + N * Repeat previous search in reverse direction. + ESC-n * Repeat previous search, spanning files. + ESC-N * Repeat previous search, reverse dir. & spanning files. + ^O^N ^On * Search forward for (_N-th) OSC8 hyperlink. + ^O^P ^Op * Search backward for (_N-th) OSC8 hyperlink. + ^O^L ^Ol Jump to the currently selected OSC8 hyperlink. + ESC-u Undo (toggle) search highlighting. + ESC-U Clear search highlighting. + &_p_a_t_t_e_r_n * Display only matching lines. + --------------------------------------------------- + Search is case-sensitive unless changed with -i or -I. + A search pattern may begin with one or more of: + ^N or ! Search for NON-matching lines. + ^E or * Search multiple files (pass thru END OF FILE). + ^F or @ Start search at FIRST file (for /) or last file (for ?). + ^K Highlight matches, but don't move (KEEP position). + ^R Don't use REGULAR EXPRESSIONS. + ^S _n Search for match in _n-th parenthesized subpattern. + ^W WRAP search if no match found. + ^L Enter next character literally into pattern. + --------------------------------------------------------------------------- + + JJUUMMPPIINNGG + + g < ESC-< * Go to first line in file (or line _N). + G > ESC-> * Go to last line in file (or line _N). + p % * Go to beginning of file (or _N percent into file). + t * Go to the (_N-th) next tag. + T * Go to the (_N-th) previous tag. + { ( [ * Find close bracket } ) ]. + } ) ] * Find open bracket { ( [. + ESC-^F _<_c_1_> _<_c_2_> * Find close bracket _<_c_2_>. + ESC-^B _<_c_1_> _<_c_2_> * Find open bracket _<_c_1_>. + --------------------------------------------------- + Each "find close bracket" command goes forward to the close bracket + matching the (_N-th) open bracket in the top line. + Each "find open bracket" command goes backward to the open bracket + matching the (_N-th) close bracket in the bottom line. + + m_<_l_e_t_t_e_r_> Mark the current top line with . + M_<_l_e_t_t_e_r_> Mark the current bottom line with . + '_<_l_e_t_t_e_r_> Go to a previously marked position. + '' Go to the previous position. From ea491c8d022d4d8c8ed6ccb28bf7d143f52d34b1 Mon Sep 17 00:00:00 2001 From: Trae AI Date: Fri, 2 Jan 2026 16:26:30 +0800 Subject: [PATCH 4/6] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E8=A7=86=E9=A2=91?= =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E5=92=8C=E6=98=BE=E7=A4=BA=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Reject.js | 170 +++++++++++++++++++++++---- node_modules/.package-lock.json | 4 + package-lock.json | 4 + supply.html | 199 ++++++++++++++++++++++++++++---- 4 files changed, 334 insertions(+), 43 deletions(-) diff --git a/Reject.js b/Reject.js index 948d720..8f17782 100644 --- a/Reject.js +++ b/Reject.js @@ -768,37 +768,48 @@ app.post('/api/supplies/create', async (req, res) => { // 生成唯一的productId const productId = `product_${Date.now()}_${Math.floor(Math.random() * 1000)}`; - // 处理图片上传 + // 处理媒体文件上传(图片和视频) let uploadedImageUrls = []; if (Array.isArray(imageUrls) && imageUrls.length > 0) { - console.log('开始处理图片上传,共', imageUrls.length, '张图片'); + console.log('开始处理媒体文件上传,共', imageUrls.length, '个文件'); - for (const imageUrl of imageUrls) { - if (imageUrl.startsWith('data:image/')) { + for (const mediaUrl of imageUrls) { + if (mediaUrl.startsWith('data:image/') || mediaUrl.startsWith('data:video/')) { // 处理DataURL - const base64Data = imageUrl.replace(/^data:image\/(png|jpeg|jpg|gif);base64,/, ''); + let base64Data, ext, fileType; + if (mediaUrl.startsWith('data:image/')) { + // 图片类型 + base64Data = mediaUrl.replace(/^data:image\/(png|jpeg|jpg|gif);base64,/, ''); + ext = mediaUrl.match(/^data:image\/(png|jpeg|jpg|gif);base64,/)?.[1] || 'png'; + fileType = 'image'; + } else { + // 视频类型 + base64Data = mediaUrl.replace(/^data:video\/(mp4|mov|avi|wmv|flv);base64,/, ''); + ext = mediaUrl.match(/^data:video\/(mp4|mov|avi|wmv|flv);base64,/)?.[1] || 'mp4'; + fileType = 'video'; + } + let buffer = Buffer.from(base64Data, 'base64'); - const ext = imageUrl.match(/^data:image\/(png|jpeg|jpg|gif);base64,/)?.[1] || 'png'; const filename = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}.${ext}`; try { // 不再添加水印,前端已处理 console.log('【水印处理】前端已添加水印,跳过后端水印处理'); - // 使用OSS上传带水印的图片 - const ossUrl = await OssUploader.uploadBuffer(buffer, filename, `products/${productName || 'general'}`, 'image'); + // 使用OSS上传媒体文件 + const ossUrl = await OssUploader.uploadBuffer(buffer, filename, `products/${productName || 'general'}`, fileType); uploadedImageUrls.push(ossUrl); - console.log('图片上传成功:', ossUrl); + console.log(`${fileType}上传成功:`, ossUrl); } catch (uploadError) { - console.error('图片上传失败:', uploadError.message); - // 继续上传其他图片,不中断流程 + console.error(`${fileType}上传失败:`, uploadError.message); + // 继续上传其他文件,不中断流程 } } else { // 已经是URL,直接使用 - uploadedImageUrls.push(imageUrl); + uploadedImageUrls.push(mediaUrl); } } - console.log('图片处理完成,成功上传', uploadedImageUrls.length, '张图片'); + console.log('媒体文件处理完成,成功上传', uploadedImageUrls.length, '个文件'); } // 创建商品数据 @@ -883,16 +894,86 @@ app.post('/api/supplies/create', async (req, res) => { } }); -// 图片上传API - /api/upload-image +// 媒体文件上传API - /api/upload-media +console.log('正在注册媒体文件上传API路由: /api/upload-media'); +app.post('/api/upload-media', async (req, res) => { + console.log('收到媒体文件上传请求:', req.body); + try { + const { fileData, fileName, folder = 'general' } = req.body; + + // 验证参数 + if (!fileData) { + return sendResponse(res, false, null, '文件数据不能为空'); + } + + let base64Data, ext, fileType; + if (fileData.startsWith('data:image/')) { + // 图片类型 + base64Data = fileData.replace(/^data:image\/(png|jpeg|jpg|gif);base64,/, ''); + ext = fileData.match(/^data:image\/(png|jpeg|jpg|gif);base64,/)?.[1] || 'png'; + fileType = 'image'; + } else if (fileData.startsWith('data:video/')) { + // 视频类型 + base64Data = fileData.replace(/^data:video\/(mp4|mov|avi|wmv|flv);base64,/, ''); + ext = fileData.match(/^data:video\/(mp4|mov|avi|wmv|flv);base64,/)?.[1] || 'mp4'; + fileType = 'video'; + } else { + return sendResponse(res, false, null, '不支持的文件类型'); + } + + let buffer = Buffer.from(base64Data, 'base64'); + const filename = fileName || `${Date.now()}-${Math.random().toString(36).substr(2, 9)}.${ext}`; + + try { + // 使用OSS上传媒体文件 + const ossUrl = await OssUploader.uploadBuffer(buffer, filename, `uploads/${folder}`, fileType); + console.log(`${fileType}上传成功:`, ossUrl); + + sendResponse(res, true, { + url: ossUrl, + fileType: fileType, + message: `${fileType}上传成功` + }, `${fileType}上传成功`); + } catch (uploadError) { + console.error(`${fileType}上传失败:`, uploadError.message); + sendResponse(res, false, null, `${fileType}上传失败: ${uploadError.message}`); + } + } catch (error) { + console.error('媒体文件上传API错误:', error.message); + sendResponse(res, false, null, `媒体文件上传失败: ${error.message}`); + } +}); + +// 图片上传API - /api/upload-image(兼容旧接口) console.log('正在注册图片上传API路由: /api/upload-image'); app.post('/api/upload-image', async (req, res) => { - console.log('收到图片上传请求'); - // 注意:这里需要实现实际的图片上传逻辑,包括OSS配置 - // 由于当前缺少OSS配置,返回模拟数据 - sendResponse(res, true, { - imageUrl: 'https://example.com/image.jpg', - message: '图片上传成功' - }, '图片上传成功'); + console.log('收到图片上传请求,转发到媒体文件上传API'); + // 将请求转发到媒体文件上传API + const uploadMediaHandler = app._router.stack.find(layer => + layer.route && layer.route.path === '/api/upload-media' && layer.route.methods['post'] + ); + + if (uploadMediaHandler && uploadMediaHandler.route && uploadMediaHandler.route.stack[0]) { + return uploadMediaHandler.route.stack[0].handle(req, res); + } else { + sendResponse(res, false, null, '图片上传API内部错误'); + } +}); + +// 视频上传API - /api/upload-video(专门的视频上传接口) +console.log('正在注册视频上传API路由: /api/upload-video'); +app.post('/api/upload-video', async (req, res) => { + console.log('收到视频上传请求,转发到媒体文件上传API'); + // 将请求转发到媒体文件上传API + const uploadMediaHandler = app._router.stack.find(layer => + layer.route && layer.route.path === '/api/upload-media' && layer.route.methods['post'] + ); + + if (uploadMediaHandler && uploadMediaHandler.route && uploadMediaHandler.route.stack[0]) { + return uploadMediaHandler.route.stack[0].handle(req, res); + } else { + sendResponse(res, false, null, '视频上传API内部错误'); + } }); // 获取审核失败原因API - /api/supplies/:id/reject-reason @@ -1089,6 +1170,53 @@ app.put('/api/supplies/:id/edit', async (req, res) => { } } +<<<<<<< Updated upstream +======= + // 处理媒体文件上传(图片和视频) + let uploadedImageUrls = []; + if (Array.isArray(imageUrls) && imageUrls.length > 0) { + console.log('开始处理编辑媒体文件上传,共', imageUrls.length, '个文件'); + + for (const mediaUrl of imageUrls) { + if (mediaUrl.startsWith('data:image/') || mediaUrl.startsWith('data:video/')) { + // 处理DataURL + let base64Data, ext, fileType; + if (mediaUrl.startsWith('data:image/')) { + // 图片类型 + base64Data = mediaUrl.replace(/^data:image\/(png|jpeg|jpg|gif);base64,/, ''); + ext = mediaUrl.match(/^data:image\/(png|jpeg|jpg|gif);base64,/)?.[1] || 'png'; + fileType = 'image'; + } else { + // 视频类型 + base64Data = mediaUrl.replace(/^data:video\/(mp4|mov|avi|wmv|flv);base64,/, ''); + ext = mediaUrl.match(/^data:video\/(mp4|mov|avi|wmv|flv);base64,/)?.[1] || 'mp4'; + fileType = 'video'; + } + + let buffer = Buffer.from(base64Data, 'base64'); + const filename = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}.${ext}`; + + try { + // 不再添加水印,前端已处理 + console.log('【水印处理】前端已添加水印,跳过后端水印处理'); + + // 使用OSS上传媒体文件 + const ossUrl = await OssUploader.uploadBuffer(buffer, filename, `products/${productName || 'general'}`, fileType); + uploadedImageUrls.push(ossUrl); + console.log(`${fileType}上传成功:`, ossUrl); + } catch (uploadError) { + console.error(`${fileType}上传失败:`, uploadError.message); + // 继续上传其他文件,不中断流程 + } + } else { + // 已经是URL,直接使用 + uploadedImageUrls.push(mediaUrl); + } + } + console.log('媒体文件处理完成,成功上传', uploadedImageUrls.length, '个文件'); + } + +>>>>>>> Stashed changes // 更新货源信息 const updateQuery = ` UPDATE products diff --git a/node_modules/.package-lock.json b/node_modules/.package-lock.json index ee13f04..f39158a 100644 --- a/node_modules/.package-lock.json +++ b/node_modules/.package-lock.json @@ -1,5 +1,9 @@ { +<<<<<<< Updated upstream "name": "Review2", +======= + "name": "boss", +>>>>>>> Stashed changes "lockfileVersion": 3, "requires": true, "packages": { diff --git a/package-lock.json b/package-lock.json index a42c8c5..0ba49cc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,9 @@ { +<<<<<<< Updated upstream "name": "Review2", +======= + "name": "boss", +>>>>>>> Stashed changes "lockfileVersion": 3, "requires": true, "packages": { diff --git a/supply.html b/supply.html index 395d679..0b149c2 100644 --- a/supply.html +++ b/supply.html @@ -1052,13 +1052,13 @@
- +
- +
+
- +
图片要求:
1. 箱子堆头图(不得出现地址、电话及货源信息,直营包场除外);
@@ -1066,7 +1066,11 @@
3. 敲开鸡蛋后清晰显示蛋清、蛋黄状态,以体现新鲜度;
4. 其他能佐证蛋重、品种的辅助图片。
+<<<<<<< Updated upstream
最多上传5张图片
+======= +
最多上传2个媒体文件(图片或视频)
+>>>>>>> Stashed changes
@@ -1398,13 +1402,21 @@ - +
- +
+<<<<<<< Updated upstream
提示:图片暂不支持编辑
+======= +
最多上传2个媒体文件(图片或视频)
+ + +>>>>>>> Stashed changes
@@ -1860,14 +1872,17 @@ const reader = new FileReader(); reader.onload = async function(e) { - let imageUrl = e.target.result; + let mediaUrl = e.target.result; + let isVideo = file.type.startsWith('video/'); - // 为图片添加水印 - imageUrl = await addWatermarkToImage(imageUrl); + // 只为图片添加水印,视频不添加 + if (!isVideo) { + mediaUrl = await addWatermarkToImage(mediaUrl); + } - supplyData.uploadedImages.push(imageUrl); + supplyData.uploadedImages.push(mediaUrl); renderUploadedImages(); - // 图片上传后自动保存表单数据 + // 媒体文件上传后自动保存表单数据 saveFormData(); }; @@ -3563,6 +3578,94 @@ document.getElementById('imageUpload').click(); } +<<<<<<< Updated upstream +======= + // 触发编辑页面图片上传 + function triggerEditImageUpload() { + document.getElementById('editImageUpload').click(); + } + + // 处理编辑页面图片上传 + function handleEditImageUpload(event) { + const files = event.target.files; + const editUploadImages = document.getElementById('editUploadImages'); + + for (let i = 0; i < files.length; i++) { + if (editCurrentImages.length >= 2) { + alert('最多只能上传2个媒体文件(图片或视频)'); + break; + } + + const file = files[i]; + const reader = new FileReader(); + + reader.onload = async function(e) { + let mediaUrl = e.target.result; + let isVideo = file.type.startsWith('video/'); + + try { + // 只为图片添加水印,视频不添加 + if (!isVideo) { + mediaUrl = await addWatermarkToImage(mediaUrl); + } + + // 添加到当前媒体列表 + editCurrentImages.push(mediaUrl); + + // 更新显示 + updateEditImageDisplay(); + } catch (error) { + console.error('媒体处理失败:', error); + alert('媒体处理失败,请重试'); + } + }; + + reader.readAsDataURL(file); + } + + // 清空文件输入,以便再次选择同一文件 + event.target.value = ''; + } + + // 删除编辑页面图片 + function deleteEditImage(imageUrl) { + // 从当前图片列表中删除 + const index = editCurrentImages.indexOf(imageUrl); + if (index > -1) { + editCurrentImages.splice(index, 1); + } + + // 更新显示 + updateEditImageDisplay(); + } + + // 更新编辑页面媒体显示(图片或视频) + function updateEditImageDisplay() { + const editUploadImages = document.getElementById('editUploadImages'); + editUploadImages.innerHTML = ''; + + editCurrentImages.forEach(mediaUrl => { + const imageElement = document.createElement('div'); + imageElement.className = 'upload-image'; + + let mediaHtml = ''; + if (mediaUrl.startsWith('data:video/') || mediaUrl.endsWith('.mp4') || mediaUrl.endsWith('.mov') || mediaUrl.endsWith('.avi') || mediaUrl.endsWith('.wmv') || mediaUrl.endsWith('.flv')) { + // 视频文件 + mediaHtml = ``; + } else { + // 图片文件 + mediaHtml = `商品图片`; + } + + imageElement.innerHTML = ` + ${mediaHtml} + + `; + editUploadImages.appendChild(imageElement); + }); + } + +>>>>>>> Stashed changes // 为图片添加水印(前端Canvas实现) function addWatermarkToImage(imageUrl) { return new Promise((resolve, reject) => { @@ -3616,8 +3719,13 @@ const uploadArea = document.getElementById('uploadImages'); for (let i = 0; i < files.length; i++) { +<<<<<<< Updated upstream if (supplyData.uploadedImages.length >= 5) { alert('最多只能上传5张图片'); +======= + if (supplyData.uploadedImages.length >= 2) { + alert('最多只能上传2个媒体文件(图片或视频)'); +>>>>>>> Stashed changes break; } @@ -3643,16 +3751,26 @@ event.target.value = ''; } - // 渲染已上传图片 + // 渲染已上传媒体文件(图片和视频) function renderUploadedImages() { const uploadArea = document.getElementById('uploadImages'); uploadArea.innerHTML = ''; - supplyData.uploadedImages.forEach((imageUrl, index) => { + supplyData.uploadedImages.forEach((mediaUrl, index) => { const imageElement = document.createElement('div'); imageElement.className = 'upload-image'; + + let mediaHtml = ''; + if (mediaUrl.startsWith('data:video/')) { + // 视频文件 + mediaHtml = ``; + } else { + // 图片文件 + mediaHtml = `商品图片`; + } + imageElement.innerHTML = ` - 商品图片 + ${mediaHtml}
`; uploadArea.appendChild(imageElement); @@ -3942,28 +4060,65 @@ } const previewModal = document.getElementById('imagePreview'); - const previewImage = document.getElementById('previewImage'); - // 更新图片显示 + // 更新媒体显示 updatePreviewImage(); - // 重置缩放和拖动状态 - resetImageTransform(); + // 重置缩放和拖动状态(仅图片需要) + const isVideo = imageUrl.startsWith('data:video/') || imageUrl.endsWith('.mp4') || imageUrl.endsWith('.mov') || imageUrl.endsWith('.avi') || imageUrl.endsWith('.wmv') || imageUrl.endsWith('.flv'); + if (!isVideo) { + resetImageTransform(); + } previewModal.classList.add('active'); - // 设置图片查看器事件 - setupImageViewerEvents(); + // 设置图片查看器事件(仅图片需要) + if (!isVideo) { + setupImageViewerEvents(); + } } - // 更新预览图片 + // 专门的视频预览函数 + function previewMedia(mediaUrl, mediaList = []) { + previewImage(mediaUrl, mediaList); + } + + // 更新预览媒体(图片或视频) function updatePreviewImage() { + // 获取预览元素 const previewImage = document.getElementById('previewImage'); + const previewVideo = document.createElement('video'); + previewVideo.id = 'previewVideo'; + previewVideo.className = 'preview-video'; + previewVideo.style.maxWidth = '100%'; + previewVideo.style.maxHeight = '100%'; + previewVideo.controls = true; + previewVideo.muted = false; + const previewCounter = document.getElementById('previewCounter'); + const mediaUrl = currentPreviewImages[currentPreviewIndex]; + + // 判断是图片还是视频 + const isVideo = mediaUrl.startsWith('data:video/') || mediaUrl.endsWith('.mp4') || mediaUrl.endsWith('.mov') || mediaUrl.endsWith('.avi') || mediaUrl.endsWith('.wmv') || mediaUrl.endsWith('.flv'); - previewImage.src = currentPreviewImages[currentPreviewIndex]; + // 移除旧的视频元素(如果存在) + const existingVideo = document.getElementById('previewVideo'); + if (existingVideo) { + existingVideo.remove(); + } + + if (isVideo) { + // 视频预览 + previewImage.style.display = 'none'; + previewVideo.src = mediaUrl; + previewImage.parentElement.appendChild(previewVideo); + } else { + // 图片预览 + previewImage.style.display = 'block'; + previewImage.src = mediaUrl; + } - // 更新图片计数器 + // 更新计数器 if (previewCounter) { previewCounter.textContent = `${currentPreviewIndex + 1}/${currentPreviewImages.length}`; } From adfb2db2a65b00cf2aa4d1a4d1cc206e71cc2339 Mon Sep 17 00:00:00 2001 From: Trae AI Date: Fri, 2 Jan 2026 16:32:51 +0800 Subject: [PATCH 5/6] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=A7=86=E9=A2=91?= =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E5=8A=9F=E8=83=BD=EF=BC=8C=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=9B=BE=E7=89=87Urls=E5=88=B0=E7=BC=96=E8=BE=91API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Reject.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Reject.js b/Reject.js index 8f17782..23d9d64 100644 --- a/Reject.js +++ b/Reject.js @@ -1170,8 +1170,7 @@ app.put('/api/supplies/:id/edit', async (req, res) => { } } -<<<<<<< Updated upstream -======= + // 处理媒体文件上传(图片和视频) let uploadedImageUrls = []; if (Array.isArray(imageUrls) && imageUrls.length > 0) { @@ -1216,20 +1215,20 @@ app.put('/api/supplies/:id/edit', async (req, res) => { console.log('媒体文件处理完成,成功上传', uploadedImageUrls.length, '个文件'); } ->>>>>>> Stashed changes + // 更新货源信息 const updateQuery = ` UPDATE products SET productName = ?, price = ?, quantity = ?, grossWeight = ?, yolk = ?, specification = ?, supplyStatus = ?, description = ?, region = ?, - producting = ?, product_contact = ?, contact_phone = ? + producting = ?, product_contact = ?, contact_phone = ?, imageUrls = ? WHERE id = ? `; await connection.query(updateQuery, [ productName, price.toString(), parseInt(quantity), grossWeight, yolk, specification, supplyStatus, description, region, - producting, productContact, contactPhone, productId + producting, productContact, contactPhone, JSON.stringify(uploadedImageUrls), productId ]); // 提交事务 From ea38eb3000227e6fed5ae0184d03881208162c42 Mon Sep 17 00:00:00 2001 From: Trae AI Date: Fri, 2 Jan 2026 16:58:37 +0800 Subject: [PATCH 6/6] Upload changes to two branch --- Reject.js | 16 +- supply.html | 1791 +++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 1538 insertions(+), 269 deletions(-) diff --git a/Reject.js b/Reject.js index 23d9d64..f72809a 100644 --- a/Reject.js +++ b/Reject.js @@ -3,6 +3,7 @@ const bodyParser = require('body-parser'); const cors = require('cors'); const mysql = require('mysql2/promise'); const path = require('path'); +const OssUploader = require('./oss-uploader'); const app = express(); const PORT = 3000; @@ -714,8 +715,6 @@ app.post('/api/suppliers/:id/terminate', async (req, res) => { } }); -// 导入OSS上传工具 -const OssUploader = require('./oss-uploader'); // 导入图片处理工具 const ImageProcessor = require('./image-processor'); @@ -1170,9 +1169,9 @@ app.put('/api/supplies/:id/edit', async (req, res) => { } } - // 处理媒体文件上传(图片和视频) let uploadedImageUrls = []; + const { imageUrls } = req.body; if (Array.isArray(imageUrls) && imageUrls.length > 0) { console.log('开始处理编辑媒体文件上传,共', imageUrls.length, '个文件'); @@ -1215,20 +1214,27 @@ app.put('/api/supplies/:id/edit', async (req, res) => { console.log('媒体文件处理完成,成功上传', uploadedImageUrls.length, '个文件'); } + // 如果有新上传的图片,更新imageUrls字段 + if (uploadedImageUrls.length > 0) { + await connection.query( + 'UPDATE products SET imageUrls = ? WHERE id = ?', + [JSON.stringify(uploadedImageUrls), productId] + ); + } // 更新货源信息 const updateQuery = ` UPDATE products SET productName = ?, price = ?, quantity = ?, grossWeight = ?, yolk = ?, specification = ?, supplyStatus = ?, description = ?, region = ?, - producting = ?, product_contact = ?, contact_phone = ?, imageUrls = ? + producting = ?, product_contact = ?, contact_phone = ? WHERE id = ? `; await connection.query(updateQuery, [ productName, price.toString(), parseInt(quantity), grossWeight, yolk, specification, supplyStatus, description, region, - producting, productContact, contactPhone, JSON.stringify(uploadedImageUrls), productId + producting, productContact, contactPhone, productId ]); // 提交事务 diff --git a/supply.html b/supply.html index 0b149c2..7929fd6 100644 --- a/supply.html +++ b/supply.html @@ -267,6 +267,23 @@ background-color: #8c8c8c; } + .copy-supply-btn { + display: inline-block; + padding: 4px 12px; + border: 1px solid #d9d9d9; + border-radius: 4px; + font-size: 12px; + background-color: #fff; + color: #333; + cursor: pointer; + margin-left: 8px; + } + + .copy-supply-btn:hover { + border-color: #1677ff; + color: #1677ff; + } + .supply-details { display: grid; grid-template-columns: repeat(2, 1fr); @@ -464,6 +481,29 @@ transition: all 0.3s; } + .delete-image-btn { + position: absolute; + top: 5px; + right: 5px; + width: 24px; + height: 24px; + background-color: rgba(245, 34, 45, 0.9); + color: white; + border: none; + border-radius: 50%; + font-size: 16px; + line-height: 20px; + text-align: center; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + } + + .delete-image-btn:hover { + background-color: #f5222d; + } + .upload-image:hover { transform: scale(1.05); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); @@ -952,6 +992,102 @@ height: 150px; } } + + /* 件数输入样式 */ + #quantityContainer { + display: flex; + flex-direction: column; + gap: 10px; + } + + .quantity-item { + display: flex; + align-items: center; + gap: 10px; + } + + .quantity-input { + flex: 1; + } + + .remove-quantity-btn { + width: 32px; + height: 32px; + border: none; + background-color: #ff4d4f; + color: white; + border-radius: 50%; + font-size: 16px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + } + + .add-quantity-btn { + align-self: flex-start; + padding: 8px 16px; + border: 1px dashed #1677ff; + background-color: white; + color: #1677ff; + border-radius: 4px; + cursor: pointer; + font-size: 14px; + } + + .add-quantity-btn:hover { + background-color: #e6f7ff; + } + + /* 规格和件数对样式 */ + #specQuantityContainer, + #editSpecQuantityContainer { + display: flex; + flex-direction: column; + gap: 10px; + } + + .spec-quantity-pair { + display: flex; + align-items: center; + gap: 10px; + background-color: #fafafa; + padding: 10px; + border-radius: 8px; + border: 1px solid #f0f0f0; + } + + .spec-value { + flex: 1; + padding: 10px; + border: 1px solid #d9d9d9; + border-radius: 8px; + font-size: 14px; + box-sizing: border-box; + background-color: white; + cursor: pointer; + } + + .quantity-input { + width: 120px; + background-color: white; + } + + .add-spec-quantity-btn { + align-self: flex-start; + padding: 8px 16px; + border: 1px dashed #1677ff; + background-color: white; + color: #1677ff; + border-radius: 4px; + cursor: pointer; + font-size: 14px; + margin-top: 5px; + } + + .add-spec-quantity-btn:hover { + background-color: #e6f7ff; + } @@ -997,7 +1133,7 @@ -
+ - +
- -
- 请选择规格 - + +
+ 请选择产品包装 +
-
@@ -1135,6 +1266,19 @@
+ +
+ +
+ +
+ + +
+ + +
+
@@ -1145,24 +1289,18 @@
- -
- - -
- - -
- - -
- + +
+ + +
+
@@ -1184,6 +1322,8 @@ + + @@ -1206,7 +1346,7 @@
第三方货源: 贸易商货源
@@ -1228,7 +1368,7 @@
@@ -1254,7 +1394,7 @@
(如果有色卡可以在货源描述里面填写色度)
@@ -1276,7 +1416,7 @@
@@ -1303,7 +1443,7 @@
@@ -1325,7 +1465,7 @@
@@ -1350,7 +1490,7 @@
@@ -1363,6 +1503,28 @@
+ + +
+
+
+

选择产品包装

+ +
+ +
+
+ +
+
+ +
+
- +
- +
-<<<<<<< Updated upstream -
提示:图片暂不支持编辑
-======= -
最多上传2个媒体文件(图片或视频)
- +
最多上传2张图片
+ ->>>>>>> Stashed changes
@@ -1459,12 +1617,12 @@
- +
- -
- 请选择规格 - + +
+ 请选择产品包装 +
@@ -1479,6 +1637,19 @@
+ +
+ +
+ +
+ + +
+ + +
+
@@ -1489,24 +1660,18 @@
- -
- - -
- - -
- - -
- + +
+ + +
+
@@ -1534,7 +1699,7 @@
@@ -1561,7 +1726,7 @@
第三方货源: 贸易商货源
@@ -1583,7 +1748,7 @@
@@ -1605,7 +1770,7 @@
@@ -1631,7 +1796,7 @@
(如果有色卡可以在货源描述里面填写色度)
@@ -1653,7 +1818,7 @@
@@ -1667,6 +1832,28 @@
+ +
+
+
+

选择产品包装

+ +
+ +
+
+ +
+
+ +
+
+
@@ -1675,7 +1862,7 @@
@@ -1722,7 +1909,7 @@ // 编辑相关全局变量 let currentEditSupplyId = null; - let editSelectedSpec = ''; + let editSelectedSpec = []; let editSelectedProvince = ''; let editSelectedCity = ''; let editSelectedDistrict = ''; @@ -1738,7 +1925,11 @@ let editAllYolkTypes = ['红心', '黄心', '双色', '未知']; let editFilteredYolkTypes = [...editAllYolkTypes]; let editSelectedYolk = ''; + let editAllProductingOptions = ['1*360枚新包装', '1*360枚旧包新拖', '1*360枚旧包旧拖', '1*420枚新包装', '1*480枚新包装', '30枚蛋托散装', '360枚散托']; + let editFilteredProductingOptions = [...editAllProductingOptions]; + let editSelectedProducting = ''; let editFilteredContacts = []; + let editCurrentImages = []; // 编辑时当前的图片列表 let editSelectedContactId = ''; // 联系人数据 @@ -1789,7 +1980,11 @@ loadSupplies(); loadContacts(); + loadFormData(); // 加载保存的表单数据,包括联系人信息 bindEvents(); + + // 初始化至少一个规格和件数对 + addSpecQuantityPair(); }; // 绑定事件 @@ -1863,8 +2058,8 @@ // 处理拖拽或粘贴的图片 async function handleDroppedImages(imageFiles) { for (let i = 0; i < imageFiles.length; i++) { - if (supplyData.uploadedImages.length >= 5) { - alert('最多只能上传5张图片'); + if (supplyData.uploadedImages.length >= 2) { + alert('最多只能上传2张图片'); break; } @@ -1872,17 +2067,14 @@ const reader = new FileReader(); reader.onload = async function(e) { - let mediaUrl = e.target.result; - let isVideo = file.type.startsWith('video/'); + let imageUrl = e.target.result; - // 只为图片添加水印,视频不添加 - if (!isVideo) { - mediaUrl = await addWatermarkToImage(mediaUrl); - } + // 为图片添加水印 + imageUrl = await addWatermarkToImage(imageUrl); - supplyData.uploadedImages.push(mediaUrl); + supplyData.uploadedImages.push(imageUrl); renderUploadedImages(); - // 媒体文件上传后自动保存表单数据 + // 图片上传后自动保存表单数据 saveFormData(); }; @@ -1893,7 +2085,12 @@ // 规格选择功能 let allSpecOptions = ['净重47+', '净重46-47', '净重45-46', '净重44-45', '净重43-44', '净重42-43', '净重41-42', '净重40-41', '净重39-40', '净重38-39', '净重37-39', '净重37-38', '净重36-38', '净重36-37', '净重35-36', '净重34-35', '净重33-34', '净重32-33', '净重32-34', '净重31-32', '净重30-35', '净重30-34', '净重30-32', '净重30-31', '净重29-31', '净重29-30', '净重28-29', '净重28以下', '毛重52以上', '毛重50-51', '毛重48-49', '毛重47-48', '毛重46-47', '毛重45-47', '毛重45-46', '毛重44-45', '毛重43-44', '毛重42-43', '毛重41-42', '毛重40-41', '毛重38-39', '毛重36-37', '毛重34-35', '毛重32-33', '毛重30-31', '毛重30以下']; let filteredSpecOptions = [...allSpecOptions]; - let selectedSpec = ''; + let selectedSpec = []; + + // 产品包装选择功能 + let allProductingOptions = ['1*360枚新包装', '1*360枚旧包新拖', '1*360枚旧包旧拖', '1*420枚新包装', '1*480枚新包装', '30枚蛋托散装', '360枚散托']; + let filteredProductingOptions = [...allProductingOptions]; + let selectedProducting = ''; // 货源类型选择功能 let allSourceTypeOptions = ['平台货源', '鸡场直销', '第三方货源']; @@ -1944,21 +2141,46 @@ const option = document.createElement('div'); option.className = 'select-item'; option.textContent = spec; + + // 检查当前规格是否已被选中 + if (selectedSpec.includes(spec)) { + option.classList.add('selected'); + } + + // 单击选择逻辑 option.onclick = () => { - // 移除所有选项的选中状态 + // 单选逻辑:移除所有选中状态 document.querySelectorAll('.select-item').forEach(item => { item.classList.remove('selected'); }); - // 添加当前选项的选中状态 + + // 添加当前选中状态 option.classList.add('selected'); - selectedSpec = spec; + + // 更新selectedSpec数组为单选 + selectedSpec = [spec]; }; + + // 双击确认逻辑 option.ondblclick = () => { - // 双击直接确认选择 - document.getElementById('specDisplayText').textContent = spec; - document.getElementById('specValue').value = spec; - hideSpecSelectModal(); + // 确保当前选项被选中 + if (!option.classList.contains('selected')) { + // 单选逻辑:移除所有选中状态 + document.querySelectorAll('.select-item').forEach(item => { + item.classList.remove('selected'); + }); + + // 添加当前选中状态 + option.classList.add('selected'); + + // 更新selectedSpec数组为单选 + selectedSpec = [spec]; + } + + // 自动确认选择 + confirmSpecSelection(); }; + specOptionsList.appendChild(option); }); } @@ -1977,13 +2199,163 @@ // 确认规格选择 function confirmSpecSelection() { - if (selectedSpec) { - document.getElementById('specDisplayText').textContent = selectedSpec; - document.getElementById('specValue').value = selectedSpec; + if (currentSpecInput) { + // 如果是为规格和件数对选择规格 + if (selectedSpec.length > 0) { + currentSpecInput.value = selectedSpec.join(', '); + } else { + currentSpecInput.value = ''; + } + } else { + // 否则,为旧的规格选择逻辑 + if (selectedSpec.length > 0) { + document.getElementById('specDisplayText').textContent = selectedSpec.join(', '); + document.getElementById('specValue').value = JSON.stringify(selectedSpec); + // 根据选中的规格数量动态调整件数输入框数量 + adjustQuantityInputs(); + } else { + document.getElementById('specDisplayText').textContent = '请选择规格'; + document.getElementById('specValue').value = '[]'; + } } hideSpecSelectModal(); } + // 添加件数输入项 + function addQuantityItem() { + const container = document.getElementById('quantityContainer'); + const quantityItem = document.createElement('div'); + quantityItem.className = 'quantity-item'; + quantityItem.innerHTML = ` + + + `; + container.insertBefore(quantityItem, container.lastElementChild); + } + + // 删除件数输入项 + function removeQuantityItem(btn) { + const container = document.getElementById('quantityContainer'); + const quantityItems = container.querySelectorAll('.quantity-item'); + + // 至少保留一个件数输入项 + if (quantityItems.length > 1) { + const quantityItem = btn.parentElement; + quantityItem.remove(); + } + } + + // 根据选中的规格数量调整件数输入框数量 + function adjustQuantityInputs() { + const container = document.getElementById('quantityContainer'); + const quantityItems = container.querySelectorAll('.quantity-item'); + const specCount = selectedSpec.length; + + // 如果规格数量大于件数输入框数量,添加缺少的输入框 + while (quantityItems.length < specCount) { + addQuantityItem(); + } + } + + // 添加规格和件数对 + function addSpecQuantityPair() { + const container = document.getElementById('specQuantityPairs'); + const pair = document.createElement('div'); + pair.className = 'spec-quantity-pair'; + pair.innerHTML = ` + + + + `; + container.appendChild(pair); + } + + // 删除规格和件数对 + function removeSpecQuantityPair(btn) { + const pair = btn.parentElement; + const specInput = pair.querySelector('.spec-value'); + const quantityInput = pair.querySelector('.quantity-input'); + + // 清除当前对的输入值 + if (specInput) specInput.value = ''; + if (quantityInput) quantityInput.value = ''; + } + + // 为规格和件数对显示规格选择弹窗 + function showSpecSelectModalForPair(input) { + // 保存当前点击的输入框 + currentSpecInput = input; + showSpecSelectModal(); + } + + // 保存当前点击的规格输入框 + let currentSpecInput = null; + + // 显示产品包装选择弹窗 + function showProductingSelectModal() { + const productingSelectModal = document.getElementById('productingSelectModal'); + productingSelectModal.classList.add('active'); + // 重置搜索输入 + document.getElementById('productingSearchInput').value = ''; + filteredProductingOptions = [...allProductingOptions]; + generateProductingOptions(); + } + + // 隐藏产品包装选择弹窗 + function hideProductingSelectModal() { + const productingSelectModal = document.getElementById('productingSelectModal'); + productingSelectModal.classList.remove('active'); + } + + // 生成产品包装选项 + function generateProductingOptions() { + const productingOptionsList = document.getElementById('productingOptionsList'); + productingOptionsList.innerHTML = ''; + + filteredProductingOptions.forEach(producting => { + const option = document.createElement('div'); + option.className = 'select-item'; + option.textContent = producting; + option.onclick = () => { + // 移除所有选项的选中状态 + document.querySelectorAll('.select-item').forEach(item => { + item.classList.remove('selected'); + }); + // 添加当前选项的选中状态 + option.classList.add('selected'); + selectedProducting = producting; + }; + option.ondblclick = () => { + // 双击直接确认选择 + document.getElementById('productingDisplayText').textContent = producting; + document.getElementById('productingValue').value = producting; + hideProductingSelectModal(); + }; + productingOptionsList.appendChild(option); + }); + } + + // 过滤产品包装选项 + function filterProductingOptions() { + const searchInput = document.getElementById('productingSearchInput'); + const searchKeyword = searchInput.value.toLowerCase(); + + filteredProductingOptions = allProductingOptions.filter(producting => { + return producting.toLowerCase().includes(searchKeyword); + }); + + generateProductingOptions(); + } + + // 确认产品包装选择 + function confirmProductingSelection() { + if (selectedProducting) { + document.getElementById('productingDisplayText').textContent = selectedProducting; + document.getElementById('productingValue').value = selectedProducting; + } + hideProductingSelectModal(); + } + // 显示货源类型选择弹窗 function showSourceTypeSelectModal() { const sourceTypeSelectModal = document.getElementById('sourceTypeSelectModal'); @@ -2997,10 +3369,40 @@ // 确保contacts是一个数组 contacts = result.data || []; console.log('联系人数据加载成功:', contacts); + + // 保存到本地缓存,添加时间戳和版本号 + const contactsCache = { + data: contacts, + version: '1.0', + timestamp: Date.now() + }; + localStorage.setItem('contactsCache', JSON.stringify(contactsCache)); + updateContactSelects(); } catch (error) { console.error('加载联系人数据失败:', error); - // 出错时将contacts设为空数组,避免后续错误 + + // 尝试从本地缓存加载 + try { + const cachedContacts = localStorage.getItem('contactsCache'); + if (cachedContacts) { + const contactsCache = JSON.parse(cachedContacts); + // 检查缓存是否有效(7天内) + const cacheExpiry = 7 * 24 * 60 * 60 * 1000; + if (Date.now() - contactsCache.timestamp < cacheExpiry) { + contacts = contactsCache.data || []; + console.log('从本地缓存加载联系人数据:', contacts); + updateContactSelects(); + return; + } else { + console.log('联系人缓存已过期'); + } + } + } catch (cacheError) { + console.error('加载联系人缓存失败:', cacheError); + } + + // 出错且无有效缓存时,将contacts设为空数组 contacts = []; } } @@ -3260,8 +3662,50 @@ const status = statusMap[supply.status] || { text: '未知', class: 'status-draft' }; - // 处理图片 - const imageUrl = supply.imageUrls && supply.imageUrls.length > 0 ? supply.imageUrls[0] : ''; + // 处理媒体文件(图片和视频) + let firstMediaUrl = ''; + let mediaType = 'image'; // 默认为图片 + let mediaPreviewHTML = ''; + + if (supply.imageUrls && supply.imageUrls.length > 0) { + // 获取第一个媒体文件 + firstMediaUrl = supply.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)))) { + mediaType = 'video'; + mediaPreviewHTML = ` + +
+ `; + } else { + mediaType = 'image'; + mediaPreviewHTML = ` + ${supply.productName} + `; + } + } else { + // 无媒体文件时的占位符 + mediaPreviewHTML = ` +
+ 暂无媒体 +
+ `; + } // 处理操作按钮 let actionsHTML = ''; @@ -3285,28 +3729,83 @@ `; } + // 解析规格和件数为数组 + 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 || '0']; + } + + // 生成规格-件数对应关系的HTML + let specQuantityBoxes = ''; + const maxLength = Math.max(specifications.length, quantities.length); + for (let i = 0; i < maxLength; i++) { + const spec = specifications[i] || '无'; + const quantity = quantities[i] || '0'; + specQuantityBoxes += `
• 规格${i + 1}: ${spec} - 件数: ${quantity}件
`; + } + return `
-
- ${supply.productName} +
+ ${mediaPreviewHTML}
-
- ${supply.productName} - ${status.text} -
-
-
蛋黄: ${supply.yolk || '无'}
-
规格: ${supply.specification || supply.spec || '无'}
-
货源状态: ${supply.supplyStatus || '未设置'}
-
货源描述: ${supply.description || '无'}
-
件数: ${supply.quantity || '0'}件
-
斤重: ${supply.grossWeight || ''}斤
-
地区: ${supply.region || '未设置'}
-
价格: ¥${supply.price || '0'}
-
创建时间: ${formatDate(supply.created_at)}
+ +
+ +
+
+ ${supply.productName} + ${status.text} + +
+ +
+
蛋黄: ${supply.yolk || '无'}
+
货源状态: ${supply.supplyStatus || '未设置'}
+
货源描述: ${supply.description || '无'}
+
斤重: ${supply.grossWeight || ''}斤
+
地区: ${supply.region || '未设置'}
+
价格: ¥${supply.price || '0'}
+
创建时间: ${formatDate(supply.created_at)}
+
+
+ +
+ ${specQuantityBoxes} +
${actionsHTML} @@ -3323,6 +3822,183 @@ return `${date.getFullYear()}/${(date.getMonth() + 1).toString().padStart(2, '0')}/${date.getDate().toString().padStart(2, '0')} ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`; } + // 复制货源信息 + function copySupply(supplyId) { + try { + // 查找对应的货源信息 + let supply = null; + + // 1. 首先尝试直接从所有货源数组中查找 + if (supplyData.supplies && supplyData.supplies.length > 0) { + supply = supplyData.supplies.find(item => { + return String(item.id) === String(supplyId) || String(item.productId) === String(supplyId) || String(item._id) === String(supplyId); + }); + } + + // 2. 如果没找到,遍历所有状态的货源列表查找 + if (!supply) { + const allSupplies = [ + ...supplyData.publishedSupplies, + ...supplyData.pendingSupplies, + ...supplyData.rejectedSupplies, + ...supplyData.draftSupplies + ]; + + supply = allSupplies.find(item => { + return String(item.id) === String(supplyId) || String(item.productId) === String(supplyId) || String(item._id) === String(supplyId); + }); + } + + // 3. 如果没找到,遍历所有分页货源列表查找 + if (!supply) { + const paginatedSupplies = [ + ...(supplyData.paginatedSupplies.published || []), + ...(supplyData.paginatedSupplies.pending || []), + ...(supplyData.paginatedSupplies.rejected || []), + ...(supplyData.paginatedSupplies.draft || []) + ]; + + supply = paginatedSupplies.find(item => { + return String(item.id) === String(supplyId) || String(item.productId) === String(supplyId) || String(item._id) === String(supplyId); + }); + } + + // 4. 如果还是没找到,尝试从DOM中获取 + if (!supply) { + const supplyItem = document.querySelector(`[data-id="${supplyId}"]`); + if (supplyItem) { + // 从DOM中提取关键信息 + const productName = supplyItem.querySelector('.supply-name').textContent.replace(/已上架|审核中|审核失败|已隐藏|已下架|复制/g, '').trim(); + const priceText = supplyItem.querySelector('.detail-item:last-child').textContent; + const price = priceText.replace(/价格: ¥/, '').trim(); + + // 创建一个简化的supply对象 + supply = { + id: supplyId, + productName: productName, + price: price + }; + } + } + + // 调试日志 + console.log('复制货源信息:', { + supplyId: supplyId, + supplyFound: !!supply, + allSuppliesCount: (supplyData.supplies ? supplyData.supplies.length : 0) + + supplyData.publishedSupplies.length + + supplyData.pendingSupplies.length + + supplyData.rejectedSupplies.length + + supplyData.draftSupplies.length, + paginatedSuppliesCount: + (supplyData.paginatedSupplies.published ? supplyData.paginatedSupplies.published.length : 0) + + (supplyData.paginatedSupplies.pending ? supplyData.paginatedSupplies.pending.length : 0) + + (supplyData.paginatedSupplies.rejected ? supplyData.paginatedSupplies.rejected.length : 0) + + (supplyData.paginatedSupplies.draft ? supplyData.paginatedSupplies.draft.length : 0), + supplyDataKeys: supply ? Object.keys(supply) : 'none', + supplyData: supply + }); + + if (!supply) { + alert('未找到对应的货源信息'); + return; + } + + // 保存到全局变量 + supplyData.copiedSupply = supply; + + // 打开创建货源弹窗 + showAddSupplyModal(); + + // 填充表单数据 + fillFormWithSupplyData(supply); + + alert('货源信息已复制到创建表单'); + } catch (error) { + console.error('复制货源失败:', error); + alert('复制货源失败,请重试'); + } + } + + // 用货源数据填充表单 + function fillFormWithSupplyData(supply) { + // 重置表单 + resetForm(); + + // 填充表单字段 + document.getElementById('price').value = supply.price || ''; + document.getElementById('quantity').value = supply.quantity || ''; + document.getElementById('grossWeight').value = supply.grossWeight || ''; + document.getElementById('description').value = supply.description || ''; + + // 填充选择字段 + // 商品名称 + document.getElementById('productName').value = supply.productName || ''; + document.getElementById('productNameDisplayText').textContent = supply.productName || '请选择商品名称'; + selectedProductName = supply.productName || ''; + + // 货源类型 + document.getElementById('sourceType').value = supply.sourceType || ''; + document.getElementById('sourceTypeDisplayText').textContent = supply.sourceType || '请选择货源类型'; + selectedSourceType = supply.sourceType || ''; + + // 蛋黄类型 + document.getElementById('yolk').value = supply.yolk || ''; + document.getElementById('yolkDisplayText').textContent = supply.yolk || '请选择蛋黄类型'; + selectedYolk = supply.yolk || ''; + + // 地区 + document.getElementById('regionValue').value = supply.region || ''; + document.getElementById('regionDisplayText').textContent = supply.region || '请选择地区'; + + // 规格 + document.getElementById('specValue').value = supply.specification || supply.spec || ''; + document.getElementById('specDisplayText').textContent = (supply.specification || supply.spec) || '请选择规格'; + selectedSpec = supply.specification || supply.spec || ''; + + // 货源状态 + document.getElementById('supplyStatus').value = supply.supplyStatus || ''; + + // 联系人 + document.getElementById('contactId').value = supply.contactId || ''; + document.getElementById('contactIdDisplayText').textContent = supply.product_contact || '请选择联系人'; + selectedContactId = supply.contactId || ''; + + // 产品包装 + document.getElementById('productingValue').value = supply.producting || ''; + document.getElementById('productingDisplayText').textContent = supply.producting || '请选择产品包装'; + selectedProducting = supply.producting || ''; + + // 不复制图片,保持图片列表为空 + supplyData.uploadedImages = []; + renderUploadedImages(); + } + + // 检查是否为重复货源 + async function checkDuplicateSupply(formData) { + try { + // 构建查询参数 + const queryParams = new URLSearchParams({ + productName: formData.productName, + spec: formData.specification || formData.spec, + region: formData.region, + price: formData.price, + checkDuplicate: true + }); + + const response = await fetch(`/api/supplies?${queryParams}`); + const result = await response.json(); + + if (result.success && result.data && result.data.length > 0) { + return true; // 存在重复货源 + } + return false; // 不存在重复货源 + } catch (error) { + console.error('检查重复货源失败:', error); + return false; // 出错时默认允许创建 + } + } + // 切换列表展开/折叠 function toggleSection(type) { const listElement = document.getElementById(`${type}List`); @@ -3404,16 +4080,53 @@ // 重置表单函数(全局函数,以便多个地方可以调用) function resetForm() { - document.getElementById('createSupplyForm').reset(); - document.getElementById('uploadImages').innerHTML = ''; + // 重置表单 + const createSupplyForm = document.getElementById('createSupplyForm'); + if (createSupplyForm) { + createSupplyForm.reset(); + } + + // 重置图片 + const uploadImages = document.getElementById('uploadImages'); + if (uploadImages) { + uploadImages.innerHTML = ''; + } supplyData.uploadedImages = []; + + // 重置规格-件数对 + const specQuantityPairs = document.getElementById('specQuantityPairs'); + if (specQuantityPairs) { + specQuantityPairs.innerHTML = ''; + // 重新添加一个空的规格-件数对 + addSpecQuantityPair(); + } + // 重置自定义选择状态 - document.getElementById('specDisplayText').textContent = '请选择规格'; - document.getElementById('regionDisplayText').textContent = '请选择地区'; - document.getElementById('sourceTypeDisplayText').textContent = '请选择货源类型'; - document.getElementById('productNameDisplayText').textContent = '请选择商品名称'; - document.getElementById('yolkDisplayText').textContent = '请选择蛋黄类型'; - document.getElementById('contactIdDisplayText').textContent = '请选择联系人'; + const regionDisplayText = document.getElementById('regionDisplayText'); + if (regionDisplayText) { + regionDisplayText.textContent = '请选择地区'; + } + + const sourceTypeDisplayText = document.getElementById('sourceTypeDisplayText'); + if (sourceTypeDisplayText) { + sourceTypeDisplayText.textContent = '请选择货源类型'; + } + + const productNameDisplayText = document.getElementById('productNameDisplayText'); + if (productNameDisplayText) { + productNameDisplayText.textContent = '请选择商品名称'; + } + + const yolkDisplayText = document.getElementById('yolkDisplayText'); + if (yolkDisplayText) { + yolkDisplayText.textContent = '请选择蛋黄类型'; + } + + const contactIdDisplayText = document.getElementById('contactIdDisplayText'); + if (contactIdDisplayText) { + contactIdDisplayText.textContent = '请选择联系人'; + } + // 重置选择变量 selectedSpec = ''; selectedProvince = ''; @@ -3422,25 +4135,50 @@ selectedProductName = ''; selectedYolk = ''; selectedContactId = ''; + // 确保隐藏字段也被重置 - document.getElementById('specValue').value = ''; - document.getElementById('regionValue').value = ''; - document.getElementById('supplyStatus').value = ''; - document.getElementById('sourceType').value = ''; - document.getElementById('productName').value = ''; - document.getElementById('yolk').value = ''; - document.getElementById('contactId').value = ''; + const specValue = document.getElementById('specValue'); + if (specValue) specValue.value = ''; + + const regionValue = document.getElementById('regionValue'); + if (regionValue) regionValue.value = ''; + + const supplyStatus = document.getElementById('supplyStatus'); + if (supplyStatus) supplyStatus.value = ''; + + const sourceType = document.getElementById('sourceType'); + if (sourceType) sourceType.value = ''; + + const productName = document.getElementById('productName'); + if (productName) productName.value = ''; + + const yolk = document.getElementById('yolk'); + if (yolk) yolk.value = ''; + + const contactId = document.getElementById('contactId'); + if (contactId) contactId.value = ''; + // 重置货源状态按钮样式 - document.getElementById('preSaleBtn').style.borderColor = '#d9d9d9'; - document.getElementById('preSaleBtn').style.color = '#666'; - document.getElementById('inStockBtn').style.borderColor = '#d9d9d9'; - document.getElementById('inStockBtn').style.color = '#666'; + const preSaleBtn = document.getElementById('preSaleBtn'); + if (preSaleBtn) { + preSaleBtn.style.borderColor = '#d9d9d9'; + preSaleBtn.style.color = '#666'; + } + + const inStockBtn = document.getElementById('inStockBtn'); + if (inStockBtn) { + inStockBtn.style.borderColor = '#d9d9d9'; + inStockBtn.style.color = '#666'; + } } // 显示创建货源模态框 function showAddSupplyModal() { document.getElementById('createSupplyModal').style.display = 'flex'; + // 重新加载联系人数据,确保最新 + loadContacts(); + // 尝试从localStorage加载保存的表单数据 const savedFormData = localStorage.getItem('supplyFormDraft'); if (savedFormData) { @@ -3464,6 +4202,7 @@ const contactId = document.getElementById('contactId'); const specDisplayText = document.getElementById('specDisplayText'); const regionDisplayText = document.getElementById('regionDisplayText'); + const contactIdDisplayText = document.getElementById('contactIdDisplayText'); // 安全地设置表单值 if (productName) productName.value = formData.productName || ''; @@ -3485,6 +4224,10 @@ if (regionDisplayText && formData.regionDisplay) { regionDisplayText.textContent = formData.regionDisplay; } + // 恢复联系人显示文本 + if (contactIdDisplayText && formData.contactIdDisplay) { + contactIdDisplayText.textContent = formData.contactIdDisplay; + } // 恢复其他自定义下拉框显示文本 if (sourceTypeDisplayText && formData.sourceTypeDisplay) { sourceTypeDisplayText.textContent = formData.sourceTypeDisplay; @@ -3578,8 +4321,6 @@ document.getElementById('imageUpload').click(); } -<<<<<<< Updated upstream -======= // 触发编辑页面图片上传 function triggerEditImageUpload() { document.getElementById('editImageUpload').click(); @@ -3592,7 +4333,7 @@ for (let i = 0; i < files.length; i++) { if (editCurrentImages.length >= 2) { - alert('最多只能上传2个媒体文件(图片或视频)'); + alert('最多只能上传2张图片'); break; } @@ -3600,23 +4341,20 @@ const reader = new FileReader(); reader.onload = async function(e) { - let mediaUrl = e.target.result; - let isVideo = file.type.startsWith('video/'); + let imageUrl = e.target.result; try { - // 只为图片添加水印,视频不添加 - if (!isVideo) { - mediaUrl = await addWatermarkToImage(mediaUrl); - } + // 为图片添加水印 + imageUrl = await addWatermarkToImage(imageUrl); - // 添加到当前媒体列表 - editCurrentImages.push(mediaUrl); + // 添加到当前图片列表 + editCurrentImages.push(imageUrl); // 更新显示 updateEditImageDisplay(); } catch (error) { - console.error('媒体处理失败:', error); - alert('媒体处理失败,请重试'); + console.error('图片处理失败:', error); + alert('图片处理失败,请重试'); } }; @@ -3639,36 +4377,44 @@ updateEditImageDisplay(); } - // 更新编辑页面媒体显示(图片或视频) + // 更新编辑页面媒体文件显示 function updateEditImageDisplay() { const editUploadImages = document.getElementById('editUploadImages'); editUploadImages.innerHTML = ''; - editCurrentImages.forEach(mediaUrl => { - const imageElement = document.createElement('div'); - imageElement.className = 'upload-image'; + editCurrentImages.forEach(imageUrl => { + const mediaElement = document.createElement('div'); + mediaElement.className = 'upload-image'; - let mediaHtml = ''; - if (mediaUrl.startsWith('data:video/') || mediaUrl.endsWith('.mp4') || mediaUrl.endsWith('.mov') || mediaUrl.endsWith('.avi') || mediaUrl.endsWith('.wmv') || mediaUrl.endsWith('.flv')) { + let mediaContent; + if (imageUrl.startsWith('data:video/')) { // 视频文件 - mediaHtml = ``; + mediaContent = ` + + + `; } else { // 图片文件 - mediaHtml = `商品图片`; + mediaContent = ` + 商品图片 + + `; } - imageElement.innerHTML = ` - ${mediaHtml} - - `; - editUploadImages.appendChild(imageElement); + mediaElement.innerHTML = mediaContent; + editUploadImages.appendChild(mediaElement); }); } ->>>>>>> Stashed changes - // 为图片添加水印(前端Canvas实现) + // 为媒体文件添加水印(前端Canvas实现,仅处理图片,视频跳过) function addWatermarkToImage(imageUrl) { return new Promise((resolve, reject) => { + // 如果是视频文件,直接返回,不添加水印 + if (imageUrl.startsWith('data:video/')) { + resolve(imageUrl); + return; + } + const img = new Image(); img.crossOrigin = 'anonymous'; // 允许跨域图片 @@ -3719,13 +4465,8 @@ const uploadArea = document.getElementById('uploadImages'); for (let i = 0; i < files.length; i++) { -<<<<<<< Updated upstream - if (supplyData.uploadedImages.length >= 5) { - alert('最多只能上传5张图片'); -======= if (supplyData.uploadedImages.length >= 2) { - alert('最多只能上传2个媒体文件(图片或视频)'); ->>>>>>> Stashed changes + alert('最多只能上传2张图片'); break; } @@ -3751,29 +4492,32 @@ event.target.value = ''; } - // 渲染已上传媒体文件(图片和视频) + // 渲染已上传媒体文件(图片或视频) function renderUploadedImages() { const uploadArea = document.getElementById('uploadImages'); uploadArea.innerHTML = ''; supplyData.uploadedImages.forEach((mediaUrl, index) => { - const imageElement = document.createElement('div'); - imageElement.className = 'upload-image'; + const mediaElement = document.createElement('div'); + mediaElement.className = 'upload-image'; - let mediaHtml = ''; + let mediaContent; if (mediaUrl.startsWith('data:video/')) { // 视频文件 - mediaHtml = ``; + mediaContent = ` + +
+ `; } else { // 图片文件 - mediaHtml = `商品图片`; + mediaContent = ` + 商品图片 +
+ `; } - imageElement.innerHTML = ` - ${mediaHtml} -
- `; - uploadArea.appendChild(imageElement); + mediaElement.innerHTML = mediaContent; + uploadArea.appendChild(mediaElement); }); } @@ -3921,6 +4665,138 @@ } } + // 加载表单数据 + function loadFormData() { + try { + const savedData = localStorage.getItem('supplyFormDraft'); + if (savedData) { + const formData = JSON.parse(savedData); + + // 恢复联系人信息 + if (formData.contactId) { + const contactElement = document.getElementById('contactId'); + const contactDisplayElement = document.getElementById('contactIdDisplayText'); + if (contactElement && contactDisplayElement) { + contactElement.value = formData.contactId; + contactDisplayElement.textContent = formData.contactIdDisplay || '请选择联系人'; + selectedContactId = formData.contactId; + } + } + + // 恢复商品名称 + if (formData.productName) { + const productNameElement = document.getElementById('productName'); + const productNameDisplayElement = document.getElementById('productNameDisplayText'); + if (productNameElement && productNameDisplayElement) { + productNameElement.value = formData.productName; + productNameDisplayElement.textContent = formData.productNameDisplay || '请选择商品名称'; + selectedProductName = formData.selectedProductName || ''; + } + } + + // 恢复价格 + if (formData.price) { + const priceElement = document.getElementById('price'); + if (priceElement) { + priceElement.value = formData.price; + } + } + + // 恢复数量 + if (formData.quantity) { + const quantityElement = document.getElementById('quantity'); + if (quantityElement) { + quantityElement.value = formData.quantity; + } + } + + // 恢复蛋黄类型 + if (formData.yolk) { + const yolkElement = document.getElementById('yolk'); + const yolkDisplayElement = document.getElementById('yolkDisplayText'); + if (yolkElement && yolkDisplayElement) { + yolkElement.value = formData.yolk; + yolkDisplayElement.textContent = formData.yolkDisplay || '请选择蛋黄类型'; + selectedYolk = formData.selectedYolk || ''; + } + } + + // 恢复规格 + if (formData.specification) { + const specValueElement = document.getElementById('specValue'); + const specDisplayElement = document.getElementById('specDisplayText'); + if (specValueElement && specDisplayElement) { + specValueElement.value = formData.specification; + specDisplayElement.textContent = formData.specificationDisplay || '请选择规格'; + selectedSpec = formData.selectedSpec || ''; + } + } + + // 恢复货源状态 + if (formData.supplyStatus) { + const supplyStatusElement = document.getElementById('supplyStatus'); + if (supplyStatusElement) { + supplyStatusElement.value = formData.supplyStatus; + setSupplyStatus(formData.supplyStatus); + } + } + + // 恢复货源类型 + if (formData.sourceType) { + const sourceTypeElement = document.getElementById('sourceType'); + const sourceTypeDisplayElement = document.getElementById('sourceTypeDisplayText'); + if (sourceTypeElement && sourceTypeDisplayElement) { + sourceTypeElement.value = formData.sourceType; + sourceTypeDisplayElement.textContent = formData.sourceTypeDisplay || '请选择货源类型'; + selectedSourceType = formData.selectedSourceType || ''; + } + } + + // 恢复描述 + if (formData.description) { + const descriptionElement = document.getElementById('description'); + if (descriptionElement) { + descriptionElement.value = formData.description; + } + } + + // 恢复地区 + if (formData.region) { + const regionValueElement = document.getElementById('regionValue'); + const regionDisplayElement = document.getElementById('regionDisplayText'); + if (regionValueElement && regionDisplayElement) { + regionValueElement.value = formData.region; + regionDisplayElement.textContent = formData.regionDisplay || '请选择地区'; + selectedProvince = formData.selectedProvince || ''; + selectedCity = formData.selectedCity || ''; + } + } + + // 恢复种类 + if (formData.category) { + const categoryElement = document.getElementById('category'); + const categoryDisplayElement = document.getElementById('categoryDisplayText'); + if (categoryElement && categoryDisplayElement) { + categoryElement.value = formData.category; + categoryDisplayElement.textContent = formData.categoryDisplay || '请选择种类'; + selectedCategory = formData.selectedCategory || ''; + } + } + + // 恢复图片 + if (Array.isArray(formData.imageUrls)) { + supplyData.uploadedImages = formData.imageUrls; + renderUploadedImages(); + } + + console.log('表单数据已从localStorage加载'); + } + } catch (e) { + console.error('加载表单数据失败:', e); + // 加载失败时不影响用户体验,静默处理错误 + } + } + // 创建货源 // 防止重复提交的标志位 let isSubmitting = false; @@ -3935,14 +4811,31 @@ const userInfo = checkLogin(); if (!userInfo) return; + // 获取规格和件数数据 + const pairs = document.querySelectorAll('.spec-quantity-pair'); + const specifications = []; + const quantities = []; + + pairs.forEach(pair => { + const specValue = pair.querySelector('.spec-value').value.trim(); + const quantityValue = pair.querySelector('.quantity-input').value.trim(); + + if (specValue) { + specifications.push(specValue); + // 件数可以为0,所以只检查是否有值(包括"0") + quantities.push(quantityValue); + } + }); + const formData = { productName: document.getElementById('productName').value, category: document.getElementById('category').value, price: document.getElementById('price').value, - quantity: document.getElementById('quantity').value, + quantity: quantities.join(','), // 将件数以英文逗号分隔的字符串形式提交 grossWeight: document.getElementById('grossWeight').value, yolk: document.getElementById('yolk').value, - specification: document.getElementById('specValue').value, + specification: specifications.join(','), // 将规格以中文逗号分隔的字符串形式提交 + producting: document.getElementById('productingValue').value, supplyStatus: document.getElementById('supplyStatus').value, sourceType: document.getElementById('sourceType').value, description: document.getElementById('description').value, @@ -3965,7 +4858,11 @@ alert('请输入价格'); return; } - if (!formData.quantity) { + if (specifications.length === 0) { + alert('请选择规格'); + return; + } + if (quantities.length === 0) { alert('请输入件数'); return; } @@ -3989,6 +4886,18 @@ alert('请上传至少一张商品图片'); return; } + // 增强联系人信息验证 + if (!formData.contactId || formData.contactId.trim() === '') { + alert('联系人信息缺失,请分配联系人'); + return; + } + + // 检查是否为重复货源 + const isDuplicate = await checkDuplicateSupply(formData); + if (isDuplicate) { + alert('检测到重复的货源数据,不允许创建'); + return; + } try { // 设置为提交中状态,禁用按钮 @@ -4060,65 +4969,44 @@ } const previewModal = document.getElementById('imagePreview'); + const previewImage = document.getElementById('previewImage'); - // 更新媒体显示 + // 更新图片显示 updatePreviewImage(); - // 重置缩放和拖动状态(仅图片需要) - const isVideo = imageUrl.startsWith('data:video/') || imageUrl.endsWith('.mp4') || imageUrl.endsWith('.mov') || imageUrl.endsWith('.avi') || imageUrl.endsWith('.wmv') || imageUrl.endsWith('.flv'); - if (!isVideo) { - resetImageTransform(); - } + // 重置缩放和拖动状态 + resetImageTransform(); previewModal.classList.add('active'); - // 设置图片查看器事件(仅图片需要) - if (!isVideo) { - setupImageViewerEvents(); - } - } - - // 专门的视频预览函数 - function previewMedia(mediaUrl, mediaList = []) { - previewImage(mediaUrl, mediaList); + // 设置图片查看器事件 + setupImageViewerEvents(); } - // 更新预览媒体(图片或视频) + // 更新预览媒体文件(图片或视频) function updatePreviewImage() { - // 获取预览元素 const previewImage = document.getElementById('previewImage'); - const previewVideo = document.createElement('video'); - previewVideo.id = 'previewVideo'; - previewVideo.className = 'preview-video'; - previewVideo.style.maxWidth = '100%'; - previewVideo.style.maxHeight = '100%'; - previewVideo.controls = true; - previewVideo.muted = false; - + const previewVideo = document.getElementById('previewVideo'); const previewCounter = document.getElementById('previewCounter'); - const mediaUrl = currentPreviewImages[currentPreviewIndex]; - - // 判断是图片还是视频 - const isVideo = mediaUrl.startsWith('data:video/') || mediaUrl.endsWith('.mp4') || mediaUrl.endsWith('.mov') || mediaUrl.endsWith('.avi') || mediaUrl.endsWith('.wmv') || mediaUrl.endsWith('.flv'); - // 移除旧的视频元素(如果存在) - const existingVideo = document.getElementById('previewVideo'); - if (existingVideo) { - existingVideo.remove(); - } + const mediaUrl = currentPreviewImages[currentPreviewIndex]; - if (isVideo) { - // 视频预览 - previewImage.style.display = 'none'; + // 根据媒体类型显示相应的元素 + if (mediaUrl.startsWith('data:video/') || + mediaUrl.match(/\.(mp4|mov|avi|wmv|flv|webm|mkv)$/i) || + mediaUrl.includes('video')) { + // 视频文件 previewVideo.src = mediaUrl; - previewImage.parentElement.appendChild(previewVideo); + previewVideo.style.display = 'block'; + previewImage.style.display = 'none'; } else { - // 图片预览 - previewImage.style.display = 'block'; + // 图片文件 previewImage.src = mediaUrl; + previewImage.style.display = 'block'; + previewVideo.style.display = 'none'; } - // 更新计数器 + // 更新图片计数器 if (previewCounter) { previewCounter.textContent = `${currentPreviewIndex + 1}/${currentPreviewImages.length}`; } @@ -4145,12 +5033,23 @@ // 关闭图片预览 function closeImagePreview() { const previewModal = document.getElementById('imagePreview'); + const previewVideo = document.getElementById('previewVideo'); + + // 停止视频播放并重置视频状态 + if (previewVideo) { + previewVideo.pause(); + previewVideo.src = ''; + previewVideo.load(); + } + previewModal.classList.remove('active'); } - // 重置图片变换 + // 重置媒体变换 function resetImageTransform() { const previewImage = document.getElementById('previewImage'); + const previewVideo = document.getElementById('previewVideo'); + scale = 1; lastScale = 1; pointX = 0; @@ -4159,10 +5058,17 @@ updateImageTransform(); } - // 更新图片变换 + // 更新媒体变换 function updateImageTransform() { const previewImage = document.getElementById('previewImage'); - previewImage.style.transform = `translate(${pointX}px, ${pointY}px) scale(${scale})`; + const previewVideo = document.getElementById('previewVideo'); + + // 对当前显示的媒体元素应用变换 + if (previewImage.style.display !== 'none') { + previewImage.style.transform = `translate(${pointX}px, ${pointY}px) scale(${scale})`; + } else if (previewVideo.style.display !== 'none') { + previewVideo.style.transform = `translate(${pointX}px, ${pointY}px) scale(${scale})`; + } } // 设置图片查看器事件 @@ -4435,23 +5341,8 @@ // 准备发布货源 async function preparePublishSupply(id) { - if (confirm('确定要上架该货源吗?上架后将进入审核流程。')) { - try { - const response = await fetch(`/api/supplies/${id}/publish`, { - method: 'POST' - }); - const result = await response.json(); - if (result.success) { - alert('上架成功,已进入审核流程'); - loadSupplies(); - } else { - alert('上架失败: ' + (result.message || '未知错误')); - } - } catch (error) { - console.error('上架失败:', error); - alert('上架失败: 网络错误'); - } - } + // 点击上架按钮时,先进入编辑页面,并设置为上架模式 + showEditSupply(id, true); } // 显示审核失败原因 @@ -4486,7 +5377,7 @@ } // 显示编辑货源 - async function showEditSupply(id) { + async function showEditSupply(id, publishMode = false) { try { // 查找要编辑的货源,考虑类型转换 const supply = supplyData.supplies.find(s => String(s.id) === String(id)); @@ -4497,6 +5388,8 @@ } currentEditSupplyId = id; + // 保存发布模式状态 + window.currentEditPublishMode = publishMode; // 填充表单数据 // 货源类型 @@ -4521,36 +5414,116 @@ document.getElementById('editSpecValue').value = supply.specification || ''; editSelectedSpec = supply.specification || ''; + // 产品包装 + document.getElementById('editProductingDisplayText').textContent = supply.producting || '请选择产品包装'; + document.getElementById('editProductingValue').value = supply.producting || ''; + editSelectedProducting = supply.producting || ''; + // 货源状态(使用按钮组) const supplyStatus = supply.supplyStatus || '预售'; document.getElementById('editSupplyStatus').value = supplyStatus; setEditSupplyStatus(supplyStatus); // 分配联系人 - document.getElementById('editContactIdDisplayText').textContent = supply.contactId || '请选择联系人'; + // 显示联系人名称而不是ID + const contactName = supply.product_contact || supply.contactId || '请选择联系人'; + document.getElementById('editContactIdDisplayText').textContent = contactName; document.getElementById('editContactId').value = supply.contactId || ''; editSelectedContactId = supply.contactId || ''; document.getElementById('editDescription').value = supply.description || ''; document.getElementById('editRegionDisplayText').textContent = supply.region || '请选择地区'; document.getElementById('editRegionValue').value = supply.region || ''; document.getElementById('editPrice').value = supply.price || ''; - document.getElementById('editQuantity').value = supply.quantity || ''; document.getElementById('editGrossWeight').value = supply.grossWeight || ''; + // 规格和件数 - 支持中文逗号分隔和英文逗号分隔字符串 + 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 || '0']; + } + + // 动态生成规格-件数对输入框 + const specQuantityPairs = document.getElementById('editSpecQuantityPairs'); + // 清空现有对 + specQuantityPairs.innerHTML = ''; + + // 如果没有规格-件数对,添加一个空的 + if (specifications.length === 0) { + addEditSpecQuantityPair(); + } else { + // 根据规格数量生成相应的规格-件数对 + for (let i = 0; i < specifications.length; i++) { + const pair = document.createElement('div'); + pair.className = 'spec-quantity-pair'; + pair.innerHTML = ` + + + + `; + specQuantityPairs.appendChild(pair); + } + } + // 显示商品图片 const editUploadImages = document.getElementById('editUploadImages'); editUploadImages.innerHTML = ''; if (supply.imageUrls && Array.isArray(supply.imageUrls)) { - supply.imageUrls.forEach(imageUrl => { + supply.imageUrls.forEach((imageUrl, index) => { const imageElement = document.createElement('div'); imageElement.className = 'upload-image'; imageElement.innerHTML = ` 商品图片 + `; editUploadImages.appendChild(imageElement); }); } + // 保存当前图片列表到全局变量,用于编辑时使用 + 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; + } else { + saveButton.textContent = '保存'; + saveButton.onclick = saveEditSupply; + } + } + // 显示编辑模态框 document.getElementById('editSupplyModal').style.display = 'flex'; } catch (error) { @@ -4563,6 +5536,52 @@ function hideEditSupplyModal() { document.getElementById('editSupplyModal').style.display = 'none'; currentEditSupplyId = null; + // 重置发布模式状态 + window.currentEditPublishMode = false; + } + + // 编辑后上架货源 + async function publishSupplyAfterEdit() { + try { + // 先保存编辑的货源信息 + const saveResult = await saveEditSupply(); + if (saveResult) { + // 保存成功后,上架货源 + await publishSupply(currentEditSupplyId); + // 关闭模态框 + hideEditSupplyModal(); + } + } catch (error) { + console.error('上架失败:', error); + alert('上架失败: ' + (error.message || '未知错误')); + } + } + + // 上架货源 + async function publishSupply(id) { + if (!id) { + alert('货源ID不存在'); + return; + } + + try { + const response = await fetch(`/api/supplies/${id}/publish`, { + method: 'POST' + }); + + const result = await response.json(); + if (result.success) { + alert('上架成功'); + loadSupplies(); + } else { + alert('上架失败: ' + (result.message || '未知错误')); + } + return result.success; + } catch (error) { + console.error('上架失败:', error); + alert('上架失败: ' + error.message); + return false; + } } // 编辑规格选择功能 @@ -4593,18 +5612,46 @@ const option = document.createElement('div'); option.className = 'select-item'; option.textContent = spec; - if (spec === editSelectedSpec) { + + // 检查当前规格是否已被选中 + if (editSelectedSpec.includes(spec)) { option.classList.add('selected'); } + + // 单击选择逻辑 option.onclick = () => { - // 移除所有选项的选中状态 + // 单选逻辑:移除所有选中状态 document.querySelectorAll('#editSpecOptionsList .select-item').forEach(item => { item.classList.remove('selected'); }); - // 添加当前选项的选中状态 + + // 添加当前选中状态 option.classList.add('selected'); - editSelectedSpec = spec; + + // 更新editSelectedSpec数组为单选 + editSelectedSpec = [spec]; + }; + + // 双击确认逻辑 + option.ondblclick = () => { + // 确保当前选项被选中 + if (!option.classList.contains('selected')) { + // 单选逻辑:移除所有选中状态 + document.querySelectorAll('#editSpecOptionsList .select-item').forEach(item => { + item.classList.remove('selected'); + }); + + // 添加当前选中状态 + option.classList.add('selected'); + + // 更新editSelectedSpec数组为单选 + editSelectedSpec = [spec]; + } + + // 自动确认选择 + confirmEditSpecSelection(); }; + specOptionsList.appendChild(option); }); } @@ -4623,13 +5670,164 @@ // 确认编辑规格选择 function confirmEditSpecSelection() { - if (editSelectedSpec) { - document.getElementById('editSpecDisplayText').textContent = editSelectedSpec; - document.getElementById('editSpecValue').value = editSelectedSpec; + if (currentEditSpecInput) { + // 如果是为规格和件数对选择规格 + if (editSelectedSpec.length > 0) { + currentEditSpecInput.value = editSelectedSpec.join(', '); + } else { + currentEditSpecInput.value = ''; + } + } else { + // 否则,为旧的规格选择逻辑 + if (editSelectedSpec.length > 0) { + document.getElementById('editSpecDisplayText').textContent = editSelectedSpec.join(', '); + document.getElementById('editSpecValue').value = JSON.stringify(editSelectedSpec); + // 根据选中的规格数量动态调整件数输入框数量 + adjustEditQuantityInputs(); + } else { + document.getElementById('editSpecDisplayText').textContent = '请选择规格'; + document.getElementById('editSpecValue').value = '[]'; + } } hideEditSpecSelectModal(); } + // 添加编辑件数输入项 + function addEditQuantityItem() { + const container = document.getElementById('editQuantityContainer'); + const quantityItem = document.createElement('div'); + quantityItem.className = 'quantity-item'; + quantityItem.innerHTML = ` + + + `; + container.insertBefore(quantityItem, container.lastElementChild); + } + + // 删除编辑件数输入项 + function removeEditQuantityItem(btn) { + const container = document.getElementById('editQuantityContainer'); + const quantityItems = container.querySelectorAll('.quantity-item'); + + // 至少保留一个件数输入项 + if (quantityItems.length > 1) { + const quantityItem = btn.parentElement; + quantityItem.remove(); + } + } + + // 根据选中的规格数量调整编辑件数输入框数量 + function adjustEditQuantityInputs() { + const container = document.getElementById('editQuantityContainer'); + const quantityItems = container.querySelectorAll('.quantity-item'); + const specCount = editSelectedSpec.length; + + // 如果规格数量大于件数输入框数量,添加缺少的输入框 + while (quantityItems.length < specCount) { + addEditQuantityItem(); + } + } + + // 添加编辑规格和件数对 + function addEditSpecQuantityPair() { + const container = document.getElementById('editSpecQuantityPairs'); + const pair = document.createElement('div'); + pair.className = 'spec-quantity-pair'; + pair.innerHTML = ` + + + + `; + container.appendChild(pair); + } + + // 删除编辑规格和件数对 + function removeEditSpecQuantityPair(btn) { + const pair = btn.parentElement; + const specInput = pair.querySelector('.spec-value'); + const quantityInput = pair.querySelector('.quantity-input'); + + // 清除当前对的输入值 + if (specInput) specInput.value = ''; + if (quantityInput) quantityInput.value = ''; + } + + // 为编辑规格和件数对显示规格选择弹窗 + function showEditSpecSelectModalForPair(input) { + // 保存当前点击的输入框 + currentEditSpecInput = input; + showEditSpecSelectModal(); + } + + // 保存当前点击的编辑规格输入框 + let currentEditSpecInput = null; + + // 编辑产品包装选择功能 + // 显示编辑产品包装选择弹窗 + function showEditProductingSelectModal() { + const editProductingSelectModal = document.getElementById('editProductingSelectModal'); + editProductingSelectModal.classList.add('active'); + // 重置搜索输入 + document.getElementById('editProductingSearchInput').value = ''; + editFilteredProductingOptions = [...editAllProductingOptions]; + generateEditProductingOptions(); + } + + // 隐藏编辑产品包装选择弹窗 + function hideEditProductingSelectModal() { + const editProductingSelectModal = document.getElementById('editProductingSelectModal'); + editProductingSelectModal.classList.remove('active'); + } + + // 生成编辑产品包装选项 + function generateEditProductingOptions() { + const editProductingOptionsList = document.getElementById('editProductingOptionsList'); + editProductingOptionsList.innerHTML = ''; + + editFilteredProductingOptions.forEach(producting => { + const option = document.createElement('div'); + option.className = 'select-item'; + option.textContent = producting; + option.onclick = () => { + // 移除所有选项的选中状态 + document.querySelectorAll('#editProductingOptionsList .select-item').forEach(item => { + item.classList.remove('selected'); + }); + // 添加当前选项的选中状态 + option.classList.add('selected'); + editSelectedProducting = producting; + }; + option.ondblclick = () => { + // 双击直接确认选择 + document.getElementById('editProductingDisplayText').textContent = producting; + document.getElementById('editProductingValue').value = producting; + hideEditProductingSelectModal(); + }; + editProductingOptionsList.appendChild(option); + }); + } + + // 过滤编辑产品包装选项 + function filterEditProductingOptions() { + const searchInput = document.getElementById('editProductingSearchInput'); + const searchKeyword = searchInput.value.toLowerCase(); + + editFilteredProductingOptions = editAllProductingOptions.filter(producting => { + return producting.toLowerCase().includes(searchKeyword); + }); + + generateEditProductingOptions(); + } + + // 确认编辑产品包装选择 + function confirmEditProductingSelection() { + if (editSelectedProducting) { + document.getElementById('editProductingDisplayText').textContent = editSelectedProducting; + document.getElementById('editProductingValue').value = editSelectedProducting; + } + hideEditProductingSelectModal(); + } + // 编辑地区选择功能 // 显示编辑地区选择弹窗 function showEditRegionSelectModal() { @@ -5190,37 +6388,83 @@ async function saveEditSupply() { if (!currentEditSupplyId) { alert('货源ID不存在'); - return; + return false; } try { + // 获取规格和件数数据 + const pairs = document.querySelectorAll('#editSpecQuantityPairs .spec-quantity-pair'); + const specifications = []; + const quantities = []; + + pairs.forEach(pair => { + const specValue = pair.querySelector('.spec-value').value.trim(); + const quantityValue = pair.querySelector('.quantity-input').value.trim(); + + if (specValue && quantityValue) { + specifications.push(specValue); + quantities.push(quantityValue); + } + }); + const formData = { productName: document.getElementById('editProductName').value, sourceType: document.getElementById('editSourceType').value, category: document.getElementById('editCategory').value, price: document.getElementById('editPrice').value, - quantity: document.getElementById('editQuantity').value, + quantity: quantities.join(','), // 将件数以英文逗号分隔的字符串形式提交 grossWeight: document.getElementById('editGrossWeight').value, yolk: document.getElementById('editYolk').value, - specification: document.getElementById('editSpecValue').value, + specification: specifications.join(','), // 将规格以中文逗号分隔的字符串形式提交 + producting: document.getElementById('editProductingValue').value, supplyStatus: document.getElementById('editSupplyStatus').value, description: document.getElementById('editDescription').value, region: document.getElementById('editRegionValue').value, - contactId: document.getElementById('editContactId').value + contactId: document.getElementById('editContactId').value, + imageUrls: editCurrentImages // 添加编辑后的图片列表 }; // 验证表单 if (!formData.productName) { alert('请选择商品名称'); - return; + return false; + } + if (!formData.category) { + alert('请选择种类'); + return false; } if (!formData.price) { alert('请输入价格'); - return; + return false; } - if (!formData.quantity) { + if (specifications.length === 0) { + alert('请选择规格'); + return false; + } + if (quantities.length === 0) { alert('请输入件数'); - return; + return false; + } + if (!formData.supplyStatus) { + alert('请选择货源状态(预售/现货)'); + return false; + } + if (!formData.sourceType) { + alert('请选择货源类型'); + return false; + } + if (!formData.region) { + alert('请选择地区'); + return false; + } + if (!formData.contactId) { + alert('请选择联系人'); + return false; + } + // 增强联系人信息验证 + if (!formData.contactId || formData.contactId.trim() === '') { + alert('联系人信息缺失,请分配联系人'); + return false; } const response = await fetch(`/api/supplies/${currentEditSupplyId}/edit`, { @@ -5233,15 +6477,21 @@ const result = await response.json(); if (result.success) { - alert('编辑成功'); - hideEditSupplyModal(); - loadSupplies(); + // 如果是直接编辑,显示成功消息;如果是编辑后上架,不显示,由上架函数处理 + if (!window.currentEditPublishMode) { + alert('编辑成功'); + hideEditSupplyModal(); + loadSupplies(); + } + return true; } else { alert('编辑失败: ' + (result.message || '未知错误')); + return false; } } catch (error) { console.error('编辑失败:', error); alert('编辑失败: 网络错误'); + return false; } } @@ -5249,16 +6499,29 @@ function hideEditSupplyModal() { document.getElementById('editSupplyModal').style.display = 'none'; currentEditSupplyId = null; + // 重置发布模式状态 + window.currentEditPublishMode = false; } // 处理点击模态框外部关闭 window.onclick = function(event) { - const modals = document.querySelectorAll('.modal'); - modals.forEach(modal => { - if (event.target === modal) { - modal.style.display = 'none'; - } - }); + // 确保只处理click事件,不处理拖拽选择文本导致的mouseup事件 + if (event.type === 'click') { + const modals = document.querySelectorAll('.modal'); + modals.forEach(modal => { + if (event.target === modal) { + // 只在真正点击模态框背景时关闭,不在选择文本拖拽到外部时关闭 + // 检查事件是否是通过点击触发的,而不是通过拖拽 + if (!event.isTrusted) return; + + // 检查是否有正在进行的选择操作 + const selection = window.getSelection(); + if (selection && selection.toString()) return; + + modal.style.display = 'none'; + } + }); + } }