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