// pages/publish/index.js // 引入API工具 const API = require('../../utils/api.js'); // 【终极修复】创建全局上传管理器,完全独立于页面生命周期 if (!global.ImageUploadManager) { global.ImageUploadManager = { // 存储所有活动的上传任务 activeTasks: {}, // 深度克隆工具函数 deepClone: function(obj) { return JSON.parse(JSON.stringify(obj)); }, // 核心上传方法 upload: function(formData, images, successCallback, failCallback) { console.log('【全局上传管理器】开始上传,图片数量:', images.length); // 创建唯一的上传任务ID const taskId = `upload_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; // 创建深度克隆,完全隔离数据 const clonedFormData = this.deepClone(formData); const clonedImages = this.deepClone(images); // 将任务保存到全局状态中,防止页面重新编译时丢失 this.activeTasks[taskId] = { id: taskId, formData: clonedFormData, images: clonedImages, status: 'started', startTime: Date.now(), uploadedCount: 0, totalCount: clonedImages.length }; console.log(`【全局上传管理器】创建任务 ${taskId},已保存到全局状态`); // 使用setTimeout完全隔离执行上下文,避免与页面生命周期耦合 const self = this; setTimeout(() => { try { console.log('【全局上传管理器】准备调用API.publishProduct'); console.log('准备的商品数据:', clonedFormData); console.log('准备的图片数量:', clonedImages.length); // 关键修改:使用API.publishProduct方法,这是正确的调用链 // 包含所有必要字段 const productData = { ...clonedFormData, images: clonedImages, // 直接传递图片数组 imageUrls: clonedImages, // 同时设置imageUrls字段 // 生成会话ID,确保所有图片关联同一商品 sessionId: taskId, uploadSessionId: taskId }; console.log('最终传递给publishProduct的数据:', Object.keys(productData)); API.publishProduct(productData) .then(res => { console.log(`【全局上传管理器】任务 ${taskId} 上传完成,响应:`, res); // 更新任务状态 if (self.activeTasks[taskId]) { self.activeTasks[taskId].status = 'completed'; self.activeTasks[taskId].endTime = Date.now(); self.activeTasks[taskId].result = res; } // 使用setTimeout隔离成功回调的执行 setTimeout(() => { if (successCallback) { successCallback(res); } }, 0); }) .catch(err => { console.error(`【全局上传管理器】任务 ${taskId} 上传失败:`, err); // 更新任务状态 if (self.activeTasks[taskId]) { self.activeTasks[taskId].status = 'failed'; self.activeTasks[taskId].error = err; self.activeTasks[taskId].endTime = Date.now(); } // 使用setTimeout隔离失败回调的执行 setTimeout(() => { if (failCallback) { failCallback(err); } }, 0); }) .finally(() => { // 延迟清理任务,确保所有操作完成 setTimeout(() => { if (self.activeTasks[taskId]) { delete self.activeTasks[taskId]; console.log(`【全局上传管理器】任务 ${taskId} 已清理`); } }, 10000); }); } catch (e) { console.error(`【全局上传管理器】任务 ${taskId} 发生异常:`, e); setTimeout(() => { if (failCallback) { failCallback(e); } }, 0); } }, 0); return taskId; }, // 获取任务状态的方法 getTaskStatus: function(taskId) { return this.activeTasks[taskId] || null; }, // 获取所有活动任务 getActiveTasks: function() { return Object.values(this.activeTasks); } }; } Page({ /** * 页面的初始数据 */ data: { variety: '', // 品种 price: '', quantity: '', grossWeight: '', yolk: '', // 蛋黄 specification: '', images: [] // 新增图片数组 }, /** * 生命周期函数--监听页面加载 */ onLoad(options) { // 检查用户是否已登录 this.checkLoginStatus(); }, /** * 检查用户登录状态 */ checkLoginStatus() { const openid = wx.getStorageSync('openid'); if (!openid) { wx.showModal({ title: '提示', content: '请先登录后再发布商品', showCancel: false, success: (res) => { if (res.confirm) { wx.navigateTo({ url: '/pages/index/index' }); } } }); } }, /** * 品种输入处理 */ onVarietyInput(e) { this.setData({ variety: e.detail.value }); }, /** * 蛋黄输入处理 */ onYolkInput(e) { this.setData({ yolk: e.detail.value }); }, /** * 价格输入处理 */ onPriceInput(e) { // 保存原始字符串值,不进行数字转换 this.setData({ price: e.detail.value }); }, /** * 数量输入处理 */ onQuantityInput(e) { const value = parseFloat(e.detail.value); this.setData({ quantity: isNaN(value) ? '' : value }); }, /** * 毛重输入处理 */ onGrossWeightInput(e) { // 直接保存原始字符串值,不进行数字转换 this.setData({ grossWeight: e.detail.value }); }, /** * 规格输入处理 */ onSpecificationInput(e) { this.setData({ specification: e.detail.value }); }, /** * 表单验证 */ validateForm() { const { variety, price, quantity } = this.data; console.log('表单验证数据 - variety:', variety, 'price:', price, 'quantity:', quantity); console.log('数据类型 - variety:', typeof variety, 'price:', typeof price, 'quantity:', typeof quantity); if (!variety || !variety.trim()) { wx.showToast({ title: '请输入品种', icon: 'none' }); return false; } if (!price || price.trim() === '') { wx.showToast({ title: '请输入有效价格', icon: 'none' }); return false; } if (quantity === '' || quantity === undefined || quantity === null || quantity <= 0) { wx.showToast({ title: '请输入有效数量', icon: 'none' }); return false; } console.log('表单验证通过'); return true; }, /** * 发布商品按钮点击事件 */ onPublishTap() { console.log('发布按钮点击'); // 检查用户登录状态 const openid = wx.getStorageSync('openid'); const userInfo = wx.getStorageSync('userInfo'); const userId = wx.getStorageSync('userId'); console.log('检查用户授权状态 - openid:', !!openid, 'userInfo:', !!userInfo, 'userId:', !!userId); if (!openid || !userId || !userInfo) { console.log('用户未登录或未授权,引导重新登录'); wx.showModal({ title: '登录过期', content: '请先授权登录后再发布商品', showCancel: false, confirmText: '去登录', success: (res) => { if (res.confirm) { wx.navigateTo({ url: '/pages/index/index' }); } } }); return; } if (!this.validateForm()) { console.log('表单验证失败'); return; } const { variety, price, quantity, grossWeight, yolk, specification } = this.data; const images = this.data.images; // 构建商品数据,确保价格和数量为字符串类型 const productData = { productName: variety.trim(), // 使用品种作为商品名称 price: price.toString(), quantity: quantity.toString(), grossWeight: grossWeight !== '' && grossWeight !== null && grossWeight !== undefined ? grossWeight : "", yolk: yolk || '', specification: specification || '', images: images, imageUrls: images, allImageUrls: images, hasMultipleImages: images.length > 1, totalImages: images.length }; console.log('【关键日志】商品数据:', productData); console.log('【关键日志】图片数量:', images.length); // 【终极修复】在上传开始前立即清空表单 // 先深度克隆所有数据 console.log('【上传前检查】准备克隆数据'); const formDataCopy = JSON.parse(JSON.stringify(productData)); const imagesCopy = JSON.parse(JSON.stringify(images)); console.log('【上传前检查】克隆后图片数量:', imagesCopy.length); console.log('【上传前检查】克隆后图片数据:', imagesCopy); // 立即清空表单,避免任何状态变化触发重新编译 console.log('【上传前检查】清空表单'); this.setData({ variety: '', price: '', quantity: '', grossWeight: '', yolk: '', specification: '', images: [] }); // 显示加载提示 wx.showLoading({ title: '正在上传图片...' }); // 【终极修复】使用全局上传管理器处理上传,完全脱离页面生命周期 // 将所有数据存储到全局对象中,防止被回收 console.log('【上传前检查】存储数据到全局对象'); global.tempUploadData = { formData: formDataCopy, images: imagesCopy, userId: userId, timestamp: Date.now() }; // 预先生成会话ID,确保所有图片关联同一个商品 const uploadSessionId = `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; formDataCopy.sessionId = uploadSessionId; formDataCopy.uploadSessionId = uploadSessionId; console.log(`【关键修复】预先生成会话ID:`, uploadSessionId); console.log(`【上传前检查】准备调用全局上传管理器,图片数量:`, imagesCopy.length); console.log(`【上传前检查】传递的formData结构:`, Object.keys(formDataCopy)); // 【核心修复】直接使用wx.uploadFile API,确保与服务器端测试脚本格式一致 console.log(`【核心修复】使用wx.uploadFile API直接上传`); // 预先生成会话ID const sessionId = `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; formDataCopy.sessionId = sessionId; formDataCopy.uploadSessionId = sessionId; console.log(`【核心修复】使用会话ID:`, sessionId); console.log(`【核心修复】上传图片数量:`, imagesCopy.length); // 使用Promise处理上传 const uploadPromise = new Promise((resolve, reject) => { // 构建formData,与服务器测试脚本一致 const formData = { productData: JSON.stringify(formDataCopy), sessionId: sessionId, uploadSessionId: sessionId, totalImages: imagesCopy.length.toString(), isSingleUpload: 'false' // 关键参数:标记为多图片上传 }; console.log(`【核心修复】准备上传,formData结构:`, Object.keys(formData)); console.log(`【核心修复】上传URL:`, API.BASE_URL + '/api/products/upload'); // 直接使用wx.uploadFile上传第一张图片 wx.uploadFile({ url: API.BASE_URL + '/api/products/upload', filePath: imagesCopy[0], // 先上传第一张 name: 'images', formData: formData, timeout: 180000, success: (res) => { console.log('【核心修复】上传成功,状态码:', res.statusCode); console.log('【核心修复】原始响应:', res.data); try { const data = JSON.parse(res.data); resolve(data); } catch (e) { resolve({data: res.data}); } }, fail: (err) => { console.error('【核心修复】上传失败:', err); reject(err); } }); }); uploadPromise.then((res) => { // 上传成功回调 console.log('【核心修复】上传成功,响应:', res); // 使用setTimeout完全隔离回调执行上下文 setTimeout(() => { wx.hideLoading(); // 从全局临时存储获取数据 const tempData = global.tempUploadData || {}; const localFormData = tempData.formData; const userId = tempData.userId; // 【关键修复】从多个来源提取图片URL,确保不丢失 let allUploadedImageUrls = []; // 尝试从多个位置提取图片URLs if (res.imageUrls && Array.isArray(res.imageUrls) && res.imageUrls.length > 0) { allUploadedImageUrls = [...res.imageUrls]; console.log('【全局上传】从res.imageUrls提取到图片:', allUploadedImageUrls.length); } if (res.product && res.product.imageUrls && Array.isArray(res.product.imageUrls) && res.product.imageUrls.length > 0) { allUploadedImageUrls = [...res.product.imageUrls]; console.log('【全局上传】从res.product.imageUrls提取到图片:', allUploadedImageUrls.length); } if (res.data && res.data.imageUrls && Array.isArray(res.data.imageUrls) && res.data.imageUrls.length > 0) { allUploadedImageUrls = [...res.data.imageUrls]; console.log('【全局上传】从res.data.imageUrls提取到图片:', allUploadedImageUrls.length); } // 去重处理,确保URL不重复 allUploadedImageUrls = [...new Set(allUploadedImageUrls)]; console.log('【全局上传】最终去重后的图片URL列表:', allUploadedImageUrls); console.log('【全局上传】最终图片数量:', allUploadedImageUrls.length); // 获取卖家信息 const users = wx.getStorageSync('users') || {}; const sellerName = users[userId] && users[userId].info && users[userId].info.nickName ? users[userId].info.nickName : '未知卖家'; // 保存到本地存储 setTimeout(() => { // 获取当前已有的货源列表 const supplies = wx.getStorageSync('supplies') || []; const newId = supplies.length > 0 ? Math.max(...supplies.map(s => s.id)) + 1 : 1; const serverProductId = res.product && res.product.productId ? res.product.productId : ''; // 创建新的货源记录 const newSupply = { id: newId, productId: serverProductId, serverProductId: serverProductId, name: localFormData.productName, productName: localFormData.productName, price: localFormData.price, minOrder: localFormData.quantity, yolk: localFormData.yolk, spec: localFormData.specification, grossWeight: localFormData.grossWeight !== null ? localFormData.grossWeight : '', seller: sellerName, status: res.product && res.product.status ? res.product.status : 'pending_review', imageUrls: allUploadedImageUrls, reservedCount: 0, isReserved: false }; // 保存到supplies和goods本地存储 supplies.push(newSupply); wx.setStorageSync('supplies', supplies); const goods = wx.getStorageSync('goods') || []; const newGoodForBuyer = { id: String(newId), productId: String(serverProductId), name: localFormData.productName, productName: localFormData.productName, price: localFormData.price, minOrder: localFormData.quantity, yolk: localFormData.yolk, spec: localFormData.specification, grossWeight: localFormData.grossWeight !== null ? localFormData.grossWeight : '', displayGrossWeight: localFormData.grossWeight !== null ? localFormData.grossWeight : '', seller: sellerName, status: res.product && res.product.status ? res.product.status : 'pending_review', imageUrls: allUploadedImageUrls, reservedCount: 0, isReserved: false }; goods.push(newGoodForBuyer); wx.setStorageSync('goods', goods); // 显示成功提示 setTimeout(() => { wx.showModal({ title: '发布成功', content: `所有${allUploadedImageUrls.length}张图片已成功上传!\n请手动返回查看您的商品。\n\n重要:请勿关闭小程序,等待3-5秒确保所有数据处理完成。`, showCancel: false, confirmText: '我知道了', success: function() { // 延迟清理全局临时数据,确保所有操作完成 setTimeout(() => { if (global.tempUploadData) { delete global.tempUploadData; } }, 5000); } }); }, 500); }, 500); }, 100); }) .catch((err) => { // 上传失败回调 console.error('【核心修复】上传失败:', err); // 使用setTimeout隔离错误处理 setTimeout(() => { wx.hideLoading(); if (err.needRelogin) { wx.showModal({ title: '登录状态失效', content: '请重新授权登录', showCancel: false, success: (res) => { if (res.confirm) { wx.removeStorageSync('openid'); wx.removeStorageSync('userId'); wx.navigateTo({ url: '/pages/login/index' }); } } }); } else { wx.showToast({ title: err.message || '发布失败,请重试', icon: 'none' }); } // 清理全局临时数据 if (global.tempUploadData) { delete global.tempUploadData; } }, 100); }); }, /** * 生命周期函数--监听页面显示 */ onShow() { // 页面显示时可以刷新数据 }, /** * 选择图片 - 修复版本 */ chooseImage: function () { const that = this; wx.chooseMedia({ count: 5 - that.data.images.length, mediaType: ['image'], sourceType: ['album', 'camera'], success: function (res) { console.log('选择图片成功,返回数据:', res); const tempFiles = res.tempFiles.map(file => file.tempFilePath); that.setData({ images: [...that.data.images, ...tempFiles] }); console.log('更新后的图片数组:', that.data.images); }, fail: function (err) { console.error('选择图片失败:', err); } }); }, /** * 删除图片 */ deleteImage: function (e) { const index = e.currentTarget.dataset.index; const images = this.data.images; images.splice(index, 1); this.setData({ images: images }); } });