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. 430
      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');
}, 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);

1
Review

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

430
supply.html

@ -1261,7 +1261,7 @@
<div>3. 敲开鸡蛋后清晰显示蛋清、蛋黄状态,以体现新鲜度;</div>
<div>4. 其他能佐证蛋重、品种的辅助图片。</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>
@ -1408,6 +1408,7 @@
</div>
<div class="modal-footer">
<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>
</div>
</div>
@ -1671,12 +1672,12 @@
</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 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)">
<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>
</div>
@ -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,13 +2733,45 @@
// 更新显示
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 = `
<div style="position: relative; width: 100px; height: 100px;">
<video
src="${firstMediaUrl}"
style="width: 100px; height: 100px; object-fit: cover; border-radius: 8px;"
onclick="previewImage('${firstMediaUrl}', ${JSON.stringify(supply.imageUrls || []).replace(/"/g, '&quot;')})"
style="width: 100%; height: 100%; object-fit: cover; border-radius: 8px;"
onclick="previewImage('${firstMediaUrl}', ${JSON.stringify(imageUrls || []).replace(/\"/g, '&quot;')})"
muted
preload="metadata"
playsinline
></video>
<div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: white; font-size: 24px; pointer-events: none;"></div>
<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 {
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, '&quot;')})"
onclick="previewImage('${firstMediaUrl}', ${JSON.stringify(imageUrls || []).replace(/\"/g, '&quot;')})"
>
`;
}
@ -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 = `
<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.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 = `
<video src="${imageUrl}" alt="商品视频" onclick="previewImage('${imageUrl}')" controls style="width: 100%; height: 100%; object-fit: cover;"></video>
@ -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 = `
<img src="${imageUrl}" alt="商品图片" onclick="previewImage('${imageUrl}')">
<button class="delete-image-btn" onclick="deleteEditImage('${imageUrl}')">×</button>
let mediaContent;
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);
});
}
// 保存当前图片列表到全局变量,用于编辑时使用
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) {

Loading…
Cancel
Save