Upload useExport.ts
Browse files- frontend/src/hooks/useExport.ts +105 -3
frontend/src/hooks/useExport.ts
CHANGED
@@ -818,9 +818,32 @@ export default () => {
|
|
818 |
return isSVGBase64 || isSVGUrl
|
819 |
}
|
820 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
821 |
// 导出PPTX文件
|
822 |
-
const exportPPTX = (_slides: Slide[], masterOverwrite: boolean, ignoreMedia: boolean) => {
|
823 |
exporting.value = true
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
824 |
const pptx = new pptxgen()
|
825 |
|
826 |
if (viewportRatio.value === 0.625) pptx.layout = 'LAYOUT_16x10'
|
@@ -974,8 +997,87 @@ export default () => {
|
|
974 |
else if (el.type === 'shape') {
|
975 |
if (el.special) {
|
976 |
const svgRef = document.querySelector(`.thumbnail-list .base-element-${el.id} svg`) as HTMLElement
|
977 |
-
|
978 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
979 |
|
980 |
const options: pptxgen.ImageProps = {
|
981 |
data: base64SVG,
|
|
|
818 |
return isSVGBase64 || isSVGUrl
|
819 |
}
|
820 |
|
821 |
+
// 确保元素已渲染的辅助函数
|
822 |
+
const ensureElementsRendered = async () => {
|
823 |
+
return new Promise<void>((resolve) => {
|
824 |
+
// 强制重绘
|
825 |
+
document.body.offsetHeight;
|
826 |
+
|
827 |
+
// 等待下一个动画帧
|
828 |
+
requestAnimationFrame(() => {
|
829 |
+
requestAnimationFrame(() => {
|
830 |
+
resolve();
|
831 |
+
});
|
832 |
+
});
|
833 |
+
});
|
834 |
+
};
|
835 |
+
|
836 |
// 导出PPTX文件
|
837 |
+
const exportPPTX = async (_slides: Slide[], masterOverwrite: boolean, ignoreMedia: boolean) => {
|
838 |
exporting.value = true
|
839 |
+
|
840 |
+
try {
|
841 |
+
// 确保所有元素已渲染
|
842 |
+
await ensureElementsRendered();
|
843 |
+
} catch (error) {
|
844 |
+
console.warn('Failed to ensure elements rendered:', error);
|
845 |
+
}
|
846 |
+
|
847 |
const pptx = new pptxgen()
|
848 |
|
849 |
if (viewportRatio.value === 0.625) pptx.layout = 'LAYOUT_16x10'
|
|
|
997 |
else if (el.type === 'shape') {
|
998 |
if (el.special) {
|
999 |
const svgRef = document.querySelector(`.thumbnail-list .base-element-${el.id} svg`) as HTMLElement
|
1000 |
+
|
1001 |
+
// 改进的尺寸检查逻辑
|
1002 |
+
if (!svgRef) {
|
1003 |
+
console.warn(`SVG element not found for shape ${el.id}`);
|
1004 |
+
continue;
|
1005 |
+
}
|
1006 |
+
|
1007 |
+
// 获取多种尺寸信息进行判断
|
1008 |
+
const clientWidth = svgRef.clientWidth;
|
1009 |
+
const clientHeight = svgRef.clientHeight;
|
1010 |
+
const boundingRect = svgRef.getBoundingClientRect();
|
1011 |
+
const computedStyle = window.getComputedStyle(svgRef);
|
1012 |
+
|
1013 |
+
// 更智能的尺寸判断
|
1014 |
+
const hasValidSize = (
|
1015 |
+
(clientWidth > 0 && clientHeight > 0) ||
|
1016 |
+
(boundingRect.width > 0 && boundingRect.height > 0) ||
|
1017 |
+
(parseFloat(computedStyle.width) > 0 && parseFloat(computedStyle.height) > 0)
|
1018 |
+
);
|
1019 |
+
|
1020 |
+
if (!hasValidSize) {
|
1021 |
+
console.warn(`Invalid SVG dimensions for shape ${el.id}:`, {
|
1022 |
+
clientWidth,
|
1023 |
+
clientHeight,
|
1024 |
+
boundingRect: { width: boundingRect.width, height: boundingRect.height },
|
1025 |
+
computedStyle: { width: computedStyle.width, height: computedStyle.height }
|
1026 |
+
});
|
1027 |
+
|
1028 |
+
// 降级到普通形状处理
|
1029 |
+
console.info(`Falling back to path-based export for shape ${el.id}`);
|
1030 |
+
const scale = {
|
1031 |
+
x: el.width / el.viewBox[0],
|
1032 |
+
y: el.height / el.viewBox[1],
|
1033 |
+
};
|
1034 |
+
const points = formatPoints(toPoints(el.path), scale);
|
1035 |
+
|
1036 |
+
let fillColor = formatColor(el.fill);
|
1037 |
+
if (el.gradient) {
|
1038 |
+
const colors = el.gradient.colors;
|
1039 |
+
const color1 = colors[0].color;
|
1040 |
+
const color2 = colors[colors.length - 1].color;
|
1041 |
+
const color = tinycolor.mix(color1, color2).toHexString();
|
1042 |
+
fillColor = formatColor(color);
|
1043 |
+
}
|
1044 |
+
if (el.pattern) fillColor = formatColor('#00000000');
|
1045 |
+
const opacity = el.opacity === undefined ? 1 : el.opacity;
|
1046 |
+
|
1047 |
+
const fallbackOptions: pptxgen.ShapeProps = {
|
1048 |
+
x: el.left / ratioPx2Inch.value,
|
1049 |
+
y: el.top / ratioPx2Inch.value,
|
1050 |
+
w: el.width / ratioPx2Inch.value,
|
1051 |
+
h: el.height / ratioPx2Inch.value,
|
1052 |
+
fill: { color: fillColor.color, transparency: (1 - fillColor.alpha * opacity) * 100 },
|
1053 |
+
points,
|
1054 |
+
};
|
1055 |
+
|
1056 |
+
if (el.flipH) fallbackOptions.flipH = el.flipH;
|
1057 |
+
if (el.flipV) fallbackOptions.flipV = el.flipV;
|
1058 |
+
if (el.shadow) fallbackOptions.shadow = getShadowOption(el.shadow);
|
1059 |
+
if (el.outline?.width) fallbackOptions.line = getOutlineOption(el.outline);
|
1060 |
+
if (el.rotate) fallbackOptions.rotate = el.rotate;
|
1061 |
+
if (el.link) {
|
1062 |
+
const linkOption = getLinkOption(el.link);
|
1063 |
+
if (linkOption) fallbackOptions.hyperlink = linkOption;
|
1064 |
+
}
|
1065 |
+
|
1066 |
+
pptxSlide.addShape('custGeom' as pptxgen.ShapeType, fallbackOptions);
|
1067 |
+
continue;
|
1068 |
+
}
|
1069 |
+
|
1070 |
+
// SVG序列化with错误处理
|
1071 |
+
let base64SVG;
|
1072 |
+
try {
|
1073 |
+
base64SVG = svg2Base64(svgRef);
|
1074 |
+
if (!base64SVG || base64SVG === 'data:image/svg+xml;base64,') {
|
1075 |
+
throw new Error('SVG serialization returned empty result');
|
1076 |
+
}
|
1077 |
+
} catch (error) {
|
1078 |
+
console.error(`SVG serialization failed for shape ${el.id}:`, error);
|
1079 |
+
continue;
|
1080 |
+
}
|
1081 |
|
1082 |
const options: pptxgen.ImageProps = {
|
1083 |
data: base64SVG,
|