dataProcessor.uts 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611
  1. import { getToken, getTokenFromApi } from './auth'
  2. import { saveMediaInfo, saveMediaRecord, getLatestRecord, removeInfoAndRecord, getJoinList, updateData } from '@/api/work'
  3. import { globalConfig } from '@/config'
  4. // 类型定义保持不变
  5. export type ApiResponse = {
  6. code : number;
  7. msg : string;
  8. data : MediaInfoData[];
  9. }
  10. export type MediaInfoData = {
  11. pk : string;
  12. workorder ?: string;
  13. invname ?: string;
  14. productcode ?: string;
  15. cardno ?: string;
  16. model ?: string;
  17. graphid ?: string;
  18. ver ?: string;
  19. phase ?: string;
  20. processno ?: string;
  21. createtime ?: string;
  22. createuser ?: string;
  23. updatetime ?: string;
  24. updateuser ?: string;
  25. qmImageTaskClist ?: MediaRecordData[];
  26. }
  27. export type MediaRecordData = {
  28. senum ?: string;
  29. photoitem ?: string;
  30. part ?: string;
  31. partno ?: string;
  32. pk : string;
  33. exampleid ?: string;
  34. descb ?: string;
  35. num ?: number;
  36. urlspl ?: string;
  37. imgname ?: string;
  38. urlpdt ?: string;
  39. createtime ?: string;
  40. createuser ?: string;
  41. updatetime ?: string;
  42. updateuser ?: string;
  43. photoFiles_example ?: PhotoFilesExample[];
  44. }
  45. export type PhotoFilesExample = {
  46. _id ?: string,
  47. }
  48. export type UploadImg = {
  49. pk : string,
  50. path : string
  51. }
  52. export const showProgress = (index : number, title : string) => {
  53. // 在Android设备上,需要给hideLoading和showLoading之间添加延迟
  54. // 先隐藏之前的加载提示
  55. uni.hideLoading();
  56. // 添加50ms的延迟,确保hideLoading完全执行后再显示新的进度
  57. setTimeout(() => {
  58. uni.showLoading({
  59. title: `正在${title}第${index}个任务数据`,
  60. mask: true
  61. });
  62. }, 50);
  63. }
  64. //声像任务下载
  65. export const downloadDataFromAPI = async (productCode : string, callback ?: () => void) : Promise<boolean> => {
  66. try {
  67. uni.showLoading({ title: '任务开始下载' });
  68. const apiToken = await getTokenFromApi();
  69. if (apiToken == null || apiToken == '') {
  70. uni.hideLoading();
  71. uni.showToast({ title: `获取Token失败,请联系技术IT`, icon: 'error' });
  72. return false
  73. }
  74. //校验是否已经存在未执行的产品号,若已经存在则提示用户该产品号是否需要被覆盖,
  75. //如果没有则直接保存。如果有存在已经在执行中的产品号,则提示已存在执行中的任务
  76. const infoJson = await getLatestRecord(productCode, null);
  77. if (infoJson?.['data'] != null) {
  78. let info = infoJson?.['data'] as UTSJSONObject ?? {} as UTSJSONObject
  79. let ingNum = parseInt(info?.['statusRecordCount'] as string);
  80. //覆盖标识位
  81. let overwiteFlag = ref(false);
  82. // 先检查是否有任务正在执行中
  83. if (info != null && ingNum > 0) {
  84. uni.showToast({ title: `当前产品号已有任务在执行中!`, icon: 'error' });
  85. return false;
  86. }
  87. // 使用Promise来处理异步流程
  88. let deleteDataPromise = new Promise<boolean>((resolve) => {
  89. if (info != null && ingNum == 0) {
  90. //可以被覆盖,需要有提示框,给用户确认
  91. uni.showModal({
  92. title: '系统提示',
  93. content: '该产品号已存在任务是否覆盖掉?',
  94. cancelText: '取消',
  95. confirmText: '确定',
  96. success: function (res) {
  97. if (res.confirm) {
  98. // 标记为需要覆盖
  99. overwiteFlag.value = true;
  100. // 执行删除数据操作
  101. let pid = info?.['pdid'] as string;
  102. removeInfoAndRecord(pid).then((recordDelResponse) => {
  103. console.log('删除数据响应:', recordDelResponse);
  104. // 删除成功,解析Promise并允许继续执行
  105. // 确保模态框已完全关闭后再解析Promise
  106. setTimeout(() => {
  107. resolve(true);
  108. }, 300);
  109. }).catch((error) => {
  110. console.error('删除数据失败:', error);
  111. uni.showToast({ title: '删除旧数据失败', icon: 'error' });
  112. resolve(false);
  113. });
  114. } else {
  115. // 用户取消覆盖
  116. overwiteFlag.value = false;
  117. resolve(false);
  118. }
  119. }
  120. });
  121. } else {
  122. // 不需要显示确认框,直接解析Promise
  123. resolve(true);
  124. }
  125. });
  126. // 等待删除数据操作完成
  127. const canContinue : boolean = await deleteDataPromise;
  128. if (!canContinue) {
  129. // 如果不能继续(删除失败或用户取消),则终止函数执行
  130. return false;
  131. }
  132. }
  133. // 使用Promise处理HTTP请求,避免嵌套Promise
  134. return new Promise<boolean>((resolve) => {
  135. uni.request({
  136. url: `${globalConfig.host}${globalConfig.downloadURL}${productCode}`,
  137. method: 'GET',
  138. header: {
  139. 'token': apiToken
  140. },
  141. success: (res) => {
  142. let singleObject = res?.['data'] as UTSJSONObject ?? {} as UTSJSONObject;
  143. if (singleObject != null && singleObject.code == 666) {
  144. let mediaInfoList = singleObject?.['data'] as UTSJSONObject[] ?? Array<UTSJSONObject>();
  145. if (mediaInfoList != null && mediaInfoList.length > 0) {
  146. mediaInfoList.forEach(item => {
  147. if (item != null) {
  148. let data = JSON.parse<MediaInfoData>(item.toJSONString());
  149. if (data != null) {
  150. saveMediaInfo(item).then((resSave : UTSJSONObject) => {
  151. const lastIdStr = resSave?.['lastId'] as string | null;
  152. const lastId = lastIdStr != null ? parseInt(lastIdStr) : null;
  153. if (lastId != null) {
  154. let recordList = data?.['qmImageTaskClist'] as UTSJSONObject[] ?? Array<UTSJSONObject>();
  155. if (recordList != null && recordList.length > 0) {
  156. const totalRecords = recordList.length;
  157. let processedRecords = 0;
  158. // 使用async/await处理循环中的异步操作
  159. const processAllRecords = async () => {
  160. // 创建一个Map来跟踪不同photoitem的计数
  161. const photoItemCounter : Map<string, number> = new Map();
  162. for (var i = 0; i < recordList.length; i++) {
  163. // 更新进度
  164. processedRecords++;
  165. showProgress(processedRecords, '下载');
  166. const record : MediaRecordData = recordList[i] as MediaRecordData;
  167. // 获取各个字段的值
  168. const photoitem = record.photoitem as string;
  169. // 根据photoitem类型统计senum
  170. if (!photoItemCounter.has(photoitem)) {
  171. photoItemCounter.set(photoitem, 1);
  172. } else {
  173. const currentCount = photoItemCounter.get(photoitem);
  174. photoItemCounter.set(photoitem, (currentCount != null ? currentCount : 0) + 1);
  175. }
  176. const senum = photoItemCounter.get(photoitem);
  177. const finalSenum = senum != null ? senum : 1;
  178. const partno = record.partno;
  179. const part = record.part;
  180. const pk = record.pk;
  181. const descb = record.descb;
  182. const num = record.num;
  183. const imgname = record.imgname;
  184. const urlpdt = record.urlpdt;
  185. const createtime = record.createtime;
  186. const createuser = record.createuser;
  187. const updatetime = record.updatetime;
  188. const updateuser = record.updateuser;
  189. // 创建数组来存储图片ID和保存路径
  190. var exampleidArr = [] as string[];
  191. var imagePathsArr = [] as string[];
  192. let exampleList = record?.['photoFiles_example'] as UTSJSONObject[] ?? Array<UTSJSONObject>();
  193. if (exampleList != null && exampleList.length > 0) {
  194. // 下载所有图片并等待完成
  195. const imageDownloadPromises = [] as Promise<void>[];
  196. // 顺序下载和保存图片,避免并行导致的随机失败
  197. for (var j = 0; j < exampleList.length; j++) {
  198. const example : PhotoFilesExample = exampleList[j] as PhotoFilesExample;
  199. if (example._id != null) {
  200. exampleidArr.push(example._id);
  201. try {
  202. // 串行执行,等待前一张图片处理完成再处理下一张
  203. await new Promise<void>((resolve, reject) => {
  204. // 使用uni.downloadFile下载图片
  205. uni.downloadFile({
  206. url: `${globalConfig.getImgURL}${example._id}`,
  207. header: {
  208. 'token': apiToken
  209. },
  210. success: (res) => {
  211. if (res.statusCode === 200) {
  212. // 等待一小段时间确保文件完全下载
  213. setTimeout(() => {
  214. // 图片下载成功,使用uni.saveImageToPhotosAlbum保存到相册
  215. uni.saveImageToPhotosAlbum({
  216. filePath: res.tempFilePath,
  217. success: () => {
  218. console.log('图片保存成功:', res.tempFilePath);
  219. // 将保存路径添加到数组中
  220. imagePathsArr.push(res.tempFilePath);
  221. // 保存成功后等待1秒再处理下一张
  222. setTimeout(() => {
  223. resolve();
  224. }, 1000);
  225. },
  226. fail: (err) => {
  227. console.error('保存图片失败:', err);
  228. // 保存失败也继续处理下一张,但记录错误
  229. setTimeout(() => {
  230. reject(err);
  231. }, 500);
  232. }
  233. });
  234. }, 500); // 等待500ms确保文件完全下载
  235. } else {
  236. console.error('下载图片失败,状态码:', res.statusCode);
  237. reject(new Error(`下载图片失败,状态码: ${res.statusCode}`));
  238. }
  239. },
  240. fail: (err) => {
  241. console.error('请求图片失败:', err);
  242. reject(err);
  243. }
  244. });
  245. });
  246. } catch (error) {
  247. console.error(`处理第${j + 1}张图片时出错:`, error);
  248. // 出错后继续处理下一张图片
  249. }
  250. }
  251. }
  252. }
  253. // 拼接图片ID和路径
  254. const exampleid = exampleidArr.join(",");
  255. const imagePaths = imagePathsArr.join(",");
  256. console.log("=========================================>");
  257. console.log(imagePaths);
  258. // 使用三目运算符判断,当值为null时直接插入null,否则用单引号括起来
  259. var values = `${finalSenum === null ? '1' : `'${finalSenum}'`}, ${photoitem === null ? 'null' : `'${photoitem}'`}, ${data?.['productcode'] === null ? 'null' : `'${data?.['productcode']}'`},${part === null ? 'null' : `'${part}'`},${partno === null ? 'null' : `'${partno}'`},${pk === null ? 'null' : `'${pk}'`},${exampleid === null ? 'null' : `'${exampleid}'`},${descb === null ? 'null' : `'${descb}'`},${num === null ? 0 : num},1,'', ${imagePaths === null ? 'null' : `'${imagePaths}'`},${imgname === null ? 'null' : `'${imgname}'`},${urlpdt === null ? 'null' : `'${urlpdt}'`}, ${createtime === null ? 'null' : `'${createtime}'`}, ${createuser === null ? 'null' : `'${createuser}'`}, ${updatetime === null ? 'null' : `'${updatetime}'`}, ${updateuser === null ? 'null' : `'${updateuser}'`}, ${lastId === null ? 0 : lastId}`;
  260. // 只有在所有图片下载完成后才调用saveMediaRecord
  261. saveMediaRecord(values);
  262. }
  263. // 所有记录处理完成,显示完成提示
  264. setTimeout(() => {
  265. uni.hideLoading();
  266. uni.showToast({ title: `下载完成`, icon: 'success' });
  267. if (callback != null) {
  268. callback();
  269. }
  270. }, 500);
  271. };
  272. // 开始处理所有记录
  273. processAllRecords();
  274. }
  275. } else {
  276. console.log('保存媒体信息成功,但未获取到主键ID');
  277. }
  278. })
  279. }
  280. }
  281. });
  282. resolve(true);
  283. } else {
  284. uni.hideLoading();
  285. uni.showToast({ title: '未获取到数据', icon: 'error' });
  286. resolve(false);
  287. }
  288. } else {
  289. const errorMsg = singleObject.msg != null ? singleObject.msg : '未知错误';
  290. uni.hideLoading();
  291. uni.showToast({ title: `请求失败: ${errorMsg}`, icon: 'error' });
  292. resolve(false);
  293. }
  294. },
  295. fail: (err) => {
  296. console.error('请求数据失败:', err);
  297. uni.hideLoading();
  298. uni.showToast({ title: `请求失败: ${err.errMsg}`, icon: 'error' });
  299. resolve(false);
  300. }
  301. });
  302. });
  303. } catch (error) {
  304. console.error(error);
  305. uni.showToast({ title: '下载失败,请重试', icon: 'error' });
  306. uni.hideLoading();
  307. return false;
  308. }
  309. }
  310. //声像任务上传
  311. export const uploadDataToAPI = async (productCode : string, callback ?: () => void) : Promise<boolean> => {
  312. try {
  313. //暂定需要上传的数据文件
  314. //const infoJson = await getLatestRecord(productCode, null);
  315. const apiToken = await getTokenFromApi();
  316. if (apiToken == null || apiToken == '') {
  317. uni.hideLoading();
  318. uni.showToast({ title: `获取Token失败,请联系技术IT`, icon: 'error' });
  319. return false
  320. }
  321. // 获取数据
  322. const res = await getJoinList('app_media_record as r', 'app_media_info as i', 'r.*,i.productno, i.uploadFlag', 'r.pid=i.pdid', 'i.productno', productCode, null);
  323. let dataList = res?.['data'] as UTSJSONObject[] ?? Array<UTSJSONObject>();
  324. console.log(dataList);
  325. if (dataList == null || dataList.length === 0) {
  326. uni.hideLoading();
  327. uni.showToast({ title: '未获取到数据', icon: 'error' });
  328. return false;
  329. }
  330. let doneRecordList = dataList.filter(item => item.getString("status") == '3');
  331. console.log(doneRecordList);
  332. if (doneRecordList.length === 0) {
  333. uni.hideLoading();
  334. uni.showToast({ title: '上传图片数据为空', icon: 'error' });
  335. return false;
  336. }
  337. // 1. 收集所有需要上传的图片
  338. let allImagesToUpload : UploadImg[] = [];
  339. let processStep = 1;
  340. for (let index = 0; index < dataList.length; index++) {
  341. const record = dataList[index];
  342. if (record.getString('urlpdt') == '' || record.getString('urlpdt') == null
  343. || record.getString('pk') == '' || record.getString('pk') == null) {
  344. continue
  345. }
  346. showProgress(processStep, '准备上传');
  347. //收集图片信息
  348. let urlpdtStr = record.getString('urlpdt');
  349. let pk = record.getString('pk');
  350. let urlArr = urlpdtStr?.split(",") ?? [];
  351. for (let j = 0; j < urlArr.length; j++) {
  352. let path = urlArr[j];
  353. const fullFilePath = `${uni.env.USER_DATA_PATH}` + path;
  354. allImagesToUpload.push({ pk: pk ?? '', path: fullFilePath });
  355. }
  356. processStep++;
  357. }
  358. // 2. 统计总图片数量
  359. const totalImages = allImagesToUpload.length;
  360. console.log(`总共需要上传${totalImages}张图片`);
  361. if (totalImages === 0) {
  362. uni.hideLoading();
  363. uni.showToast({ title: '没有需要上传的图片', icon: 'none' });
  364. return true;
  365. }
  366. // 3. 执行第一次上传
  367. let failedImages : UploadImg[] = [];
  368. let successCount = 0;
  369. uni.showLoading({ title: `正在上传图片 (0/${totalImages})` });
  370. for (let i = 0; i < allImagesToUpload.length; i++) {
  371. const { pk, path } = allImagesToUpload[i];
  372. console.log(`开始上传文件: ${path}, 索引: ${i}, apiToken: ${apiToken}, billid: ${pk}`);
  373. try {
  374. // 串行执行,等待前一个图片上传完成再处理下一张
  375. await new Promise<void>((resolve, reject) => {
  376. // 使用uni.uploadFile进行文件上传
  377. console.log(`上传路径:${globalConfig.uploadURL}`)
  378. const uploadTask = uni.uploadFile({
  379. url: `${globalConfig.uploadURL}`,
  380. filePath: path,
  381. name: 'file', // 文件参数名
  382. header: {
  383. 'token': apiToken
  384. },
  385. formData: {
  386. 'billid': pk
  387. },
  388. success: (uploadRes) => {
  389. if (uploadRes.statusCode === 200) {
  390. console.log(`文件${path}上传成功`, uploadRes);
  391. // 解析响应数据
  392. const resData = JSON.parse(uploadRes.data) as UTSJSONObject;
  393. console.log(resData)
  394. if (resData?.['_id'] != null && resData?.['_id'] != '') {
  395. successCount++;
  396. // 等待一小段时间确保文件完全上传并处理完成
  397. setTimeout(() => {
  398. resolve();
  399. }, 1000);
  400. } else {
  401. setTimeout(() => {
  402. reject('响应数据无效');
  403. }, 500);
  404. }
  405. } else {
  406. console.error(`文件${path}上传失败,状态码:`, uploadRes.statusCode);
  407. setTimeout(() => {
  408. reject(new Error(`上传失败,状态码: ${uploadRes.statusCode}`));
  409. }, 500);
  410. }
  411. },
  412. fail: (err) => {
  413. console.error(`文件${path}上传失败`, err);
  414. // 上传失败也继续处理下一张,但记录错误
  415. setTimeout(() => {
  416. reject(err);
  417. }, 500);
  418. },
  419. complete: () => {
  420. // console.log(`文件${path}上传操作完成`);
  421. // 更新进度
  422. uni.hideLoading();
  423. uni.showLoading({ title: `正在上传图片 (${i + 1}/${totalImages})` });
  424. }
  425. });
  426. uploadTask.onProgressUpdate((res) => {
  427. console.log('上传进度' + res.progress);
  428. console.log('已经上传的数据长度' + res.totalBytesSent);
  429. console.log('预期需要上传的数据总长度' + res.totalBytesExpectedToSend);
  430. });
  431. });
  432. } catch (error) {
  433. // 捕获上传失败的错误,将失败的图片信息添加到错误数组
  434. console.log(`处理第${i + 1}张图片时出错:`, error);
  435. failedImages.push({ pk, path });
  436. // 出错后继续处理下一张图片
  437. }
  438. // 在两次上传之间增加一个短暂的延迟,避免请求过于频繁
  439. if (i < allImagesToUpload.length - 1) {
  440. await new Promise<void>((resolve) => {
  441. setTimeout(() => {
  442. resolve()
  443. }, 1000)
  444. })
  445. }
  446. }
  447. // 4. 执行重试逻辑(最多3次)
  448. const maxRetries = 3;
  449. let retryImages = [...failedImages]; // 复制初始失败的图片数组
  450. for (let retryCount = 1; retryCount <= maxRetries; retryCount++) {
  451. if (retryImages.length === 0) {
  452. // 如果没有需要重试的图片,提前结束循环
  453. break;
  454. }
  455. console.log(`开始第${retryCount}次重试上传失败的图片,共${retryImages.length}张`);
  456. uni.hideLoading();
  457. uni.showLoading({ title: `第${retryCount}次重试 (0/${retryImages.length})` });
  458. // 创建新的错误数组,用于收集本次重试失败的图片
  459. let currentFailedImages : UploadImg[] = [];
  460. let currentSuccessCount = 0;
  461. // 串行上传失败的图片
  462. for (let i = 0; i < retryImages.length; i++) {
  463. const { pk, path } = retryImages[i];
  464. console.log(`重试上传文件: ${path}, 索引: ${i}, 重试次数: ${retryCount}, apiToken: ${apiToken}, billid: ${pk}`);
  465. console.log(`重试上传路径:${globalConfig.uploadURL}`)
  466. await new Promise<void>((resolve) => {
  467. uni.uploadFile({
  468. url: `${globalConfig.uploadURL}`,
  469. filePath: path,
  470. name: 'file',
  471. header: {
  472. 'token': apiToken
  473. },
  474. formData: {
  475. 'billid': pk
  476. },
  477. success: (uploadRes) => {
  478. if (uploadRes.statusCode === 200) {
  479. console.log(`重试文件${path}上传成功`, uploadRes);
  480. const resData = JSON.parse(uploadRes.data) as UTSJSONObject;
  481. if (resData?.['_id'] != null && resData?.['_id'] != '') {
  482. currentSuccessCount++;
  483. } else {
  484. currentFailedImages.push({ pk, path });
  485. }
  486. } else {
  487. currentFailedImages.push({ pk, path });
  488. }
  489. console.log(`重试上传完成,当前成功: ${currentSuccessCount}, 当前失败: ${currentFailedImages.length}`);
  490. resolve();
  491. },
  492. fail: (err) => {
  493. console.error(`重试文件${path}上传失败`, err);
  494. currentFailedImages.push({ pk, path });
  495. resolve();
  496. },
  497. complete: () => {
  498. // console.log(`重试文件${path}上传操作完成`);
  499. // 更新进度
  500. uni.hideLoading();
  501. uni.showLoading({ title: `第${retryCount}次重试 (${i + 1}/${retryImages.length})` });
  502. }
  503. });
  504. });
  505. // 在两次上传之间增加一个短暂的延迟,避免请求过于频繁
  506. if (i < retryImages.length - 1) {
  507. await new Promise<void>((resolve) => {
  508. setTimeout(() => {
  509. resolve()
  510. }, 1000)
  511. })
  512. }
  513. }
  514. // 更新成功数量
  515. successCount += currentSuccessCount;
  516. // 更新下一次重试的图片列表为本次失败的图片
  517. retryImages = currentFailedImages;
  518. }
  519. // 5. 显示总结信息
  520. const finalFailedCount = retryImages.length;
  521. console.log(`上传总结: 总共${totalImages}张图片, 成功${successCount}张, 失败${finalFailedCount}张`);
  522. uni.hideLoading();
  523. // 三次重试后如果仍有失败的图片,显示提示
  524. if (finalFailedCount > 0) {
  525. uni.showModal({
  526. title: '上传提示',
  527. content: `总共需要上传${totalImages}张图片,成功${successCount}张,失败${finalFailedCount}张。\n经过${maxRetries}次重试后,仍有${finalFailedCount}张图片上传失败,请检查网络后重新上传。`,
  528. showCancel: false
  529. });
  530. } else {
  531. uni.showToast({
  532. title: `上传完成!共${totalImages}张图片,全部成功。`,
  533. icon: 'success'
  534. });
  535. }
  536. if (callback != null) {
  537. callback();
  538. }
  539. if (finalFailedCount === 0) {
  540. let updatedData = " uploadflag = 1 "
  541. updateData('app_media_info', updatedData, 'productno', productCode).then((res : UTSJSONObject) => {
  542. console.log(`更新完上传标识 ${productCode}`)
  543. });
  544. }
  545. return finalFailedCount === 0;
  546. } catch (error) {
  547. console.error(error);
  548. uni.showToast({ title: '上传失败,请重试', icon: 'error' });
  549. uni.hideLoading();
  550. return false;
  551. }
  552. }