云
2 天以前 c3d88ddd346f53aef4beced518e3f6f0bb815fa4
src/views/mes/pro/qrexport/download-qr-export.ts
@@ -23,6 +23,29 @@
  }
}
/**
 * 后端业务异常统一以 HTTP 200 + JSON 返回;下载接口的文件过期、任务状态异常等都属于此类。
 * 这里把真实原因提取出来,避免被误报成「分段下载失败(HTTP 200)」。
 */
async function readBusinessError(response: Response): Promise<null | string> {
  const contentType = response.headers.get('content-type') ?? '';
  if (!contentType.includes('json')) return null;
  try {
    const body = await response.clone().json();
    if (body && typeof body.msg === 'string' && body.msg) return body.msg;
  } catch {
    // 非标准 JSON 交给调用方按通用错误处理
  }
  return null;
}
/** 业务错误重试无意义(如文件已过期),直接终止。 */
function nonRetryable(message: string): Error {
  const error = new Error(message) as Error & { retryable?: boolean };
  error.retryable = false;
  return error;
}
async function requestChunk(
  url: string,
  token: string | undefined,
@@ -41,6 +64,14 @@
        signal,
      });
      if (response.status !== 206) {
        const businessError = await readBusinessError(response);
        if (businessError) throw nonRetryable(businessError);
        const contentType = response.headers.get('content-type') ?? '';
        if (response.status === 200
          && (contentType.includes('zip') || contentType.includes('octet-stream'))) {
          // 服务端忽略了 Range,直接回整包;继续分段只会反复下载同一份文件
          throw nonRetryable('服务器未启用分段下载,请升级后端后重试');
        }
        throw new Error(`分段下载失败(HTTP ${response.status})`);
      }
      if (response.headers.get('content-range') !== `bytes ${start}-${end}/${total}`) {
@@ -52,7 +83,8 @@
      }
      return bytes;
    } catch (error) {
      if (signal.aborted || attempt === 2) throw error;
      const retryable = (error as { retryable?: boolean }).retryable !== false;
      if (signal.aborted || !retryable || attempt === 2) throw error;
      await new Promise((resolve) => setTimeout(resolve, 750 * (attempt + 1)));
    }
  }
@@ -74,6 +106,8 @@
  const match = /^bytes 0-0\/(\d+)$/.exec(response.headers.get('content-range') ?? '');
  const total = Number(match?.[1]);
  if (response.status !== 206 || !Number.isSafeInteger(total) || total <= 0) {
    const businessError = await readBusinessError(response);
    if (businessError) throw nonRetryable(businessError);
    throw new Error('无法获取导出文件大小,请刷新任务后重试');
  }
  await response.arrayBuffer();