139 lines
4.7 KiB
TypeScript
139 lines
4.7 KiB
TypeScript
|
|
|
|||
|
|
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<QuestionsImages> => {
|
|||
|
|
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<number, BookPageQuestion> = {};
|
|||
|
|
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<QuestionsImages>[] = [];
|
|||
|
|
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;
|
|||
|
|
}
|