simpleUploadOverlay.ts 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. import { defineStore } from "pinia";
  2. import { ref } from "vue";
  3. import { ElMessage } from "element-plus";
  4. /** 上传占 90%,内容审核占 10% */
  5. const UPLOAD_PERCENT_MAX = 90;
  6. const AUDIT_PERCENT_MAX = 100;
  7. const AUDIT_ROTATE_MESSAGES = [
  8. "检测违规内容中...",
  9. "审核内容合规性...",
  10. "筛查敏感信息中...",
  11. "识别不良信息中...",
  12. "智能风控核验中..."
  13. ];
  14. const AUDIT_TITLE_DONE = "上传完毕,处理中...";
  15. const AUDIT_TITLE_SUCCESS = "上传成功";
  16. let uploadCreepTimer: ReturnType<typeof setInterval> | null = null;
  17. let auditMessageTimer: ReturnType<typeof setInterval> | null = null;
  18. let auditProgressTimer: ReturnType<typeof setInterval> | null = null;
  19. let auditMessageIndex = 0;
  20. let activeController: AbortController | null = null;
  21. function clearUploadCreepTimer() {
  22. if (uploadCreepTimer) {
  23. clearInterval(uploadCreepTimer);
  24. uploadCreepTimer = null;
  25. }
  26. }
  27. function clearAuditTimers() {
  28. if (auditMessageTimer) {
  29. clearInterval(auditMessageTimer);
  30. auditMessageTimer = null;
  31. }
  32. if (auditProgressTimer) {
  33. clearInterval(auditProgressTimer);
  34. auditProgressTimer = null;
  35. }
  36. }
  37. function clearAllTimers() {
  38. clearUploadCreepTimer();
  39. clearAuditTimers();
  40. }
  41. function clampPercent(n: number) {
  42. return Math.min(AUDIT_PERCENT_MAX, Math.max(0, Math.round(n)));
  43. }
  44. export const useSimpleUploadOverlayStore = defineStore("simple-upload-overlay", () => {
  45. const show = ref(false);
  46. const percent = ref(0);
  47. const title = ref("上传中");
  48. const cancelText = ref("取消上传");
  49. function beginUpload(opts?: { title?: string }) {
  50. activeController?.abort(new DOMException("已开始新的上传", "AbortError"));
  51. clearAllTimers();
  52. activeController = new AbortController();
  53. title.value = opts?.title ?? "上传中";
  54. percent.value = 0;
  55. show.value = true;
  56. auditMessageIndex = 0;
  57. /** OSS 未回调 progress 时缓慢推进,上限留到 85% 等待真实进度 */
  58. uploadCreepTimer = setInterval(() => {
  59. if (percent.value < 85) {
  60. percent.value = Math.min(85, percent.value + 1);
  61. }
  62. }, 220);
  63. return activeController.signal;
  64. }
  65. /** 单文件 OSS 上传进度 ratio 0~1 → 0~90% */
  66. function setUploadProgress(ratio: number, opts?: { skipAudit?: boolean }) {
  67. const r = Math.min(1, Math.max(0, Number(ratio) || 0));
  68. const max = opts?.skipAudit ? AUDIT_PERCENT_MAX : UPLOAD_PERCENT_MAX;
  69. const next = clampPercent(r * max);
  70. if (next > percent.value) {
  71. percent.value = next;
  72. }
  73. if (next >= UPLOAD_PERCENT_MAX - 1) {
  74. clearUploadCreepTimer();
  75. }
  76. }
  77. /** 多文件:第 index 个文件、共 total 个,单文件内 ratio 0~1 */
  78. function setMultiFileUploadProgress(fileIndex: number, totalFiles: number, ratio: number) {
  79. const total = Math.max(1, totalFiles);
  80. const idx = Math.min(Math.max(0, fileIndex), total - 1);
  81. const r = Math.min(1, Math.max(0, Number(ratio) || 0));
  82. const base = (idx / total) * UPLOAD_PERCENT_MAX;
  83. const span = UPLOAD_PERCENT_MAX / total;
  84. const next = clampPercent(base + r * span);
  85. if (next > percent.value) {
  86. percent.value = next;
  87. }
  88. if (r >= 1 && idx === total - 1) {
  89. clearUploadCreepTimer();
  90. percent.value = UPLOAD_PERCENT_MAX;
  91. }
  92. }
  93. /** 多文件审核阶段进度 */
  94. function setMultiFileAuditProgress(fileIndex: number, totalFiles: number, ratio: number) {
  95. const total = Math.max(1, totalFiles);
  96. const idx = Math.min(Math.max(0, fileIndex), total - 1);
  97. const r = Math.min(1, Math.max(0, Number(ratio) || 0));
  98. const base = UPLOAD_PERCENT_MAX + (idx / total) * (AUDIT_PERCENT_MAX - UPLOAD_PERCENT_MAX);
  99. const span = (AUDIT_PERCENT_MAX - UPLOAD_PERCENT_MAX) / total;
  100. percent.value = clampPercent(base + r * span);
  101. }
  102. function startAuditMessageRotation() {
  103. clearAuditTimers();
  104. auditMessageIndex = 0;
  105. title.value = AUDIT_TITLE_DONE;
  106. auditMessageTimer = setInterval(() => {
  107. title.value = AUDIT_ROTATE_MESSAGES[auditMessageIndex % AUDIT_ROTATE_MESSAGES.length];
  108. auditMessageIndex += 1;
  109. }, 1200);
  110. let auditP = percent.value < UPLOAD_PERCENT_MAX ? UPLOAD_PERCENT_MAX : percent.value;
  111. percent.value = UPLOAD_PERCENT_MAX;
  112. auditProgressTimer = setInterval(() => {
  113. if (auditP < 99) {
  114. auditP = Math.min(99, auditP + 0.35);
  115. percent.value = clampPercent(auditP);
  116. }
  117. }, 280);
  118. }
  119. /** 进入审核阶段:固定 90%,先显示「上传完毕,处理中...」再轮播审核文案 */
  120. function beginAuditPhase() {
  121. clearUploadCreepTimer();
  122. percent.value = UPLOAD_PERCENT_MAX;
  123. title.value = AUDIT_TITLE_DONE;
  124. setTimeout(() => {
  125. if (!show.value) return;
  126. startAuditMessageRotation();
  127. }, 600);
  128. }
  129. function beginAuditPhaseForBatch(fileIndex: number, totalFiles: number) {
  130. clearUploadCreepTimer();
  131. const total = Math.max(1, totalFiles);
  132. const idx = Math.min(Math.max(0, fileIndex), total - 1);
  133. const auditBase = UPLOAD_PERCENT_MAX + (idx / total) * (AUDIT_PERCENT_MAX - UPLOAD_PERCENT_MAX);
  134. percent.value = clampPercent(Math.max(UPLOAD_PERCENT_MAX, auditBase));
  135. title.value = AUDIT_TITLE_DONE;
  136. setTimeout(() => {
  137. if (!show.value) return;
  138. startAuditMessageRotation();
  139. }, 600);
  140. }
  141. function completeSuccess() {
  142. clearAllTimers();
  143. percent.value = AUDIT_PERCENT_MAX;
  144. title.value = AUDIT_TITLE_SUCCESS;
  145. }
  146. /** @deprecated 请用 completeSuccess */
  147. function bumpToComplete() {
  148. completeSuccess();
  149. }
  150. function dismiss() {
  151. clearAllTimers();
  152. show.value = false;
  153. percent.value = 0;
  154. title.value = "上传中";
  155. activeController = null;
  156. }
  157. function userCancel() {
  158. activeController?.abort(new DOMException("用户已取消上传", "AbortError"));
  159. dismiss();
  160. ElMessage.info("取消上传");
  161. }
  162. function getActiveAbortSignal(): AbortSignal | undefined {
  163. return activeController?.signal;
  164. }
  165. return {
  166. show,
  167. percent,
  168. title,
  169. cancelText,
  170. beginUpload,
  171. setUploadProgress,
  172. setMultiFileUploadProgress,
  173. setMultiFileAuditProgress,
  174. beginAuditPhase,
  175. beginAuditPhaseForBatch,
  176. completeSuccess,
  177. bumpToComplete,
  178. dismiss,
  179. userCancel,
  180. getActiveAbortSignal
  181. };
  182. });