import { ref } from 'vue'; import pLimit from 'p-limit'; import { type BookPageQuestion, type QuestionsImages, updateBookPageLayoutAxios } from '@/service/api/book'; import { getAliOssTokenAxios } from '@/service/api/upload'; import { initOSSClient } from '@/utils/oss'; import type { CropImageOptions, CurrBookPageAllInfo, QuestionInfo } from './types'; import { compareVersion } from '@/utils/common'; // Crop image function implementation const cropImage = async (url: string, client: any, options: CropImageOptions, id: number): Promise => { return new Promise((resolve, reject) => { const img = new Image(); img.crossOrigin = 'anonymous'; img.src = url; img.onload = async () => { try { const canvas = document.createElement('canvas'); canvas.width = options.width; canvas.height = options.height; const ctx = canvas.getContext('2d'); if (!ctx) { reject(new Error('Canvas context not available')); return; } // Draw cropped image ctx.drawImage( img, options.x, options.y, options.width, options.height, 0, 0, options.width, options.height ); // Convert to Blob canvas.toBlob(async (blob) => { if (!blob) { reject(new Error('Canvas to Blob failed')); return; } const file = new File([blob], `question_${id}_${Date.now()}.png`, { type: 'image/png' }); const path = `book/questions/${id}/${file.name}`; try { // Upload to OSS // Assuming client.put or multipartUpload. // Since initOSSClient returns an OSS client, we can use it. // Using put for small files (cropped images are usually small) const result = await client.put(path, file); // Return the result format expected by the API // The API expects a map of ID to URL? Or array of objects? // Based on usage: `tasks.push(...)` and `imageFiles.push(...list)` // And `updateBookPageLayoutAxios` takes `questionsImages: imageFiles` // Let's return an object with the ID as key and URL as value, or just the URL object if QuestionsImages is a type. // Looking at previous usage `urlObj[item.id!] = item`, it seems `QuestionsImages` might be `{ [id]: url }`. resolve({ [id]: result.url }); } catch (err) { reject(err); } }, 'image/png'); } catch (err) { reject(err); } }; img.onerror = (err) => { reject(new Error('Image load failed')); }; }); }; /** 提交 */ export function usePageLayoutSubmit() { const submmitLoading = ref(false); async function pageLayoutSubmit(currBookPage: CurrBookPageAllInfo) { try { submmitLoading.value = true; const urlObj: Record = {}; currBookPage!.currBookPageData.questions.forEach((item) => { if (item.id) { urlObj[item.id!] = item; } }); // 获取阿里云OSS STS const aliOssSTS = await getAliOssTokenAxios(); const client = initOSSClient(aliOssSTS); // 截取图片并上传 (5个并发) const imageFiles: QuestionsImages[] = []; const limit = pLimit(5); const tasks: Promise[] = []; currBookPage!.currBookPageData.layout.forEach((item) => { const question: QuestionInfo = item.question; if (currBookPage!.currBookPageData.url) { const cropImageOptions: CropImageOptions = { x: question.x, y: question.y, width: question.w, height: question.h }; const url = currBookPage!.currBookPageData.url; //截取图片并添加到上传队列 tasks.push(limit(() => cropImage(url, client, cropImageOptions, item.questionId!))); } }); const list = await Promise.all(tasks); imageFiles.push(...list); currBookPage!.currBookPageData.layout = currBookPage!.currBookPageData.layout.sort((a, b) => { return compareVersion(a.question.no, b.question.no, '-'); }); await updateBookPageLayoutAxios({ id: currBookPage!.currBookPageData!.bookPageId!, layout: JSON.stringify(currBookPage!.currBookPageData.layout), questionsImages: imageFiles, }); submmitLoading.value = false; return Promise.resolve(); } catch (error) { console.log('error====', error); submmitLoading.value = false; return Promise.reject(error); } } return { pageLayoutSubmit, submmitLoading, }; } export function getPDFPageWidth(width: number) { return width; }