|
@@ -2,16 +2,54 @@ import { defineStore } from "pinia";
|
|
|
import { ref } from "vue";
|
|
import { ref } from "vue";
|
|
|
import { ElMessage } from "element-plus";
|
|
import { ElMessage } from "element-plus";
|
|
|
|
|
|
|
|
-let progressTimer: ReturnType<typeof setInterval> | null = null;
|
|
|
|
|
|
|
+/** 上传占 90%,内容审核占 10% */
|
|
|
|
|
+const UPLOAD_PERCENT_MAX = 90;
|
|
|
|
|
+const AUDIT_PERCENT_MAX = 100;
|
|
|
|
|
+
|
|
|
|
|
+const AUDIT_ROTATE_MESSAGES = [
|
|
|
|
|
+ "检测违规内容中...",
|
|
|
|
|
+ "审核内容合规性...",
|
|
|
|
|
+ "筛查敏感信息中...",
|
|
|
|
|
+ "识别不良信息中...",
|
|
|
|
|
+ "智能风控核验中..."
|
|
|
|
|
+];
|
|
|
|
|
+
|
|
|
|
|
+const AUDIT_TITLE_DONE = "上传完毕,处理中...";
|
|
|
|
|
+const AUDIT_TITLE_SUCCESS = "上传成功";
|
|
|
|
|
+
|
|
|
|
|
+let uploadCreepTimer: ReturnType<typeof setInterval> | null = null;
|
|
|
|
|
+let auditMessageTimer: ReturnType<typeof setInterval> | null = null;
|
|
|
|
|
+let auditProgressTimer: ReturnType<typeof setInterval> | null = null;
|
|
|
|
|
+let auditMessageIndex = 0;
|
|
|
let activeController: AbortController | null = null;
|
|
let activeController: AbortController | null = null;
|
|
|
|
|
|
|
|
-function clearProgressTimer() {
|
|
|
|
|
- if (progressTimer) {
|
|
|
|
|
- clearInterval(progressTimer);
|
|
|
|
|
- progressTimer = null;
|
|
|
|
|
|
|
+function clearUploadCreepTimer() {
|
|
|
|
|
+ if (uploadCreepTimer) {
|
|
|
|
|
+ clearInterval(uploadCreepTimer);
|
|
|
|
|
+ uploadCreepTimer = null;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function clearAuditTimers() {
|
|
|
|
|
+ if (auditMessageTimer) {
|
|
|
|
|
+ clearInterval(auditMessageTimer);
|
|
|
|
|
+ auditMessageTimer = null;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (auditProgressTimer) {
|
|
|
|
|
+ clearInterval(auditProgressTimer);
|
|
|
|
|
+ auditProgressTimer = null;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+function clearAllTimers() {
|
|
|
|
|
+ clearUploadCreepTimer();
|
|
|
|
|
+ clearAuditTimers();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function clampPercent(n: number) {
|
|
|
|
|
+ return Math.min(AUDIT_PERCENT_MAX, Math.max(0, Math.round(n)));
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
export const useSimpleUploadOverlayStore = defineStore("simple-upload-overlay", () => {
|
|
export const useSimpleUploadOverlayStore = defineStore("simple-upload-overlay", () => {
|
|
|
const show = ref(false);
|
|
const show = ref(false);
|
|
|
const percent = ref(0);
|
|
const percent = ref(0);
|
|
@@ -20,28 +58,119 @@ export const useSimpleUploadOverlayStore = defineStore("simple-upload-overlay",
|
|
|
|
|
|
|
|
function beginUpload(opts?: { title?: string }) {
|
|
function beginUpload(opts?: { title?: string }) {
|
|
|
activeController?.abort(new DOMException("已开始新的上传", "AbortError"));
|
|
activeController?.abort(new DOMException("已开始新的上传", "AbortError"));
|
|
|
- clearProgressTimer();
|
|
|
|
|
|
|
+ clearAllTimers();
|
|
|
activeController = new AbortController();
|
|
activeController = new AbortController();
|
|
|
title.value = opts?.title ?? "上传中";
|
|
title.value = opts?.title ?? "上传中";
|
|
|
- percent.value = 3;
|
|
|
|
|
|
|
+ percent.value = 0;
|
|
|
show.value = true;
|
|
show.value = true;
|
|
|
- progressTimer = setInterval(() => {
|
|
|
|
|
- if (percent.value < 88) {
|
|
|
|
|
- percent.value = Math.min(88, percent.value + 2 + Math.random() * 6);
|
|
|
|
|
|
|
+ auditMessageIndex = 0;
|
|
|
|
|
+ /** OSS 未回调 progress 时缓慢推进,上限留到 85% 等待真实进度 */
|
|
|
|
|
+ uploadCreepTimer = setInterval(() => {
|
|
|
|
|
+ if (percent.value < 85) {
|
|
|
|
|
+ percent.value = Math.min(85, percent.value + 1);
|
|
|
}
|
|
}
|
|
|
- }, 260);
|
|
|
|
|
|
|
+ }, 220);
|
|
|
return activeController.signal;
|
|
return activeController.signal;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /** 单文件 OSS 上传进度 ratio 0~1 → 0~90% */
|
|
|
|
|
+ function setUploadProgress(ratio: number, opts?: { skipAudit?: boolean }) {
|
|
|
|
|
+ const r = Math.min(1, Math.max(0, Number(ratio) || 0));
|
|
|
|
|
+ const max = opts?.skipAudit ? AUDIT_PERCENT_MAX : UPLOAD_PERCENT_MAX;
|
|
|
|
|
+ const next = clampPercent(r * max);
|
|
|
|
|
+ if (next > percent.value) {
|
|
|
|
|
+ percent.value = next;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (next >= UPLOAD_PERCENT_MAX - 1) {
|
|
|
|
|
+ clearUploadCreepTimer();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** 多文件:第 index 个文件、共 total 个,单文件内 ratio 0~1 */
|
|
|
|
|
+ function setMultiFileUploadProgress(fileIndex: number, totalFiles: number, ratio: number) {
|
|
|
|
|
+ const total = Math.max(1, totalFiles);
|
|
|
|
|
+ const idx = Math.min(Math.max(0, fileIndex), total - 1);
|
|
|
|
|
+ const r = Math.min(1, Math.max(0, Number(ratio) || 0));
|
|
|
|
|
+ const base = (idx / total) * UPLOAD_PERCENT_MAX;
|
|
|
|
|
+ const span = UPLOAD_PERCENT_MAX / total;
|
|
|
|
|
+ const next = clampPercent(base + r * span);
|
|
|
|
|
+ if (next > percent.value) {
|
|
|
|
|
+ percent.value = next;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (r >= 1 && idx === total - 1) {
|
|
|
|
|
+ clearUploadCreepTimer();
|
|
|
|
|
+ percent.value = UPLOAD_PERCENT_MAX;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** 多文件审核阶段进度 */
|
|
|
|
|
+ function setMultiFileAuditProgress(fileIndex: number, totalFiles: number, ratio: number) {
|
|
|
|
|
+ const total = Math.max(1, totalFiles);
|
|
|
|
|
+ const idx = Math.min(Math.max(0, fileIndex), total - 1);
|
|
|
|
|
+ const r = Math.min(1, Math.max(0, Number(ratio) || 0));
|
|
|
|
|
+ const base = UPLOAD_PERCENT_MAX + (idx / total) * (AUDIT_PERCENT_MAX - UPLOAD_PERCENT_MAX);
|
|
|
|
|
+ const span = (AUDIT_PERCENT_MAX - UPLOAD_PERCENT_MAX) / total;
|
|
|
|
|
+ percent.value = clampPercent(base + r * span);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function startAuditMessageRotation() {
|
|
|
|
|
+ clearAuditTimers();
|
|
|
|
|
+ auditMessageIndex = 0;
|
|
|
|
|
+ title.value = AUDIT_TITLE_DONE;
|
|
|
|
|
+ auditMessageTimer = setInterval(() => {
|
|
|
|
|
+ title.value = AUDIT_ROTATE_MESSAGES[auditMessageIndex % AUDIT_ROTATE_MESSAGES.length];
|
|
|
|
|
+ auditMessageIndex += 1;
|
|
|
|
|
+ }, 1200);
|
|
|
|
|
+ let auditP = percent.value < UPLOAD_PERCENT_MAX ? UPLOAD_PERCENT_MAX : percent.value;
|
|
|
|
|
+ percent.value = UPLOAD_PERCENT_MAX;
|
|
|
|
|
+ auditProgressTimer = setInterval(() => {
|
|
|
|
|
+ if (auditP < 99) {
|
|
|
|
|
+ auditP = Math.min(99, auditP + 0.35);
|
|
|
|
|
+ percent.value = clampPercent(auditP);
|
|
|
|
|
+ }
|
|
|
|
|
+ }, 280);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** 进入审核阶段:固定 90%,先显示「上传完毕,处理中...」再轮播审核文案 */
|
|
|
|
|
+ function beginAuditPhase() {
|
|
|
|
|
+ clearUploadCreepTimer();
|
|
|
|
|
+ percent.value = UPLOAD_PERCENT_MAX;
|
|
|
|
|
+ title.value = AUDIT_TITLE_DONE;
|
|
|
|
|
+ setTimeout(() => {
|
|
|
|
|
+ if (!show.value) return;
|
|
|
|
|
+ startAuditMessageRotation();
|
|
|
|
|
+ }, 600);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function beginAuditPhaseForBatch(fileIndex: number, totalFiles: number) {
|
|
|
|
|
+ clearUploadCreepTimer();
|
|
|
|
|
+ const total = Math.max(1, totalFiles);
|
|
|
|
|
+ const idx = Math.min(Math.max(0, fileIndex), total - 1);
|
|
|
|
|
+ const auditBase = UPLOAD_PERCENT_MAX + (idx / total) * (AUDIT_PERCENT_MAX - UPLOAD_PERCENT_MAX);
|
|
|
|
|
+ percent.value = clampPercent(Math.max(UPLOAD_PERCENT_MAX, auditBase));
|
|
|
|
|
+ title.value = AUDIT_TITLE_DONE;
|
|
|
|
|
+ setTimeout(() => {
|
|
|
|
|
+ if (!show.value) return;
|
|
|
|
|
+ startAuditMessageRotation();
|
|
|
|
|
+ }, 600);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function completeSuccess() {
|
|
|
|
|
+ clearAllTimers();
|
|
|
|
|
+ percent.value = AUDIT_PERCENT_MAX;
|
|
|
|
|
+ title.value = AUDIT_TITLE_SUCCESS;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** @deprecated 请用 completeSuccess */
|
|
|
function bumpToComplete() {
|
|
function bumpToComplete() {
|
|
|
- clearProgressTimer();
|
|
|
|
|
- percent.value = 100;
|
|
|
|
|
|
|
+ completeSuccess();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function dismiss() {
|
|
function dismiss() {
|
|
|
- clearProgressTimer();
|
|
|
|
|
|
|
+ clearAllTimers();
|
|
|
show.value = false;
|
|
show.value = false;
|
|
|
percent.value = 0;
|
|
percent.value = 0;
|
|
|
|
|
+ title.value = "上传中";
|
|
|
activeController = null;
|
|
activeController = null;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -51,7 +180,6 @@ export const useSimpleUploadOverlayStore = defineStore("simple-upload-overlay",
|
|
|
ElMessage.info("取消上传");
|
|
ElMessage.info("取消上传");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- /** 供 skipSimpleUploadOverlay 的上传与 beginUpload 配套,把 fetch 绑到同一 AbortSignal */
|
|
|
|
|
function getActiveAbortSignal(): AbortSignal | undefined {
|
|
function getActiveAbortSignal(): AbortSignal | undefined {
|
|
|
return activeController?.signal;
|
|
return activeController?.signal;
|
|
|
}
|
|
}
|
|
@@ -62,6 +190,12 @@ export const useSimpleUploadOverlayStore = defineStore("simple-upload-overlay",
|
|
|
title,
|
|
title,
|
|
|
cancelText,
|
|
cancelText,
|
|
|
beginUpload,
|
|
beginUpload,
|
|
|
|
|
+ setUploadProgress,
|
|
|
|
|
+ setMultiFileUploadProgress,
|
|
|
|
|
+ setMultiFileAuditProgress,
|
|
|
|
|
+ beginAuditPhase,
|
|
|
|
|
+ beginAuditPhaseForBatch,
|
|
|
|
|
+ completeSuccess,
|
|
|
bumpToComplete,
|
|
bumpToComplete,
|
|
|
dismiss,
|
|
dismiss,
|
|
|
userCancel,
|
|
userCancel,
|