upload.ts 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. import { getToken } from '@/utils/auth';
  2. import errorCode from '@/utils/errorCode';
  3. import { tansParams } from '@/utils/common';
  4. import config from '@/config';
  5. import { getCurrentPage } from '@/utils/public';
  6. import { useInfoStore } from '@/store';
  7. interface UploadConfig {
  8. url: string;
  9. filePath: string;
  10. name?: string;
  11. header?: Record<string, any>;
  12. headers?: Record<string, any>;
  13. formData?: Record<string, any>;
  14. params?: Record<string, any>;
  15. timeout?: number;
  16. }
  17. interface UploadResult {
  18. code: number;
  19. data?: any;
  20. msg?: string;
  21. }
  22. const timeout = 10000;
  23. const { baseUrl, clientId, appid } = config;
  24. const upload = (uploadConfig: UploadConfig): Promise<UploadResult> => {
  25. // 是否需要设置 token
  26. const isToken = (uploadConfig.headers || {}).isToken === false;
  27. uploadConfig.header = uploadConfig.header || {};
  28. if (getToken() && !isToken) {
  29. uploadConfig.header['Authorization'] = 'Bearer ' + getToken();
  30. }
  31. uploadConfig.header['Clientid'] = clientId; // 默认值
  32. uploadConfig.header['xid'] = appid; // 默认值
  33. // get请求映射params参数
  34. if (uploadConfig.params) {
  35. let url = uploadConfig.url + '?' + tansParams(uploadConfig.params);
  36. url = url.slice(0, -1);
  37. uploadConfig.url = url;
  38. }
  39. return new Promise((resolve, reject) => {
  40. uni.uploadFile({
  41. timeout: uploadConfig.timeout || timeout,
  42. url: baseUrl + uploadConfig.url,
  43. filePath: uploadConfig.filePath,
  44. name: uploadConfig.name || 'file',
  45. header: uploadConfig.header,
  46. formData: uploadConfig.formData,
  47. success: (res) => {
  48. const result: UploadResult = JSON.parse(res.data);
  49. const code = result.code || 200;
  50. const msg = errorCode[code.toString()] || result.msg || errorCode['default'];
  51. if (code === 200) {
  52. resolve(result);
  53. } else if (code === 401) {
  54. uni.hideLoading();
  55. // 跳转到登录页面
  56. uni.$u.route({
  57. type: 'reLaunch',
  58. url: '/pages/login/login',
  59. params: {
  60. redirect: getCurrentPage()?.route || '',
  61. },
  62. });
  63. reject('无效的会话,或者会话已过期,请重新登录。');
  64. } else if (code === 500) {
  65. uni.showToast({
  66. title: msg,
  67. icon: 'none',
  68. });
  69. reject('500');
  70. } else if (code !== 200) {
  71. uni.showToast({
  72. title: msg,
  73. icon: 'none',
  74. });
  75. reject(code);
  76. }
  77. },
  78. fail: (error) => {
  79. console.log(error);
  80. let message = '上传失败';
  81. if (error.errMsg) {
  82. if (error.errMsg.includes('timeout')) {
  83. message = '系统接口请求超时';
  84. } else if (error.errMsg.includes('fail')) {
  85. message = '网络连接异常';
  86. }
  87. }
  88. uni.showToast({
  89. title: message,
  90. icon: 'none',
  91. });
  92. reject(error);
  93. },
  94. });
  95. });
  96. };
  97. /**
  98. * 获取文件扩展名
  99. * @param {string} url - 文件 URL
  100. * @param {object} [result] - 响应结果对象
  101. * @param {object} [result.header] - 响应头
  102. * @returns {string|null} 文件扩展名(不带点),找不到则返回 null
  103. */
  104. const getFileType = (url: string, result?: { header?: Record<string, any> }): string | null => {
  105. // 1. 首先尝试从 URL 获取扩展名
  106. if (url) {
  107. // 获取 URL 中最后一个斜杠后的文件名
  108. const filenameFromUrl = url.split('/').pop();
  109. // 提取扩展名(处理可能存在的查询参数)
  110. const urlExt = filenameFromUrl?.includes('.') ? filenameFromUrl.split('.').pop()?.split(/[?#]/)[0] : null;
  111. if (urlExt) return urlExt.toLowerCase() || 'pdf';
  112. }
  113. // 2. 如果 URL 中没有扩展名,尝试从 Content-Disposition 头获取
  114. if (result?.header?.['content-disposition']) {
  115. try {
  116. const disposition = result.header['content-disposition'];
  117. // 匹配 filename="file.ext" 或 filename*=UTF-8''file.ext 格式
  118. const filenameMatch = disposition.match(/filename\*?=(?:UTF-8''|")?([^;"\n]*)/i);
  119. if (filenameMatch && filenameMatch[1]) {
  120. const filename = filenameMatch[1].replace(/['"]/g, '');
  121. if (filename.includes('.')) {
  122. const ext = filename.split('.').pop()?.split(/[?#]/)[0];
  123. return ext?.toLowerCase() || 'pdf';
  124. }
  125. }
  126. } catch (e) {
  127. console.warn('解析 Content-Disposition 失败:', e);
  128. }
  129. }
  130. // 3. 都找不到则返回 pdf 作为默认值
  131. return 'pdf';
  132. };
  133. // 导出数据函数(按照 handleDownload 规范完善)
  134. export const exportDataFn = (config: { url: string; fileName?: string; params?: Record<string, any>; timeout?: number }, showTitle?: string): Promise<void> =>
  135. new Promise((resolve, reject) => {
  136. // 判断是否为完整 URL(以 http://或 https://开头)
  137. const isFullUrl = config.url.startsWith('http://') || config.url.startsWith('https://');
  138. // 获取基础 URL 并拼接完整路径
  139. const baseUrl = isFullUrl ? '' : import.meta.env.VITE_API_BASE_URL || config.url.split('/').slice(0, 3).join('/');
  140. const downloadUrl = baseUrl + config.url;
  141. // 获取 token
  142. const token = useInfoStore().token;
  143. // 设置请求头
  144. const header = {
  145. 'Content-Type': 'application/json',
  146. Authorization: `Bearer ${token}`,
  147. xid: appid,
  148. clientId: clientId,
  149. };
  150. uni.showLoading({
  151. title: showTitle || '正在导出数据...',
  152. mask: true,
  153. });
  154. uni.downloadFile({
  155. url: downloadUrl,
  156. header: header,
  157. success: (res) => {
  158. uni.hideLoading();
  159. if (res.statusCode === 200) {
  160. // 直接打开文件
  161. uni.openDocument({
  162. filePath: res.tempFilePath,
  163. showMenu: true,
  164. fileType: getFileType(config.fileName || config.url, res as any) as any,
  165. success: () => {
  166. resolve();
  167. },
  168. fail: (err) => {
  169. console.log('打开文档失败:', err);
  170. uni.showToast({
  171. title: '打开文件失败',
  172. icon: 'none',
  173. });
  174. reject(err);
  175. },
  176. });
  177. } else {
  178. uni.showToast({
  179. title: '下载失败',
  180. icon: 'none',
  181. });
  182. reject(res);
  183. }
  184. },
  185. fail: () => {
  186. uni.hideLoading();
  187. uni.showToast({
  188. title: '下载失败',
  189. icon: 'none',
  190. });
  191. reject();
  192. },
  193. });
  194. });
  195. export default upload;