浏览代码

feat(editor): 新增图片剪裁预览功能

- 添加剪裁预览数据结构,包括最大偏移量、尺寸和半径
- 实现剪裁预览图生成逻辑,支持缩略图展示
- 新增切换剪裁形状时的自动填充默认值功能
- 更新剪裁区域变化时的实时预览机制
- 在界面中增加位置和尺寸的数值显示
- 修改剪裁形状选择器绑定事件为 onClipShapeChange
panqiuyao 6 天之前
父节点
当前提交
a361b39f90

+ 68 - 0
frontend/src/views/components/PictureEditor/mixin/edit/index.js

@@ -31,6 +31,14 @@ export default  {
       options:TextboxConfig.fontFamily,
       fontFamilyStyle:"",
       opacityValue: 1,
+      clipPreview: {
+        dataUrl: '',
+        maxOffsetX: 0,
+        maxOffsetY: 0,
+        maxWidth: 0,
+        maxHeight: 0,
+        maxRadius: 0,
+      },
       layerState: {
         fontSize: 16,
         lineHeight: 1.2,
@@ -135,6 +143,7 @@ export default  {
           if (this.editLayer.strokeObj) {
             this.fcanvas.add(markRaw(this.editLayer.strokeObj));
           }
+          this.updateClipPreview && this.updateClipPreview()
           break;
       }
     },
@@ -407,11 +416,70 @@ export default  {
         this.updateClipStroke()
         this.updateCanvasState();
         this.fcanvas.requestRenderAll();
+        this.updateClipPreview && this.updateClipPreview(true)
       } catch (error) {
         console.error('剪裁区域创建失败:', error);
         this.$message.error('剪裁设置错误: ' + error.message);
       }
     },
+    // 切换剪裁形状,自动填充默认值(位置跟随图层,尺寸为画布一半)
+    onClipShapeChange(shape){
+      if(!this.editLayer || this.editLayer.type !== 'image'){
+        this.clipSettings.shape = shape
+        return
+      }
+      if(!shape){
+        this.clipSettings.shape = ''
+        this.clearClipPath()
+        this.updateClipPreview && this.updateClipPreview(true)
+        return
+      }
+      const obj = this.editLayer
+      const bounds = obj.getBoundingRect(true)
+      const canvasW = this.fcanvas?.width || obj.canvas?.width || 800
+      const canvasH = this.fcanvas?.height || obj.canvas?.height || 800
+      const initWidth = Math.max(10, Math.round(canvasW / 2))
+      const initHeight = Math.max(10, Math.round(canvasH / 2))
+      const initRadius = Math.max(5, Math.round(Math.min(canvasW, canvasH) / 4))
+      const initSettings = {
+        shape,
+        offsetX: Math.round(bounds.left),
+        offsetY: Math.round(bounds.top),
+        width: initWidth,
+        height: initHeight,
+        radius: initRadius,
+        rectRadius: this.clipSettings.rectRadius || 0,
+      }
+      this.clipSettings = { ...this.clipSettings, ...initSettings }
+      this.applyClipPath()
+    },
+    // 预览辅助,若用户保持旧样式可忽略 dataUrl
+    updateClipPreview(force=false){
+      if(!this.editLayer || this.editLayer.type !== 'image') return
+      const obj = this.editLayer
+      const bounds = obj.getBoundingRect(true)
+      this.clipPreview = {
+        ...this.clipPreview,
+        maxOffsetX: Math.max(0, Math.floor(bounds.width)),
+        maxOffsetY: Math.max(0, Math.floor(bounds.height)),
+        maxWidth: Math.max(10, Math.floor(bounds.width)),
+        maxHeight: Math.max(10, Math.floor(bounds.height)),
+        maxRadius: Math.max(5, Math.floor(Math.min(bounds.width, bounds.height)/2)),
+      }
+      if(force || this.clipPreview.dataUrl === ''){
+        try{
+          const cloned = obj.clone()
+          const tmp = new fabric.Canvas(document.createElement('canvas'), { width: Math.min(200, bounds.width), height: Math.min(200, bounds.height) })
+          cloned.scaleToWidth(tmp.width)
+          tmp.add(cloned)
+          tmp.renderAll()
+          this.clipPreview.dataUrl = tmp.toDataURL({ format:'png', multiplier:1 })
+          tmp.dispose()
+        }catch(e){
+          console.warn('clip preview failed', e)
+        }
+      }
+    },
     updateClipStroke() {
       if (!this.judge() || !this.clipSettings.showClipStroke) return;
 

+ 9 - 1
frontend/src/views/components/PictureEditor/mixin/edit/module/tools.js

@@ -40,7 +40,7 @@ cutout(){
   return `
     <div class="title_two">剪裁</div>
     <div class="flex left">
-      <el-select v-model="clipSettings.shape" @change="applyClipPath" size="small" style="width:100px">
+      <el-select v-model="clipSettings.shape" @change="onClipShapeChange" size="small" style="width:100px">
         <el-option label="无" value=""></el-option>
         <el-option label="矩形" value="rect"></el-option>
         <el-option label="圆形" value="circle"></el-option>
@@ -63,6 +63,7 @@ cutout(){
       
       
       
+        <div class="title_two">位置</div>
       <div  class="flex left mar-top-10 ">
             <div class="label fs-14 c-333 te-l"  style="width: 35px;">X</div>
                 <el-slider
@@ -76,6 +77,8 @@ cutout(){
                    @input="applyClipPath"
 
                 ></el-slider>
+                <span class="mar-left-10">{{clipSettings.offsetX}}</span>
+                
       </div>
       
       <div  class="flex left mar-top-10 ">
@@ -91,6 +94,7 @@ cutout(){
                    @input="applyClipPath"
 
                 ></el-slider>
+                <span class="mar-left-10">{{clipSettings.offsetY}}</span>
       </div>
       <div v-if="clipSettings.shape === 'svg'" class="flex left" style="margin-left:10px">
       <el-input-number v-model="clipSettings.svgWidth" @input="applyClipPath" :step="1" :min="1" size="small" style="width:80px"/>
@@ -99,6 +103,7 @@ cutout(){
     <!-- 矩形参数 -->
     
     <template v-if="clipSettings.shape === 'rect'">
+        <div class="title_two">大小</div>
       <div  class="flex left mar-top-10 ">
         <div class="label fs-14 c-333 te-l" style="width: 35px;">宽</div>
         
@@ -113,6 +118,7 @@ cutout(){
                    @input="applyClipPath"
 
                 ></el-slider>
+                <span class="mar-left-10">{{clipSettings.width}}</span>
         
       </div>
       <div  class="flex left mar-top-10 ">
@@ -129,6 +135,7 @@ cutout(){
                    @input="applyClipPath"
 
                 ></el-slider>
+                <span class="mar-left-10">{{clipSettings.height}}</span>
       </div>
       <div  class="flex left mar-top-10 ">
         <div class="label fs-14 c-333 te-l"  style="width: 35px;">圆角</div>
@@ -151,6 +158,7 @@ cutout(){
                    @input="applyClipPath"
 
                 ></el-slider>
+                <span class="mar-left-10">{{clipSettings.radius}}</span>
       </div>
 
     <!-- 描边参数 -->