You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

2054 lines
83 KiB

// 简化版API文件 - 仅包含必要功能
// 智能API地址选择器 - 根据环境自动切换基础URL
let BASE_URL;
// 配置常量
const SERVER_CONFIG = {
PRODUCTION: 'https://youniao.icu', // 生产服务器地址
LOCALHOST: 'http://localhost:3003', // 本地服务器地址
DEFAULT_LOCAL_IP: 'http://192.168.1.100:3003' // 默认本地IP地址
};
// 重要提示:真机调试时,请确保以下操作:
// 1. 将手机和开发机连接到同一WiFi网络
// 2. 修改上方DEFAULT_LOCAL_IP为您开发机的实际IP地址
// 3. 或者在test-mode-switch页面中设置自定义IP
// 检测当前环境是否为真机调试
function isRealDevice() {
try {
// 检查是否有明确设置的设备类型标记
const deviceType = wx.getStorageSync('__DEVICE_TYPE__');
if (deviceType === 'real') return true;
if (deviceType === 'simulator') return false;
// 尝试通过系统信息判断环境 - 增强版
try {
const systemInfo = wx.getSystemInfoSync();
// 明确识别开发者工具环境
if (systemInfo && (systemInfo.platform === 'devtools' || systemInfo.environment === 'devtools')) {
return false;
}
// 其他平台都认为是真机
return true;
} catch (infoErr) {
console.warn('系统信息获取失败,尝试其他方式判断环境:', infoErr);
}
// 最终默认:如果无法确定,优先认为是真机环境,确保用户体验
return true;
} catch (e) {
console.warn('环境检测失败,默认使用真机地址:', e);
return true;
}
}
// 设置测试模式 - 用于真机调试连接本地服务器
function setTestMode(enable, customIp = null) {
try {
wx.setStorageSync('__TEST_MODE__', enable);
if (enable && customIp) {
wx.setStorageSync('__TEST_SERVER_IP__', customIp);
}
console.log('测试模式设置:', enable ? '开启' : '关闭', customIp ? `自定义IP: ${customIp}` : '');
// 重新初始化基础URL
initBaseUrl();
return true;
} catch (e) {
console.error('设置测试模式失败:', e);
return false;
}
}
// 获取当前是否为测试模式
function isTestMode() {
try {
return !!wx.getStorageSync('__TEST_MODE__');
} catch (e) {
console.warn('获取测试模式失败,默认关闭:', e);
return false;
}
}
// 设置设备类型 - 手动控制环境类型
function setDeviceType(deviceType) {
try {
if (deviceType === 'real' || deviceType === 'simulator' || deviceType === null) {
if (deviceType === null) {
wx.removeStorageSync('__DEVICE_TYPE__');
console.log('设备类型设置已清除,将使用自动检测');
} else {
wx.setStorageSync('__DEVICE_TYPE__', deviceType);
console.log('设备类型设置为:', deviceType === 'real' ? '真机' : '模拟器');
}
// 重新初始化基础URL
initBaseUrl();
return true;
} else {
console.error('无效的设备类型,必须是 "real", "simulator" 或 null');
return false;
}
} catch (e) {
console.error('设置设备类型失败:', e);
return false;
}
}
// 导出函数供外部使用
// 将函数定义提升并直接导出,确保在小程序环境中正确识别
module.exports = {
setTestMode: setTestMode,
isTestMode: isTestMode,
setDeviceType: setDeviceType
}
// 初始化基础URL - 增强版,更可靠的服务器地址选择策略
function initBaseUrl() {
try {
// 检查是否启用测试模式
const testMode = isTestMode();
const realDevice = isRealDevice();
console.log('环境检测结果:', { testMode: testMode, isRealDevice: realDevice });
// 测试模式优先级最高
if (testMode) {
// 优先使用自定义IP
const customIp = wx.getStorageSync('__TEST_SERVER_IP__');
if (customIp) {
BASE_URL = customIp;
console.log('✅ 使用自定义测试服务器地址:', BASE_URL);
} else {
// 根据设备类型选择合适的地址
if (realDevice) {
// 真机必须使用IP地址,不能使用localhost
BASE_URL = SERVER_CONFIG.DEFAULT_LOCAL_IP;
console.log('⚠️ 使用真机测试模式,默认本地IP地址:', BASE_URL);
console.log('⚠️ 请确保:1.手机和电脑在同一WiFi 2.此IP是您电脑的实际IP');
} else {
BASE_URL = SERVER_CONFIG.LOCALHOST;
console.log('✅ 使用模拟器测试模式,localhost地址:', BASE_URL);
}
}
} else {
// 非测试模式
if (realDevice) {
// 真机环境默认使用生产地址
BASE_URL = SERVER_CONFIG.PRODUCTION;
console.log('✅ 使用生产环境服务器地址:', BASE_URL);
// 额外检查:如果生产地址连接失败,可以手动切换到测试模式
console.log('💡 提示:如果生产环境连接失败,可以进入test-mode-switch页面开启测试模式');
} else {
// 模拟器环境使用localhost
BASE_URL = SERVER_CONFIG.LOCALHOST;
console.log('✅ 使用模拟器环境服务器地址:', BASE_URL);
}
}
// 保存当前使用的服务器地址用于调试
wx.setStorageSync('__CURRENT_SERVER__', BASE_URL);
} catch (e) {
console.error('初始化基础URL失败:', e);
// 发生错误时,根据环境类型选择更合适的默认地址
const realDevice = isRealDevice();
if (realDevice) {
BASE_URL = SERVER_CONFIG.PRODUCTION;
} else {
BASE_URL = SERVER_CONFIG.LOCALHOST;
}
console.log('初始化失败,使用默认地址:', BASE_URL);
}
}
// 初始化基础URL
initBaseUrl();
function request(url, method, data) {
return new Promise(function (resolve, reject) {
// 每次请求都重新初始化BASE_URL,确保使用最新配置
initBaseUrl();
console.log('发送请求:', {
url: BASE_URL + url,
method: method || 'GET',
data: data || {}
});
wx.request({
url: BASE_URL + url,
method: method || 'GET',
data: data || {},
header: {
'content-type': 'application/json'
},
success: function (res) {
console.log('请求成功响应:', {
url: BASE_URL + url,
statusCode: res.statusCode,
data: res.data
});
// 处理200成功响应
if (res.statusCode === 200) {
// 检查响应数据是否有效 - 增强版数据验证
const isResponseValid = res.data && (typeof res.data === 'object' || typeof res.data === 'string');
// 特殊情况处理:如果响应数据为空对象
if (res.data && typeof res.data === 'object' && Object.keys(res.data).length === 0) {
console.warn('警告: 服务器返回空对象数据');
}
resolve(res.data);
} else {
console.error('请求失败,状态码:', res.statusCode, '响应:', res.data);
// 为所有非200响应创建统一的错误对象
let errorMessage = '请求失败: ' + res.statusCode;
// 如果服务器返回了message,优先使用服务器返回的消息
if (res.data && res.data.message) {
errorMessage = res.data.message;
}
const error = new Error(errorMessage);
error.statusCode = res.statusCode;
error.responseData = res.data;
error.isServerError = res.statusCode >= 500;
error.isClientError = res.statusCode >= 400 && res.statusCode < 500;
// 特别标记需要重新登录的情况
error.needRelogin = res.data && res.data.needRelogin;
reject(error);
}
},
fail: function (err) {
console.error('请求网络错误:', {
url: BASE_URL + url,
error: err
});
// 创建基础错误对象
let errorMessage = '网络连接失败';
const error = new Error(errorMessage);
error.isNetworkError = true;
error.originalError = err;
// 尝试从错误中提取状态码和响应数据
if (err.errMsg && err.errMsg.includes('request:fail')) {
// 尝试解析可能的HTTP状态码
const statusMatch = err.errMsg.match(/request:fail (\d+)/);
if (statusMatch && statusMatch[1]) {
const statusCode = parseInt(statusMatch[1]);
error.statusCode = statusCode;
error.isServerError = statusCode >= 500;
error.isClientError = statusCode >= 400 && statusCode < 500;
// 关键修复:当状态码为401时,设置needRelogin标志
if (statusCode === 401) {
error.needRelogin = true;
errorMessage = '用户未登录或登录已过期';
} else if (statusCode === 500) {
errorMessage = '服务器内部错误';
} else if (statusCode >= 500) {
errorMessage = '服务器暂时不可用';
} else if (statusCode === 400) {
errorMessage = '请求参数错误';
} else if (statusCode === 403) {
errorMessage = '无权限访问';
} else if (statusCode === 404) {
errorMessage = '请求的资源不存在';
} else {
errorMessage = '请求失败,请稍后再试';
}
} else {
// 处理其他类型的连接错误
if (err.errMsg.includes('connect')) {
errorMessage = '无法连接到服务器,请检查服务器是否运行';
} else if (err.errMsg.includes('timeout')) {
errorMessage = '服务器响应超时';
} else {
errorMessage = '请求失败,请稍后再试';
}
}
}
// 更新错误消息
error.message = errorMessage;
// 尝试提取响应数据(如果存在)
if (err.response && err.response.data) {
error.responseData = err.response.data;
// 从响应数据中提取needRelogin标志
if (err.response.data.needRelogin) {
error.needRelogin = true;
}
}
reject(error);
},
complete: function (res) {
console.log('请求完成:', {
url: BASE_URL + url,
type: res.errMsg.includes('ok') ? '成功' : '失败',
statusCode: res.statusCode
});
}
});
});
}
// 导出统一的API对象
module.exports = {
// 添加商品到购物车 - 增强版本,即使本地找不到商品也尝试直接请求服务器
addToCart: function (goodsItem) {
return new Promise((resolve, reject) => {
var openid = wx.getStorageSync('openid');
console.log('API.addToCart - openid:', openid, 'goodsItem:', goodsItem);
// 1. 验证用户登录状态
if (!openid) {
return reject(new Error('用户未登录'));
}
// 2. 验证商品信息是否存在
if (!goodsItem) {
console.error('添加到购物车失败:商品信息为空');
return reject(new Error('商品信息不存在'));
}
// 3. 获取商品ID并验证
const productId = goodsItem.productId || goodsItem.id;
if (!productId) {
console.error('添加到购物车失败:商品ID为空');
return reject(new Error('商品ID不存在'));
}
// 构建基础的商品对象,至少包含必要字段
const basicProduct = this.buildProductObject(goodsItem);
// 4. 从本地存储获取完整的商品列表,验证当前商品ID是否有效
const allGoods = wx.getStorageSync('goods') || [];
// 确保使用字符串类型进行比较,避免类型不匹配
const productIdStr = String(productId);
const validProduct = allGoods.find(item =>
String(item.id) === productIdStr || String(item.productId) === productIdStr
);
// 重要优化:使用最多两次尝试机制,确保新创建的货源也能正确处理
let attempts = 0;
const maxAttempts = 2;
const tryAddToCart = () => {
attempts++;
console.log(`尝试添加到购物车,第${attempts}/${maxAttempts}次尝试`);
// 总是先尝试直接向服务器发送请求(这是修复新创建货源的关键)
this.sendAddToCartRequest(openid, basicProduct).then(resolve).catch(err => {
console.error('添加到购物车请求失败:', err.message, '尝试次数:', attempts);
// 检查是否为外键约束错误或者服务器找不到商品的情况
const isForeignKeyError = err && (err.isForeignKeyError ||
err.message.includes('外键') ||
err.message.includes('500') ||
err.message.includes('child row') ||
err.message.includes('constraint'));
// 如果是外键约束错误且还有尝试次数,先刷新商品列表再试
if (isForeignKeyError && attempts < maxAttempts) {
console.log('检测到外键约束相关错误,刷新商品列表后重试...');
this.getProducts().then(() => {
console.log('商品列表刷新成功,准备再次尝试');
// 从刷新后的商品列表中获取最新的商品信息
const refreshedGoods = wx.getStorageSync('goods') || [];
const refreshedProduct = refreshedGoods.find(item =>
String(item.id) === productIdStr || String(item.productId) === productIdStr
);
// 即使找不到,也再次尝试,因为可能是新创建的商品还没完全同步
const updatedProduct = refreshedProduct ?
this.buildProductObject(refreshedProduct) :
basicProduct; // 使用原始商品信息再次尝试
console.log('使用的商品信息:', updatedProduct);
// 直接再次尝试,不再经过复杂的判断
this.sendAddToCartRequest(openid, updatedProduct).then(resolve).catch(reject);
}).catch(innerErr => {
console.error('刷新商品列表失败:', innerErr);
// 刷新失败也尝试再次发送请求,不轻易放弃
if (attempts < maxAttempts) {
console.log('刷新失败,但仍尝试再次发送请求');
this.sendAddToCartRequest(openid, basicProduct).then(resolve).catch(reject);
} else {
reject(new Error('商品信息已更新,请稍后重试'));
}
});
} else {
// 其他错误或已达到最大尝试次数,返回错误
reject(err);
}
});
};
// 开始尝试添加到购物车
tryAddToCart();
});
},
// 构建商品对象的辅助方法 - 增强版,确保所有必要字段都有默认值和正确格式
buildProductObject: function (goodsItem) {
console.log('构建product对象,原始goodsItem:', goodsItem);
const product = {
productId: this.sanitizeProductId(goodsItem.productId || goodsItem.id), // 安全处理商品ID,确保为有效的字符串格式
id: this.sanitizeProductId(goodsItem.id || goodsItem.productId), // 确保id字段也存在且格式正确
productName: goodsItem.productName || goodsItem.name || '未命名商品', // 品种
quantity: goodsItem.quantity || 1, // 使用传入的数量,如果没有则默认为1
price: goodsItem.price || '', // 确保价格有默认值,使用空字符串支持字符串类型
specification: goodsItem.specification || goodsItem.spec || '',
grossWeight: goodsItem.grossWeight || goodsItem.weight || '', // 使用空字符串支持字符串类型
yolk: goodsItem.yolk || goodsItem.variety || '', // 蛋黄(原品种)
// 添加额外字段以提高兼容性
name: goodsItem.productName || goodsItem.name || '未命名商品',
// 不包含productQuantity字段,因为数据库中不存在该字段
// 关闭testMode,允许真实的数据库操作
testMode: false
};
console.log('构建完成的product对象:', product);
return product;
},
// 商品ID安全处理方法,确保返回有效的字符串格式
sanitizeProductId: function (id) {
if (!id) return '';
// 移除任何可能导致问题的前缀或特殊字符
const idStr = String(id).replace(/[^0-9a-zA-Z\-_]/g, '');
console.log('ID安全处理结果:', { original: id, sanitized: idStr });
return idStr;
},
// 发送添加到购物车请求的辅助方法 - 完全符合服务器格式版
sendAddToCartRequest: function (openid, product) {
return new Promise((resolve, reject) => {
// 重要:直接使用传入的openid参数,不再本地重新获取
console.log('构建的product对象:', product);
console.log('发送添加到购物车请求,productId:', product.productId, '类型:', typeof product.productId);
console.log('用户openid:', openid);
console.log('请求URL:', '/api/cart/add');
// 前置验证:确保productId存在且类型正确
if (!product.productId) {
console.error('productId为空,无法添加到购物车');
return reject(new Error('商品信息不完整,请刷新页面后重试'));
}
// 确保productId是字符串格式
const productIdStr = String(product.productId);
if (!productIdStr || productIdStr === 'undefined' || productIdStr === 'null') {
console.error('无效的productId:', productIdStr);
return reject(new Error('商品信息不完整,请刷新页面后重试'));
}
// 不修改原始商品ID,直接使用传入的商品ID
// 重要:服务器需要原始的商品ID才能正确匹配
const finalProductId = productIdStr;
console.log('使用原始商品ID,不进行转换:', finalProductId);
// 创建新的product对象,确保所有必要字段完整
// 根据服务器端代码分析,这是最有效的请求格式
const safeProduct = {
productId: finalProductId,
productName: product.productName || '未命名商品',
quantity: product.quantity || 1,
price: product.price || '', // 使用空字符串支持字符串类型
specification: product.specification || '',
grossWeight: product.grossWeight || '', // 使用空字符串支持字符串类型
yolk: product.yolk || '',
testMode: false,
// 确保id字段也设置为与productId相同的值(服务器端会检查这两个字段)
id: finalProductId,
name: product.productName || '未命名商品'
};
// 根据服务器端代码分析,服务器期望的格式就是 { openid, product: {...} }
// 这是最简单直接的格式,服务器会自动从product对象中提取数据
const requestData = {
openid: openid,
product: safeProduct
};
console.log('最终发送的请求数据完整结构:', requestData);
request('/api/cart/add', 'POST', requestData).then(res => {
console.log('服务器原始响应:', res);
// 增强响应处理:即使服务器没有返回success标志,也要检查是否有成功的迹象
if (res && (res.success || res.code === 200 || res.status === 'success')) {
console.log('添加到购物车成功');
// 规范化响应格式,确保上层代码能正确识别success标志和预约人数
// 优先使用cart_items表的selected字段作为预约人数
const normalizedRes = {
success: true,
...res,
// 确保返回reservedCount字段,优先使用服务器返回的selected字段,然后是reservedCount或reservationCount
// 如果服务器都没有返回,则返回1表示预约成功
reservedCount: res.selected !== undefined ? res.selected :
(res.reservedCount !== undefined ? res.reservedCount :
(res.reservationCount || 1))
};
console.log('规范化后的响应(包含预约人数,优先使用selected字段):', normalizedRes);
resolve(normalizedRes);
} else {
console.error('添加到购物车失败,服务器返回:', res);
// 增强的错误处理逻辑
let errorMessage = res && res.message ? res.message : '添加到购物车失败';
reject(new Error(errorMessage));
}
}).catch(err => {
console.error('添加到购物车请求失败:', err);
console.error('错误详情:', { message: err.message, statusCode: err.statusCode, responseData: err.responseData });
// 打印完整的请求数据,而不仅仅是部分参数
console.error('完整请求数据:', requestData);
// 特别打印missingFields(如果存在)
if (err.responseData && err.responseData.missingFields) {
console.error('服务器认为缺少的字段:', err.responseData.missingFields);
}
// 增强的错误判断逻辑 - 无论状态码是什么,都从responseData中提取错误信息
if (err.responseData) {
// 提取服务器返回的具体错误信息
console.log('服务器返回错误,详细信息:', err.responseData);
const res = err.responseData;
let errorMessage = res.message || '添加到购物车失败';
// 更全面的错误分类处理
// 增强的外键约束错误检测,确保所有可能的外键错误都能被正确识别
if ((res.error && (res.error.includes('外键') || res.error.includes('constraint') || res.error.includes('foreign key') || res.error.includes('key') || res.error.includes('Child row'))) ||
res.errorDetails?.name === 'SequelizeForeignKeyConstraintError' ||
res.error?.includes('child row') ||
res.error?.includes('cannot add or update') ||
res.error?.includes('referenced column')) {
// 增加对child row错误的检测,这是外键约束失败的典型错误信息
console.log('检测到外键约束相关错误:', res.error);
// 更详细的外键约束错误处理
console.log('检测到外键约束相关错误,准备抛出特殊错误对象:', res.error);
// 1. 创建带有明确标识和丰富信息的错误对象
const foreignKeyError = new Error('商品信息已更新,请刷新页面后重试');
foreignKeyError.isForeignKeyError = true;
foreignKeyError.originalError = err; // 保存原始错误对象
foreignKeyError.productId = safeProduct.productId; // 保存尝试添加的商品ID
foreignKeyError.timestamp = new Date().toISOString(); // 添加时间戳便于调试
// 保留原始错误对象的标志
if (err) {
foreignKeyError.statusCode = err.statusCode || 500;
foreignKeyError.responseData = err.responseData || res;
foreignKeyError.isServerError = err.isServerError;
foreignKeyError.isClientError = err.isClientError;
} else {
foreignKeyError.statusCode = 500;
foreignKeyError.responseData = res;
}
// 2. 尝试后台静默刷新商品列表,为下一次操作做准备
console.log('尝试后台刷新商品列表');
this.getProducts().catch(err => {
console.error('后台刷新商品列表失败:', err);
});
// 3. 打印详细的错误上下文信息,便于调试
console.error('外键约束错误详细信息:', {
productId: safeProduct.productId,
requestData: requestData,
errorResponse: res
});
// 4. 抛出带有明确标识的错误,让上层能区分处理
reject(foreignKeyError);
return; // 提前返回,避免重复reject
} else if ((res.error && (res.error.includes('userId') || res.error.includes('用户') || res.errorDetails?.error.includes('userId'))) ||
res.details?.userId === null ||
res.code === 403 ||
res.code === 401) {
errorMessage = '用户信息已过期,请重新登录后重试';
console.log('检测到用户信息错误,提示重新登录');
} else if ((res.error && (res.error.includes('productId') || res.error.includes('商品') || res.errorDetails?.error.includes('productId'))) ||
res.details?.productId === null ||
res.error?.includes('不存在') ||
res.error?.includes('已下架')) {
errorMessage = '商品信息已更新,请刷新页面后重试';
console.log('检测到商品信息错误,提示刷新页面');
} else if (res.code === 500 || (err.message && err.message.includes('500'))) {
// 即使服务器返回500,也要提供友好的用户提示
// 特别处理包含外键约束的500错误
let isForeignKeyError = false;
if (res.error && (res.error.includes('外键') || res.error.includes('constraint') || res.error.includes('foreign key') || res.error.includes('key') || res.error.includes('child row'))) {
errorMessage = '商品信息已更新,请刷新页面后重试';
isForeignKeyError = true;
console.log('服务器500错误,但包含外键约束信息');
} else {
errorMessage = '系统繁忙,请稍后再试';
console.log('服务器内部错误,但提供友好提示');
}
// 创建错误对象并保留原始错误的标志
const error = new Error(errorMessage);
if (err) {
error.statusCode = err.statusCode;
error.responseData = err.responseData;
error.isServerError = err.isServerError;
error.isClientError = err.isClientError;
}
// 设置外键约束错误标志
error.isForeignKeyError = isForeignKeyError;
reject(error);
} else {
// 其他错误
const error = new Error(errorMessage);
if (err) {
error.statusCode = err.statusCode;
error.responseData = err.responseData;
error.isServerError = err.isServerError;
error.isClientError = err.isClientError;
}
reject(error);
}
} else if (err.message && err.message.includes('网络')) {
// 网络连接错误
console.log('网络连接失败,检查服务器连接状态');
reject(new Error('网络连接失败,请检查网络设置后重试'));
} else if (err.message && err.message.includes('500')) {
// 处理直接返回500状态码的情况
console.log('检测到服务器500错误');
reject(new Error('系统繁忙,请稍后再试'));
} else {
// 其他错误
console.log('未分类的错误:', err.message, '响应数据:', err.responseData);
reject(new Error('添加到购物车失败,请稍后重试'));
}
});
});
},
// 处理上传队列的方法 - 加强版,确保严格串行执行,避免连接数超限
_processUploadQueue: function () {
// 关键防御:检查是否正在处理队列或有上传任务正在执行,或者队列为空
if (this._isProcessingQueue || this._isUploading || this._uploadQueue.length === 0) {
console.log('_processUploadQueue 跳过,原因:',
this._isProcessingQueue ? '正在处理队列' :
this._isUploading ? '有上传任务正在执行' : '队列为空',
'活跃上传数:', this._activeUploadCount || 0);
return;
}
// 关键修复:检查活跃上传计数,如果异常(大于0),强制重置
if ((this._activeUploadCount || 0) > 0) {
console.error('检测到异常的活跃上传计数:', this._activeUploadCount, ',强制重置为0');
this._activeUploadCount = 0;
}
console.log('开始处理上传队列,队列长度:', this._uploadQueue.length, '活跃上传数:', this._activeUploadCount || 0);
this._isProcessingQueue = true;
// 从队列中取出第一个任务
const task = this._uploadQueue.shift();
if (task) {
console.log('执行队列任务,剩余队列长度:', this._uploadQueue.length);
// 关键优化:增加延迟时间到1000ms,确保前一个任务完全释放所有资源
setTimeout(() => {
// 再次检查上传状态,确保没有并发任务
if (this._isUploading) {
console.error('严重错误:队列处理时检测到上传状态已被占用,重新入队');
// 将任务重新入队
this._uploadQueue.unshift(task);
this._isProcessingQueue = false;
// 短暂延迟后重新尝试
setTimeout(() => {
this._processUploadQueue();
}, 100);
return;
}
// 执行队列任务
this.uploadProductWithRecursiveImages(
task.productData,
task.imageUrls,
task.uploadedImageUrls,
task.currentIndex,
task.retryInfo
)
.then(task.resolve)
.catch(task.reject)
.finally(() => {
console.log('队列任务执行完成,重置处理状态', '活跃上传数:', this._activeUploadCount || 0);
this._isProcessingQueue = false;
// 确保上传状态一致性
if (this._isUploading) {
console.warn('警告:队列任务完成后上传状态仍为true,强制重置为false');
this._isUploading = false;
}
// 确保活跃计数一致性
if ((this._activeUploadCount || 0) > 0) {
console.warn('警告:队列任务完成后活跃上传计数仍大于0,强制重置为0');
this._activeUploadCount = 0;
}
// 继续处理队列中的下一个任务,进一步增加延迟确保资源完全释放
setTimeout(() => {
this._processUploadQueue();
}, 1000); // 增加延迟时间到1000ms
});
}, 1000); // 增加延迟时间到1000ms
} else {
console.log('队列中无任务,结束处理');
this._isProcessingQueue = false;
}
},
// 获取商品列表的方法(用于刷新商品信息)- 修复接口路径和请求方式
getProducts: function () {
return new Promise((resolve, reject) => {
// 从本地存储获取openid
const openid = wx.getStorageSync('openid') || '';
// 使用正确的接口路径和POST请求方式
request('/api/product/list', 'POST', {
openid: openid,
status: 'all', // 请求所有状态的商品(除了hidden)
viewMode: 'seller', // 添加viewMode参数,限制只能查看当前用户的商品
page: 1,
pageSize: 100 // 获取足够多的商品
}).then(res => {
if (res && (res.code === 200 || res.success) && res.products) {
// 将商品列表存储到本地缓存
wx.setStorageSync('goods', res.products || []);
resolve(res.products);
} else {
reject(new Error('获取商品列表失败'));
}
}).catch(err => {
console.error('获取商品列表失败:', err);
reject(new Error('获取商品列表失败,请稍后重试'));
});
});
},
// 从购物车移除商品
removeFromCart: function (goodsId) {
var openid = wx.getStorageSync('openid');
console.log('API.removeFromCart - openid:', openid, 'goodsId:', goodsId);
if (!openid) {
return Promise.reject(new Error('用户未登录'));
}
// 注意:当前服务器端可能未实现/api/cart/remove接口
// 此方法会尝试调用服务器接口,但即使失败也会返回成功,确保本地操作不受影响
return request('/api/cart/remove', 'POST', {
openid: openid,
goodsId: goodsId
}).catch(err => {
console.warn('服务器移除购物车商品失败(可能是接口未实现):', err);
// 即使服务器移除失败,也返回成功,确保本地操作能继续
return { success: true, message: '本地已移除,服务器移除失败(接口可能未实现)' };
});
},
// 从所有用户的购物车中移除指定商品
removeFromAllCarts: function (supplyId) {
var openid = wx.getStorageSync('openid');
console.log('API.removeFromAllCarts - openid:', openid, 'supplyId:', supplyId);
if (!openid) {
return Promise.reject(new Error('用户未登录'));
}
// 检查是否启用服务器清理购物车功能(默认为不启用,避免调用不存在的接口)
const enableServerCleanup = false; // 可以根据实际情况修改为true
if (!enableServerCleanup) {
console.log('服务器清理购物车功能已禁用,跳过服务器调用');
return Promise.resolve({
success: true,
message: '服务器清理购物车功能已禁用,仅执行本地清理'
});
}
// 如果启用了服务器清理功能,则尝试调用接口
// 注意:当前服务器端可能未实现/api/cart/removeFromAll接口
return request('/api/cart/removeFromAll', 'POST', {
openid: openid,
supplyId: supplyId
}).catch(err => {
console.warn('服务器清理所有购物车商品失败(可能是接口未实现):', err);
// 即使服务器操作失败,也返回成功,确保本地操作不受影响
return { success: true, message: '本地已清理,服务器清理失败(接口可能未实现)' };
});
},
// 获取购物车信息
getCart: function () {
const openid = wx.getStorageSync('openid');
console.log('API.getCart - openid:', openid);
if (!openid) {
return Promise.reject(new Error('用户未登录'));
}
return request('/api/cart/get', 'POST', {
openid: openid
});
},
// 发布商品 - 支持图片上传(修复sellerId问题)
publishProduct: function (product) {
console.log('===== publishProduct调用开始 =====')
console.log('当前时间:', new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' }))
const openid = wx.getStorageSync('openid')
const userId = wx.getStorageSync('userId') // 【新增】获取userId
console.log('API.publishProduct - openid:', openid)
console.log('API.publishProduct - userId:', userId) // 【新增】打印userId
console.log('API.publishProduct - 原始商品数据:', product)
// 检查商品数据字段
if (!product) {
console.error('错误:商品数据为空')
return Promise.reject({ errMsg: '商品数据为空' })
}
// 验证必要字段
const requiredFields = ['productName', 'price', 'quantity']
const missingFields = requiredFields.filter(field => !(field in product))
if (missingFields.length > 0) {
console.error('错误:缺少必要字段:', missingFields)
} else {
console.log('所有必要字段都已提供')
}
if (!openid) {
console.error('API.publishProduct - 错误: 用户未登录')
const error = new Error('用户未登录或登录已过期')
error.needRelogin = true
return Promise.reject(error)
}
if (!userId) {
console.error('API.publishProduct - 错误: 用户ID不存在')
const error = new Error('用户信息不完整,请重新登录')
error.needRelogin = true
return Promise.reject(error)
}
// 【关键修复】构建正确的数据格式,使用userId而不是openid
const requestData = {
productName: product.productName || '',
price: String(product.price !== undefined ? product.price || '' : ''), // 确保以字符串形式传递
quantity: parseInt(product.quantity) || 0,
sellerId: userId, // 【关键修复】使用userId而不是openid
openid: openid, // 【新增】同时传递openid用于其他验证
grossWeight: String(product.grossWeight !== undefined ? product.grossWeight || '' : ''), // 确保转换为字符串
yolk: product.yolk || '',
specification: product.specification || '',
region: product.region || '', // 【新增】添加地区字段
// imageUrls字段会被忽略,因为图片会通过wx.uploadFile上传
}
console.log('API.publishProduct - 发送请求数据:', requestData)
// 如果有图片需要上传,使用wx.uploadFile
if ((product.images && product.images.length > 0) || (product.imageUrls && product.imageUrls.length > 0)) {
const imagesToUpload = product.images && product.images.length > 0 ? product.images : product.imageUrls
console.log(`检测到${imagesToUpload.length}张图片,准备上传`)
return this.uploadProductWithImages(requestData, imagesToUpload)
} else {
// 没有图片,使用普通的请求
console.log('没有检测到图片,使用普通请求')
return request('/api/products/upload', 'POST', {
productData: JSON.stringify(requestData)
}).then(res => {
console.log('===== 发布商品成功 =====')
console.log('响应状态码:', res.statusCode || '未提供')
console.log('响应数据:', res)
return res
}).catch(error => {
console.error('===== 发布商品失败 =====')
console.error('错误详情:', error)
console.error('错误消息:', error.errMsg || error.message || '未知错误')
throw error
})
}
},
// 更新商品图片 - 专门用于为已存在的商品上传图片
updateProductImages: function (productId, imageUrls, uploadData) {
console.log('===== updateProductImages调用开始 =====');
console.log('商品ID:', productId);
console.log('图片数量:', imageUrls.length);
console.log('上传数据:', uploadData);
// 使用现有的上传方法,但传递正确的参数
return this.uploadProductWithRecursiveImages(uploadData, imageUrls);
},
// 上传带图片的商品 - 改进版,确保所有图片都被实际上传到服务器
uploadProductWithImages: function (productData, imageUrls) {
console.log('===== 开始上传带图片的商品 =====');
console.log('商品数据:', productData);
console.log('图片数量:', imageUrls.length);
// 【新增】确保sellerId使用userId
const userId = wx.getStorageSync('userId');
if (userId && productData.sellerId) {
console.log('【修复】确保sellerId使用userId:', userId);
productData.sellerId = userId; // 确保使用userId
}
// 如果没有图片,使用普通请求
if (!imageUrls || imageUrls.length === 0) {
console.log('没有检测到图片,使用普通请求');
return request('/api/products/upload', 'POST', {
productData: JSON.stringify(productData)
});
}
// 创建包含所有图片URL的商品数据
const productDataWithAllImages = {
...productData,
sellerId: userId || productData.sellerId, // 【确保】使用userId
imageUrls: imageUrls, // 设置imageUrls字段,确保服务器端能正确识别
allImageUrls: imageUrls, // 添加完整的图片URL列表(备用字段)
// 生成会话ID,确保所有图片上传关联同一个商品
sessionId: `upload_${Date.now()}_${Math.floor(Math.random() * 1000000)}`,
uploadSessionId: `upload_${Date.now()}_${Math.floor(Math.random() * 1000000)}`
};
console.log('使用增强版商品数据,包含所有图片URL和会话ID');
console.log('会话ID:', productDataWithAllImages.sessionId);
console.log('sellerId:', productDataWithAllImages.sellerId); // 【新增】确认sellerId
// 关键修改:传递包含所有图片URL的完整商品数据
return this.uploadProductWithRecursiveImages(productDataWithAllImages, imageUrls);
},
// 最终版多图片上传方法 - 修复多图片上传创建重复商品问题
uploadProductWithRecursiveImages: function (productData, imageUrls = []) {
console.log('===== 最终版uploadProductWithRecursiveImages开始执行 =====');
console.log('待上传图片数量:', imageUrls.length);
// 【新增】确保sellerId使用userId
const userId = wx.getStorageSync('userId');
if (userId && productData.sellerId) {
console.log('【修复】确保sellerId使用userId:', userId);
productData.sellerId = userId;
}
// 防御性检查
if (!Array.isArray(imageUrls)) {
console.error('参数错误:imageUrls不是数组');
return Promise.reject(new Error('参数错误'));
}
// 如果没有图片,使用普通请求
if (!imageUrls || imageUrls.length === 0) {
console.log('没有图片,直接发送创建请求');
return request('/api/products/upload', 'POST', {
productData: JSON.stringify({
...productData,
isNewProduct: true
})
});
}
// 深度克隆数据,避免引用问题
const clonedProductData = JSON.parse(JSON.stringify(productData || {}));
const clonedImageUrls = JSON.parse(JSON.stringify(imageUrls || []));
// 使用从调用方传递过来的会话ID,如果没有则生成新的
const sessionId = clonedProductData.sessionId || clonedProductData.uploadSessionId || `upload_${Date.now()}_${Math.floor(Math.random() * 1000000)}`;
console.log('使用会话ID:', sessionId);
// 确保productData中有会话ID
clonedProductData.sessionId = sessionId;
clonedProductData.uploadSessionId = sessionId;
// 成功上传的图片URL数组
const uploadedImageUrls = [];
// 确保BASE_URL存在
if (!BASE_URL) {
console.error('BASE_URL未定义');
return Promise.reject(new Error('服务器地址未配置'));
}
const uploadUrl = BASE_URL + '/api/products/upload';
console.log('上传接口URL:', uploadUrl);
// 上传单个图片的函数 - 返回上传成功的URL
// 【关键修复】只上传必要的标识信息,不发送完整的商品数据,避免创建重复商品
const uploadSingleImage = (imagePath, index) => {
return new Promise((resolve) => {
console.log(`\n===== 上传第${index + 1}/${clonedImageUrls.length}张图片 =====`);
console.log(`图片路径:`, imagePath);
console.log(`会话ID:`, sessionId);
// 关键修复:只传递必要的标识信息,不包含完整的商品数据
// 这样服务器只会处理图片上传,不会创建新的商品记录
const formData = {
sessionId: sessionId,
uploadSessionId: sessionId,
productId: sessionId,
imageIndex: index.toString(),
totalImages: clonedImageUrls.length.toString(),
uploadedCount: uploadedImageUrls.length.toString(),
// 关键修复:传递完整的商品数据用于验证
productData: JSON.stringify({
...clonedProductData,
isFinalUpload: false,
isFinalStep: false,
currentImageIndex: index,
totalImages: clonedImageUrls.length
}),
// 确保包含必要的商品字段
productName: clonedProductData.productName || '',
price: clonedProductData.price || '', // 使用空字符串支持字符串类型
quantity: clonedProductData.quantity || 0,
sellerId: clonedProductData.sellerId || '',
grossWeight: clonedProductData.grossWeight || ''
};
let retryCount = 0;
const maxRetries = 3;
const doUpload = () => {
console.log(`发送上传请求,重试次数:`, retryCount);
console.log(`上传参数:`, {
url: uploadUrl,
filePath: imagePath,
name: 'images',
formData: Object.keys(formData)
});
wx.uploadFile({
url: uploadUrl,
filePath: imagePath,
name: 'images',
formData: formData,
timeout: 180000,
success: (res) => {
try {
console.log(`${index + 1}张图片上传响应状态码:`, res.statusCode);
console.log(`原始响应数据:`, res.data);
// 检查响应状态
if (res.statusCode >= 200 && res.statusCode < 300) {
// 尝试解析响应数据
let data = null;
try {
data = JSON.parse(res.data);
console.log(`解析后的响应数据:`, data);
} catch (parseError) {
console.error(`解析响应失败:`, parseError);
// 即使解析失败也继续,尝试创建一个临时URL,使用placeholder://协议前缀
resolve(`placeholder://temp_${index}_${Date.now()}`);
return;
}
// 【关键修复】从多个位置提取所有可能的图片URL
// 1. 首先检查是否有完整的URL列表
if (data && data.imageUrls && Array.isArray(data.imageUrls) && data.imageUrls.length > 0) {
console.log(`发现完整图片URL列表,长度:`, data.imageUrls.length);
// 清空并更新uploadedImageUrls数组
uploadedImageUrls.length = 0;
data.imageUrls.forEach(url => {
if (url && typeof url === 'string' && url.trim()) {
uploadedImageUrls.push(url.trim());
}
});
// 返回当前图片的URL(如果能确定)
const currentUrl = data.imageUrls[index] || data.imageUrls[data.imageUrls.length - 1];
resolve(currentUrl || `server_${index}`);
return;
}
// 2. 检查是否有单个图片URL
let imageUrl = null;
if (data) {
// 尝试所有可能的URL字段名
const urlFields = ['imageUrl', 'imgUrl', 'url', 'fileUrl', 'image', 'img'];
for (const field of urlFields) {
if (data[field] && typeof data[field] === 'string') {
imageUrl = data[field].trim();
if (imageUrl) {
console.log(`从字段${field}提取到URL:`, imageUrl);
break;
}
}
}
// 检查嵌套结构中的URL
if (!imageUrl && data.product && data.product.imageUrl) {
imageUrl = data.product.imageUrl.trim();
}
if (!imageUrl && data.data && data.data.imageUrl) {
imageUrl = data.data.imageUrl.trim();
}
}
// 3. 如果找到了URL,添加到数组
if (imageUrl) {
// 避免重复添加
if (!uploadedImageUrls.includes(imageUrl)) {
uploadedImageUrls.push(imageUrl);
console.log(`${index + 1}张图片URL添加到数组,当前长度:`, uploadedImageUrls.length);
}
resolve(imageUrl);
} else {
// 4. 如果没有找到URL,创建一个临时URL并添加到数组,使用placeholder://协议前缀
const tempUrl = `placeholder://temp_${index}_${Date.now()}`;
uploadedImageUrls.push(tempUrl);
console.log(`未找到URL,使用临时URL:`, tempUrl);
resolve(tempUrl);
}
} else {
console.error(`${index + 1}张图片上传失败,HTTP状态码:`, res.statusCode);
handleError(`HTTP错误: ${res.statusCode}`);
}
} catch (error) {
console.error(`处理第${index + 1}张图片响应时出错:`, error);
handleError(error.message || '处理响应错误');
}
},
fail: (error) => {
console.error(`${index + 1}张图片上传API调用失败:`, error);
handleError(error.errMsg || '上传失败');
},
complete: () => {
console.log(`${index + 1}张图片上传请求完成,当前已上传URL数量:`, uploadedImageUrls.length);
}
});
};
const handleError = (errorMsg) => {
if (retryCount < maxRetries) {
retryCount++;
console.log(`【重试】第${retryCount}次重试上传第${index + 1}张图片`);
// 固定延迟1秒,确保稳定重试
setTimeout(() => doUpload(), 1000);
} else {
console.error(`${index + 1}张图片上传彻底失败,已达到最大重试次数`);
// 创建失败标记URL - 使用placeholder://协议前缀,明确标识这是占位符而非真实URL
const failedUrl = `placeholder://failed_${index}_${Date.now()}`;
uploadedImageUrls.push(failedUrl);
console.log(`添加失败标记URL:`, failedUrl);
resolve(failedUrl); // 返回失败标记,继续处理
}
};
doUpload();
});
};
// 核心上传函数 - 使用async/await确保顺序执行
const uploadAllImages = async () => {
console.log('开始顺序上传所有图片,会话ID:', sessionId);
// 顺序上传每张图片
for (let i = 0; i < clonedImageUrls.length; i++) {
console.log(`\n----- 开始处理第${i + 1}张图片 -----`);
console.log(`待上传路径:`, clonedImageUrls[i]);
console.log(`已上传URL数量:`, uploadedImageUrls.length);
// 执行上传
const url = await uploadSingleImage(clonedImageUrls[i], i);
console.log(`${i + 1}张图片处理完成,返回URL:`, url);
console.log(`当前已上传URL列表:`, uploadedImageUrls);
// 添加延迟,确保服务器有足够时间处理
if (i < clonedImageUrls.length - 1) {
console.log(`等待500ms后上传下一张图片`);
await new Promise(resolve => setTimeout(resolve, 500));
}
}
console.log('\n===== 所有图片上传处理完成 =====');
console.log('总图片数量:', clonedImageUrls.length);
console.log('成功处理URL数量:', uploadedImageUrls.length);
console.log('最终URL列表:', uploadedImageUrls);
return uploadedImageUrls;
};
// 最终提交商品数据
// 【关键修复】只在这一步发送完整的商品数据,确保服务器只创建一个商品记录
const submitProduct = async (imageUrls) => {
console.log('\n===== 开始最终商品提交 =====');
console.log('会话ID:', sessionId);
console.log('提交图片URL数量:', imageUrls.length);
// 确保至少有一个URL
if (imageUrls.length === 0) {
console.error('错误:所有图片上传失败');
throw new Error('所有图片上传失败');
}
// 准备最终提交数据
// 【关键修复】在这一步发送完整的商品数据,包含所有必要的商品信息
const finalData = {
sessionId: sessionId,
uploadSessionId: sessionId,
productId: sessionId,
// 标记这是最终提交,服务器应该创建商品
isFinalUpload: 'true',
isFinalStep: 'true',
// 只在最终提交时发送完整的商品数据
productData: JSON.stringify({
...clonedProductData,
imageUrls: imageUrls,
allImageUrls: imageUrls,
isFinalUpload: true,
isFinalStep: true,
totalImages: imageUrls.length,
hasMultipleImages: imageUrls.length > 1
}),
uploadedImageUrls: JSON.stringify(imageUrls),
totalImagesUploaded: imageUrls.length.toString()
};
console.log('最终提交参数:', Object.keys(finalData));
console.log('发送完整商品数据,包含所有图片URLs');
// 发送最终请求
try {
const result = await request('/api/products/upload', 'POST', finalData);
console.log('最终提交成功:', result);
return {
...result,
// 确保返回包含所有上传的URL
imageUrls: imageUrls,
uploadedImageUrls: imageUrls
};
} catch (error) {
console.error('最终提交失败:', error);
// 即使最终提交失败,也返回已上传的URL信息
return {
success: false,
error: error.message,
imageUrls: imageUrls,
uploadedImageUrls: imageUrls
};
}
};
// 主流程
return uploadAllImages()
.then(submitProduct)
.catch(error => {
console.error('上传过程中发生错误:', error);
// 即使流程出错,也返回已上传的URL
return {
success: false,
error: error.message,
imageUrls: uploadedImageUrls,
uploadedImageUrls: uploadedImageUrls
};
});
},
// 获取商品列表 - 支持未登录用户查看公开商品
getProductList: function (status = 'published', options = {}) {
const openid = wx.getStorageSync('openid');
console.log('API.getProductList - openid:', openid ? '存在' : '不存在', 'status:', status);
// 不再因为没有openid而拒绝请求,允许未登录用户查看公开商品
// 确保分页参数存在,默认为page=1, pageSize=10
const page = options.page || 1;
const pageSize = options.pageSize || 10;
// 添加时间戳参数防止请求缓存
const requestData = {
status: status,
// 不设置默认的viewMode,让调用方根据需要设置
_t: options.timestamp || new Date().getTime(), // 添加时间戳参数防止缓存
// 始终包含分页参数
page: page,
pageSize: pageSize
};
// 无论openid是否存在,都添加到请求参数中,确保服务器接收到该参数
requestData.openid = openid || '';
// 如果options中包含viewMode,则添加到请求数据中
if (options.viewMode) {
requestData.viewMode = options.viewMode;
}
console.log('API.getProductList - 分页参数:', { page: page, pageSize: pageSize });
console.log('API.getProductList - 请求数据:', requestData);
return request('/api/product/list', 'POST', requestData).then(data => {
// 添加详细的日志记录,查看服务器返回的完整数据
console.log('===== 服务器返回的商品列表数据 =====');
console.log('完整响应数据:', data);
if (data && data.products && Array.isArray(data.products)) {
console.log('商品数量:', data.products.length);
// 增强处理:确保每个商品都包含正确的selected字段和有效的图片URL
const processedProducts = data.products.map(product => {
// 优先使用product.selected,其次使用其他可能的字段
// 这确保了即使服务器返回的数据格式不一致,前端也能正确显示预约人数
const selectedCount = product.selected !== undefined ? product.selected :
(product.reservedCount !== undefined ? product.reservedCount :
(product.reservationCount || 0));
// 记录特定商品的selected字段信息
if (String(product.id) === 'product_1760080711896_9gb6u2tig' ||
String(product.productId) === 'product_1760080711896_9gb6u2tig') {
console.log('===== 特定商品信息 =====');
console.log('原始商品ID:', product.id, 'productId:', product.productId);
console.log('原始selected字段值:', product.selected);
console.log('原始reservedCount字段值:', product.reservedCount);
console.log('原始reservationCount字段值:', product.reservationCount);
console.log('处理后的selectedCount值:', selectedCount);
}
// 返回处理后的商品数据,确保包含selected字段和原始图片URL
return {
...product,
selected: selectedCount // 确保selected字段存在
};
});
// 打印第一个商品的详细信息
if (processedProducts.length > 0) {
console.log('第一个商品的详细信息:');
console.log('- productId:', processedProducts[0].productId);
console.log('- productName:', processedProducts[0].productName);
console.log('- grossWeight:', processedProducts[0].grossWeight, '(类型:', typeof processedProducts[0].grossWeight, ')');
console.log('- selected:', processedProducts[0].selected, '(类型:', typeof processedProducts[0].selected, ')');
console.log('- 图片URL数量:', processedProducts[0].imageUrls ? (Array.isArray(processedProducts[0].imageUrls) ? processedProducts[0].imageUrls.length : 0) : 0);
console.log('- 所有可用字段:', Object.keys(processedProducts[0]));
}
// 返回处理后的数据
return {
...data,
products: processedProducts
};
}
return data;
});
},
// 获取当前用户创建的所有货源(包括草稿和已发布)- 支持分页
getAllSupplies: function (requestData = {}) {
const openid = wx.getStorageSync('openid');
console.log('API.getAllSupplies - openid:', openid, 'requestData:', requestData);
if (!openid && !requestData.testMode) {
return Promise.reject(new Error('用户未登录'));
}
// 设置默认参数
const defaultData = {
openid: openid,
viewMode: 'seller',
page: 1,
pageSize: 20
};
// 合并参数
const finalRequestData = { ...defaultData, ...requestData };
console.log('API.getAllSupplies - 最终请求参数:', finalRequestData);
return request('/api/product/list', 'POST', finalRequestData);
},
// 测试方法 - 用于调试
testAPI: function () {
console.log('测试API方法调用成功');
return Promise.resolve({ success: true, message: 'API正常工作' });
},
// 获取openid
getOpenid: function (code) {
console.log('API.getOpenid - code:', code);
return request('/api/wechat/getOpenid', 'POST', { code: code });
},
// 微信登录函数 - 增强版,支持手机号一键登录
login: function (encryptedData = null, iv = null) {
return new Promise((resolve, reject) => {
// 1. 调用微信登录接口获取code
wx.login({
success: loginRes => {
if (loginRes.code) {
console.log('微信登录成功,获取到code:', loginRes.code);
// 2. 使用code获取openid和session_key
this.getOpenid(loginRes.code).then(openidRes => {
console.log('获取openid成功,响应数据:', openidRes);
// 检查响应格式并提取数据 - 增强版支持多种格式
let openid = null;
let userId = null;
let sessionKey = null;
// 增强版格式处理:支持多种响应格式
if (typeof openidRes === 'object' && openidRes !== null) {
// 优先检查data字段(标准格式)
if (openidRes.data && typeof openidRes.data === 'object') {
openid = openidRes.data.openid || null;
userId = openidRes.data.userId || null;
sessionKey = openidRes.data.session_key || openidRes.data.sessionKey || null;
}
// 如果data中没有找到,直接从响应体中查找
if (!openid) {
openid = openidRes.openid || null;
userId = openidRes.userId || null;
sessionKey = openidRes.session_key || openidRes.sessionKey || null;
}
}
// 检查服务器返回的状态信息
const isSuccess = openidRes && (openidRes.success === true || openidRes.code === 200);
const serverMessage = openidRes && (openidRes.message || (openidRes.data && openidRes.data.message));
// 增强的错误处理:即使没有openid也提供更具体的错误信息
if (openid) {
// 存储openid、userId和sessionKey
wx.setStorageSync('openid', openid);
if (userId) {
wx.setStorageSync('userId', userId);
}
if (sessionKey) {
wx.setStorageSync('sessionKey', sessionKey);
}
console.log('登录成功,openid:', openid);
// 如果有手机号信息,上传到服务器
if (encryptedData && iv) {
console.log('上传手机号信息到服务器');
this.uploadPhoneNumberData({
openid: openid,
encryptedData: encryptedData,
iv: iv,
code: loginRes.code
}).then(phoneRes => {
console.log('手机号上传成功:', phoneRes);
// 更新userId(如果服务器返回了新的userId)
if (phoneRes.data && phoneRes.data.userId) {
wx.setStorageSync('userId', phoneRes.data.userId);
userId = phoneRes.data.userId;
}
resolve({
success: true,
data: { openid, userId, sessionKey, phoneRes }
});
}).catch(phoneErr => {
console.error('手机号上传失败:', phoneErr);
// 手机号上传失败不影响登录,仍然返回登录成功
resolve({
success: true,
data: { openid, userId, sessionKey, phoneError: phoneErr }
});
});
} else {
// 没有手机号信息,直接返回登录成功
resolve({
success: true,
data: { openid, userId, sessionKey }
});
}
} else {
console.error('登录失败,无法获取openid:', openidRes);
// 构建更具体的错误信息
let errorMsg = '登录失败,无法获取openid';
if (serverMessage) {
errorMsg += ' - ' + serverMessage;
} else if (isSuccess) {
errorMsg = '登录响应格式不匹配,服务器返回成功但缺少必要数据';
}
reject(new Error(errorMsg));
}
}).catch(err => {
console.error('获取openid失败:', err);
// 增强的错误处理,提供更多上下文
const errorMsg = err && err.message ? `获取openid失败: ${err.message}` : '获取openid失败,请稍后重试';
reject(new Error(errorMsg));
});
} else {
console.error('获取登录code失败:', loginRes);
reject(new Error('获取登录code失败: ' + loginRes.errMsg));
}
},
fail: err => {
console.error('wx.login失败:', err);
reject(new Error('微信登录失败: ' + err.errMsg));
}
});
});
},
// 上传手机号加密数据到服务器解密 - 增强版,支持401错误自动重试
uploadPhoneNumberData: function (phoneData) {
console.log('API.uploadPhoneNumberData - phoneData:', phoneData);
// 定义重试次数
const maxRetries = 1;
let retries = 0;
// 创建递归重试函数
const tryUpload = () => {
const openid = wx.getStorageSync('openid');
const sessionKey = wx.getStorageSync('sessionKey');
if (!openid) {
// 如果没有openid,先执行登录
return this.login().then(loginRes => {
// 重新尝试上传
return tryUpload();
});
}
// 检查是否包含openid,如果没有则添加
const data = {
...phoneData,
openid: phoneData.openid || openid,
sessionKey: phoneData.sessionKey || sessionKey || ''
};
return request('/api/user/decodePhone', 'POST', data).catch(error => {
console.error('上传手机号数据失败:', error);
// 检查是否是401错误且还有重试次数
if ((error.statusCode === 401 || (error.responseData && error.responseData.needRelogin)) && retries < maxRetries) {
console.log(`检测到登录过期,第${++retries}次重试登录...`);
// 清除过期的登录信息
try {
wx.removeStorageSync('openid');
wx.removeStorageSync('sessionKey');
} catch (e) {
console.error('清除过期登录信息失败:', e);
}
// 重新登录后重试
return this.login().then(() => {
console.log('重新登录成功,准备重试上传手机号数据');
return tryUpload();
});
}
// 其他错误直接抛出
throw error;
});
};
// 开始第一次尝试
return tryUpload();
},
// 上传用户信息到服务器
uploadUserInfo: function (userInfo) {
console.log('API.uploadUserInfo - userInfo:', userInfo);
const openid = wx.getStorageSync('openid');
if (!openid && !userInfo.openid) {
return Promise.reject(new Error('用户未登录'));
}
// 确保包含openid
const data = {
...userInfo,
openid: userInfo.openid || openid
};
return request('/api/user/upload', 'POST', data);
},
// 获取用户信息用于调试
getUserInfoForDebug: function () {
const openid = wx.getStorageSync('openid');
console.log('API.getUserInfoForDebug - openid:', openid);
if (!openid) {
return Promise.reject(new Error('用户未登录'));
}
return request('/api/user/debug', 'POST', {
openid: openid
});
},
// 获取用户信息
getUserInfo: function (openid) {
console.log('API.getUserInfo - openid:', openid);
// 如果没有提供openid,尝试从本地存储获取
const userOpenid = openid || wx.getStorageSync('openid');
if (!userOpenid) {
return Promise.reject(new Error('用户未登录'));
}
return request('/api/user/get', 'POST', {
openid: userOpenid
});
},
// 验证用户登录状态
validateUserLogin: function () {
const openid = wx.getStorageSync('openid');
console.log('API.validateUserLogin - openid:', openid);
if (!openid) {
return Promise.reject(new Error('用户未登录'));
}
return request('/api/user/validate', 'POST', {
openid: openid
});
},
// 撤回备案申请
withdrawSettlementApplication: function (openid) {
return request('/api/settlement/withdraw', 'POST', {
openid: openid
});
},
// 重新提交备案申请
resubmitSettlementApplication: function (applicationId) {
const openid = wx.getStorageSync('openid');
console.log('API.resubmitSettlementApplication - applicationId:', applicationId, 'openid:', openid);
if (!openid) {
return Promise.reject(new Error('用户未登录'));
}
return request(`/api/settlement/resubmit/${applicationId}`, 'POST', {
openid: openid
});
},
// 将商品状态设置为隐藏(软删除)
deleteProduct: function (productId) {
const openid = wx.getStorageSync('openid');
const sellerId = wx.getStorageSync('userId');
console.log('API.deleteProduct - openid:', openid, 'productId:', productId, 'sellerId:', sellerId);
if (!openid) {
return Promise.reject(new Error('用户未登录'));
}
return request('/api/products/delete', 'POST', {
openid: openid,
productId: productId,
sellerId: sellerId
});
},
// 将商品状态设置为下架
hideProduct: function (productId) {
const openid = wx.getStorageSync('openid');
console.log('API.hideProduct - openid:', openid, 'productId:', productId);
if (!openid) {
return Promise.reject(new Error('用户未登录'));
}
return request('/api/product/hide', 'POST', {
openid: openid,
productId: productId
});
},
// 添加BASE_URL属性,方便其他地方使用
BASE_URL: BASE_URL,
// 正确导出withdrawSettlementApplication方法
withdrawSettlementApplication: function (openid) {
return request('/api/settlement/withdraw', 'POST', {
openid: openid
});
},
// 编辑商品方法 - 修复版
editProduct: function (productId, productData) {
const openid = wx.getStorageSync('openid');
console.log('API.editProduct - openid:', openid, 'productId:', productId);
console.log('API.editProduct - 商品数据:', productData);
if (!openid) {
return Promise.reject(new Error('用户未登录'));
}
if (!productId) {
return Promise.reject(new Error('商品ID不能为空'));
}
return new Promise((resolve, reject) => {
// 【关键修复】确保包含现有的图片URL
if (!productData.imageUrls || productData.imageUrls.length === 0) {
// 从当前商品数据中获取现有图片URL
console.log('【前端修复】商品数据中没有图片URL,尝试从本地数据中获取');
// 在所有货源列表中查找当前商品
let currentProduct = null;
const allSupplies = [
...this.data.publishedSupplies,
...this.data.pendingSupplies,
...this.data.rejectedSupplies,
...this.data.draftSupplies
];
currentProduct = allSupplies.find(s =>
s.id === productId || s.serverProductId === productId
);
if (currentProduct && currentProduct.imageUrls) {
productData.imageUrls = currentProduct.imageUrls;
console.log('【前端修复】成功从本地数据获取图片URL,数量:', productData.imageUrls.length);
} else {
console.warn('【前端修复】无法从本地数据获取图片URL,使用空数组');
productData.imageUrls = [];
}
} else {
console.log('【前端修复】商品数据中已有图片URL,数量:', productData.imageUrls.length);
}
// 【修复】使用正确的图片预处理逻辑
this.processEditProductImages(productId, productData, openid)
.then(processedProductData => {
// 构建请求数据,确保包含所有必要字段并转换为正确类型
const requestData = {
openid: openid,
productId: productId,
product: {
productName: processedProductData.productName || '',
price: String(processedProductData.price !== undefined ? processedProductData.price || '' : ''), // 确保以字符串形式传递
quantity: parseInt(processedProductData.quantity) || 0,
// 添加其他可选字段
grossWeight: String(processedProductData.grossWeight !== undefined ? processedProductData.grossWeight || '' : ''), // 确保转换为字符串
yolk: processedProductData.yolk || '',
specification: processedProductData.specification || '',
region: productData.region || '', // 【重要】确保地区字段在product对象中
imageUrls: processedProductData.imageUrls || [], // 【重要】包含处理后的图片URL
status: processedProductData.status || ''
},
// 同时在顶层也传递status参数,确保服务器端能正确接收
status: processedProductData.status || ''
};
console.log('API.editProduct - 发送请求数据:', requestData);
console.log('API.editProduct - 请求URL:', BASE_URL + '/api/product/edit');
return request('/api/product/edit', 'POST', requestData);
})
.then(res => {
console.log('===== 编辑商品成功 =====');
console.log('响应数据:', res);
resolve(res);
})
.catch(error => {
console.error('===== 编辑商品失败 =====');
console.error('错误详情:', error);
console.error('错误消息:', error.errMsg || error.message || '未知错误');
reject(error);
});
});
},
// 辅助方法:根据商品ID查找商品
findProductById: function (productId) {
const allSupplies = [
...this.data.publishedSupplies,
...this.data.pendingSupplies,
...this.data.rejectedSupplies,
...this.data.draftSupplies
];
return allSupplies.find(s =>
s.id === productId || s.serverProductId === productId
);
},
// 【修复】编辑商品图片预处理方法 - 使用现有的上传方法
processEditProductImages: function (productId, productData, openid) {
return new Promise((resolve, reject) => {
console.log('【图片预处理】开始处理编辑商品图片...');
const imageUrls = productData.imageUrls || [];
console.log('【图片预处理】原始图片URL:', imageUrls);
// 识别临时图片
const tempImageUrls = imageUrls.filter(url =>
url && (url.startsWith('http://tmp/') || url.startsWith('wxfile://'))
);
if (tempImageUrls.length === 0) {
console.log('【图片预处理】没有临时图片需要处理');
resolve(productData);
return;
}
console.log(`【图片预处理】发现${tempImageUrls.length}张临时图片,开始上传...`);
// 【关键修复】上传临时图片时传递商品ID,确保使用相同的文件夹
const uploadData = {
productId: productId, // 传递商品ID,服务器端使用相同的文件夹
openid: openid,
isEdit: true // 标记为编辑操作
};
this.uploadProductImages(productId, tempImageUrls, uploadData)
.then(uploadResult => {
console.log('【图片预处理】临时图片上传成功:', uploadResult);
// 【关键修复】避免重复添加图片URL
// 只保留非临时图片URL(已存在的OSS图片)
const existingOssImageUrls = imageUrls.filter(url =>
url && !url.startsWith('http://tmp/') && !url.startsWith('wxfile://')
);
// 【修复】使用去重后的图片URL
// 从上传结果中获取所有图片URL(包括原有的和新上传的)
let allImageUrls = [];
if (uploadResult.allImageUrls && Array.isArray(uploadResult.allImageUrls)) {
allImageUrls = [...new Set(uploadResult.allImageUrls)]; // 去重
} else if (uploadResult.imageUrls && Array.isArray(uploadResult.imageUrls)) {
allImageUrls = [...new Set(uploadResult.imageUrls)]; // 去重
} else {
// 如果没有返回完整的图片列表,则合并现有和新上传的图片
let uploadedImageUrls = [];
if (uploadResult.results && Array.isArray(uploadResult.results)) {
uploadResult.results.forEach(result => {
if (result.imageUrls && Array.isArray(result.imageUrls)) {
uploadedImageUrls = [...uploadedImageUrls, ...result.imageUrls];
}
});
}
allImageUrls = [...new Set([...existingOssImageUrls, ...uploadedImageUrls])]; // 合并并去重
}
console.log('【图片预处理】最终图片URL(去重后):', allImageUrls);
// 返回处理后的商品数据
resolve({
...productData,
imageUrls: allImageUrls
});
})
.catch(error => {
console.error('【图片预处理】临时图片上传失败:', error);
reject(new Error('图片上传失败: ' + (error.message || '未知错误')));
});
});
},
// 【确保这个方法存在】上传商品图片 - 修复版,专门用于为已存在商品上传图片
uploadProductImages: function (productId, imageUrls) {
return new Promise((resolve, reject) => {
if (!productId) {
reject(new Error('商品ID不能为空'))
return
}
if (!imageUrls || imageUrls.length === 0) {
resolve({ success: true, message: '没有图片需要上传' })
return
}
console.log('开始为已存在商品上传图片,商品ID:', productId, '图片数量:', imageUrls.length)
// 获取openid
const openid = wx.getStorageSync('openid')
if (!openid) {
reject(new Error('用户未登录'))
return
}
// 【关键修复】使用专门的图片上传方法,而不是创建新商品
this.uploadImagesToExistingProduct(productId, imageUrls, openid)
.then(resolve)
.catch(reject)
})
},
// 【确保这个方法存在】上传商品图片 - 确保顺序执行
uploadImagesToExistingProduct: function (productId, imageUrls, openid) {
return new Promise((resolve, reject) => {
console.log('【图片上传】开始为已存在商品上传图片,商品ID:', productId);
// 【关键修复】顺序上传图片,避免并发问题
const uploadSequentially = async () => {
const results = [];
for (let i = 0; i < imageUrls.length; i++) {
try {
console.log(`顺序上传第${i + 1}/${imageUrls.length}张图片`);
const result = await new Promise((resolveUpload, rejectUpload) => {
const formData = {
productId: productId,
openid: openid,
action: 'add_images_only',
imageIndex: i,
totalImages: imageUrls.length,
isUpdate: 'true',
timestamp: Date.now()
};
// 【修复】使用模块内部的 BASE_URL 而不是 API.BASE_URL
wx.uploadFile({
url: BASE_URL + '/api/products/upload', // 直接使用 BASE_URL
filePath: imageUrls[i],
name: 'images',
formData: formData,
success: (res) => {
if (res.statusCode === 200) {
try {
const data = JSON.parse(res.data);
if (data.success) {
console.log(`${i + 1}张图片上传成功,当前总数:`, data.totalCount);
resolveUpload(data);
} else {
rejectUpload(new Error(data.message || '图片上传失败'));
}
} catch (parseError) {
rejectUpload(new Error('服务器响应格式错误'));
}
} else {
rejectUpload(new Error(`HTTP ${res.statusCode}`));
}
},
fail: (err) => {
rejectUpload(new Error('网络错误: ' + err.errMsg));
}
});
});
results.push(result);
// 添加延迟,避免服务器处理压力过大
if (i < imageUrls.length - 1) {
await new Promise(resolve => setTimeout(resolve, 500));
}
} catch (error) {
console.error(`${i + 1}张图片上传失败:`, error);
// 继续上传其他图片,不中断流程
results.push({ success: false, error: error.message });
}
}
return results;
};
uploadSequentially()
.then(results => {
// 取最后一个成功的结果作为最终状态
const successfulResults = results.filter(r => r && r.success);
if (successfulResults.length > 0) {
const lastResult = successfulResults[successfulResults.length - 1];
// 【修复】确保返回完整的图片URL列表
const allImageUrls = lastResult.allImageUrls || lastResult.imageUrls || [];
resolve({
success: true,
message: `成功上传${successfulResults.length}张图片`,
imageUrls: lastResult.imageUrls || [],
allImageUrls: allImageUrls, // 确保包含所有图片
uploadedCount: successfulResults.length,
totalCount: lastResult.totalCount || allImageUrls.length,
results: results
});
} else {
reject(new Error('所有图片上传失败'));
}
})
.catch(error => {
console.error('图片上传失败:', error);
reject(error);
});
});
},
// 更新商品联系人信息
updateProductContacts: function () {
return request('/api/products/update-contacts', 'POST');
},
/**
* 上传入驻申请文件
* @param {String} filePath - 本地文件路径
* @param {String} fileType - 文件类型(如:license, proof, brand)
* @returns {Promise<Object>} - 上传结果
*/
uploadSettlementFile: function (filePath, fileType) {
return new Promise((resolve, reject) => {
const openid = wx.getStorageSync('openid');
const userId = wx.getStorageSync('userId');
if (!openid) {
reject(new Error('用户未登录'));
return;
}
if (!filePath) {
reject(new Error('文件路径不能为空'));
return;
}
if (!fileType) {
reject(new Error('文件类型不能为空'));
return;
}
// 生成会话ID,确保文件上传的唯一性
const sessionId = `settlement_${Date.now()}_${Math.floor(Math.random() * 1000000)}`;
console.log('开始上传入驻文件:', filePath, '文件类型:', fileType);
console.log('上传会话ID:', sessionId);
// 使用wx.uploadFile直接上传文件,参考publish页面的上传实现
wx.uploadFile({
url: BASE_URL + '/api/settlement/upload',
filePath: filePath,
name: 'file',
formData: {
openid: openid,
userId: userId || '',
fileType: fileType,
sessionId: sessionId,
uploadSessionId: sessionId
},
timeout: 180000, // 3分钟超时
success: (res) => {
try {
console.log('入驻文件上传响应状态码:', res.statusCode);
console.log('原始响应数据:', res.data);
// 检查响应状态
if (res.statusCode >= 200 && res.statusCode < 300) {
const data = JSON.parse(res.data);
console.log('解析后的响应数据:', data);
if (data.success) {
console.log('入驻文件上传成功:', data.data?.fileUrl || '');
resolve(data.data || {});
} else {
console.error('入驻文件上传失败:', data.message);
reject(new Error(data.message || '文件上传失败'));
}
} else {
console.error('入驻文件上传失败,HTTP状态码:', res.statusCode);
reject(new Error(`HTTP错误: ${res.statusCode}`));
}
} catch (e) {
console.error('解析上传响应失败:', e);
reject(new Error('服务器响应格式错误'));
}
},
fail: (err) => {
console.error('入驻文件上传API调用失败:', err);
reject(new Error('网络错误: ' + err.errMsg));
}
});
});
},
};