891 lines
31 KiB
JavaScript
891 lines
31 KiB
JavaScript
// 移动端统一请求工具 - 与mobile工程保持一致
|
|
import {
|
|
generateAesKey,
|
|
encryptBase64,
|
|
decryptBase64,
|
|
encryptWithAes,
|
|
decryptWithAes,
|
|
encrypt,
|
|
decrypt,
|
|
} from './crypto.js';
|
|
import { ENCRYPT_CONFIG, REQUEST_CONFIG } from './config.js';
|
|
import { sessionCache, userStorage } from './storage.js';
|
|
|
|
/**
|
|
* 获取token
|
|
*/
|
|
function getToken() {
|
|
try {
|
|
const token = uni.getStorageSync('access_token');
|
|
console.log(
|
|
'🔍 获取Token:',
|
|
token ? `Bearer ${token.substring(0, 20)}...` : '❌ Token为空',
|
|
);
|
|
return token ? `Bearer ${token}` : '';
|
|
} catch (e) {
|
|
console.error('❌ 获取Token失败:', e);
|
|
return '';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 处理401未授权响应
|
|
*/
|
|
function handleUnauthorized(message = '登录已失效,请重新登录') {
|
|
console.log('🔒 处理401未授权响应:', message);
|
|
|
|
// 获取当前页面路径
|
|
const pages = getCurrentPages();
|
|
const currentPage = pages[pages.length - 1];
|
|
const currentPath = '/' + currentPage.route;
|
|
|
|
// 如果是新生报名页面,不跳转到登录页,只是静默处理
|
|
if (currentPath === '/pages/enrollment/index') {
|
|
console.log('📋 新生报名页面401错误,使用兜底数据,不跳转登录页');
|
|
return;
|
|
}
|
|
|
|
// 清除登录信息
|
|
try {
|
|
uni.removeStorageSync('access_token');
|
|
uni.removeStorageSync('userInfo');
|
|
uni.removeStorageSync('isLoggedIn');
|
|
console.log('📤 已清除本地登录信息');
|
|
} catch (e) {
|
|
console.error('❌ 清除登录信息失败:', e);
|
|
}
|
|
|
|
// 显示提示信息
|
|
uni.showToast({
|
|
title: message,
|
|
icon: 'none',
|
|
duration: 2000,
|
|
});
|
|
|
|
// 延迟跳转,确保Toast显示
|
|
setTimeout(() => {
|
|
// 如果当前不是登录页,则跳转到登录页
|
|
if (currentPath !== '/pages/login/index') {
|
|
console.log('🔄 从', currentPath, '跳转到登录页');
|
|
uni.reLaunch({
|
|
url: '/pages/login/index',
|
|
fail: (error) => {
|
|
console.error('❌ 跳转登录页失败:', error);
|
|
// 如果reLaunch失败,尝试其他方式
|
|
uni.redirectTo({
|
|
url: '/pages/login/index',
|
|
});
|
|
},
|
|
});
|
|
}
|
|
}, 500);
|
|
}
|
|
|
|
/**
|
|
* 统一请求方法
|
|
*/
|
|
export const request = (options) => {
|
|
return new Promise((resolve, reject) => {
|
|
// 获取token
|
|
const token = getToken();
|
|
|
|
// 处理GET请求的params参数
|
|
let url = REQUEST_CONFIG.baseURL + options.url;
|
|
if (options.params && Object.keys(options.params).length > 0) {
|
|
// 将params对象转换为查询字符串
|
|
const queryString = Object.keys(options.params)
|
|
.filter((key) => options.params[key] !== undefined && options.params[key] !== null)
|
|
.map(
|
|
(key) =>
|
|
`${encodeURIComponent(key)}=${encodeURIComponent(options.params[key])}`,
|
|
)
|
|
.join('&');
|
|
|
|
if (queryString) {
|
|
url += (url.indexOf('?') > -1 ? '&' : '?') + queryString;
|
|
}
|
|
|
|
console.log('🔍 处理GET请求params:', {
|
|
原始params: options.params,
|
|
查询字符串: queryString,
|
|
最终URL: url,
|
|
});
|
|
}
|
|
|
|
// 处理请求配置
|
|
const config = {
|
|
url: url,
|
|
method: options.method || 'GET',
|
|
data: options.data || {},
|
|
timeout: options.timeout || REQUEST_CONFIG.timeout,
|
|
responseType: options.responseType || 'text', // 支持responseType参数
|
|
header: {
|
|
// 吴自龙 注释 get方法,后台不接受json 20250811
|
|
// 'Content-Type': 'application/json',
|
|
authorization: token,
|
|
clientid: options.clientId || REQUEST_CONFIG.clientId,
|
|
// 注意:在H5环境中,浏览器会自动设置referer,手动设置会报错
|
|
// referer: options.referer || REQUEST_CONFIG.referer,
|
|
...options.header,
|
|
},
|
|
};
|
|
|
|
// 检查特殊header配置
|
|
const isToken = options.header?.isToken === false;
|
|
const isRepeatSubmit = options.header?.repeatSubmit === false;
|
|
const isEncrypt = options.header?.isEncrypt === 'true';
|
|
|
|
// 如果不需要token,移除authorization
|
|
if (isToken) {
|
|
delete config.header.authorization;
|
|
}
|
|
|
|
// 防重复提交检查
|
|
if (!isRepeatSubmit && (config.method === 'POST' || config.method === 'PUT')) {
|
|
const requestObj = {
|
|
url: config.url,
|
|
data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data,
|
|
time: new Date().getTime(),
|
|
};
|
|
|
|
const sessionObj = sessionCache.get('sessionObj');
|
|
if (sessionObj) {
|
|
const s_url = sessionObj.url;
|
|
const s_data = sessionObj.data;
|
|
const s_time = sessionObj.time;
|
|
const interval = REQUEST_CONFIG.repeatSubmitInterval;
|
|
|
|
if (
|
|
s_data === requestObj.data &&
|
|
requestObj.time - s_time < interval &&
|
|
s_url === requestObj.url
|
|
) {
|
|
const message = '数据正在处理,请勿重复提交';
|
|
console.warn(`[${s_url}]: ` + message);
|
|
uni.showToast({
|
|
title: message,
|
|
icon: 'none',
|
|
});
|
|
return reject(new Error(message));
|
|
}
|
|
}
|
|
sessionCache.set('sessionObj', requestObj);
|
|
}
|
|
|
|
// 加密处理
|
|
if (
|
|
REQUEST_CONFIG.encryptEnabled &&
|
|
isEncrypt &&
|
|
(config.method === 'POST' || config.method === 'PUT')
|
|
) {
|
|
try {
|
|
console.log('🔐 开始请求加密处理...');
|
|
|
|
// 生成AES密钥
|
|
const aesKey = generateAesKey();
|
|
|
|
// 使用RSA加密AES密钥
|
|
const encryptedKey = encrypt(encryptBase64(aesKey));
|
|
if (!encryptedKey) {
|
|
throw new Error('RSA加密AES密钥失败');
|
|
}
|
|
|
|
// 设置加密头
|
|
config.header[ENCRYPT_CONFIG.ENCRYPT_HEADER] = encryptedKey;
|
|
|
|
// 加密请求数据
|
|
const dataToEncrypt =
|
|
typeof config.data === 'object' ? JSON.stringify(config.data) : config.data;
|
|
config.data = encryptWithAes(dataToEncrypt, aesKey);
|
|
|
|
console.log('✅ 请求数据加密完成');
|
|
} catch (error) {
|
|
console.error('❌ 请求加密失败:', error);
|
|
uni.showToast({
|
|
title: '请求加密失败',
|
|
icon: 'none',
|
|
});
|
|
return reject(error);
|
|
}
|
|
}
|
|
|
|
// FormData处理
|
|
if (config.data instanceof globalThis.FormData) {
|
|
delete config.header['Content-Type'];
|
|
}
|
|
|
|
console.log('📤 发送请求:', {
|
|
url: config.url,
|
|
method: config.method,
|
|
hasToken: !!token,
|
|
isEncrypt: isEncrypt,
|
|
});
|
|
|
|
// 发送请求
|
|
uni.request({
|
|
...config,
|
|
success: (res) => {
|
|
console.log('📥 收到响应:', {
|
|
statusCode: res.statusCode,
|
|
url: config.url,
|
|
hasEncryptHeader: !!res.header[ENCRYPT_CONFIG.ENCRYPT_HEADER],
|
|
});
|
|
|
|
// 解密处理 - 只对JSON响应进行解密
|
|
if (
|
|
REQUEST_CONFIG.encryptEnabled &&
|
|
res.header[ENCRYPT_CONFIG.ENCRYPT_HEADER] &&
|
|
config.responseType === 'text'
|
|
) {
|
|
try {
|
|
console.log('🔓 开始响应解密处理...');
|
|
|
|
// 获取加密的AES密钥
|
|
const keyStr = res.header[ENCRYPT_CONFIG.ENCRYPT_HEADER];
|
|
|
|
// RSA解密获取AES密钥
|
|
const base64Str = decrypt(keyStr);
|
|
if (!base64Str) {
|
|
throw new Error('RSA解密AES密钥失败');
|
|
}
|
|
|
|
// Base64解码获取AES密钥
|
|
const aesKey = decryptBase64(base64Str);
|
|
|
|
// AES解密响应数据
|
|
const decryptedData = decryptWithAes(res.data, aesKey);
|
|
|
|
// 解析JSON
|
|
res.data = JSON.parse(decryptedData);
|
|
|
|
console.log('✅ 响应数据解密完成');
|
|
} catch (error) {
|
|
console.error('❌ 响应解密失败:', error);
|
|
uni.showToast({
|
|
title: '响应解密失败',
|
|
icon: 'none',
|
|
});
|
|
return reject(error);
|
|
}
|
|
}
|
|
|
|
// 处理响应
|
|
if (res.statusCode === 200) {
|
|
// 对于arraybuffer类型的响应,直接返回数据
|
|
if (config.responseType === 'arraybuffer') {
|
|
resolve(res.data);
|
|
return;
|
|
}
|
|
|
|
// 检查业务状态码
|
|
if (res.data && res.data.code !== undefined) {
|
|
if (res.data.code === 200) {
|
|
resolve(res.data);
|
|
} else if (res.data.code === 401) {
|
|
// 401未授权,跳转到登录页
|
|
console.log('🔒 Token失效,自动跳转到登录页');
|
|
handleUnauthorized(res.data.msg || '登录已失效,请重新登录');
|
|
reject(res.data);
|
|
} else {
|
|
// 其他业务错误
|
|
const errorMsg = res.data.msg || '请求失败';
|
|
if (res.data.code !== 404) {
|
|
// 404错误静默处理
|
|
uni.showToast({
|
|
title: errorMsg,
|
|
icon: 'none',
|
|
});
|
|
}
|
|
reject(res.data);
|
|
}
|
|
} else {
|
|
resolve(res.data);
|
|
}
|
|
} else if (res.statusCode === 401) {
|
|
// HTTP 401未授权
|
|
console.log('🔒 HTTP 401未授权,自动跳转到登录页');
|
|
handleUnauthorized('登录已失效,请重新登录');
|
|
reject(res);
|
|
} else if (res.statusCode === 404) {
|
|
// 404错误静默处理
|
|
console.log('404错误被静默处理,不显示用户提示');
|
|
reject(res);
|
|
} else {
|
|
// 其他HTTP状态码错误
|
|
const errorMsg = `请求失败: ${res.statusCode}`;
|
|
uni.showToast({
|
|
title: errorMsg,
|
|
icon: 'none',
|
|
});
|
|
reject(res);
|
|
}
|
|
},
|
|
fail: (error) => {
|
|
console.error('❌ 网络请求失败:', error);
|
|
let message = '网络请求失败';
|
|
|
|
if (error.errMsg) {
|
|
if (error.errMsg.includes('timeout')) {
|
|
message = '请求超时,请检查网络连接';
|
|
} else if (error.errMsg.includes('fail')) {
|
|
message = '网络连接失败,请检查网络';
|
|
}
|
|
}
|
|
|
|
uni.showToast({
|
|
title: message,
|
|
icon: 'none',
|
|
});
|
|
reject(error);
|
|
},
|
|
});
|
|
});
|
|
};
|
|
|
|
// API方法封装
|
|
export const api = {
|
|
// 用户登录 - 使用统一的request方法和加密拦截器
|
|
login: (data) => {
|
|
console.log('🔐 开始登录流程...');
|
|
|
|
return request({
|
|
url: '/auth/login',
|
|
method: 'POST',
|
|
data: {
|
|
username: data.username,
|
|
password: data.password,
|
|
tenantId: data.tenantId || '000000',
|
|
clientId: data.clientId || REQUEST_CONFIG.clientId,
|
|
grantType: data.grantType || 'password',
|
|
},
|
|
header: {
|
|
isToken: false, // 登录接口不需要token
|
|
isEncrypt: 'true', // 启用加密
|
|
},
|
|
});
|
|
},
|
|
|
|
// 获取用户信息
|
|
getUserInfo: () => {
|
|
const token = getToken();
|
|
console.log(
|
|
'🎫 获取用户信息,当前token:',
|
|
token ? token.substring(0, 20) + '...' : '无token',
|
|
);
|
|
|
|
return request({
|
|
url: '/system/user/getInfo',
|
|
method: 'GET',
|
|
});
|
|
},
|
|
|
|
// 获取租户列表
|
|
getTenantList: (isToken = false) =>
|
|
request({
|
|
url: '/auth/tenant/list',
|
|
method: 'GET',
|
|
header: {
|
|
isToken: isToken,
|
|
},
|
|
}),
|
|
|
|
// 注销登录
|
|
logout: () =>
|
|
request({
|
|
url: '/auth/logout',
|
|
method: 'POST',
|
|
}),
|
|
};
|
|
|
|
// 便捷请求方法封装
|
|
export const get = (url, data, header) => {
|
|
return request({
|
|
url,
|
|
method: 'GET',
|
|
data,
|
|
header,
|
|
});
|
|
};
|
|
|
|
export const post = (url, data, header) => {
|
|
return request({
|
|
url,
|
|
method: 'POST',
|
|
data,
|
|
header,
|
|
});
|
|
};
|
|
|
|
export const put = (url, data, header) => {
|
|
return request({
|
|
url,
|
|
method: 'PUT',
|
|
data,
|
|
header,
|
|
});
|
|
};
|
|
|
|
export const del = (url, data, header) => {
|
|
return request({
|
|
url,
|
|
method: 'DELETE',
|
|
data,
|
|
header,
|
|
});
|
|
};
|
|
|
|
// 文件上传方法
|
|
export const uploadFile = (url, filePath, formData) => {
|
|
return new Promise((resolve, reject) => {
|
|
const token = getToken();
|
|
const clientid = uni.getStorageSync('clientid') || REQUEST_CONFIG.clientId;
|
|
|
|
// 构建完整URL - 使用统一配置的服务器地址
|
|
let fullUrl;
|
|
if (REQUEST_CONFIG.baseURL.startsWith('/')) {
|
|
// 开发环境,baseURL是'/api',需要使用完整的服务器URL
|
|
fullUrl = `${REQUEST_CONFIG.serverUrl}${url}`;
|
|
} else {
|
|
// 生产环境,直接拼接
|
|
fullUrl = REQUEST_CONFIG.baseURL + url;
|
|
}
|
|
|
|
console.log('🚀 上传文件URL:', fullUrl);
|
|
console.log('🗂️ 文件路径:', filePath);
|
|
console.log('📊 表单数据:', formData);
|
|
|
|
const headers = {
|
|
// 移除Content-Type设置,让uni.uploadFile自动处理
|
|
authorization: token,
|
|
clientid: clientid,
|
|
};
|
|
|
|
uni.uploadFile({
|
|
url: fullUrl,
|
|
filePath,
|
|
name: 'file',
|
|
formData,
|
|
header: headers,
|
|
success: (res) => {
|
|
console.log('✅ 上传成功响应:', res);
|
|
try {
|
|
const data = JSON.parse(res.data);
|
|
resolve(data);
|
|
} catch (error) {
|
|
console.error('❌ 解析响应数据失败:', error);
|
|
reject(new Error('解析响应数据失败'));
|
|
}
|
|
},
|
|
fail: (err) => {
|
|
console.error('❌ 上传失败:', err);
|
|
reject(new Error(err.errMsg || '上传失败'));
|
|
},
|
|
});
|
|
});
|
|
};
|
|
|
|
// 文件下载方法(支持多平台)
|
|
export const downloadFile = (url, fileName = null) => {
|
|
return new Promise((resolve, reject) => {
|
|
const token = getToken();
|
|
const clientid = uni.getStorageSync('clientid') || REQUEST_CONFIG.clientId;
|
|
console.log('REQUEST_CONFIG.baseURL:', REQUEST_CONFIG.baseURL);
|
|
|
|
// 构建完整URL - 使用统一配置的服务器地址
|
|
let fullUrl;
|
|
if (REQUEST_CONFIG.baseURL.startsWith('/')) {
|
|
// 开发环境,baseURL是'/api',需要使用完整的服务器URL
|
|
fullUrl = `${REQUEST_CONFIG.serverUrl}${url}`;
|
|
} else {
|
|
// 生产环境,直接拼接
|
|
fullUrl = REQUEST_CONFIG.baseURL + url;
|
|
}
|
|
|
|
console.log('📥 下载文件URL:', fullUrl);
|
|
|
|
const headers = {
|
|
authorization: token,
|
|
clientid: clientid,
|
|
};
|
|
|
|
// #ifdef H5
|
|
// H5端使用fetch + blob的方式实现浏览器下载
|
|
console.log('🌐 H5端下载处理');
|
|
|
|
fetch(fullUrl, {
|
|
method: 'GET',
|
|
headers: headers,
|
|
})
|
|
.then((response) => {
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
return response.blob();
|
|
})
|
|
.then((blob) => {
|
|
// 创建下载链接
|
|
const downloadUrl = window.URL.createObjectURL(blob);
|
|
const link = document.createElement('a');
|
|
link.href = downloadUrl;
|
|
|
|
// 尝试从响应头获取文件名,或使用传入的文件名
|
|
let downloadFileName = fileName;
|
|
if (!downloadFileName) {
|
|
// 从URL中提取文件名
|
|
const urlParts = url.split('/');
|
|
downloadFileName = urlParts[urlParts.length - 1] || 'download';
|
|
}
|
|
link.download = downloadFileName;
|
|
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
document.body.removeChild(link);
|
|
|
|
// 清理blob URL
|
|
window.URL.revokeObjectURL(downloadUrl);
|
|
|
|
console.log('✅ H5下载触发成功');
|
|
resolve({ statusCode: 200, message: 'H5下载已触发' });
|
|
})
|
|
.catch((error) => {
|
|
console.error('❌ H5下载失败:', error);
|
|
reject(new Error(error.message || 'H5下载失败'));
|
|
});
|
|
// #endif
|
|
|
|
// #ifndef H5
|
|
// 非H5端使用uni.downloadFile
|
|
uni.downloadFile({
|
|
url: fullUrl,
|
|
header: headers,
|
|
success: (res) => {
|
|
console.log('✅ 下载成功:', res);
|
|
resolve(res);
|
|
},
|
|
fail: (err) => {
|
|
console.error('❌ 下载失败:', err);
|
|
reject(new Error(err.errMsg || '下载失败'));
|
|
},
|
|
});
|
|
// #endif
|
|
});
|
|
};
|
|
|
|
// 通用下载方法
|
|
export const download = (url, params, fileName) => {
|
|
// 显示加载提示
|
|
uni.showLoading({
|
|
title: '正在下载数据,请稍候',
|
|
mask: true,
|
|
});
|
|
|
|
return request({
|
|
url: url,
|
|
method: 'POST',
|
|
data: params,
|
|
header: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
},
|
|
timeout: 1200000, // 20分钟超时
|
|
responseType: 'arraybuffer',
|
|
})
|
|
.then((resp) => {
|
|
// 处理下载响应
|
|
console.log('文件下载完成:', fileName);
|
|
uni.hideLoading();
|
|
|
|
// 这里需要根据平台进行不同的文件保存处理
|
|
// 具体实现可能需要平台特定的代码
|
|
return resp;
|
|
})
|
|
.catch((error) => {
|
|
console.error('下载失败:', error);
|
|
uni.hideLoading();
|
|
uni.showToast({
|
|
title: '下载文件出现错误,请联系管理员!',
|
|
icon: 'none',
|
|
});
|
|
throw error;
|
|
});
|
|
};
|
|
|
|
// 微信环境专用文件上传方法(解决微信浏览器兼容性问题)
|
|
export const uploadFileForWeChat = (url, file, formData, options = {}) => {
|
|
return new Promise((resolve, reject) => {
|
|
const token = getToken();
|
|
const clientid = uni.getStorageSync('clientid') || REQUEST_CONFIG.clientId;
|
|
|
|
console.log('🔧 微信环境专用文件上传...');
|
|
console.log('📁 文件对象:', file);
|
|
console.log('📊 表单数据:', formData);
|
|
|
|
// 构建完整URL
|
|
let fullUrl;
|
|
if (REQUEST_CONFIG.baseURL.startsWith('/')) {
|
|
fullUrl = `${REQUEST_CONFIG.serverUrl}${url}`;
|
|
} else {
|
|
fullUrl = REQUEST_CONFIG.baseURL + url;
|
|
}
|
|
|
|
console.log('🚀 上传URL:', fullUrl);
|
|
|
|
// 检测是否为blob文件
|
|
if (file._blob || (file.url && file.url.startsWith('blob:'))) {
|
|
console.log('🎨 检测到Blob文件,使用FormData+fetch方案');
|
|
return uploadBlobFileWithFetch(fullUrl, file, formData, token, clientid)
|
|
.then(resolve)
|
|
.catch(reject);
|
|
}
|
|
|
|
// 检测文件路径格式并尝试不同的上传策略
|
|
const filePath = file.url || file.tempFilePath || file.path;
|
|
|
|
if (!filePath) {
|
|
const error = new Error('文件路径为空,无法上传');
|
|
console.error('❌', error.message);
|
|
return reject(error);
|
|
}
|
|
|
|
console.log('📂 使用文件路径上传:', filePath);
|
|
|
|
// 策略1: 先尝试标准的uni.uploadFile
|
|
console.log('🔄 策略1: 尝试标准uni.uploadFile');
|
|
|
|
uni.uploadFile({
|
|
url: fullUrl,
|
|
filePath: filePath,
|
|
name: 'file',
|
|
formData,
|
|
header: {
|
|
authorization: token,
|
|
clientid: clientid,
|
|
},
|
|
success: (res) => {
|
|
console.log('✅ 标准uni.uploadFile成功:', res);
|
|
try {
|
|
const data = JSON.parse(res.data);
|
|
resolve(data);
|
|
} catch (error) {
|
|
console.error('❌ 解析响应数据失败:', error);
|
|
reject(new Error('解析响应数据失败'));
|
|
}
|
|
},
|
|
fail: (err) => {
|
|
console.log('⚠️ 标准uni.uploadFile失败,尝试备用方案:', err);
|
|
|
|
// 策略2: 尝试使用FileReader转换后上传
|
|
if (typeof FileReader !== 'undefined') {
|
|
console.log('🔄 策略2: 尝试FileReader+fetch方案');
|
|
|
|
// 通过input[type=file]重新选择文件进行上传
|
|
createFileInputForUpload(fullUrl, formData, token, clientid, file)
|
|
.then(resolve)
|
|
.catch((fetchError) => {
|
|
console.error('❌ FileReader+fetch方案也失败:', fetchError);
|
|
reject(
|
|
new Error(
|
|
`所有上传方案失败: ${err.errMsg || err.message} / ${
|
|
fetchError.message
|
|
}`,
|
|
),
|
|
);
|
|
});
|
|
} else {
|
|
// 策略3: 提示用户使用系统相册
|
|
console.log('🔄 策略3: 引导用户使用系统相册');
|
|
reject(new Error('当前环境不支持此文件格式,请使用系统相册选择图片'));
|
|
}
|
|
},
|
|
});
|
|
});
|
|
};
|
|
|
|
// 使用FormData+fetch上传Blob文件(专门处理Canvas生成的图片)
|
|
function uploadBlobFileWithFetch(url, file, formData, token, clientid) {
|
|
return new Promise((resolve, reject) => {
|
|
try {
|
|
console.log('🎨 开始Blob文件上传流程...');
|
|
|
|
const formDataObj = new FormData();
|
|
|
|
// 添加基本表单数据
|
|
Object.keys(formData || {}).forEach((key) => {
|
|
formDataObj.append(key, formData[key]);
|
|
});
|
|
|
|
// 处理Blob文件
|
|
let blob = file._blob;
|
|
let fileName = file.name || 'upload-image.png';
|
|
let mimeType = file.type || 'image/png';
|
|
|
|
if (!blob && file.url && file.url.startsWith('blob:')) {
|
|
// 如果没有直接的blob引用,尝试从blob URL获取
|
|
console.log('🔗 从blob URL获取文件数据...');
|
|
|
|
fetch(file.url)
|
|
.then((response) => response.blob())
|
|
.then((fetchedBlob) => {
|
|
formDataObj.append('file', fetchedBlob, fileName);
|
|
return performFetchUpload(url, formDataObj, token, clientid);
|
|
})
|
|
.then(resolve)
|
|
.catch(reject);
|
|
return;
|
|
}
|
|
|
|
if (!blob) {
|
|
throw new Error('无法获取Blob数据');
|
|
}
|
|
|
|
// 直接添加blob到formData
|
|
console.log('📎 添加Blob到FormData:', {
|
|
fileName,
|
|
mimeType,
|
|
size: blob.size,
|
|
});
|
|
|
|
formDataObj.append('file', blob, fileName);
|
|
|
|
// 执行fetch上传
|
|
performFetchUpload(url, formDataObj, token, clientid).then(resolve).catch(reject);
|
|
} catch (error) {
|
|
console.error('❌ Blob文件上传准备失败:', error);
|
|
reject(error);
|
|
}
|
|
});
|
|
}
|
|
|
|
// 执行fetch上传请求
|
|
function performFetchUpload(url, formData, token, clientid) {
|
|
return fetch(url, {
|
|
method: 'POST',
|
|
headers: {
|
|
authorization: token,
|
|
clientid: clientid,
|
|
// 注意:不要设置Content-Type,让浏览器自动设置multipart/form-data边界
|
|
},
|
|
body: formData,
|
|
})
|
|
.then((response) => {
|
|
console.log('📡 Fetch上传响应状态:', response.status);
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
}
|
|
|
|
return response.text();
|
|
})
|
|
.then((responseText) => {
|
|
console.log('📥 Fetch上传响应数据:', responseText);
|
|
|
|
try {
|
|
const data = JSON.parse(responseText);
|
|
return data;
|
|
} catch (parseError) {
|
|
console.error('❌ 解析响应JSON失败:', parseError);
|
|
throw new Error('服务器返回数据格式错误');
|
|
}
|
|
});
|
|
}
|
|
|
|
// 创建隐藏的file input来重新选择文件(微信环境后备方案)
|
|
function createFileInputForUpload(url, formData, token, clientid, originalFile) {
|
|
return new Promise((resolve, reject) => {
|
|
try {
|
|
console.log('📝 创建隐藏文件选择器...');
|
|
|
|
// 在微信环境中提示用户重新选择文件
|
|
uni.showModal({
|
|
title: '文件上传',
|
|
content: '检测到上传兼容性问题,需要重新选择文件。点击确定后请重新选择图片。',
|
|
showCancel: true,
|
|
success: (modalRes) => {
|
|
if (modalRes.confirm) {
|
|
// 重新选择文件
|
|
uni.chooseImage({
|
|
count: 1,
|
|
sourceType: ['album', 'camera'],
|
|
success: (chooseRes) => {
|
|
console.log('🔄 重新选择文件成功:', chooseRes);
|
|
|
|
if (chooseRes.tempFilePaths && chooseRes.tempFilePaths.length > 0) {
|
|
const newFilePath = chooseRes.tempFilePaths[0];
|
|
|
|
// 使用新选择的文件进行上传
|
|
uni.uploadFile({
|
|
url: url,
|
|
filePath: newFilePath,
|
|
name: 'file',
|
|
formData,
|
|
header: {
|
|
authorization: token,
|
|
clientid: clientid,
|
|
},
|
|
success: (res) => {
|
|
console.log('✅ 重新上传成功:', res);
|
|
try {
|
|
const data = JSON.parse(res.data);
|
|
resolve(data);
|
|
} catch (error) {
|
|
reject(new Error('解析响应数据失败'));
|
|
}
|
|
},
|
|
fail: (err) => {
|
|
console.error('❌ 重新上传失败:', err);
|
|
reject(new Error(err.errMsg || '重新上传失败'));
|
|
},
|
|
});
|
|
} else {
|
|
reject(new Error('未选择任何文件'));
|
|
}
|
|
},
|
|
fail: (err) => {
|
|
console.error('❌ 重新选择文件失败:', err);
|
|
reject(new Error('重新选择文件失败'));
|
|
},
|
|
});
|
|
} else {
|
|
reject(new Error('用户取消了重新选择文件'));
|
|
}
|
|
},
|
|
});
|
|
} catch (error) {
|
|
console.error('❌ 创建文件选择器失败:', error);
|
|
reject(error);
|
|
}
|
|
});
|
|
}
|
|
|
|
// 检测是否在微信环境
|
|
export const isWeChat = () => {
|
|
if (typeof navigator === 'undefined') return false;
|
|
return /MicroMessenger/i.test(navigator.userAgent || '');
|
|
};
|
|
|
|
// 智能文件上传方法(根据环境自动选择最佳方案)
|
|
export const smartUploadFile = (url, fileOrPath, formData, options = {}) => {
|
|
console.log('🧠 智能文件上传开始...');
|
|
console.log('🌐 当前环境:', {
|
|
isWeChat: isWeChat(),
|
|
userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : 'unknown',
|
|
});
|
|
|
|
// 如果在微信环境或遇到特殊文件格式,使用增强版上传
|
|
if (
|
|
isWeChat() ||
|
|
(typeof fileOrPath === 'object' &&
|
|
(fileOrPath._blob || fileOrPath.url?.startsWith('blob:')))
|
|
) {
|
|
console.log('📱 使用微信环境专用上传方法');
|
|
return uploadFileForWeChat(url, fileOrPath, formData, options);
|
|
}
|
|
|
|
// 否则使用标准上传方法
|
|
console.log('💻 使用标准上传方法');
|
|
const filePath =
|
|
typeof fileOrPath === 'string'
|
|
? fileOrPath
|
|
: fileOrPath.url || fileOrPath.tempFilePath || fileOrPath.path;
|
|
return uploadFile(url, filePath, formData);
|
|
};
|