Browse Source

更新供应链管理系统代码:

1. 添加.gitignore文件,忽略不必要的文件
2. 修改Reject.js文件
3. 修改supply.html文件,修复事件监听器重复绑定、DOM空指针和数据类型转换问题
4. 删除Review文件
Boss2
Default User 2 months ago
parent
commit
ac01b1be37
  1. 124
      .gitignore
  2. 86
      Reject.js
  3. 1
      Review
  4. 442
      supply.html

124
.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.*

86
Reject.js

@ -85,6 +85,47 @@ setInterval(() => {
// console.log('发送心跳消息: ping'); // console.log('发送心跳消息: ping');
}, 30000); }, 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 // 配置CORS
app.use(cors()); app.use(cors());
app.use((req, res, next) => { app.use((req, res, next) => {
@ -192,31 +233,31 @@ app.get('/api/supplies', async (req, res) => {
let whereClause = ''; let whereClause = '';
let params = []; let params = [];
// 默认过滤掉status为hidden的货源,实现软删除效果
whereClause += ` WHERE status != 'hidden'`;
// 添加搜索条件 // 添加搜索条件
if (actualSearch) { 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}%`); params.push(`%${actualSearch}%`, `%${actualSearch}%`, `%${actualSearch}%`);
} }
// 添加手机号搜索 // 添加手机号搜索
if (phoneNumber) { if (phoneNumber) {
whereClause += actualSearch ? ' AND' : ' WHERE'; whereClause += ` AND u.phoneNumber LIKE ?`;
whereClause += ` u.phoneNumber LIKE ?`;
params.push(`%${phoneNumber}%`); params.push(`%${phoneNumber}%`);
} }
// 添加状态筛选 // 添加状态筛选
if (status) { if (status) {
whereClause += (actualSearch || phoneNumber) ? ' AND' : ' WHERE'; whereClause += ` AND status = ?`;
whereClause += ` status = ?`;
params.push(status); params.push(status);
} }
// 添加sellerId筛选,只返回指定用户的货源 // 添加sellerId筛选,只返回指定用户的货源
const { sellerId } = req.query; const { sellerId } = req.query;
if (sellerId) { if (sellerId) {
whereClause += (actualSearch || phoneNumber || status) ? ' AND' : ' WHERE'; whereClause += ` AND p.sellerId = ?`;
whereClause += ` p.sellerId = ?`;
params.push(sellerId); params.push(sellerId);
} }
@ -799,17 +840,8 @@ app.post('/api/supplies/create', async (req, res) => {
sellerId = 'default_seller'; sellerId = 'default_seller';
} }
// 检查是否为重复货源 // 移除重复货源检测限制,允许创建相同货源
console.log('检查是否为重复货源...'); 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, '检测到重复的货源数据,不允许创建');
}
// 处理联系人信息 // 处理联系人信息
let productContact = ''; let productContact = '';
@ -920,18 +952,18 @@ app.post('/api/supplies/create', async (req, res) => {
if (columns.length === 0) { if (columns.length === 0) {
// 没有quality字段,不包含quality字段的插入 // 没有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 = [ 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, yolk, specification, producting, region, productData.status, productData.supplyStatus, productData.sourceType,
productData.description, productData.rejectReason, productData.imageUrls, new Date(), new Date(), productData.description, productData.rejectReason, productData.imageUrls, new Date(), new Date(),
productContact, contactPhone, req.body.autoOfflineTime, req.body.autoOfflineDays, req.body.autoOfflineHours // 添加联系人信息和自动下架时间、小时数 productContact, contactPhone, req.body.autoOfflineTime, req.body.autoOfflineDays, req.body.autoOfflineHours // 添加联系人信息和自动下架时间、小时数
]; ];
} else { } else {
// 有quality字段,包含quality字段的插入 // 有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 = [ 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, yolk, specification, producting, quality, region, productData.status, productData.supplyStatus,
productData.sourceType, productData.description, productData.rejectReason, productData.imageUrls, productData.sourceType, productData.description, productData.rejectReason, productData.imageUrls,
new Date(), new Date(), productContact, contactPhone, req.body.autoOfflineTime, req.body.autoOfflineDays, req.body.autoOfflineHours // 添加联系人信息和自动下架时间、小时数 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, '货源不存在'); return sendResponse(res, false, null, '货源不存在');
} }
// 删除货源 // 将货源状态改为hidden,而不是删除
await connection.query( await connection.query(
'DELETE FROM products WHERE id = ?', 'UPDATE products SET status = ? WHERE id = ?',
[productId] ['hidden', productId]
); );
// 提交事务 // 提交事务
@ -1247,10 +1279,10 @@ app.post('/api/supplies/:id/delete', async (req, res) => {
broadcastMessage({ broadcastMessage({
type: 'supply_update', type: 'supply_update',
supplyId: productId, supplyId: productId,
action: 'delete' action: 'update'
}); });
sendResponse(res, true, null, '货源删除成功'); sendResponse(res, true, null, '货源删除');
} catch (error) { } catch (error) {
console.error('删除货源失败:', error.message); console.error('删除货源失败:', error.message);
console.error('错误详情:', error); console.error('错误详情:', error);

1
Review

@ -1 +0,0 @@
Subproject commit 785502c7a13bf341be514c9a73cbd7df2d5c5d40

442
supply.html

@ -1261,7 +1261,7 @@
<div>3. 敲开鸡蛋后清晰显示蛋清、蛋黄状态,以体现新鲜度;</div> <div>3. 敲开鸡蛋后清晰显示蛋清、蛋黄状态,以体现新鲜度;</div>
<div>4. 其他能佐证蛋重、品种的辅助图片。</div> <div>4. 其他能佐证蛋重、品种的辅助图片。</div>
</div> </div>
<div style="font-size: 12px; color: #999; margin-top: 10px; text-align: left;">最多上传2张图片</div> <div style="font-size: 12px; color: #999; margin-top: 10px; text-align: left;">最多上传2张图片或视频</div>
</div> </div>
</div> </div>
@ -1408,6 +1408,7 @@
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button class="modal-btn modal-btn-cancel" onclick="hideAddSupplyModal()">取消</button> <button class="modal-btn modal-btn-cancel" onclick="hideAddSupplyModal()">取消</button>
<button class="modal-btn" onclick="clearFormMemory()">清除</button>
<button class="modal-btn modal-btn-primary" onclick="addSupply()">创建</button> <button class="modal-btn modal-btn-primary" onclick="addSupply()">创建</button>
</div> </div>
</div> </div>
@ -1671,12 +1672,12 @@
</div> </div>
<div class="add-image" onclick="triggerEditImageUpload()">+</div> <div class="add-image" onclick="triggerEditImageUpload()">+</div>
<div style="font-size: 12px; color: #999; margin-top: 10px; text-align: left;">最多上传2张图片</div> <div style="font-size: 12px; color: #999; margin-top: 10px; text-align: left;">最多上传2张图片或视频</div>
</div> </div>
<div style="font-size: 12px; color: #999; margin-top: 10px;">最多上传2张图片</div> <div style="font-size: 12px; color: #999; margin-top: 10px;">最多上传2张图片或视频</div>
<input type="file" id="editImageUpload" multiple accept="image/*,video/mp4,video/mov,video/avi,video/wmv,video/flv" style="display: none;" onchange="handleEditImageUpload(event)"> <input type="file" id="editImageUpload" multiple accept="image/*,video/mp4,video/mov,video/avi,video/wmv,video/flv" style="display: none;" onchange="handleEditImageUpload(event)">
<button type="button" onclick="triggerEditImageUpload()" style="margin-top: 10px; padding: 8px 16px; background-color: #f0f0f0; border: 1px solid #d9d9d9; border-radius: 4px; font-size: 14px; cursor: pointer;"> <button type="button" onclick="triggerEditImageUpload()" style="margin-top: 10px; padding: 8px 16px; background-color: #f0f0f0; border: 1px solid #d9d9d9; border-radius: 4px; font-size: 14px; cursor: pointer;">
添加图片 添加图片或视频
</button> </button>
</div> </div>
@ -2539,11 +2540,12 @@
uploadArea.style.backgroundColor = '#fff'; uploadArea.style.backgroundColor = '#fff';
const files = Array.from(e.dataTransfer.files); const files = Array.from(e.dataTransfer.files);
// 过滤出图片文件 // 过滤出图片和视频文件
const imageFiles = files.filter(file => file.type.startsWith('image/')); const imageFiles = files.filter(file => file.type.startsWith('image/'));
const videoFiles = files.filter(file => file.type.startsWith('video/'));
if (imageFiles.length > 0) { if (imageFiles.length > 0 || videoFiles.length > 0) {
handleDroppedImages(imageFiles); handleDroppedMedia(imageFiles, videoFiles);
} }
}); });
@ -2580,11 +2582,12 @@
editUploadArea.style.padding = '0'; editUploadArea.style.padding = '0';
const files = Array.from(e.dataTransfer.files); const files = Array.from(e.dataTransfer.files);
// 过滤出图片文件 // 过滤出图片和视频文件
const imageFiles = files.filter(file => file.type.startsWith('image/')); const imageFiles = files.filter(file => file.type.startsWith('image/'));
const videoFiles = files.filter(file => file.type.startsWith('video/'));
if (imageFiles.length > 0) { if (imageFiles.length > 0 || videoFiles.length > 0) {
handleEditDroppedImages(imageFiles); handleEditDroppedMedia(imageFiles, videoFiles);
} }
}); });
} }
@ -2597,9 +2600,10 @@
// 检查是否在创建或编辑货源模态框中(现在使用active类来控制显示) // 检查是否在创建或编辑货源模态框中(现在使用active类来控制显示)
if (createSupplyModal && createSupplyModal.classList.contains('active')) { if (createSupplyModal && createSupplyModal.classList.contains('active')) {
// 检查是否有粘贴的图片 // 检查是否有粘贴的图片和视频
const items = e.clipboardData.items; const items = e.clipboardData.items;
const imageFiles = []; const imageFiles = [];
const videoFiles = [];
for (let i = 0; i < items.length; i++) { for (let i = 0; i < items.length; i++) {
if (items[i].type.startsWith('image/')) { if (items[i].type.startsWith('image/')) {
@ -2607,16 +2611,22 @@
if (file) { if (file) {
imageFiles.push(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) { if (imageFiles.length > 0 || videoFiles.length > 0) {
handleDroppedImages(imageFiles); handleDroppedMedia(imageFiles, videoFiles);
} }
} else if (editSupplyModal && editSupplyModal.classList.contains('active')) { } else if (editSupplyModal && editSupplyModal.classList.contains('active')) {
// 检查是否有粘贴的图片 // 检查是否有粘贴的图片和视频
const items = e.clipboardData.items; const items = e.clipboardData.items;
const imageFiles = []; const imageFiles = [];
const videoFiles = [];
for (let i = 0; i < items.length; i++) { for (let i = 0; i < items.length; i++) {
if (items[i].type.startsWith('image/')) { if (items[i].type.startsWith('image/')) {
@ -2624,21 +2634,27 @@
if (file) { if (file) {
imageFiles.push(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) { if (imageFiles.length > 0 || videoFiles.length > 0) {
handleEditDroppedImages(imageFiles); handleEditDroppedMedia(imageFiles, videoFiles);
} }
} }
}); });
} }
// 处理拖拽或粘贴的图片 // 处理拖拽或粘贴的图片和视频
async function handleDroppedImages(imageFiles) { async function handleDroppedMedia(imageFiles, videoFiles) {
// 处理图片
for (let i = 0; i < imageFiles.length; i++) { for (let i = 0; i < imageFiles.length; i++) {
if (supplyData.uploadedImages.length >= 2) { if (supplyData.uploadedImages.length >= 2) {
alert('最多只能上传2张图片'); alert('最多只能上传2张图片/视频');
break; break;
} }
@ -2659,13 +2675,46 @@
reader.readAsDataURL(file); 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++) { for (let i = 0; i < imageFiles.length; i++) {
if (editCurrentImages.length >= 2) { if (editCurrentImages.length >= 2) {
alert('最多只能上传2张图片'); alert('最多只能上传2张图片/视频');
break; break;
} }
@ -2684,11 +2733,43 @@
// 更新显示 // 更新显示
updateEditImageDisplay(); updateEditImageDisplay();
} catch (error) { } catch (error) {
console.error('图片处理失败:', error); console.error('媒体处理失败:', error);
alert('图片处理失败,请重试'); 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); reader.readAsDataURL(file);
} }
} }
@ -4423,25 +4504,31 @@
let mediaType = 'image'; // 默认为图片 let mediaType = 'image'; // 默认为图片
let mediaPreviewHTML = ''; 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/') || if (firstMediaUrl.startsWith('data:video/') ||
firstMediaUrl.match(/\.(mp4|mov|avi|wmv|flv|webm|mkv)$/i) || 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'; mediaType = 'video';
mediaPreviewHTML = ` mediaPreviewHTML = `
<video <div style="position: relative; width: 100px; height: 100px;">
src="${firstMediaUrl}" <video
style="width: 100px; height: 100px; object-fit: cover; border-radius: 8px;" src="${firstMediaUrl}"
onclick="previewImage('${firstMediaUrl}', ${JSON.stringify(supply.imageUrls || []).replace(/"/g, '&quot;')})" style="width: 100%; height: 100%; object-fit: cover; border-radius: 8px;"
muted onclick="previewImage('${firstMediaUrl}', ${JSON.stringify(imageUrls || []).replace(/\"/g, '&quot;')})"
preload="metadata" muted
></video> preload="metadata"
<div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: white; font-size: 24px; pointer-events: none;"></div> playsinline
></video>
<div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: white; font-size: 24px; pointer-events: none; background-color: rgba(0, 0, 0, 0.5); border-radius: 50%; width: 40px; height: 40px; display: flex; align-items: center; justify-content: center;"></div>
</div>
`; `;
} else { } else {
mediaType = 'image'; mediaType = 'image';
@ -4450,7 +4537,7 @@
src="${firstMediaUrl}" src="${firstMediaUrl}"
alt="${supply.productName}" alt="${supply.productName}"
style="width: 100px; height: 100px; object-fit: cover; border-radius: 8px;" style="width: 100px; height: 100px; object-fit: cover; border-radius: 8px;"
onclick="previewImage('${firstMediaUrl}', ${JSON.stringify(supply.imageUrls || []).replace(/"/g, '&quot;')})" onclick="previewImage('${firstMediaUrl}', ${JSON.stringify(imageUrls || []).replace(/\"/g, '&quot;')})"
> >
`; `;
} }
@ -4787,9 +4874,85 @@
if (productingDisplay) productingDisplay.textContent = supply.producting || '请选择产品包装'; if (productingDisplay) productingDisplay.textContent = supply.producting || '请选择产品包装';
selectedProducting = 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(); 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 = `
<input type="text" class="spec-value" placeholder="请选择规格" readonly onclick="showSpecSelectModalForPair(this)" value="${specifications[i] || ''}">
<input type="number" class="form-input quantity-input" placeholder="请输入件数" onwheel="this.blur()" value="${quantities[i] || ''}">
<button type="button" class="remove-quantity-btn" onclick="removeSpecQuantityPair(this)">×</button>
`;
specQuantityPairs.appendChild(pair);
}
}
}
} }
// 检查是否为重复货源 // 检查是否为重复货源
@ -5001,6 +5164,22 @@
inStockBtn.style.borderColor = '#d9d9d9'; inStockBtn.style.borderColor = '#d9d9d9';
inStockBtn.style.color = '#666'; 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++) { for (let i = 0; i < files.length; i++) {
if (editCurrentImages.length >= 2) { if (editCurrentImages.length >= 2) {
alert('最多只能上传2张图片'); alert('最多只能上传2张图片或视频');
break; break;
} }
@ -5226,7 +5405,7 @@
mediaElement.className = 'upload-image'; mediaElement.className = 'upload-image';
let mediaContent; let mediaContent;
if (imageUrl.startsWith('data:video/')) { if (imageUrl.startsWith('data:video/') || imageUrl.match(/\.(mp4|mov|avi|wmv|flv|webm|mkv)$/i)) {
// 视频文件 // 视频文件
mediaContent = ` mediaContent = `
<video src="${imageUrl}" alt="商品视频" onclick="previewImage('${imageUrl}')" controls style="width: 100%; height: 100%; object-fit: cover;"></video> <video src="${imageUrl}" alt="商品视频" onclick="previewImage('${imageUrl}')" controls style="width: 100%; height: 100%; object-fit: cover;"></video>
@ -5249,7 +5428,7 @@
function addWatermarkToImage(imageUrl) { function addWatermarkToImage(imageUrl) {
return new Promise((resolve, reject) => { 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); resolve(imageUrl);
return; return;
} }
@ -5305,7 +5484,7 @@
for (let i = 0; i < files.length; i++) { for (let i = 0; i < files.length; i++) {
if (supplyData.uploadedImages.length >= 2) { if (supplyData.uploadedImages.length >= 2) {
alert('最多只能上传2张图片'); alert('最多只能上传2张图片/视频');
break; break;
} }
@ -5450,6 +5629,7 @@
const regionValue = document.getElementById('regionValue'); const regionValue = document.getElementById('regionValue');
const contactId = document.getElementById('contactId'); const contactId = document.getElementById('contactId');
const category = document.getElementById('category'); const category = document.getElementById('category');
const freshness = document.getElementById('freshness');
const specDisplayText = document.getElementById('specDisplayText'); const specDisplayText = document.getElementById('specDisplayText');
const regionDisplayText = document.getElementById('regionDisplayText'); const regionDisplayText = document.getElementById('regionDisplayText');
const sourceTypeDisplayText = document.getElementById('sourceTypeDisplayText'); const sourceTypeDisplayText = document.getElementById('sourceTypeDisplayText');
@ -5457,11 +5637,13 @@
const yolkDisplayText = document.getElementById('yolkDisplayText'); const yolkDisplayText = document.getElementById('yolkDisplayText');
const contactIdDisplayText = document.getElementById('contactIdDisplayText'); const contactIdDisplayText = document.getElementById('contactIdDisplayText');
const categoryDisplayText = document.getElementById('categoryDisplayText'); const categoryDisplayText = document.getElementById('categoryDisplayText');
const freshnessDisplayText = document.getElementById('freshnessDisplayText');
// 确保所有字段都是安全获取的 // 确保所有字段都是安全获取的
const formData = { const formData = {
productName: productName ? productName.value : '', productName: productName ? productName.value : '',
category: category ? category.value : '', category: category ? category.value : '',
freshness: freshness ? freshness.value : '',
price: price ? price.value : '', price: price ? price.value : '',
quantity: quantity ? quantity.value : '', quantity: quantity ? quantity.value : '',
grossWeight: grossWeight ? grossWeight.value : '', grossWeight: grossWeight ? grossWeight.value : '',
@ -5480,6 +5662,7 @@
yolkDisplay: yolkDisplayText ? yolkDisplayText.textContent : '请选择蛋黄类型', yolkDisplay: yolkDisplayText ? yolkDisplayText.textContent : '请选择蛋黄类型',
contactIdDisplay: contactIdDisplayText ? contactIdDisplayText.textContent : '请选择联系人', contactIdDisplay: contactIdDisplayText ? contactIdDisplayText.textContent : '请选择联系人',
categoryDisplay: categoryDisplayText ? categoryDisplayText.textContent : '请选择种类', categoryDisplay: categoryDisplayText ? categoryDisplayText.textContent : '请选择种类',
freshnessDisplay: freshnessDisplayText ? freshnessDisplayText.textContent : '请选择新鲜程度',
// 深拷贝图片数组,避免引用问题 // 深拷贝图片数组,避免引用问题
imageUrls: Array.isArray(supplyData.uploadedImages) ? [...supplyData.uploadedImages] : [], imageUrls: Array.isArray(supplyData.uploadedImages) ? [...supplyData.uploadedImages] : [],
// 保存自定义选择状态 // 保存自定义选择状态
@ -5491,6 +5674,7 @@
selectedYolk: selectedYolk || '', selectedYolk: selectedYolk || '',
selectedContactId: selectedContactId || '', selectedContactId: selectedContactId || '',
selectedCategory: selectedCategory || '', selectedCategory: selectedCategory || '',
selectedFreshness: selectedFreshness || '',
// 添加时间戳,用于调试和潜在的过期处理 // 添加时间戳,用于调试和潜在的过期处理
lastSaved: new Date().toISOString() 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() { function loadFormData() {
try { 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)) { if (Array.isArray(formData.imageUrls)) {
supplyData.uploadedImages = formData.imageUrls; supplyData.uploadedImages = formData.imageUrls;
@ -5669,11 +5938,12 @@
// 获取自动下架分钟数 // 获取自动下架分钟数
const autoOfflineMinutes = document.getElementById('autoOfflineMinutes').value; 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(); const autoOfflineTime = new Date();
autoOfflineTime.setMinutes(autoOfflineTime.getMinutes() + parseFloat(autoOfflineMinutes)); autoOfflineTime.setMinutes(autoOfflineTime.getMinutes() + minutes);
const formData = { const formData = {
productName: document.getElementById('productName').value, productName: document.getElementById('productName').value,
@ -5712,12 +5982,16 @@
alert('请输入价格'); alert('请输入价格');
return; return;
} }
if (specifications.length === 0) { // 验证规格和件数 - 确保至少有一个有效的规格-件数对
alert('请选择规格'); let hasValidPair = false;
return; for (let i = 0; i < specifications.length; i++) {
if (specifications[i].trim() !== '' && quantities[i].trim() !== '') {
hasValidPair = true;
break;
}
} }
if (quantities.length === 0) { if (!hasValidPair) {
alert('请输入件数'); alert('请至少添加一个有效的规格和件数对');
return; return;
} }
if (!formData.supplyStatus) { if (!formData.supplyStatus) {
@ -6187,7 +6461,7 @@
// 显示编辑货源 // 显示编辑货源
// 删除货源 // 删除货源
async function deleteSupply(id) { async function deleteSupply(id) {
if (confirm('确定要删除该货源吗?删除后将无法恢复。')) { if (confirm('确定要删除该货源吗?')) {
try { try {
const response = await fetch(`/api/supplies/${id}/delete`, { const response = await fetch(`/api/supplies/${id}/delete`, {
method: 'POST' method: 'POST'
@ -6404,20 +6678,38 @@
// 显示商品图片 // 显示商品图片
const editUploadImages = document.getElementById('editUploadImages'); const editUploadImages = document.getElementById('editUploadImages');
editUploadImages.innerHTML = ''; editUploadImages.innerHTML = '';
if (supply.imageUrls && Array.isArray(supply.imageUrls)) { // 确保imageUrls是数组
supply.imageUrls.forEach((imageUrl, index) => { 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'); const imageElement = document.createElement('div');
imageElement.className = 'upload-image'; imageElement.className = 'upload-image';
imageElement.innerHTML = `
<img src="${imageUrl}" alt="商品图片" onclick="previewImage('${imageUrl}')"> let mediaContent;
<button class="delete-image-btn" onclick="deleteEditImage('${imageUrl}')">×</button> if (mediaUrl.startsWith('data:video/') || mediaUrl.match(/\.(mp4|mov|avi|wmv|flv|webm|mkv)$/i)) {
`; // 视频文件
mediaContent = `
<video src="${mediaUrl}" alt="商品视频" onclick="previewImage('${mediaUrl}')" controls style="width: 100%; height: 100%; object-fit: cover;"></video>
<button class="delete-image-btn" onclick="deleteEditImage('${mediaUrl}')">×</button>
`;
} else {
// 图片文件
mediaContent = `
<img src="${mediaUrl}" alt="商品图片" onclick="previewImage('${mediaUrl}')">
<button class="delete-image-btn" onclick="deleteEditImage('${mediaUrl}')">×</button>
`;
}
imageElement.innerHTML = mediaContent;
editUploadImages.appendChild(imageElement); editUploadImages.appendChild(imageElement);
}); });
} }
// 保存当前图片列表到全局变量,用于编辑时使用 // 保存当前图片列表到全局变量,用于编辑时使用
editCurrentImages = supply.imageUrls && Array.isArray(supply.imageUrls) ? [...supply.imageUrls] : []; editCurrentImages = [...editImageUrls];
// 根据模式修改保存按钮文本和状态 // 根据模式修改保存按钮文本和状态
const saveButton = document.querySelector('#editSupplyModal .modal-btn-primary'); const saveButton = document.querySelector('#editSupplyModal .modal-btn-primary');
@ -7487,15 +7779,13 @@
// 获取自动下架分钟数 // 获取自动下架分钟数
const autoOfflineMinutes = document.getElementById('editAutoOfflineMinutes')?.value; 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; const autoOfflineTime = new Date();
if (autoOfflineMinutes) { autoOfflineTime.setMinutes(autoOfflineTime.getMinutes() + minutes);
autoOfflineTime = new Date(); const autoOfflineTimeISO = autoOfflineTime.toISOString();
autoOfflineTime.setMinutes(autoOfflineTime.getMinutes() + parseFloat(autoOfflineMinutes));
autoOfflineTime = autoOfflineTime.toISOString();
}
const formData = { const formData = {
productName: document.getElementById('editProductName').value, productName: document.getElementById('editProductName').value,
@ -7515,7 +7805,7 @@
contactId: document.getElementById('editContactId').value, contactId: document.getElementById('editContactId').value,
freshness: document.getElementById('editFreshness').value, freshness: document.getElementById('editFreshness').value,
imageUrls: editCurrentImages, // 添加编辑后的图片列表 imageUrls: editCurrentImages, // 添加编辑后的图片列表
autoOfflineTime: autoOfflineTime, // 自动下架时间 autoOfflineTime: autoOfflineTimeISO, // 自动下架时间
autoOfflineDays: null, // 不再使用天数,设置为null autoOfflineDays: null, // 不再使用天数,设置为null
autoOfflineHours: autoOfflineHours // 自动下架小时数(分钟转换而来) autoOfflineHours: autoOfflineHours // 自动下架小时数(分钟转换而来)
}; };
@ -7533,12 +7823,16 @@
alert('请输入价格'); alert('请输入价格');
return false; return false;
} }
if (specifications.length === 0) { // 验证规格和件数 - 确保至少有一个有效的规格-件数对
alert('请选择规格'); let hasValidPair = false;
return false; for (let i = 0; i < specifications.length; i++) {
if (specifications[i].trim() !== '' && quantities[i].trim() !== '') {
hasValidPair = true;
break;
}
} }
if (quantities.length === 0) { if (!hasValidPair) {
alert('请输入件数'); alert('请至少添加一个有效的规格和件数对');
return false; return false;
} }
if (!formData.supplyStatus) { if (!formData.supplyStatus) {

Loading…
Cancel
Save