|
|
@@ -90,7 +90,7 @@ export default () => {
|
|
|
const readJSON = (jsonData: string | any, cover = false) => {
|
|
|
try {
|
|
|
console.log('readJSON 开始执行:', { jsonData, cover })
|
|
|
-
|
|
|
+
|
|
|
let parsedData
|
|
|
if (typeof jsonData === 'string') {
|
|
|
parsedData = JSON.parse(jsonData)
|
|
|
@@ -99,7 +99,7 @@ export default () => {
|
|
|
else {
|
|
|
parsedData = jsonData
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 提取所有可能的数据
|
|
|
const slides = parsedData.slides || parsedData
|
|
|
const title = parsedData.title
|
|
|
@@ -107,9 +107,9 @@ export default () => {
|
|
|
const width = parsedData.width
|
|
|
const height = parsedData.height
|
|
|
const viewportRatio = parsedData.viewportRatio || (height && width ? height / width : undefined)
|
|
|
-
|
|
|
+
|
|
|
console.log('提取的数据:', { slides: slides.length, title, theme, width, height, viewportRatio })
|
|
|
-
|
|
|
+
|
|
|
// 更新幻灯片数据
|
|
|
if (cover) {
|
|
|
console.log('覆盖模式:更新幻灯片数据')
|
|
|
@@ -126,24 +126,24 @@ export default () => {
|
|
|
console.log('添加模式:添加幻灯片数据')
|
|
|
addSlidesFromData(slides)
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 同步更新其他相关内容
|
|
|
if (title !== undefined) {
|
|
|
console.log('正在更新标题:', title)
|
|
|
slidesStore.setTitle(title)
|
|
|
console.log('标题更新完成')
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
if (theme !== undefined) {
|
|
|
console.log('正在更新主题:', theme)
|
|
|
slidesStore.setTheme(theme)
|
|
|
console.log('主题更新完成')
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 更新视口尺寸(如果提供了的话)
|
|
|
if (width !== undefined && height !== undefined) {
|
|
|
console.log('正在触发视口尺寸更新事件:', { width, height, viewportRatio })
|
|
|
-
|
|
|
+
|
|
|
// 同时也要更新slidesStore中的相关数据
|
|
|
if (slidesStore.setViewportSize) {
|
|
|
console.log('正在更新store中的视口尺寸')
|
|
|
@@ -153,22 +153,22 @@ export default () => {
|
|
|
console.log('视口比例已更新:', viewportRatio)
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
- window.dispatchEvent(new CustomEvent('viewportSizeUpdated', {
|
|
|
+
|
|
|
+ window.dispatchEvent(new CustomEvent('viewportSizeUpdated', {
|
|
|
detail: { width, height, viewportRatio }
|
|
|
}))
|
|
|
console.log('视口尺寸更新事件已触发')
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 导入成功后,触发画布尺寸更新
|
|
|
// 使用 nextTick 确保DOM更新完成后再触发
|
|
|
console.log('开始触发画布尺寸更新事件...')
|
|
|
nextTick(() => {
|
|
|
console.log('DOM更新完成,触发 slidesDataUpdated 事件')
|
|
|
// 触发自定义事件,通知需要更新画布尺寸的组件
|
|
|
- window.dispatchEvent(new CustomEvent('slidesDataUpdated', {
|
|
|
- detail: {
|
|
|
- slides,
|
|
|
+ window.dispatchEvent(new CustomEvent('slidesDataUpdated', {
|
|
|
+ detail: {
|
|
|
+ slides,
|
|
|
cover,
|
|
|
title,
|
|
|
theme,
|
|
|
@@ -176,10 +176,10 @@ export default () => {
|
|
|
height,
|
|
|
viewportRatio,
|
|
|
timestamp: Date.now()
|
|
|
- }
|
|
|
+ }
|
|
|
}))
|
|
|
console.log('slidesDataUpdated 事件已触发')
|
|
|
-
|
|
|
+
|
|
|
// 检查并调整幻灯片索引,确保在有效范围内
|
|
|
const newSlideCount = slides.length
|
|
|
const currentIndex = slidesStore.slideIndex
|
|
|
@@ -187,10 +187,10 @@ export default () => {
|
|
|
console.log('调整幻灯片索引:', currentIndex, '->', Math.max(0, newSlideCount - 1))
|
|
|
slidesStore.updateSlideIndex(Math.max(0, newSlideCount - 1))
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
console.log('画布尺寸更新事件处理完成')
|
|
|
})
|
|
|
-
|
|
|
+
|
|
|
console.log('readJSON 执行成功')
|
|
|
return { success: true, slides, title, theme, width, height, viewportRatio }
|
|
|
}
|
|
|
@@ -249,46 +249,46 @@ export default () => {
|
|
|
|
|
|
const rotateLine = (line: PPTLineElement, angleDeg: number) => {
|
|
|
const { start, end } = line
|
|
|
-
|
|
|
+
|
|
|
const angleRad = angleDeg * Math.PI / 180
|
|
|
-
|
|
|
+
|
|
|
const midX = (start[0] + end[0]) / 2
|
|
|
const midY = (start[1] + end[1]) / 2
|
|
|
-
|
|
|
+
|
|
|
const startTransX = start[0] - midX
|
|
|
const startTransY = start[1] - midY
|
|
|
const endTransX = end[0] - midX
|
|
|
const endTransY = end[1] - midY
|
|
|
-
|
|
|
+
|
|
|
const cosA = Math.cos(angleRad)
|
|
|
const sinA = Math.sin(angleRad)
|
|
|
-
|
|
|
+
|
|
|
const startRotX = startTransX * cosA - startTransY * sinA
|
|
|
const startRotY = startTransX * sinA + startTransY * cosA
|
|
|
-
|
|
|
+
|
|
|
const endRotX = endTransX * cosA - endTransY * sinA
|
|
|
const endRotY = endTransX * sinA + endTransY * cosA
|
|
|
-
|
|
|
+
|
|
|
const startNewX = startRotX + midX
|
|
|
const startNewY = startRotY + midY
|
|
|
const endNewX = endRotX + midX
|
|
|
const endNewY = endRotY + midY
|
|
|
-
|
|
|
+
|
|
|
const beforeMinX = Math.min(start[0], end[0])
|
|
|
const beforeMinY = Math.min(start[1], end[1])
|
|
|
-
|
|
|
+
|
|
|
const afterMinX = Math.min(startNewX, endNewX)
|
|
|
const afterMinY = Math.min(startNewY, endNewY)
|
|
|
-
|
|
|
+
|
|
|
const startAdjustedX = startNewX - afterMinX
|
|
|
const startAdjustedY = startNewY - afterMinY
|
|
|
const endAdjustedX = endNewX - afterMinX
|
|
|
const endAdjustedY = endNewY - afterMinY
|
|
|
-
|
|
|
+
|
|
|
const startAdjusted: [number, number] = [startAdjustedX, startAdjustedY]
|
|
|
const endAdjusted: [number, number] = [endAdjustedX, endAdjustedY]
|
|
|
const offset = [afterMinX - beforeMinX, afterMinY - beforeMinY]
|
|
|
-
|
|
|
+
|
|
|
return {
|
|
|
start: startAdjusted,
|
|
|
end: endAdjusted,
|
|
|
@@ -368,7 +368,7 @@ export default () => {
|
|
|
|
|
|
if (axis === 'y') newElement.left = 2 * centerX - element.left - element.width
|
|
|
if (axis === 'x') newElement.top = 2 * centerY - element.top - element.height
|
|
|
-
|
|
|
+
|
|
|
return newElement
|
|
|
})
|
|
|
}
|
|
|
@@ -426,11 +426,11 @@ export default () => {
|
|
|
options?: { tolerance?: number }
|
|
|
): Promise<File> => {
|
|
|
const tolerance = options?.tolerance ?? 30; // 容差值,控制哪些颜色被视为白色
|
|
|
-
|
|
|
+
|
|
|
// 1. 将输入数据统一为 Blob 或可直接用于加载的 URL
|
|
|
let imageUrl: string;
|
|
|
let blob: Blob;
|
|
|
-
|
|
|
+
|
|
|
if (typeof data === 'string') {
|
|
|
// 如果是 Base64,直接用作 src(data URL)
|
|
|
imageUrl = data.startsWith('data:') ? data : `data:image/png;base64,${data}`;
|
|
|
@@ -441,7 +441,7 @@ export default () => {
|
|
|
} else {
|
|
|
throw new Error('Unsupported data type');
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 2. 加载图像到 Image 元素
|
|
|
const img = await new Promise<HTMLImageElement>((resolve, reject) => {
|
|
|
const image = new Image();
|
|
|
@@ -451,42 +451,42 @@ export default () => {
|
|
|
// 如果图像来自跨域,可能需要设置 crossOrigin
|
|
|
// image.crossOrigin = 'anonymous';
|
|
|
});
|
|
|
-
|
|
|
+
|
|
|
// 3. 创建 Canvas 并绘制图像
|
|
|
const canvas = document.createElement('canvas');
|
|
|
canvas.width = img.width;
|
|
|
canvas.height = img.height;
|
|
|
const ctx = canvas.getContext('2d')!;
|
|
|
ctx.drawImage(img, 0, 0);
|
|
|
-
|
|
|
+
|
|
|
// 4. 获取像素数据并处理
|
|
|
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
|
const dataArray = imageData.data;
|
|
|
-
|
|
|
+
|
|
|
for (let i = 0; i < dataArray.length; i += 4) {
|
|
|
const r = dataArray[i];
|
|
|
const g = dataArray[i + 1];
|
|
|
const b = dataArray[i + 2];
|
|
|
-
|
|
|
+
|
|
|
// 判断颜色是否接近白色(RGB 都大于 255 - tolerance)
|
|
|
if (r > 255 - tolerance && g > 255 - tolerance && b > 255 - tolerance) {
|
|
|
dataArray[i + 3] = 0; // 设置 Alpha 为 0(完全透明)
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 5. 将修改后的像素放回 Canvas
|
|
|
ctx.putImageData(imageData, 0, 0);
|
|
|
-
|
|
|
+
|
|
|
// 6. 将 Canvas 转换为 PNG Blob
|
|
|
const outputBlob = await new Promise<Blob>((resolve) =>
|
|
|
canvas.toBlob((blob) => resolve(blob!), 'image/png')
|
|
|
);
|
|
|
-
|
|
|
+
|
|
|
// 7. 清理对象 URL(如果之前创建过)
|
|
|
if (typeof data !== 'string') {
|
|
|
URL.revokeObjectURL(imageUrl);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 8. 返回 File 对象
|
|
|
return new File([outputBlob], filename, { type: 'image/png' });
|
|
|
};
|
|
|
@@ -513,7 +513,7 @@ export default () => {
|
|
|
const key = `${file.name.split('.')[0]}_${Date.now()}.${ext}`;
|
|
|
|
|
|
const params = {
|
|
|
- Key: "pptto/"+key,
|
|
|
+ Key: "pptto/" + key,
|
|
|
ContentType: file.type,
|
|
|
Body: file,
|
|
|
ACL: 'public-read',
|
|
|
@@ -1276,7 +1276,7 @@ export default () => {
|
|
|
fixedRatio: true,
|
|
|
rotate: 0,
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 如果 src 是 base64,触发上传
|
|
|
if (el.src && typeof el.src === 'string' && el.src.startsWith('data:')) {
|
|
|
const uploadTask = (async () => {
|
|
|
@@ -1291,7 +1291,7 @@ export default () => {
|
|
|
})();
|
|
|
uploadTasks.push(uploadTask);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
|
|
|
slide.elements.push(element)
|
|
|
|
|
|
@@ -1364,7 +1364,7 @@ export default () => {
|
|
|
|
|
|
slide.elements.push(element);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
|
|
|
// ---------- 形状 ----------
|
|
|
else if (el.type === 'shape') {
|
|
|
@@ -1383,18 +1383,18 @@ export default () => {
|
|
|
|
|
|
const gradient: Gradient | undefined = el.fill?.type === 'gradient'
|
|
|
? {
|
|
|
- type: el.fill.value.path === 'line' ? 'linear' : 'radial',
|
|
|
- colors: el.fill.value.colors.map(item => ({
|
|
|
- ...item,
|
|
|
- pos: parseInt(item.pos),
|
|
|
- })),
|
|
|
- rotate: el.fill.value.rot,
|
|
|
- }
|
|
|
+ type: el.fill.value.path === 'line' ? 'linear' : 'radial',
|
|
|
+ colors: el.fill.value.colors.map(item => ({
|
|
|
+ ...item,
|
|
|
+ pos: parseInt(item.pos),
|
|
|
+ })),
|
|
|
+ rotate: el.fill.value.rot,
|
|
|
+ }
|
|
|
: undefined;
|
|
|
|
|
|
const pattern: string | undefined = el.fill?.type === 'image' ? el.fill.value.picBase64 : undefined;
|
|
|
const fill = el.fill?.type === 'color' ? el.fill.value : '';
|
|
|
-
|
|
|
+ let style = getStyle(convertFontSizePtToPx(el.content, ratio)) + (el.pathBBox.pWidth ? ";width:" + (el.pathBBox.pWidth) + "px;height:" + (el.pathBBox.pHeight) + "px;" : "") //设置字体的样式等,这里由于不支持的样式在里面会过滤
|
|
|
const element: PPTShapeElement = {
|
|
|
type: 'shape',
|
|
|
id: nanoid(10),
|
|
|
@@ -1409,6 +1409,7 @@ export default () => {
|
|
|
pattern,
|
|
|
fixedRatio: false,
|
|
|
rotate: el.rotate,
|
|
|
+ pathBBox: el.pathBBox,
|
|
|
outline: {
|
|
|
color: el.borderColor,
|
|
|
width: +(el.borderWidth * ratio).toFixed(2),
|
|
|
@@ -1416,7 +1417,7 @@ export default () => {
|
|
|
},
|
|
|
text: {
|
|
|
content: convertFontSizePtToPx(el.content, ratio),
|
|
|
- style: getStyle(convertFontSizePtToPx(el.content, ratio)),
|
|
|
+ style: style,
|
|
|
defaultFontName: theme.value.fontName,
|
|
|
defaultColor: theme.value.fontColor,
|
|
|
align: vAlignMap[el.vAlign] || 'middle',
|
|
|
@@ -1455,7 +1456,7 @@ export default () => {
|
|
|
else if (el.path && el.path.indexOf('NaN') === -1) {
|
|
|
const { maxX, maxY } = getSvgPathRange(el.path);
|
|
|
element.path = el.path;
|
|
|
- element.viewBox = poriginWidth? [maxX, maxY]: [originWidth, originHeight];
|
|
|
+ element.viewBox = poriginWidth ? [maxX, maxY] : [originWidth, originHeight];
|
|
|
//element.viewBox = [originWidth || maxX, originHeight || maxY];
|
|
|
//element.viewBox = originWidth? [(originWidth/(poriginWidth||1)), (originHeight/(poriginHeight||1))] : [maxX, maxY];
|
|
|
//element.viewBox = [poriginWidth || maxX, poriginHeight || maxY];
|
|
|
@@ -1471,7 +1472,7 @@ export default () => {
|
|
|
element.path = el.path!;
|
|
|
}
|
|
|
const { maxX, maxY } = getSvgPathRange(element.path);
|
|
|
- element.viewBox = [maxX, maxY];
|
|
|
+ element.viewBox = poriginWidth ? [maxX, maxY] : [originWidth, originHeight];
|
|
|
//element.viewBox = [originWidth || maxX, originHeight || maxY];
|
|
|
//element.viewBox = [poriginWidth || originWidth || maxX, poriginHeight || originHeight || maxY];
|
|
|
//element.viewBox = [poriginWidth || originWidth || maxX, poriginHeight || originHeight || maxY];
|
|
|
@@ -1782,33 +1783,33 @@ export default () => {
|
|
|
accessKeyId: 'AKIATLPEDU37QV5CHLMH',
|
|
|
secretAccessKey: 'Q2SQw37HfolS7yeaR1Ndpy9Jl4E2YZKUuuy2muZR',
|
|
|
} // 秘钥形式的登录上传
|
|
|
-
|
|
|
+
|
|
|
window.AWS.config.update(credentials)
|
|
|
window.AWS.config.region = 'cn-northwest-1' // 设置区域
|
|
|
-
|
|
|
+
|
|
|
const s3 = new window.AWS.S3({ params: { Bucket: 'ccrb' } })
|
|
|
-
|
|
|
+
|
|
|
// 解析文件名
|
|
|
const bucketUrl = 'https://ccrb.s3.cn-northwest-1.amazonaws.com.cn/'
|
|
|
if (!url.startsWith(bucketUrl)) {
|
|
|
reject(new Error('Invalid S3 URL format'))
|
|
|
return
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
const name = decodeURIComponent(url.split(bucketUrl)[1])
|
|
|
// const name = url.split(bucketUrl)[1]
|
|
|
console.log('aws-name:', name)
|
|
|
-
|
|
|
+
|
|
|
if (!name) {
|
|
|
reject(new Error('Could not extract file name from URL'))
|
|
|
return
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
const params = {
|
|
|
Bucket: 'ccrb',
|
|
|
Key: name,
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
s3.getObject(params, (err: any, data: any) => {
|
|
|
if (err) {
|
|
|
console.error('S3 getObject error:', err, err.stack)
|
|
|
@@ -1822,11 +1823,11 @@ export default () => {
|
|
|
})
|
|
|
}
|
|
|
|
|
|
-
|
|
|
+
|
|
|
const getFile2 = (url: string): Promise<{ data: any }> => {
|
|
|
return new Promise((resolve, reject) => {
|
|
|
console.log('直接使用原始 URL 获取文件:', url)
|
|
|
-
|
|
|
+
|
|
|
// 直接使用 fetch 获取文件,浏览器会自动处理 URL 解码
|
|
|
fetch(url)
|
|
|
.then(response => {
|