jack 12 hours ago
parent
commit
74d2ef904c

+ 67 - 66
src/hooks/useImport.ts

@@ -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: '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',
       } // 秘钥形式的登录上传
-      
+
       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 => {

+ 2 - 1
src/types/slides.ts

@@ -368,7 +368,8 @@ export interface PPTShapeElement extends PPTBaseElement {
   special?: boolean
   text?: ShapeText
   pathFormula?: ShapePathFormulasKeys
-  keypoints?: number[]
+  keypoints?: number[],
+  pathBBox?: object
 }
 
 

+ 5 - 1
src/views/components/element/ShapeElement/BaseShapeElement.vue

@@ -142,10 +142,14 @@ const text = computed<ShapeText>(() => {
   word-break: break-word;
 
   &.top {
-    justify-content: flex-start;
+    justify-content: space-around;
   }
   &.middle {
     justify-content: center;
+    left: 50%;
+    top: 50%;
+    -webkit-transform: translate(-50%,-50%);
+    transform: translate(-50%,-50%);
   }
   &.bottom {
     justify-content: flex-end;

+ 1 - 1
src/views/components/element/ShapeElement/index.vue

@@ -248,7 +248,7 @@ const startEdit = () => {
   }
 
   &.top {
-    justify-content: flex-start;
+    justify-content: space-around;
   }
   &.middle {
     justify-content: center;

+ 1 - 1
src/views/components/element/TextElement/index.vue

@@ -239,7 +239,7 @@ watch(isHandleElement, () => {
   }
 
   &.top {
-    justify-content: flex-start;
+    justify-content: space-around;
   }
   &.middle {
     justify-content: center;