const getStreamMessage async (currentPicture, fileId) { isPageLoading.value true allChoose.value true isFinish.value false uni.showLoading({ title: 加载中..., mask: true }); // 兼容处理结束 isLoading.value true; // 请求获取数据 const data { fileId: fileId, filePath: currentPicture } try { await homeworkCheckApi(data, (json) { activeTabsShow.value 0 updateRealTimeDisplay(json) }) } catch (error) { isFinish.value true; isLoading.value false; uni.showToast({ title: 网络错误请稍后再试, icon: none }) console.log(error) } } const updateRealTimeDisplay (data) { allChoose.value false isFinish.value true if (data.result) { topicList.value.push(data.result) paperAnswer.value topicList.value } else if ( data.errorResult) { errorList.value data.errorResult } else if (data.token_type stop) { isComplete.value true } } export const homeworkCheckApi (data : any, onChunk) { return new Promise(async (resolve, reject) { try { // 1. 先将图片转换为base64 const base64Data await filePathToBase64(data.filePath); console.log(filePath, data.filePath) // #ifdef H5 await h5StreamRequest(data, base64Data, onChunk, resolve, reject); // #endif // #ifdef APP-PLUS await appStreamRequest(data, base64Data, onChunk, resolve, reject); // #endif // #ifdef MP-WEIXIN await miniprogramStreamRequest(data, base64Data, onChunk, resolve, reject); // #endif } catch (error) { reject(error); } finally { uni.hideLoading(); } }); }; const appStreamRequest async ( data: any, base64Data: any, onChunk: (json: any) void, resolve: () void, reject: (error: any) void ): Promisevoid { return new Promise((innerResolve, innerReject) { const requestData { ...data, file: base64Data, }; const xhr plus.net ? new plus.net.XMLHttpRequest() : new XMLHttpRequest(); xhr.open(POST, ${config.baseURL}/education/model/checkHomeWork); xhr.setRequestHeader(Content-Type, application/json); xhr.setRequestHeader(Authorization, Bearer ${uni.getStorageSync(token)}); xhr.setRequestHeader(Accept, text/event-stream); let receivedLength 0; let buffer ; let fullResponse ; xhr.onreadystatechange function() { console.log(XHR状态: ${xhr.readyState}, 状态码: ${xhr.status}); if (xhr.readyState 3) { // 正在接收数据 if (xhr.responseText xhr.responseText.length fullResponse.length) { const newData xhr.responseText.substring(fullResponse.length); fullResponse xhr.responseText; console.log(收到新数据:, newData.length, 字符); if (newData) { buffer newData; buffer processBuffer(buffer, onChunk); } } } if (xhr.readyState 4) { if (xhr.status 200) { console.log(请求完成状态码:, xhr.status); // 处理最后剩余的数据 if (buffer.trim()) { processBuffer(buffer, onChunk); } resolve(); // 调用外层resolve innerResolve(); // 调用内层resolve } else { const error new Error(Request failed with status ${xhr.status}); reject(error); innerReject(error); } } }; // 使用 onprogress 事件处理流式数据 // xhr.onprogress function(event) { // // 获取新增的数据 // const newData xhr.responseText.substring(receivedLength); // receivedLength xhr.responseText.length; // console.log(收到新数据块:, newData.length, 字符); // if (newData) { // buffer newData; // // 处理缓冲区中的数据 // buffer processBuffer(buffer, onChunk); // } // }; xhr.onerror function() { const error new Error(Network error); reject(error); innerReject(error); }; console.log(开始发送APP流式请求...); xhr.send(JSON.stringify(requestData)); }); }; // 处理缓冲区的通用函数 const processBuffer (buffer: string, onChunk: (json: any) void): string { const lines buffer.split(\n); const remainingLines []; for (const line of lines) { const trimmedLine line.trim(); if (!trimmedLine) continue; if (trimmedLine.startsWith(data:)) { const dataStr trimmedLine.substring(5).trim(); // 过滤掉所有非JSON数据 if (!dataStr || dataStr [DONE] || dataStr Process || !dataStr.startsWith({) || !dataStr.endsWith(})) { console.log(跳过非JSON数据:, dataStr); continue; } try { const jsonData JSON.parse(dataStr); console.log(解析到有效数据:, jsonData); if (typeof onChunk function) { onChunk(jsonData); } } catch (e) { console.warn(解析 JSON 失败跳过该数据块:, dataStr); // 如果解析失败保留这一行到剩余缓冲区可能是不完整的JSON remainingLines.push(line); continue; } } else { // 不以data:开头的行保留到剩余缓冲区 remainingLines.push(line); } } // 返回剩余的不完整行 return remainingLines.join(\n); }; // 小程序环境的流式请求保留你的原始逻辑 const miniprogramStreamRequest (data, base64Data, onChunk, resolve, reject) { return new Promise((resolve, reject) { const requestTask uni.request({ url: ${config.baseURL}/education/model/checkHomeWork, method: POST, header: { Content-Type: application/json, Accept: text/event-stream, Authorization: Bearer ${uni.getStorageSync(token)} }, data: { ...data, file: data:image/png;base64,${base64Data}, }, enableChunked: true, responseType: arraybuffer, success: (res) { uni.hideLoading(); console.log(数据接收成功) if (res.statusCode 200) { resolve(res.data); } else { reject(new Error(请求失败: ${res.statusCode})); } }, fail: () { uni.hideLoading(); reject(new Error(请求失败)); }, }); let buffer ; // UTF-8 解码函数 function utf8ArrayToString(uint8Array) { let str ; let i 0; const len uint8Array.length; while (i len) { const byte1 uint8Array[i]; if (byte1 0x80) { str String.fromCharCode(byte1); i; } else if (byte1 0xc0 byte1 0xe0) { if (i 1 len) break; const byte2 uint8Array[i 1]; str String.fromCharCode(((byte1 0x1f) 6) | (byte2 0x3f)); i 2; } else if (byte1 0xe0 byte1 0xf0) { if (i 2 len) break; const byte2 uint8Array[i 1]; const byte3 uint8Array[i 2]; str String.fromCharCode( ((byte1 0x0f) 12) | ((byte2 0x3f) 6) | (byte3 0x3f) ); i 3; } else if (byte1 0xf0 byte1 0xf8) { if (i 3 len) break; const byte2 uint8Array[i 1]; const byte3 uint8Array[i 2]; const byte4 uint8Array[i 3]; let codePoint ((byte1 0x07) 18) | ((byte2 0x3f) 12) | ((byte3 0x3f) 6) | (byte4 0x3f); if (codePoint 0xffff) { codePoint - 0x10000; str String.fromCharCode(0xd800 | (codePoint 10)); str String.fromCharCode(0xdc00 | (codePoint 0x3ff)); } else { str String.fromCharCode(codePoint); } i 4; } else { i; } } return str; } // 检查是否支持分块接收 if (requestTask typeof requestTask.onChunkReceived function) { requestTask.onChunkReceived((chunk) { const uint8Array new Uint8Array(chunk.data); const textChunk utf8ArrayToString(uint8Array); buffer textChunk; const chunks buffer.split(\n\n); for (let i 0; i chunks.length - 1; i) { const chunkData chunks[i]; if (chunkData.startsWith(data:)) { try { const dataStr chunkData.substring(5).trim(); if (dataStr dataStr ! [DONE]) { const json JSON.parse(dataStr); if (typeof onChunk function) { onChunk(json); } } if (dataStr [DONE]) { console.log(数据接收完毕) } } catch (e) { console.warn(解析 JSON 失败, e); } } } buffer chunks[chunks.length - 1]; }); } else { console.warn(当前环境不支持流式响应); } }); }; // H5 环境的流式请求 const h5StreamRequest (data, base64Data, onChunk, resolve, reject) { return new Promise((resolve, reject) { const requestData { ...data, file: data:image/png;base64,${base64Data}, }; // 使用 Fetch API 进行流式请求 fetch(${config.baseURL}/education/model/checkHomeWork, { method: POST, headers: { Content-Type: application/json, Accept: text/event-stream, Authorization: Bearer ${uni.getStorageSync(token)} }, body: JSON.stringify(requestData), }) .then(response { if (!response.ok) { throw new Error(HTTP error! status: ${response.status}); } const reader response.body.getReader(); const decoder new TextDecoder(); let buffer ; function readStream() { reader.read().then(({ done, value }) { if (done) { resolve(); return; } // 解码并处理数据块 const chunk decoder.decode(value, { stream: true }); buffer chunk; // 按 SSE 格式处理数据 const lines buffer.split(\n); buffer lines.pop() || ; // 保留最后不完整的行 for (const line of lines) { const trimmed line.trim(); if (trimmed.startsWith(data:)) { try { const dataStr trimmed.substring(5).trim(); if (dataStr dataStr ! [DONE]) { const json JSON.parse(dataStr); if (typeof onChunk function) { onChunk(json); } } } catch (e) { console.warn(解析 JSON 失败, e, 数据:, trimmed); } } } // 继续读取 readStream(); }).catch(reject); } readStream(); }) .catch(reject); }); }; // 将文件路径转换为base64 const filePathToBase64 (filePath) { return new Promise((resolve, reject) { // #ifdef MP-WEIXIN // 小程序平台 uni.getFileSystemManager().readFile({ filePath: filePath, encoding: base64, success: (res) { resolve(res.data); }, fail: reject, }); // #endif // #ifdef H5 // H5 平台 convertImageToBase64H5(filePath) .then(resolve) .catch(reject); // #endif // #ifdef APP-PLUS convertImageToBase64App(filePath) .then(resolve) .catch(reject); // #endif }); }; const convertImageToBase64App (filePath) { return new Promise((resolve, reject) { // 使用HTML5 API plus.io.resolveLocalFileSystemURL(filePath, (entry) { entry.file((file) { const fileReader new plus.io.FileReader(); fileReader.onloadend (evt) { // 这里直接返回的就是完整的data URL resolve(evt.target.result); }; fileReader.onerror (error) { reject(new Error(文件读取失败: JSON.stringify(error))); }; fileReader.readAsDataURL(file); }, (error) { reject(new Error(获取文件失败: JSON.stringify(error))); }); }, (error) { reject(new Error(解析文件路径失败: JSON.stringify(error))); }); }); } // H5环境的实现 const convertImageToBase64H5 (url) { return new Promise((resolve, reject) { const img new Image(); img.crossOrigin anonymous; img.onload function () { const canvas document.createElement(canvas); const ctx canvas.getContext(2d); canvas.width img.width; canvas.height img.height; ctx.drawImage(img, 0, 0); try { const dataURL canvas.toDataURL(image/png); // 移除 data:image/png;base64, 前缀 const base64 dataURL.replace(/^data:image\/\w;base64,/, ); resolve(base64); } catch (error) { reject(error); } }; img.onerror reject; img.src url; }); };
企业数字化 ERP 产品动态
相关推荐
游戏库项目日报 项目日报项目名称:游戏库 —— 基于 Android 原生 SQLite 的游戏管理应用 项目编号:ZZQ‑GAME‑2026‑001 日期:2026‑09‑21今日 / 本周工作处于第 1‑2 周【需求与设计】阶段。已完成完整立项文档整理,明确项目目标、功能范围、… · 2026/9/24 17:37:41
iOS 27.0 适配:statusBarFrame 废弃警告彻底解决方案 前言iOS 27.0 适配:statusBarFrame 废弃警告彻底解决方案最近升级 Xcode、适配 iOS 27.0 系统后,很多 iOS 老项目都会出现一条系统警告:-[UIApplication statusBarFrame] API has been deprecated and is a no-op on 27.0 and later.看似只是… · 2026/9/24 17:37:41
一排“储物柜“的智慧:从 C# 数组到 List<T>,看懂数据集合的进化之路 一句话概括: 数组是编译器为你划出的一排连号储物柜——快、稳、但一旦建成就不能扩建;List<T> 则是会自动扩建的智能储物间。搞懂它们的取舍,你才算真正会"存数据"。一、先从一个尴尬场景说起
你要统计班里 5 名学生的成绩。… · 2026/9/24 17:37:41
Python本地人脸识别签到系统:可部署、可调试、可交付 简介:本资源是一个基于Python实现的轻量级GUI人脸识别签到系统,面向人工智能初学者、高校课程设计学生及中小型考勤场景开发者,解决传统签到效率低、易代签等问题。压缩包共20个文件(95KB),含6个核心Python… · 2026/9/24 18:12:10
Python NBA球员数据可视化实战:从抓取清洗到交互大屏 简介:基于Python的NBA球员数据可视化分析项目,是一份面向毕业设计、期末大作业的高分参考实现。项目由学长手写并获导师高度认可,代码附有详细注释,即使刚接触Python的新手也能快速理解逻辑并部署运行。资源共20个文件,… · 2026/9/24 18:12:10
基于Python与OpenCV的人脸识别门禁系统开发实战 简介:这是一套基于Python的人脸识别智能小区门禁管理系统源码,面向Python学习者、计算机专业学生及安防系统开发者,用于解决小区出入身份验证与门禁自动化管理问题。资源包共101个文件,大小约12.17MB,文件类型涵盖.py源… · 2026/9/24 18:12:03
随机森林在锂离子电池剩余寿命预测中的实用教程 简介:基于Python随机森林模型的锂离子电池剩余寿命预测项目资料,面向机器学习入门与进阶人群,适用于毕业设计、课程设计、大作业或工程实训。资源在调研阶段深入比较了锂离子电池剩余寿命预测的常用方法,对机器学习模型与传统物理… · 2026/9/24 18:12:03
用随机森林预测锂离子电池剩余寿命:从数据到部署的完整指南 简介:面向锂电池寿命预测研究的Python随机森林项目资料,涵盖从电池充放电数据整理到剩余寿命回归预测的完整流程,适合高校学生用于毕设、课程设计或初期工程实践,也适合希望了解随机森林在工业数据上应用的新手按需学习。压缩包共… · 2026/9/24 18:12:03
网易云音乐39.5万条情感标签数据:从清洗到情感分类模型实战 简介:网易云音乐情感分类数据集面向自然语言处理、音乐推荐及情感分析领域的研究者与数据科学爱好者,提供约39.5万条来自网易云音乐官方平台的歌曲情感标签数据。每条记录包含歌曲ID、歌单ID与情感标签三项核心信息,可支撑情感分类模型训练、… · 2026/9/24 18:11:51
基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程 简介:这是一套面向计算机、人工智能、自动化等专业学生与教师的毕业设计级项目资源,围绕YOLOv8实现渔船作业监控系统,可用于毕设、课程设计、大作业或项目立项演示。压缩包共97个文件,约24.21MB,以70个Python源码文件为… · 2026/9/24 0:00:13
1D-CNN时间序列建模实战:从Conv1d原理到工业落地 简介:面向时间序列数据建模的一维卷积神经网络完整实现,适合深度学习入门者及需要快速验证时序模型的研究者,能够从音频、文本、传感器或股价等序列中挖掘局部特征与时间依赖。压缩包体积很小,只有3KB,内含3个Python脚… · 2026/9/24 0:00:26
柔软的L:汉语语流中被忽视的舌肌张力控制 1. 这个“L”不是字母表里的L,而是舌尖上的L最近在几个方言群和语音教学社群里,反复看到有人发一句:“也说字母L:柔软的长舌”。初看以为是英语发音课笔记,点开才发现全是方言爱好者、播音系学生、语言康复师甚至戏曲演… · 2026/9/24 0:00:44