/** * 镭射卡五图批量生成(Phase 1:纯客户端 Canvas,无后端接口) */ import { ref } from 'vue' import { CASTLOVE_FORM_KEY, consumeGenerationFlowPayload, persistLaserPreviewImages, } from '@/utils/castloveGenerationFlow.js' import { generateLaserVariantBatch } from '@/utils/laser-card/laserBatchExport.js' const DEFAULT_CANVAS_ID = 'laserBatchCanvas' const DEFAULT_MIN_DURATION_MS = 1600 /** * @param {Object} [options] * @param {string} [options.canvasId] * @param {number} [options.minDurationMs] * @param {number} [options.flowStartedAt] 页面 onMounted 时的时间戳,用于最短展示时长 */ export function useLaserBatchGenerate(options = {}) { const progress = ref(0) const running = ref(false) const error = ref(null) let progressTimer = null let flowStartedAt = options.flowStartedAt ?? Date.now() const canvasId = options.canvasId || DEFAULT_CANVAS_ID const minDurationMs = options.minDurationMs ?? DEFAULT_MIN_DURATION_MS const stopProgress = () => { if (progressTimer) { clearInterval(progressTimer) progressTimer = null } } const simulateProgress = () => { stopProgress() progressTimer = setInterval(() => { if (progress.value < 90) { const increment = Math.random() * 5 + 2 progress.value = Math.min(90, progress.value + increment) } }, 500) } const completeProgress = () => new Promise((resolve) => { stopProgress() const finalInterval = setInterval(() => { if (progress.value < 100) { progress.value = Math.min(100, progress.value + 5) } else { clearInterval(finalInterval) setTimeout(resolve, 500) } }, 50) }) const waitMinDuration = async (minMs) => { if (!minMs || minMs <= 0) return const elapsed = Date.now() - flowStartedAt if (elapsed < minMs) { await new Promise((r) => setTimeout(r, minMs - elapsed)) } } const readFormData = () => { const formStr = uni.getStorageSync(CASTLOVE_FORM_KEY) if (!formStr) { throw new Error('缺少表单数据') } return JSON.parse(formStr) } const resolveImagePath = (formData) => { const imagePath = formData?.image || formData?.uploadedImage || '' if (!imagePath) { throw new Error('缺少上传图片') } return imagePath } /** * 执行五图合成并写入 Storage * @param {Object} [runOptions] * @param {Object} [runOptions.flow] consumeGenerationFlowPayload 结果;缺省则自动 consume * @returns {Promise} 本地临时 JPG 路径列表 */ const run = async (runOptions = {}) => { if (running.value) { throw new Error('生成进行中') } running.value = true error.value = null simulateProgress() try { const flow = runOptions.flow ?? consumeGenerationFlowPayload() const minMs = flow?.minDurationMs ?? minDurationMs const formData = readFormData() const imagePath = resolveImagePath(formData) const paths = await generateLaserVariantBatch(imagePath, canvasId) await waitMinDuration(minMs) persistLaserPreviewImages(paths) await completeProgress() return paths } catch (e) { error.value = e stopProgress() throw e } finally { running.value = false } } const resetProgress = () => { stopProgress() progress.value = 0 } const setFlowStartedAt = (ts) => { flowStartedAt = ts } return { progress, running, error, simulateProgress, completeProgress, stopProgress, resetProgress, setFlowStartedAt, run, } }