image.png
<script lang="ts" setup>
import { Plus, Close, ArrowLeftBold } from '@element-plus/icons-vue'
import { onMounted, ref, reactive, onUnmounted, nextTick, unref, computed } from 'vue'
import { calculateScaledDimensions } from '@/hooks/web/usePictureSize'
import { ImageCropping } from '@/components/ImageCroppings/index'
import { ElMessage, ElMessageBox, useZIndex, ElTooltip } from 'element-plus'
import { updatePoster } from '@/api/xr'
import Compressor from 'compressorjs'
import { useStorage } from '@/hooks/web/useStorage'
import {
getCavasList,
generateMaterials,
getHbImg,
addGenerateRecords,
generateInpaint,
uploadImages,
getPostersInfo,
getRecentPosters,
renamePosters,
getPosterInfo,
objectSegmentation,
matting,
generateExtend
} from '@/api/xr'
import * as fabric from 'fabric'
// import { fabric } from 'fabric'
// import { FabricImage } from 'fabric'
import { ArcText } from '@/extension/object/ArcText'
import { VerticalText } from '@/extension/object/VerticalText'

import generateRecord from '@/views/index/generateRecord.vue'
import leftTool from '@/views/index/leftTool.vue'
import hbTopTool from '@/views/index/hbTopTool.vue'
import proportionalValue from '@/views/index/proportionalValue.vue'
import { useRoute, useRouter } from 'vue-router'
const route = useRoute()
const { push } = useRouter()
import hbBottomTool from '@/views/index/hbBottomTool.vue'
import hbCreate from '@/views/index/hbCreate.vue'
import generateList from '@/views/index/generateList.vue'
import { throttle, debounce } from 'lodash'
const contrastImgRef2 = ref<InstanceType<typeof contrastImg2>>()
import contrastImg2 from '@/components/contrastImg/index2.vue'
import { v4 as uuidv4 } from 'uuid'
const canvasContainerRef = ref<HTMLElement>()
const parentDragResizRef = ref<HTMLElement>()
const canvasRef = ref<HTMLElement>()
const hbTopToolHtml = ref<any>(null)
const leftToolHtml = ref<any>(null)
const GenerateRecord = ref<any>(null)
const generateListHtml = ref<any>(null)
const hbCreateRef = ref<any>(null)
const enlargeSceneMatchRef = ref<any>(null)
const { getStorage, setStorage } = useStorage('localStorage')
import EnlargeSceneMatch from '@/views/index/EnlargeSceneMatch.vue' //一键扩景展示区
const state = reactive({
brushInfo: {} as any,
btnText: '素材生成',
contentPlaceholder: '描述你的素材创意。如:海边、日落、闪电、流星...',
gloLoading: false,
gloLoading2: false,
boxWidth: 500,
boxHeight: 500,
isChildActive: true,
toolbar: [

{
  id: 1,
  name: '自由模式',
  aspectRatio: '1'
},
{
  id: 2,
  name: '原始比例',
  aspectRatio: '1'
},
{
  id: 3,
  name: '1:1',
  aspectRatio: '1/1'
},
{
  id: 4,
  name: '9:16',
  aspectRatio: '9/16'
},
{
  id: 5,
  name: '16:9',
  aspectRatio: '16/9'
},
{
  id: 6,
  name: '3:4',
  aspectRatio: '3/4'
},
{
  id: 7,
  name: '4:3',
  aspectRatio: '4/3'
}

],
toolbarActive: 0,
dialogdraggableResizable: false,
parentModeType: 1,
history: [] as any,
parentResultType: 0, //0:刚进入优化页面;1:打开消除笔;2:点击消除;3:点击修改图像
drawnPaths: [] as any, //笔画线条路径坐标点
imageData: '',
genarateHbArr: [] as any[], //记录文生图历史数据
brushComplete: false,
lastInpaintParams: {} as any, //记录上一次重绘的参数
generateName: '',
inpaintImageId: '',
inpaintImageUrl: '',
brushStatus: 0, //0:未选择;1:消除笔;2:修改图像
centerDialogVisible: false,
mattingDialogVisible: false,
mattingDialogVisible_Second: false,
imagePath:

'https://hips.hearstapps.com/hmg-prod/images/%E5%AE%8B%E6%99%BA%E5%AD%9D-1597774015.jpg?crop=0.500xw:1.00xh;0.500xw,0&resize=640:*',

chooseCanvasObject: {} as any,
_cw: 0, //画布宽高
_ch: 0,
away_top: 30, //去掉默认高度
baseMapUrl: '',
_w: 500, //初始比例/灰色层 宽高、定义裁剪区域
_h: 500,
zoom: 100, //缩放比例
isDragging: false,
isCtrlKey: false,
lastPosters: [] as any[],
imageWidth: 500,
imageHeight: 500,
iamgeInitUrl: '',
imageInitId: '',
initCtx: '',
points: [] as any[],
point: 1,
mattingObjectUrl: '',
mattingGenerate: {} as any,

baseMap: {

x: 100, //起始坐标
y: 50,
minWidth: 300,
minHeight: 400,
handles: ['tm', 'bm', 'ml', 'mr'],
// handles: ['tl', 'tm', 'tr', 'mr', 'br', 'bm', 'bl', 'ml'],
lockAspectRatio: false, //是否缩放宽高比
aspectRatio: 3 / 4, //图片宽高比例
changeW: 500, //扩图宽高
changeH: 500,
x2: 100, //子级边距
y2: 50

},
cropBg: false,
loading2: false,
contrastImgStatus: false,
kuotuText: '扩图',
kuotuTextloading: '一键扩景中',
generateData: {} as any,
interTime: null as any,
drdc: null as any,
imageObject: null as any,
loadingText: '',
oldInpimgW: 0,
oldInpimgH: 0,
guiding3: 1,
guiding4: 1
})
let canvas = {} as any
defineExpose({})
import { useEventBus } from '@/hooks/event/useEventBus'
import { format } from 'path'
import { tr } from 'element-plus/es/locale'
const { on, emit, off } = useEventBus()
// 计算 xx
const textareaNumber = computed(() => {
return 1
})
const setguiding3 = () => {
state.guiding3 = -1
setStorage('guiding3', -1)
}
const setguiding4 = () => {
state.guiding4 = -1
setStorage('guiding4', -1)
}
// 点击扩图
const enlargeMap = () => {
state.dialogdraggableResizable = true
nextTick(() => {

let data = {
  imgUrl: state.baseMapUrl,
  resolution: [state._w, state._h],
  scale: '自由模式'
}
enlargeSceneMatchRef.value!.imgInfo(data)

})
}
// 操作区域居中
const getCenter = () => {
let boxW = parentDragResizRef.value!.offsetWidth
let boxH = parentDragResizRef.value!.offsetHeight
state.baseMap.x = (boxW - state.baseMap.changeW) / 2
state.baseMap.y = (boxH - state.baseMap.changeH) / 2
state.baseMap.x2 = Number(((state.baseMap.changeW - state._w * 0.8) / 2).toFixed())
state.baseMap.y2 = Number(((state.baseMap.changeH - state._h * 0.8) / 2).toFixed())
}
// 切换比例
const switchingRatio = (item, index) => {
state.toolbarActive = index
let data = {

imgUrl: state.baseMapUrl,
resolution: [state._w, state._h],
scale: item.name

}
enlargeSceneMatchRef.value!.imgInfo(data)
}

// 底图扩图 立即生成
const baseMapGeneration = () => {
console.log('底图扩图')
state.loading2 = true
let boxW = parentDragResizRef.value!.offsetWidth
let boxH = parentDragResizRef.value!.offsetHeight
contrastImgRef2.value?.getOldImg({ imgUrl: state.baseMapUrl }, boxW, boxH)
let params = enlargeSceneMatchRef.value!.genarateImage()
console.log(params)
params.imageId = getStorage('sourceImageId')
generateExtend(params)

.then((res) => {
  if (res && res.code == 200) {
    state.contrastImgStatus = true
    state._w = res.data.width
    state._h = res.data.height
    state.kuotuText = '完成扩图'
    let info = enlargeSceneMatchRef.value?.getImgInfo()
    contrastImgRef2.value?.getNewImg({ imgUrl: res.data.imageUrl }, boxW, boxH, info)
    state.generateData = res.data
    return
  }
})
.finally(() => {
  state.loading2 = false
})

}
// 应用扩图
const playGenerate = () => {
state.dialogdraggableResizable = false
canvas.clipPath.left = state._cw / 2
canvas.clipPath.top = state._ch / 2 - state.away_top
canvas.clipPath.width = state._w
canvas.clipPath.height = state._h
canvas.getObjects().forEach(function (obj) {

// 检查对象的某些属性,例如 type, id 或其他自定义属性
if (obj.type === 'image' && obj.id === 'hbImg') {
  console.log('找到了对象:', obj)
  obj.setSrc(state.generateData.imageUrl, { crossOrigin: 'anonymous' }).then(() => {
    canvas.renderAll()
  })
}

})

//更新缓存数据
let hbMessage = getStorage('hbMessage')
state.baseMapUrl = state.generateData.imageUrl
hbMessage.imageUrl = state.generateData.imageUrl
hbMessage.imageId = state.generateData.imageId
hbMessage.backgroundImageSize.width = state.generateData.width
hbMessage.backgroundImageSize.height = state.generateData.height
setStorage('hbMessage', hbMessage)
// state.baseMap.minWidth = state._w = state.generateData.width
// state.baseMap.changeW = state._w //+ 200

// state.baseMap.minHeight = state._h = state.generateData.height
// state.baseMap.changeH = state._h //+ 100
// loadImageAndRect(result.data.backgroundImageUrl)
setStorage('sourceImageId', state.generateData.imageId)
setStorage('sourceImageUrl', state.generateData.imageUrl)

state.contrastImgStatus = false
state.kuotuText = '扩图'

let currentPosterId = getStorage('currentPosterId')
let params2 = {

posterId: currentPosterId,
type: 1,
imageList: [state.generateData.imageId]

}
addGenerateRecords(params2).then((res) => {

if (res.code === 200) {
  GenerateRecord.value!.refGenerImg()
}

})
}
var startX = 0
var startY = 0 // 用于记录鼠标按下时的位置

const fonts = [
new FontFace('huiwenfangsong', 'url(fonts/huiwenfangsong.ttf)'),
new FontFace('FeiboZhengdianBody', 'url(fonts/FeiboZhengdianBody.otf)'),
new FontFace('SmileySans-Oblique', 'url(fonts/SmileySans-Oblique.ttf)'),
new FontFace('Fragrant-comfortable', 'url(fonts/Fragrant-comfortable.ttf)'),
new FontFace('Slidefu-Regular', 'url(fonts/Slidefu-Regular.ttf)'),
new FontFace('Optimal-Title', 'url(fonts/Optimal-Title.ttf)')
]

const queryData = async () => {
// 查询最近打开海报列表
getRecentPosters().then((res) => {

if (res && res.code == 200) {
  state.lastPosters = res.data
}

})
}
// 缓存加载的图片
let cachedImage = null

// 预加载图片
const preloadImage = (src) => {
return new Promise((resolve, reject) => {

const img = new Image()
img.src = src
img.onload = () => resolve(img)
img.onerror = (err) => reject(err)

})
}
import imagePath from '@/assets/img/mtr.png'
let isDrawing = false
let drawingModePoints = [] as any
let currentPath
onMounted(async () => {
// 在初始化时加载图片
preloadImage(imagePath)

.then((img) => {
  cachedImage = img // 缓存图片
})
.catch((err) => {
  console.error('图片加载失败:', err)
})

state.guiding3 = getStorage('guiding3') != null ? getStorage('guiding3') : 1
state.guiding4 = getStorage('guiding4') != null ? getStorage('guiding4') : 1

document.getElementById('downImage').style.display = 'flex'
queryData()

let currentPosterId = getStorage('currentPosterId')
if (currentPosterId) {

let params = {
  posterId: currentPosterId,
  step: 1
}
updatePoster(params)
getPosterInfo({ posterId: currentPosterId }).then((result) => {
  if (result && result.code == 200) {
    state.generateName = result.data.name
    setStorage('currentPosterName', result.data.name)
    setStorage('hbMessage', result.data)
    state.baseMap.minWidth = state._w = result.data.backgroundImageSize.width
    state.baseMap.changeW = state._w //+ 200

    state.baseMap.minHeight = state._h = result.data.backgroundImageSize.height
    state.baseMap.changeH = state._h //+ 100
    loadImageAndRect(result.data.backgroundImageUrl)
    setStorage('sourceImageId', result.data.backgroundImageId)
    setStorage('sourceImageUrl', result.data.backgroundImageUrl)
    nextTick(() => {
      generateListHtml.value!.updateGenerateName({ name: state.generateName })

      requestAnimationFrame(() => {
        Promise.all(fonts.map((font) => font.load())).then((loadedFonts) => {
          // 将所有加载的字体添加到 `document.fonts`
          loadedFonts.forEach((font) => document.fonts.add(font))
          document.fonts.ready.then(() => {
            initTextObject(JSON.parse(result.data.metaData) || [])
          })
        })

        //开启定时保存
        state.interTime = setInterval(() => {
          saveData(getStorage('currentPosterId'))
        }, 10000)
      })
    })
  }
})

}

window.addEventListener('keydown', function (event) {

if (event.key == '' || event.keyCode == 32) {
}
if (event.ctrlKey) {
  state.isCtrlKey = true
}
// console.log('空格键', state.isDragging)

console.log(event.key)
if (event.key == 'Delete') {
  let obj = canvas.getActiveObjects()
  if (obj) {
    console.log('删除选中元素')
    for (var i = 0; i < obj.length; i++) {
      canvas.remove(obj[i])
    }
    canvas.discardActiveObject()
  }
}
if (event.key == 'Backspace') {
  let object = canvas.getActiveObjects()
  if (object.length == 1) {
    let activity = object[0]
    if (textObject.includes(activity.type)) {
      console.log(activity.isEditing)
      if (activity.isEditing) {
        return
      }
    }
  }
  for (var i = 0; i < object.length; i++) {
    canvas.remove(object[i])
  }
  canvas.discardActiveObject()
}

})

window.addEventListener('keyup', function (event) {

if (event.key === ' ' || event.keyCode === 32) {
  // 空格键的 keyCode 是 32
  state.isDragging = false
  // 停止拖动画布
  canvas.defaultCursor = 'default' // 恢复鼠标指针样式为默认图标
}
if (event.ctrlKey) {
  state.isCtrlKey = false
}

})
state._cw = canvasContainerRef.value!.offsetWidth
state._ch = canvasContainerRef.value!.offsetHeight
on('setZoom', setZoom)
initCanvas()
// initData()
window.addEventListener('resize', resizeCanvas)
nextTick(() => {})

on('saveDataByNew', (id) => {

console.log('保存咯')
saveData(id)

})
on('onDown', () => {

exportImage()

})
})
const generateSvgPath = (points) => {
if (points.length < 2) return ''

let path = M ${points[0].x} ${points[0].y}
for (let i = 1; i < points.length - 1; i++) {

const midX = (points[i].x + points[i + 1].x) / 2
const midY = (points[i].y + points[i + 1].y) / 2
path += ` Q ${points[i].x} ${points[i].y} ${midX} ${midY}`

}
return path
}
// 缩放画布
const setZoom = (scaleFactor) => {
state.zoom = scaleFactor
// console.log('设置缩放级别和中心点', scaleFactor, scaleFactor)
// 设置缩放级别和中心点
var zoom = scaleFactor / 100 // 计算新的缩放级别
var point = new fabric.Point(state._cw / 2, state._ch / 2 - state.away_top) // 中心点
// 应用缩放并调整画布位置以保持中心点不变
canvas.zoomToPoint(point, zoom)
}

onUnmounted(() => {
document.getElementById('downImage').style.display = 'none'
window.removeEventListener('resize', resizeCanvas)
clearInterval(state.interTime)
off('saveDataByNew')
off('onDown')
if (getStorage('currentPosterId') != null && getStorage('currentPosterId') != '') {

saveData(getStorage('currentPosterId'))

}
})
const initCanvas = async () => {
canvas.backgroundColor = 'rgba(0,0,0,0)' // 设置背景为透明
canvas.backgroundOpacity = 0 // 确保背景透明度为0
console.log('画布宽高', state._cw, state._ch)
canvas = new fabric.Canvas('canvasContainer', {

width: state._cw,
height: state._ch
// selectionFullyContained: true // 精准选择

}) // 实例化fabric,并绑定到canvas元素上
canvas.controlsAboveOverlay = true
;(fabric.Object.prototype.borderColor = '#008cff'), //'#008CFF'

// 设置所有新创建对象的角样式为 'circle'
(fabric.Object.prototype.cornerStyle = 'circle')

// 修改控制点的填充色为白色
fabric.Object.prototype.cornerColor = '#ffffff'
// 修改控制点的大小为10px
fabric.Object.prototype.cornerSize = 12
// 设置控制点不透明,即可以盖住其下的控制线
fabric.Object.prototype.transparentCorners = false
// 修改控制点的边框颜色为gray灰色
fabric.Object.prototype.cornerStrokeColor = '#ffffff'
// 单独修改旋转控制点距离主体的纵向距离为-20px
// fabric.Object.prototype.controls.mtr.offsetY = -30
// // 单独修改旋转控制点,光标移动到该点上时的样式为pointer,一个手的形状
// fabric.Object.prototype.controls.mtr.cursorStyle = 'pointer'

canvas.on('mouse:down', function (options) {

let e = options.e
if (e.altKey === true) {
  // 开始拖动画布
  canvas.defaultCursor = 'move' // 改变鼠标指针样式为移动图标
  state.isDragging = true
  startX = e.clientX // 记录鼠标按下时的 X 坐标
  startY = e.clientY // 记录鼠标按下时的 Y 坐标
  // console.log('鼠标按下状态?', startX, startY)
} else {
  if (state.parentModeType == 2 && state.brushStatus == 2) {
    //自由绘图模式
    isDrawing = true
    const pointer = canvas.getPointer(e)
    drawingModePoints = [pointer] // 起点
  } else {
    var activeObjects = canvas.getActiveObjects()
    if (activeObjects.length > 1) {
      // 多个被选中时
      let isAllText = true
      activeObjects.forEach(function (obj) {
        if (!textObject.includes(obj.type)) {
          // 除了新选中的对象外,取消其他对象的选中状态
          isAllText = false
        }
      })
      // canvas.requestRenderAll(); // 重新渲染canvas以反映新的选择状态
      if (isAllText) {
        //代表选中的都是文字
        //目前这里仅选中左侧文本,不做顶部属性变动
        state.parentModeType = 4
      }
    } else if (activeObjects.length == 1) {
      // 单个被选中时
      let activeObject = activeObjects[0]
      // console.log(activeObject.width)
      if (textObject.includes(activeObject.type)) {
        //此时单选选中的时文字的时候
        console.log(activeObject.showIndex)
        state.parentModeType = 4
        leftToolHtml.value!.toolMode(4)
        requestAnimationFrame(() => {
          //父调子
          hbTopToolHtml.value!.selectTextMessage(activeObject)
        })

        // if (activeObject.data != null && activeObject.data.effects != null) {
        //   // 当字体存在动画的时候
        //   setFontEffect(activeObject.data.effects)
        // }

        // if (activeObject.type == 'image') {
        //   state.parentModeType = 3
        //   leftToolHtml.value!.toolMode(3)
        // }
      } else if (activeObject.type == 'image') {
        console.log(activeObject.showIndex)
        state.parentModeType = 3
        nextTick(() => {
          requestAnimationFrame(() => {
            console.log(hbTopToolHtml.value)
            hbTopToolHtml.value!.useImg(activeObject)
            leftToolHtml.value!.toolMode(3)
          })
        })
      }
    } else {
      //未选中时
      if (state.parentModeType == 3) {
        hbTopToolHtml.value && hbTopToolHtml.value!.useStatus()
      }
    }
  }
}

})
canvas.on('mouse:wheel', function (options) {

let e = options.e
e.preventDefault() // 阻止默认事件,防止页面滚动
e.stopPropagation() // 阻止事件冒泡
//&&    !(state.parentModeType == 2 && (state.brushStatus == 1 || state.brushStatus == 2))
if (e.ctrlKey) {
  state.isCtrlKey = true
  var delta = e.deltaY || e.detail || e.wheelDelta
  var zoom = canvas.getZoom()
  var zoomIn = delta > 0 // 判断是缩放还是放大
  zoom = zoomIn ? zoom - 0.1 : zoom + 0.1 // 根据需要调整缩放速度
  zoom = Math.min(Math.max(0.1, zoom), 3) // 设置缩放范围限制
  state.zoom = zoom * 100
}

})
canvas.on('mouse:move', function (options) {

let e = options.e
let opt = options.target
if (state.isDragging) {
  // 如果正在拖动画布,则更新画布位置
  let vpt = canvas.viewportTransform // 聚焦视图的转换
  vpt[4] += e.clientX - startX
  vpt[5] += e.clientY - startY
  // canvas.setViewportTransform(vpt)
  // canvas.relativePan({ x: -vpt[4], y: -vpt[5] })
  canvas.requestRenderAll() // 重新渲染
  startX = e.clientX
  startY = e.clientY
}
//自由绘图模式
if (state.parentModeType == 2 && state.brushStatus == 2) {
  canvas.selection = false
  const offsetX = canvas.viewportTransform[4]
  const offsetY = canvas.viewportTransform[5]
  // console.log('画布偏移量', offsetX, offsetY)
  const vpt = canvas.viewportTransform // 获取视口变换矩阵
  const point = options.pointer
  let x
  let y
  x = (point.x - vpt[4]) / vpt[3] // options.pointer.x
  y = (point.y - vpt[5]) / vpt[3] //options.pointer.y
  drawingCursor.set({ left: x, top: y, visible: true }).setCoords()
  canvas.forEachObject((obj) => {
    if (obj.isCursor) {
      canvas.bringObjectToFront(obj)
      // obj.bringToFront()
    }
  })
  canvas.renderAll()

  if (isDrawing) {
    const pointer = canvas.getPointer(e)
    drawingModePoints.push(pointer)
    // 实时预览路径(可选,影响性能可去掉)
    if (currentPath) canvas.remove(currentPath)
    const pathStr = generateSvgPath(drawingModePoints)
    console.log(pathStr)
    currentPath = new fabric.Path(pathStr, {
      stroke:
        state.brushInfo.color || `rgba(255, 255, 255, ` + (state.brushInfo.opacity || 1) + `)`,
      fill: '',
      strokeWidth: state.brushInfo.width || 30,
      strokeLineCap: 'round',
      strokeLineJoin: 'round',
      objectCaching: false,
      selectable: false, //是否可以被选中
      evented: false, //是否响应事件
      hasBorders: false, //是否显示边框
      hasControls: false // 不显示控制点
    })
    canvas.add(currentPath)
    canvas.requestRenderAll()
  }
}
//鼠标在不同区域显示
if (canvas.openDrawingMode) {
  const vpt = canvas.viewportTransform // 获取视口变换矩阵
  const point = options.pointer
  // 将鼠标坐标转换到原始画布坐标系(考虑缩放和平移)
  const rawX = (point.x - vpt[4]) / vpt[3] //  // (当前X - 平移X)/缩放X‌:
  const rawY = (point.y - vpt[5]) / vpt[3] // (当前Y - 平移Y)/缩放Y‌:

  // 原始限制区域参数(假设state中的值为原始未缩放值)
  const limitLeft = state._cw / 2 - state._w / 2
  const limitTop = state._ch / 2 - state._h / 2 - state.away_top
  const limitRight = state._cw / 2 + state._w / 2
  const limitBottom = state._ch / 2 + state._h / 2 - state.away_top

  // 判断是否在限制区域内
  if (rawX < limitLeft || rawY < limitTop || rawX > limitRight || rawY > limitBottom) {
    canvas.defaultCursor = 'default' // 区域外显示默认光标‌
  } else {
    canvas.defaultCursor = 'none' // 区域内隐藏光标
  }
}

})
canvas.on('mouse:up', function (opt) {

if (isDrawing) {
  // console.log('画笔坐标点集合:', drawingModePoints)
  if (drawingModePoints.length == 1) {
    //只点了一下没拖动
    const circle = new fabric.Circle({
      left: drawingModePoints[0].x,
      top: drawingModePoints[0].y,
      radius: state.brushInfo.width / 2 || 30, // 半径大小
      fill:
        state.brushInfo.color || `rgba(255, 255, 255, ` + (state.brushInfo.opacity || 1) + `)`,
      originX: 'center',
      originY: 'center',
      selectable: false, //是否可以被选中
      evented: false, //是否响应事件
      hasBorders: false, //是否显示边框
      hasControls: false // 不显示控制点
    })
    canvas.add(circle)
  } else {
    // 将临时 path 克隆为正式对象(可选中)
    if (currentPath) {
      /* currentPath.clone((clonedObj) => {
        // clonedObj 是克隆后的对象
        canvas.add(clonedObj)
      }) */
      // const finalPath = fabric.util.object.clone(currentPath)
      // canvas.add(finalPath)
      // canvas.remove(currentPath)

      currentPath = null
    }
  }
  state.drawnPaths.push(drawingModePoints)

  state.history.push([...canvas.getObjects().filter((obj) => !obj['isCursor'])]) // 保存当前状态到历史记录中
  drawingModePoints = []
}

canvas.selection = true
isDrawing = false
canvas.setViewportTransform(canvas.viewportTransform)
state.isDragging = false
state.isCtrlKey = false

})
canvas.on('mouse:out', function (opt) {

drawingCursor && drawingCursor.set({ visible: false }).setCoords()
canvas.renderAll()

})
canvas.on('object:scaling', function (options) {

let opt = options.target
const corner = options.transform.corner
if (corner === 'mtr') {
} else {
}

})
// state.history.push([...canvas.getObjects()])
canvas.on('path:created', (e) => {

const vpt = canvas.viewportTransform
// state.history.push([...canvas.getObjects().filter((obj) => !obj['isCursor'])]) // 保存当前状态到历史记录中
// const path = e.path as fabric.Path
// path.selectable = false
// state.drawnPaths.push(path)

})
}

// 退出画笔模式时清理光标
function disableDrawingMode() {
// 清除所有光标指示器
canvas.forEachObject((obj) => {

if (obj.isCursor) {
  canvas.remove(obj)
}

})

canvas.openDrawingMode = false
canvas.defaultCursor = 'default'
canvas.hoverCursor = 'default'

canvas.renderAll()
}

const initData = () => {
let params = {

posterId: getStorage('currentPosterId')

}
getPostersInfo(params).then((res) => {

if (res.code === 200) {
  let param = res.data
  console.log(JSON.parse(param.metaData))

  // const imgUrl = getStorage('sourceImageUrl')
  loadImageAndRect(param.backgroundImageUrl)

  requestAnimationFrame(() => {
    document.fonts.ready.then(() => {
      initTextObject(JSON.parse(param.metaData) || [])
    })
  })
}

})
}
/**

  • 初始化加载文字、图片对象
    */

const initTextObject = (initData) => {
for (var i = 0; i < initData.length; i++) {

let params = initData[i]
if (textObject.includes(params.type)) {
  if (params.type == 'VerticalText') {
    let textbox = new VerticalText(params.text, {
      left: params.left, // 文本框的 X 坐标
      top: params.top, // 文本框的 Y 坐标
      width: params.width, // 文本框的宽度
      fontSize: params.fontSize, // 字体大小
      borderColor: params.borderColor, // 边框颜色
      fill: params.fill, // 文本颜色
      hasControls: params.hasControls, // 允许调整大小和旋转
      hasBorders: params.hasBorders, // 显示边框
      textAlign: params.textAlign, // 文本对齐方式
      editingBorderColor: params.editingBorderColor,
      fontFamily: params.fontFamily,
      splitByGrapheme: params.splitByGrapheme,
      cornerStyle: params.cornerStyle, // 控制点样式
      cornerColor: params.cornerColor, // 控制点颜色
      transparentCorners: params.transparentCorners, // 控制点不透明
      lockScalingFlip: params.lockScalingFlip, // 允许翻转
      lockUniScaling: params.lockUniScaling, // 允许非等比缩放
      selectable: true, // 确保对象可选择
      evented: true, // 确保对象可交互
      zIndex: params.zIndex,
      data: params.data,
      shadow: params.shadow,
      stroke: params.stroke,
      originX: params.originX,
      originY: params.originY,
      angle: params.angle,
      showIndex: params.showIndex,
      scaleX: params.scaleX,
      scaleY: params.scaleY
    })
    textbox.setControlVisible('ml', false)
    // textbox.setControlVisible('mt', false)
    textbox.setControlVisible('mr', false)
    // textbox.setControlVisible('mb', false)
    textbox.selectionStart = textbox.text.length
    textbox.selectionEnd = textbox.text.length
    modifyControls(textbox)
    canvas.add(textbox)
    canvas.moveObjectTo(textbox, params.showIndex)

    textbox.on('modified', function () {
      // 计算新的字号
      const newFontSize = textbox.fontSize * textbox.scaleX
      console.log(newFontSize)

      // 限制最小字号,防止过小导致不可见
      if (newFontSize > 10) {
        textbox.set({
          fontSize: newFontSize,
          scaleX: 1, // 归一化,防止 Fabric.js 的默认缩放影响
          scaleY: 1
        })
        textbox.setCoords() // 更新坐标
        canvas.renderAll()
        hbTopToolHtml.value!.changeFontSize(newFontSize)
      }
    })
  } else {
    let textbox = new fabric.Textbox(params.text, {
      left: params.left, // 文本框的 X 坐标
      top: params.top, // 文本框的 Y 坐标
      width: params.width, // 文本框的宽度
      fontSize: params.fontSize, // 字体大小
      borderColor: params.borderColor, // 边框颜色
      fill: params.fill, // 文本颜色
      hasControls: params.hasControls, // 允许调整大小和旋转
      hasBorders: params.hasBorders, // 显示边框
      textAlign: params.textAlign, // 文本对齐方式
      editingBorderColor: params.editingBorderColor,
      fontFamily: params.fontFamily,
      splitByGrapheme: params.splitByGrapheme,
      cornerStyle: params.cornerStyle, // 控制点样式
      cornerColor: params.cornerColor, // 控制点颜色
      transparentCorners: params.transparentCorners, // 控制点不透明
      lockScalingFlip: params.lockScalingFlip, // 允许翻转
      lockUniScaling: params.lockUniScaling, // 允许非等比缩放
      selectable: params.selectable, // 确保对象可选择
      evented: params.evented, // 确保对象可交互
      zIndex: params.zIndex,
      data: params.data,
      shadow: params.shadow,
      stroke: params.stroke,
      originX: params.originX,
      originY: params.originY,
      angle: params.angle,
      showIndex: params.showIndex,
      scaleX: params.scaleX,
      scaleY: params.scaleY
    })
    textbox.setControlVisible('ml', false)
    textbox.setControlVisible('mt', false)
    textbox.setControlVisible('mr', false)
    textbox.setControlVisible('mb', false)
    textbox.selectionStart = textbox.text.length
    textbox.selectionEnd = textbox.text.length
    modifyControls(textbox)
    canvas.add(textbox)
    // textbox.moveTo(params.showIndex)
    canvas.moveObjectTo(textbox, params.showIndex)

    textbox.on('modified', function () {
      // 计算新的字号
      const newFontSize = textbox.fontSize * textbox.scaleX

      // 限制最小字号,防止过小导致不可见
      if (newFontSize > 10) {
        textbox.set({
          fontSize: newFontSize,
          scaleX: 1, // 归一化,防止 Fabric.js 的默认缩放影响
          scaleY: 1,
          width: textbox.width * textbox.scaleX
        })
        textbox.setCoords() // 更新坐标
        canvas.renderAll()
        hbTopToolHtml.value!.changeFontSize(newFontSize)
      }
    })
  }
} else if (params.type == 'image') {
  fabric.Image.fromURL(params.data.imageUrl, { crossOrigin: 'anonymous' }).then(
    (img) => {
      // 设置图片的位置和大小
      img.set({
        originX: params.originX,
        originY: params.originY,
        left: params.left, // 图片的左坐标
        top: params.top, // 图片的顶坐标
        width: params.width,
        height: params.height,
        selectable: params.selectable, // 禁用选择
        evented: params.evented, // 禁用事件
        data: params.data,
        zIndex: params.zIndex,
        angle: params.angle,
        showIndex: params.showIndex,
        scaleX: params.scaleX,
        scaleY: params.scaleY,
        cornerStyle: 'circle', // 控制点样式
        cornerColor: 'white', // 控制点颜色
        transparentCorners: false // 控制点不透明
      })
      // 添加图片到画布
      modifyControls(img)
      canvas.add(img)
      // img.moveTo(params.showIndex)
      canvas.moveObjectTo(img, params.showIndex)
    },
    { crossOrigin: 'anonymous' }
  )
}

}
// 重新渲染画布
canvas.renderAll()
}

/**

  • 系统数据保存
    */

const saveData = (currentPosterId) => {
let allData = canvas.getObjects()
// console.log(allData)
let params: any[] = []

for (let i = 0; i < allData.length; i++) {

// console.log(allData[i])
if (textObject.includes(allData[i].type)) {
  //文字类的
  let textParams = {
    text: allData[i].text,
    fontSize: allData[i].fontSize,
    type: allData[i].type,
    fontWeight: allData[i].fontWeight,
    fontFamily: allData[i].fontFamily,
    textAlign: allData[i].textAlign,
    fill: allData[i].fill,
    left: allData[i].left, // 文本框的 X 坐标
    top: allData[i].top, // 文本框的 Y 坐标
    width: allData[i].width, // 文本框的宽度
    borderColor: allData[i].borderColor, // 边框颜色
    hasControls: allData[i].hasControls, // 允许调整大小和旋转
    hasBorders: allData[i].hasBorders, // 显示边框
    editingBorderColor: allData[i].editingBorderColor,
    splitByGrapheme: allData[i].splitByGrapheme,
    cornerStyle: allData[i].cornerStyle, // 控制点样式
    cornerColor: allData[i].cornerColor, // 控制点颜色
    transparentCorners: allData[i].transparentCorners, // 控制点不透明
    lockScalingFlip: allData[i].lockScalingFlip, // 允许翻转
    lockUniScaling: allData[i].lockUniScaling, // 允许非等比缩放
    selectable: allData[i].selectable, // 确保对象可选择
    evented: allData[i].evented, // 确保对象可交互
    zIndex: allData[i].zIndex,
    showIndex: allData[i].showIndex,
    data: {
      effect: allData[i].data.effect
    },
    shadow: allData[i].shadow,
    stroke: allData[i].stroke,
    originX: allData[i].originX,
    originY: allData[i].originY,
    angle: allData[i].angle,
    scaleX: allData[i].scaleX,
    scaleY: allData[i].scaleY
  }
  params.push(textParams)
} else if (allData[i].type == 'image' && allData[i].id != 'hbImg') {
  //图片
  console.log(allData[i].zIndex)
  let imageParams = {
    left: allData[i].left, // 图片的左坐标
    top: allData[i].top, // 图片的顶坐标
    width: allData[i].width,
    height: allData[i].height,
    originX: allData[i].originX,
    originY: allData[i].originY,
    selectable: allData[i].selectable, // 禁用选择
    evented: allData[i].evented, // 禁用事件
    data: {
      imageId: allData[i].data.imageId,
      imageUrl: allData[i].data.imageUrl,
      effect: allData[i].data.effect
    },
    zIndex: allData[i].zIndex,
    showIndex: allData[i].showIndex,
    type: allData[i].type,
    angle: allData[i].angle,
    scaleX: allData[i].scaleX,
    scaleY: allData[i].scaleY
  }
  params.push(imageParams)
}

}

// const currentPosterId = getStorage('currentPosterId')
let data = {

posterId: currentPosterId,
metaData: JSON.stringify(params)

}
updatePoster(data)
}

// 每隔 1 分钟执行一次保存
// setInterval(saveData, 60000);

/**

  • 单个对象直接修改层级
    */

// const updatezIndex = () => {
// var activeObject = canvas.getActiveObject()
// activeObject.setZIndex(5)
// }

//淡入淡出
const fadeEffec = (activeObject) => {
let active = false
if (activeObject == null) {

active = true
activeObject = canvas.getActiveObject()
canvas.bringObjectToFront(activeObject)
// activeObject.bringToFront()

}

canvas.discardActiveObject()

if (textObject.includes(activeObject.type)) {

state.drdc = activeObject.animate(
  { opacity: 0 },
  {
    duration: 1000,
    onChange: canvas.renderAll.bind(canvas),
    onComplete: () => {
      state.drdc = activeObject.animate(
        { opacity: 1 },
        {
          duration: 1000,
          onChange: canvas.renderAll.bind(canvas),
          onComplete: () => {
            if (active) {
              // activeObject.moveTo(activeObject.showIndex)
              canvas.moveObjectTo(activeObject, activeObject.showIndex)
              canvas.setActiveObject(activeObject)
            }
            canvas.renderAll()
            state.gloLoading2 = false
          }
        }
      )
    }
  }
)

}
}

/**

  • 颜色渐变
    */

const colorFillFun = (activeObject) => {
let active = false
if (activeObject == null) {

active = true
activeObject = canvas.getActiveObject()
// activeObject.bringToFront()
canvas.bringObjectToFront(activeObject)

}

canvas.discardActiveObject()

if (textObject.includes(activeObject.type)) {

let color = activeObject.fill

let hue = 0
let animationFrame

function animateGradient() {
  if (hue >= 360) {
    cancelAnimationFrame(animationFrame) // 停止动画
    activeObject.set('fill', color)
    if (active) {
      // activeObject.moveTo(activeObject.showIndex)
      canvas.moveObjectTo(activeObject, activeObject.showIndex)
      canvas.setActiveObject(activeObject)
    }
    canvas.renderAll()
    state.gloLoading2 = false
    return
  }
  hue += 5 // 每次增加色相
  updateGradient(hue)
  animationFrame = requestAnimationFrame(animateGradient)
}

animateGradient()

// 限制动画时长(如 3 秒后自动停止)
// setTimeout(() => {
//   cancelAnimationFrame(animationFrame);
//   activeObject.set('fill', color);
//   canvas.renderAll();
// }, 3000);

function updateGradient(hue) {
  const color1 = `hsl(${hue}, 100%, 50%)`
  const color2 = `hsl(${(hue + 120) % 360}, 100%, 50%)`
  activeObject.set(
    'fill',
    new fabric.Gradient({
      type: 'linear',
      coords: { x1: 0, y1: 0, x2: activeObject.width, y2: 0 },
      colorStops: [
        { offset: 0, color: color1 },
        { offset: 1, color: color2 }
      ]
    })
  )
  canvas.renderAll()
}

}
}

const rotateText = (activeObject) => {
let active = false
if (activeObject == null) {

active = true
activeObject = canvas.getActiveObject()
// activeObject.bringToFront()
canvas.bringObjectToFront(activeObject)

}

canvas.discardActiveObject()
if (textObject.includes(activeObject.type)) {

activeObject.animate(
  { angle: activeObject.angle + 360 },
  {
    onChange: canvas.renderAll.bind(canvas),
    duration: 2000,
    // easing: fabric.util.ease.easeInCubic,
    onComplete: () => {
      if (active) {
        // activeObject.moveTo(activeObject.showIndex)
        canvas.moveObjectTo(activeObject, activeObject.showIndex)
        canvas.setActiveObject(activeObject)
      }
      canvas.renderAll()
      state.gloLoading2 = false
    }
  }
)

}
}
const scaleX = (activeObject) => {
activeObject.animate(

{ scaleX: 1.2 },
{
  duration: 500,
  onChange: canvas.renderAll.bind(canvas),
  onComplete: () => {
    activeObject.animate(
      { scaleX: 1 },
      {
        duration: 500,
        onChange: canvas.renderAll.bind(canvas),
        onComplete: () => {}
      }
    )
  }
}

)
}
const scaleY = (activeObject, active) => {
activeObject.animate(

{ scaleY: 1.2 },
{
  duration: 500,
  onChange: canvas.renderAll.bind(canvas),
  onComplete: () => {
    activeObject.animate(
      { scaleY: 1 },
      {
        duration: 500,
        onChange: canvas.renderAll.bind(canvas),
        onComplete: () => {
          if (active) {
            // activeObject.moveTo(activeObject.showIndex)
            canvas.moveObjectTo(activeObject, activeObject.showIndex)
            canvas.setActiveObject(activeObject)
          }
          // canvas.setActiveObject(activeObject)
          canvas.renderAll()
        }
      }
    )
  }
}

)
}

//缩放
const scale = (activeObject) => {
let active = false
if (activeObject == null) {

active = true
activeObject = canvas.getActiveObject()
canvas.bringObjectToFront(activeObject)
// activeObject.bringToFront()

}
canvas.discardActiveObject()

if (textObject.includes(activeObject.type)) {

scaleX(activeObject)
scaleY(activeObject, active)
state.gloLoading2 = false

}
}

//左右移动
const movePosition = (activeObject) => {
let active = false
if (activeObject == null) {

active = true
activeObject = canvas.getActiveObject()
canvas.bringObjectToFront(activeObject)
// activeObject.bringToFront()

}

canvas.discardActiveObject()

if (textObject.includes(activeObject.type)) {

activeObject.animate(
  { left: activeObject.left - activeObject.width / 2 },
  {
    duration: 500,
    easing: fabric.util.ease.easeInOutCubic, // 使用easing函数
    onChange: canvas.renderAll.bind(canvas),
    onComplete: () => {
      activeObject.animate(
        { left: activeObject.left + activeObject.width },
        {
          duration: 1000,
          easing: fabric.util.ease.easeInOutCubic, // 使用easing函数
          onChange: canvas.renderAll.bind(canvas),
          onComplete: () => {
            activeObject.animate(
              { left: activeObject.left - activeObject.width / 2 },
              {
                duration: 500,
                easing: fabric.util.ease.easeInOutCubic, // 使用easing函数
                onChange: canvas.renderAll.bind(canvas),
                onComplete: () => {
                  if (active) {
                    // activeObject.moveTo(activeObject.showIndex)
                    canvas.moveObjectTo(activeObject, activeObject.showIndex)
                    canvas.setActiveObject(activeObject)
                  }
                  canvas.renderAll()
                  state.gloLoading2 = false
                }
              }
            )
          }
        }
      )
    }
  }
)

}
}

const setFontEffect = (effect) => {
console.log(canvas.getActiveObjects())

if (effect.effect == 0) {

state.gloLoading2 = false

}
if (canvas.getActiveObjects().length == 1) {

if (textObject.includes(canvas.getActiveObjects()[0].type)) {
  let data = canvas.getActiveObjects()[0].data
  if (data == null) {
    canvas.getActiveObjects()[0].set('data', { effect: effect.effect })
  } else {
    data.effect = effect.effect
  }
  canvas.getActiveObjects()[0].set('data', data)
  if (effect.effect == 1) {
    state.gloLoading2 = true
    fadeEffec(null)
  }
  if (effect.effect == 2) {
    state.gloLoading2 = true
    colorFillFun(null)
  }

  if (effect.effect == 3) {
    state.gloLoading2 = true
    rotateText(null)
  }

  if (effect.effect == 4) {
    state.gloLoading2 = true
    scale(null)
  }

  if (effect.effect == 5) {
    state.gloLoading2 = true
    movePosition(null)
  }
}

}
}

// 图片抖动
const imgShake = (object) => {
let active = false
if (object == null) {

active = true
object = canvas.getActiveObject()
// object.bringToFront()
canvas.bringObjectToFront(object)

}
canvas.discardActiveObject()

if (object.type == 'image') {

shakeEffect(10)

}

// 抖动效果 offset-偏移量 count-执行次数 每次62.5ms 总共1秒
function shakeEffect(offset, count = 16) {

console.log('count:' + count)
var left
if (count % 4 == 0) {
  left = object.left + offset
} else if (count % 4 == 1) {
  left = object.left - offset
} else if (count % 4 == 2) {
  left = object.left - offset
} else if (count % 4 == 3) {
  left = object.left + offset
}
if (count == 0) {
  if (active) {
    // object.moveTo(object.showIndex)
    canvas.moveObjectTo(object, object.showIndex)
    canvas.setActiveObject(object)
  }
  // canvas.setActiveObject(object)
  canvas.renderAll()
  state.gloLoading2 = false
  return
}
count--

object.animate(
  { left: left },
  {
    duration: 62.5,
    onChange: canvas.renderAll.bind(canvas),
    onComplete: () => shakeEffect(offset, count)
  }
)

}
}

// 图片淡入淡出
const imgFadeEffec = (object) => {
let active = false
if (object == null) {

active = true
object = canvas.getActiveObject()
// object.bringToFront()
canvas.bringObjectToFront(object)
// state.drdc.cancel();

// fabric.util.animate.stop(object);

}
canvas.discardActiveObject()

if (object.type == 'image') {

object.animate(
  { opacity: 0 },
  {
    duration: 1000,
    onChange: canvas.renderAll.bind(canvas),
    onComplete: () => {
      object.animate(
        { opacity: 1 },
        {
          duration: 1000,
          onChange: canvas.renderAll.bind(canvas),
          onComplete: () => {
            if (active) {
              // object.moveTo(object.showIndex)
              canvas.moveObjectTo(object, object.showIndex)
              canvas.setActiveObject(object)
            }
            canvas.renderAll()
            state.gloLoading2 = false
          }
        }
      )
    }
  }
)

}
}

//图片旋转
const imgRotate = (object) => {
let active = false
if (object == null) {

active = true
object = canvas.getActiveObject()
// object.bringToFront()
canvas.bringObjectToFront(object)

}
canvas.discardActiveObject()

if (object.type == 'image') {

object.animate(
  { angle: object.angle + 360 },
  {
    duration: 2000,
    onChange: canvas.renderAll.bind(canvas),
    onComplete: () => {
      if (active) {
        // object.moveTo(object.showIndex)
        canvas.moveObjectTo(object, object.showIndex)
        canvas.setActiveObject(object)
      }
      canvas.renderAll()
      state.gloLoading2 = false
    }
    // onComplete: () => {
    //   object.animate({'angle': object.angle + 360}, {
    //   duration: 1000,
    //   onChange: canvas.renderAll.bind(canvas)
    // })
    // }
  }
)

}
}

//图片缩放
const imgScale = (object) => {
let active = false
if (object == null) {

active = true
object = canvas.getActiveObject()
// object.bringToFront()
canvas.bringObjectToFront(object)

}
const start = object.scaleX
const end = object.scaleX * 1.2
canvas.discardActiveObject()

if (object.type == 'image') {

fabric.util.animate({
  startValue: start,
  endValue: end,
  duration: 1000,
  onChange: function (value) {
    object.set({ scaleX: value, scaleY: value })
    canvas.renderAll()
  },
  onComplete: () => {
    fabric.util.animate({
      startValue: end,
      endValue: start,
      duration: 1000,
      onChange: function (value) {
        object.set({ scaleX: value, scaleY: value })
        canvas.renderAll()
      },
      onComplete: () => {
        if (active) {
          // object.moveTo(object.showIndex)
          canvas.moveObjectTo(object, object.showIndex)
          canvas.setActiveObject(object)
        }
        canvas.renderAll()
        state.gloLoading2 = false
      }
    })
  }
})

}
}

// 图片翻转
function imgFlip(object) {
let active = false
if (object == null) {

active = true
object = canvas.getActiveObject()
// object.bringToFront()
canvas.bringObjectToFront(object)

}
canvas.discardActiveObject()

if (object.type == 'image') {

object.animate(
  { scaleX: -1 * object.scaleX },
  {
    duration: 1000,
    onChange: canvas.renderAll.bind(canvas),
    onComplete: () => {
      object.animate(
        { scaleX: -1 * object.scaleX },
        {
          duration: 1000,
          onChange: canvas.renderAll.bind(canvas),
          onComplete: () => {
            if (active) {
              // object.moveTo(object.showIndex)
              canvas.moveObjectTo(object, object.showIndex)
              canvas.setActiveObject(object)
            }
            canvas.renderAll()
            state.gloLoading2 = false
          }
        }
      )
      // canvas.setActiveObject(object)
      canvas.renderAll()
    }
  }
)

}
}

const imgDynamicEffect = (effect) => {
// if (canvas.getActiveObjects().length == 1 && canvas.getActiveObjects()[0].type == 'image') {
// state.imageObject = canvas.getActiveObjects()[0];
// // fabric.runningAnimations.cancelByTarget(state.imageObject);
// } else {
// if (state.imageObject == null) {
// return;
// } else {
// if (fabric.runningAnimations) {
// fabric.runningAnimations.cancelByTarget(state.imageObject);
// }
// let data = state.imageObject.data;
// if (data.effect == 1) {
// state.imageObject.set("opacity",1);
// }
// if (data.effect == 2) {
// state.imageObject.set("angle",0);
// }
// canvas.renderAll();
// canvas.setActiveObject(state.imageObject);
// }
// }

// if (state.imageObject == null) {
// console.log("1122");
// if (canvas.getActiveObjects().length == 1 && canvas.getActiveObjects()[0].type == 'image') {
// state.imageObject = canvas.getActiveObjects()[0];
// } else {
// return;
// }
// } else {

// if (canvas.getActiveObjects().length == 1 && canvas.getActiveObjects()[0].type == 'image') {

// if (canvas.getActiveObjects()[0].id == state.imageObject.id) {
// if (fabric.runningAnimations) {
// fabric.runningAnimations.cancelByTarget(state.imageObject);
// }
// let data = state.imageObject.data;
// if (data.effect == 1) {
// state.imageObject.set("opacity", 1);
// }
// if (data.effect == 2) {
// state.imageObject.set("angle", 0);
// }
// canvas.renderAll();

// canvas.setActiveObject(state.imageObject);

// } else {
// state.imageObject = canvas.getActiveObjects()[0];
// }
// }
// }

if (canvas.getActiveObjects().length == 1) {

if (canvas.getActiveObjects()[0].type == 'image') {
  let data = canvas.getActiveObjects()[0].data
  if (data == null) {
    canvas.getActiveObjects()[0].set('data', { effect: effect.effect })
  } else {
    data.effect = effect.effect
  }
  canvas.getActiveObjects()[0].set('data', data)
  if (effect.effect == 0) {
    state.gloLoading2 = false
  }
  if (effect.effect == 1) {
    //淡入淡出
    state.gloLoading2 = true
    imgFadeEffec(null)
  }
  if (effect.effect == 2) {
    //旋转
    state.gloLoading2 = true
    imgRotate(null)
  }

  if (effect.effect == 3) {
    //缩放
    state.gloLoading2 = true
    imgScale(null)
  }

  if (effect.effect == 4) {
    //翻转
    state.gloLoading2 = true
    imgFlip(null)
  }

  if (effect.effect == 5) {
    //抖动
    state.gloLoading2 = true
    imgShake(null)
  }
}

}
}

let textObject = ['VerticalText', 'textbox', 'text']
/**

  • 修改文字颜色
    */

const updateTextColor = async (color) => {
var activeObject = canvas.getActiveObjects()
console.log(color)
for (let i = 0; i < activeObject.length; i++) {

console.log('选中的对象:', activeObject[i].type)

if (textObject.includes(activeObject[i].type)) {
  activeObject[i].set('fill', color.color)
  canvas.renderAll()
}

}
}

/**

  • 修改文字字体大小
    */

const updateTextFontSize = (fontSize) => {
var activeObject = canvas.getActiveObjects()

for (let i = 0; i < activeObject.length; i++) {

if (textObject.includes(activeObject[i].type)) {
  activeObject[i].set('fontSize', fontSize.fontSize)
  canvas.renderAll()
}

}
}

/**

  • 修改字体粗细
    */

const updateTextFontBold = (type) => {
var activeObject = canvas.getActiveObjects()

for (let i = 0; i < activeObject.length; i++) {

if (textObject.includes(activeObject[i].type)) {
  activeObject[i].set('fontWeight', type.fontBold)
  canvas.renderAll()
}

}

//normal bold
}

/**

  • 修改字体
    */

const updateTextFontFamily = (fontfamily) => {
var activeObject = canvas.getActiveObjects()

for (let i = 0; i < activeObject.length; i++) {

if (textObject.includes(activeObject[i].type)) {
  activeObject[i].set('fontFamily', fontfamily)
}

}
}

/**

  • 修改文字位置
    */

const updateTextFontPosition = (position) => {
var activeObject = canvas.getActiveObjects()
for (let i = 0; i < activeObject.length; i++) {

if (textObject.includes(activeObject[i].type) && activeObject[i].type != 'VerticalText') {
  activeObject[i].set('textAlign', position.type)
  canvas.renderAll()
}

}
}

/**

  • 修改文字字体
    */

const updateFontFamily = (type) => {
var activeObject = canvas.getActiveObjects()
for (let i = 0; i < activeObject.length; i++) {

if (textObject.includes(activeObject[i].type)) {
  activeObject[i].set('fontFamily', type.fontFamily)
  canvas.renderAll()
}

}
}
const captureImg = (activity) => {
//导出静态图
// const canvas = document.getElementById('your-canvas');
// const dataURL = canvas.toDataURL('image/png')
state.zoom = 100 //缩放重置
// var transform = canvas.viewportTransform.slice()
canvas.viewportTransform = [1, 0, 0, 1, 0, 0]

var dataURL = canvas.toDataURL({

format: 'png', // 可以是 'jpeg', 'png', 等
quality: 1, // 仅当格式为 'jpeg' 时有效
left: state._cw / 2 - state._w / 2, // 裁剪区域的左上角 x 坐标
top: state._ch / 2 - state.away_top - state._h / 2, // 裁剪区域的左上角 y 坐标
width: state._w, // 裁剪区域的宽度
height: state._h // 裁剪区域的高度

})
// console.log('导出指定区域', dataURL)

//下载图片
const link = document.createElement('a')
link.download = uuidv4() + '.png'
link.href = dataURL

link.click()
state.gloLoading = false
link.remove()

if (activity.length > 0) {

canvas.setActiveObject(activity[0])

}

// canvas.viewportTransform = transform
}

/**

  • 截取gif图片
    */

const capturerFun = (activity) => {
state.zoom = 100 //缩放重置
// var transform = canvas.viewportTransform.slice()
canvas.viewportTransform = [1, 0, 0, 1, 0, 0]
const capturer = new CCapture({ format: 'gif', framerate: 30, workersPath: './', name: uuidv4() })

// 👇 指定导出区域
const clipX = state._cw / 2 - state._w / 2
const clipY = state._ch / 2 - state.away_top - state._h / 2
const clipWidth = state._w
const clipHeight = state._h

// 创建离屏 canvas 用于裁剪导出区域
const offscreenCanvas = document.createElement('canvas')
offscreenCanvas.width = clipWidth
offscreenCanvas.height = clipHeight
const offscreenCtx = offscreenCanvas.getContext('2d')

const startRecording = () => {

capturer.start()

}

const stopRecording = () => {

capturer.stop()
// canvas.viewportTransform = transform
capturer.save((blob) => {
  // ✅ 1. 你可以在这里拿到完整的 GIF Blob 文件
  // ✅ 2. 创建下载链接
  const url = URL.createObjectURL(blob)
  const a = document.createElement('a')
  a.href = url
  a.download = uuidv4() + '.gif'
  document.body.appendChild(a)

  // ✅ 3. 模拟下载(监听 Blob 被完整加载)
  fetch(url)
    .then((res) => res.blob())
    .then(() => {
      // ✅ 下载内容已经读取完成(或至少 ready)
      state.gloLoading = false
      URL.revokeObjectURL(url) // 清理
      a.remove()
    })
  // 触发下载
  a.click()
})

}

let angle = 0

const captureClippedFrame = () => {

// 复制主 canvas 指定区域到离屏 canvas
offscreenCtx.clearRect(0, 0, clipWidth, clipHeight)
offscreenCtx.drawImage(
  canvas.lowerCanvasEl, // 原始 fabric canvas
  clipX,
  clipY,
  clipWidth,
  clipHeight, // 要截取的区域
  0,
  0,
  clipWidth,
  clipHeight // 画到离屏 canvas 的位置
)

capturer.capture(offscreenCanvas) // 捕捉离屏 canvas(即裁剪区域)

}

const animate = () => {

captureClippedFrame() // 录制当前帧(只导出部分区域)

if (angle < 60) {
  angle++
  requestAnimationFrame(animate)
} else {
  stopRecording() // 录制完成
  angle = 0
  if (activity.length > 0) {
    canvas.setActiveObject(activity[0])
  }
}

}

startRecording()
animate()
}

/**

  • 修改文字横版竖版
    */

const setDirection = () => {
// const placeholderText = '请输入文字...'
// let textbox = new VerticalText(placeholderText, {
// left: state._cw / 2, // 文本框的 X 坐标
// top: state._ch / 2 - state.away_top, // 文本框的 Y 坐标
// // width: 300, // 文本框的宽度
// fontSize: 42, // 字体大小
// borderColor: 'white', // 边框颜色
// fill: 'white', // 文本颜色
// hasControls: true, // 允许调整大小和旋转
// hasBorders: true, // 显示边框
// // textAlign: 'left', // 文本对齐方式
// // editingBorderColor: 'red',
// fontFamily: 'huiwenfangsong',
// splitByGrapheme: true,
// cornerStyle: 'circle', // 控制点样式
// cornerColor: 'white', // 控制点颜色
// transparentCorners: false, // 控制点不透明
// lockScalingFlip: false, // 允许翻转
// lockUniScaling: false, // 允许非等比缩放
// selectable: true, // 确保对象可选择
// evented: true, // 确保对象可交互
// data: {
// effect: 0
// },
// originX: 'center',
// originY: 'center',
// })
// canvas.add(textbox)
// canvas.setActiveObject(textbox)
// return;

let objectActivity = canvas.getActiveObjects()

if (objectActivity.length != 1) {

return

}
let obj = objectActivity[0]

canvas.remove(obj)

if (obj.type == 'VerticalText') {

//当前竖版,改成横版
let arctext = new fabric.Textbox(obj.text, {
  left: obj.left, // 文本框的 X 坐标
  top: obj.top, // 文本框的 Y 坐标
  width: obj.height, // 文本框的宽度
  fontSize: obj.fontSize, // 字体大小
  borderColor: obj.borderColor, // 边框颜色
  fill: obj.fill, // 文本颜色
  hasControls: true, // 允许调整大小和旋转
  hasBorders: true, // 显示边框
  textAlign: 'left', // 文本对齐方式
  // editingBorderColor: 'red',
  fontFamily: obj.fontFamily,
  splitByGrapheme: true,
  cornerStyle: 'circle', // 控制点样式
  cornerColor: 'white', // 控制点颜色
  transparentCorners: false, // 控制点不透明
  lockScalingFlip: false, // 允许翻转
  lockUniScaling: false, // 允许非等比缩放
  selectable: true, // 确保对象可选择
  evented: true, // 确保对象可交互
  data: {
    effect: 0
  },
  originX: 'center',
  originY: 'center',
  showIndex: obj.showIndex,
  shadow: obj.shadow,
  stroke: obj.stroke
})
canvas.add(arctext)
canvas.setActiveObject(arctext)
arctext.enterEditing()

arctext.on('modified', function () {
  // 计算新的字号
  const newFontSize = arctext.fontSize * arctext.scaleX
  console.log(newFontSize)

  // 限制最小字号,防止过小导致不可见
  if (newFontSize > 10) {
    arctext.set({
      fontSize: newFontSize,
      scaleX: 1, // 归一化,防止 Fabric.js 的默认缩放影响
      scaleY: 1,
      width: arctext.width * arctext.scaleX
    })
    arctext.setCoords() // 更新坐标
    canvas.renderAll()
    hbTopToolHtml.value!.changeFontSize(newFontSize)
  }
})

} else {

let arctext = new VerticalText(obj.text, obj)
canvas.add(arctext)
canvas.setActiveObject(arctext)
arctext.enterEditing()

arctext.on('modified', function () {
  // 计算新的字号
  const newFontSize = arctext.fontSize * arctext.scaleX
  console.log(newFontSize)

  // 限制最小字号,防止过小导致不可见
  if (newFontSize > 10) {
    arctext.set({
      fontSize: newFontSize,
      scaleX: 1, // 归一化,防止 Fabric.js 的默认缩放影响
      scaleY: 1
    })
    arctext.setCoords() // 更新坐标
    canvas.renderAll()
    hbTopToolHtml.value!.changeFontSize(newFontSize)
  }
})

}
canvas.renderAll()
}

/**

  • 添加文字类操作
  • @param type 类型 目前添加的时候暂时用不到type
    */

const addText = (type, options) => {
let length = canvas.getObjects().length
console.log(length)
const placeholderText = '请输入文字...'
let textbox = new fabric.Textbox(placeholderText, {

left: state._cw / 2, // 文本框的 X 坐标
top: state._ch / 2 - state.away_top, // 文本框的 Y 坐标
width: 300, // 文本框的宽度
fontSize: 42, // 字体大小
borderColor: 'white', // 边框颜色
fill: 'white', // 文本颜色
backgroundColor: '',
hasControls: true, // 允许调整大小和旋转
hasBorders: true, // 显示边框
textAlign: 'left', // 文本对齐方式
// editingBorderColor: 'red',
fontFamily: 'huiwenfangsong',
splitByGrapheme: true,
cornerStyle: 'circle', // 控制点样式
cornerColor: 'white', // 控制点颜色
transparentCorners: false, // 控制点不透明
lockScalingFlip: false, // 允许翻转
lockUniScaling: false, // 允许非等比缩放
selectable: true, // 确保对象可选择
evented: true, // 确保对象可交互
data: {
  effect: 0
},
originX: 'center',
originY: 'center',
showIndex: length

})
// textbox.setControlVisible('ml', false)
textbox.setControlVisible('mt', false)
// textbox.setControlVisible('mr', false)
textbox.setControlVisible('mb', false)
textbox.selectionStart = textbox.text.length
textbox.selectionEnd = textbox.text.length
modifyControls(textbox)
canvas.add(textbox)
canvas.setActiveObject(textbox)
textbox.enterEditing()

textbox.on('modified', function () {

// 计算新的字号
const newFontSize = textbox.fontSize * textbox.scaleX
console.log(newFontSize)

// 限制最小字号,防止过小导致不可见
if (newFontSize > 10) {
  textbox.set({
    fontSize: newFontSize,
    scaleX: 1, // 归一化,防止 Fabric.js 的默认缩放影响
    scaleY: 1,
    width: textbox.width * textbox.scaleX
  })
  textbox.setCoords() // 更新坐标
  canvas.renderAll()
  hbTopToolHtml.value!.changeFontSize(newFontSize)
}

})

// 重新渲染画布
canvas.renderAll()

hbTopToolHtml.value!.selectTextMessage(textbox)
}
let drawingCursor
// 点击修改图像 进入自由绘制模式
const drawFree = (param) => {
if (canvas) {

if (param.status) {
  // 设置缩放级别和中心点
  /* var zoom = 1 // 计算新的缩放级别
  var point = new fabric.Point(state._cw / 2, state._ch / 2 - state.away_top) // 中心点
  // 应用缩放并调整画布位置以保持中心点不变
  canvas.zoomToPoint(point, zoom) */

  state.parentResultType = param.status
  if (param.status === 1) {
    //点击消除笔
    state.brushStatus = 1
  } else {
    //点击修改图像
    state.brushStatus = 2
  }

  // 创建一个新的圆形指示器
  drawingCursor = new fabric.Circle({
    originX: 'center',
    originY: 'center',
    left: state._cw / 2,
    top: state._ch / 2,
    radius: param.width / 2,
    fill: param.color || `rgba(255, 255, 255, ` + (param.opacity || 1) + `)`, //圆形颜色
    selectable: false,
    evented: false, // 不影响事件
    isCursor: true, // 自定义标识,方便移除
    visible: false
  })

  state.brushInfo = param
  //取消全部选中
  canvas.discardActiveObject()

  requestAnimationFrame(() => {
    //将底图放在最上层
    canvas.getObjects().forEach((obj) => {
      if (obj.id == 'hbImg') {
        // obj.bringToFront()
        canvas.bringObjectToFront(obj)
      }
      if (obj.id != 'hbImg') {
        obj.visible = false
      }
    })

    state.history = []
    state.drawnPaths = []

    //鼠标光标设置为圆形
    canvas.bringObjectToFront(drawingCursor)
    canvas.add(drawingCursor)

    canvas.openDrawingMode = true
    // 设置鼠标光标为 none,隐藏默认十字形
    /* canvas.freeDrawingCursor = 'none'
    var freeDrawingBrush = new fabric.PencilBrush(canvas)
    canvas.freeDrawingBrush = freeDrawingBrush
    canvas.freeDrawingBrush.color =
      param.color || `rgba(255, 255, 255, ` + (param.opacity || 1) + `)` // 设置画笔颜色
    canvas.freeDrawingBrush.width = param.width || 30
    canvas.renderAll()

    updateBrushOpacity({ bushOpacity: param.opacity || 1, color: param.color || '#ffffff' }) */

    state.history.push([...canvas.getObjects()])
  })
} else {
  //取消消除笔
  canvas.openDrawingMode = false
  state.parentResultType = 0
  brushClear()
  disableDrawingMode()

  //将底图放在最底层
  canvas.getObjects().forEach((obj) => {
    if (obj.id != 'hbImg') {
      obj.visible = true
    }
    if (obj.id == 'hbImg') {
      // obj.moveTo(1)
      canvas.moveObjectTo(obj, 0)
    }
  })

  setZoom(state.zoom)
}

}
}
//画笔撤销
const brushUndo = () => {
console.log('画笔撤销')
if (state.history.length > 1) {

// 至少有一个状态可以撤销(除了初始状态)
state.history.pop() // 获取上一个状态
// console.log('删除画布上的元素包括底图', state.history.length)
// canvas.loadFromJSON(previousState, canvas.renderAll.bind(canvas)) // 加载上一个状态并渲染
if (state.drawnPaths.length > 0) {
  const lastPath = state.drawnPaths.pop()
  // console.log('删除记录的点', state.drawnPaths.length)
  canvas?.remove(lastPath as fabric.Path)
}
canvas?.clear()
const lastState = state.history[state.history.length - 1]
if (lastState) {
  lastState.forEach((object) => {
    canvas?.add(object)
  })
}

} else {

console.log('No more history to undo.') // 没有更多历史记录可以撤销时的提示

}
}
//点击消除按钮
const clearCommit = (param) => {
if (canvas) {

state.zoom = 100 //缩放重置
canvas.viewportTransform = [1, 0, 0, 1, 0, 0]
requestAnimationFrame(() => {
  // 隐藏除画笔外的元素
  canvas.getObjects().forEach((obj) => {
    if (obj.type != 'path') {
      obj.set({
        visible: false
      })
    }
  })
  state.imageData = canvas.toDataURL({
    format: 'png', // 可以是 'jpeg', 'png', 等
    quality: 1, // 仅当格式为 'jpeg' 时有效
    left: state._cw / 2 - state._w / 2, // 裁剪区域的左上角 x 坐标
    top: state._ch / 2 - state.away_top - state._h / 2, // 裁剪区域的左上角 y 坐标
    width: state._w, // 裁剪区域的宽度
    height: state._h // 裁剪区域的高度
  })
  canvas.getObjects().forEach((obj) => {
    if (obj.type != 'path') {
      obj.set({
        visible: true
      })
    }
  })
  console.log(state.imageData)
  if (state.imageData) {
    // console.log('state.imageData', state.imageData)
    // 将 Base64 数据转换为 Blob 对象
    const byteCharacters = atob(state.imageData.split(',')[1])
    const byteNumbers = new Array(byteCharacters.length)
    for (let i = 0; i < byteCharacters.length; i++) {
      byteNumbers[i] = byteCharacters.charCodeAt(i)
    }
    const byteArray = new Uint8Array(byteNumbers)
    const blob = new Blob([byteArray], { type: 'image/png' })

    // 创建 FormData 对象
    const formData = new FormData()
    formData.append('file', blob, 'drawing.png')
    const url = window.location.origin + '/api/v2/upload/images'
    fetch(url, {
      method: 'POST', // 或者使用 'post'(不区分大小写)
      body: formData
    })
      .then((response) => {
        if (!response.ok) {
          ElMessage.error('网络响应不是ok状态')
        }
        return response.json() // 如果需要,可以返回解析后的JSON数据
      })
      .then((res) => {
        const result = res.data
        const sourceImageId = getStorage('sourceImageId')
        const currentPosterId = getStorage('currentPosterId')
        if (result) {
          state.parentResultType = 2
          state.brushComplete = true
          canvas.openDrawingMode = false
          let params = {
            maskImageId: result.imageId,
            sourceImageId: sourceImageId,
            mode: param.status,
            prompt: param.status == 1 ? param.content : ''
          }
          state.lastInpaintParams = params
          state.gloLoading = true
          state.loadingText = '图片重绘中...'
          //海报图片重绘
          generateInpaint(params)
            .then((res) => {
              if (res.code === 200 && res.data && res.data.imageId) {
                state.inpaintImageId = res.data.imageId
                state.inpaintImageUrl = res.data.imageUrl
                let params2 = {
                  posterId: currentPosterId,
                  type: 1,
                  imageList: [res.data.imageId]
                }
                saveVocabulary(res.data)
                addGenerateRecords(params2)
                canvas?.clear()
                loadImageAndRect(res.data.imageUrl)
              }
            })
            .finally(() => {
              state.gloLoading = false
            })
        }
      })
      .catch(() => {
        ElMessage.error('上传失败')
      })
  }
})

}
}
const reClear = () => {
let currentPosterId = getStorage('currentPosterId')
state.gloLoading = true
state.loadingText = '图片重绘中...'
generateInpaint(state.lastInpaintParams)

.then((res) => {
  if (res.code === 200) {
    state.inpaintImageId = res.data.imageId
    state.inpaintImageUrl = res.data.imageUrl
    let params2 = {
      posterId: currentPosterId,
      type: 1,
      imageList: [res.data.imageId]
    }
    saveVocabulary(res.data)
    addGenerateRecords(params2)
    canvas?.clear()
    loadImageAndRect(res.data.imageUrl)
  }
})
.finally(() => {
  state.gloLoading = false
})

}
//重新编辑
const reEdit = (req) => {
// canvas.clear()
//加载原图
brushClear()
state.brushComplete = false
hbTopToolHtml.value!.openEraser(req)
}
//完成
const complete = async () => {
state.history = []
state.drawnPaths = []
state.history.push([...canvas.getObjects()])

setStorage('sourceImageId', state.inpaintImageId)
setStorage('sourceImageUrl', state.inpaintImageUrl)
canvas?.clear()
loadImageAndRect(state.inpaintImageUrl)
state.parentResultType = 0
state.brushComplete = false
canvas.openDrawingMode = false
hbTopToolHtml.value!.closeBrush()
let currentPosterId = getStorage('currentPosterId')
getPosterInfo({ posterId: currentPosterId }).then((result) => {

if (result && result.code == 200) {
  requestAnimationFrame(() => {
    Promise.all(fonts.map((font) => font.load())).then((loadedFonts) => {
      // 将所有加载的字体添加到 `document.fonts`
      loadedFonts.forEach((font) => document.fonts.add(font))
      document.fonts.ready.then(() => {
        initTextObject(JSON.parse(result.data.metaData) || [])
      })
    })
  })
}

})

let params = {

posterId: currentPosterId,
backgroundImageId: state.inpaintImageId,
backgroundImageUrl: state.inpaintImageUrl

}
updatePoster(params)
GenerateRecord.value!.refGenerImg()

setZoom(state.zoom)
}
// 保持历史记录(localStorage)
const saveVocabulary = (historicalImgInfo: any) => {
if (JSON.parse(getStorage('genarateHbHistory')) === null) {

state.genarateHbArr = [historicalImgInfo]
setStorage('genarateHbHistory', JSON.stringify(state.genarateHbArr))
return

}
// 获取历史记录
let genarateHbHistoryArr = JSON.parse(getStorage('genarateHbHistory'))

// 添加新条目
genarateHbHistoryArr = [historicalImgInfo, ...genarateHbHistoryArr].slice(0, 20) // 确保历史记录不超过20条

// 更新本地存储
setStorage('genarateHbHistory', JSON.stringify(genarateHbHistoryArr))
// 更新组件状态
state.genarateHbArr = genarateHbHistoryArr
}
// 清空画布
const brushClear = async () => {
const firstState = state.history[0]
if (firstState) {

canvas?.clear()
firstState.forEach((object) => {
  canvas?.add(object)
})
//将底图放在最上层
canvas.getObjects().forEach((obj) => {
  if (obj.id == 'hbImg') {
    // obj.bringToFront()
    canvas.bringObjectToFront(obj)
  }
})
canvas.forEachObject((obj) => {
  if (obj.isCursor) {
    // obj.bringToFront()
    canvas.bringObjectToFront(obj)
  }
})

}

await nextTick()
state.history = []
state.drawnPaths = []
state.history.push([...canvas.getObjects()])
}
// 画笔颜色
const updateBrushColor = (param) => {
if (canvas) {

const color = hexToRgba(param.color, param.bushOpacity)
const rgb = color.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d\.]+))?\)$/)
if (rgb && rgb.length === 5) {
  state.brushInfo.color = `rgba(${rgb[1]}, ${rgb[2]}, ${rgb[3]}, ${param.bushOpacity})`
}
drawingCursor.set({ fill: param.color || `rgba(255, 255, 255, ` + (param.opacity || 1) + `)` })

}
}
const updateBrushSize = (param) => {
if (canvas) {

state.brushInfo.width = param.brushSize
drawingCursor.set({ radius: param.brushSize / 2 })

}
}
const updateBrushOpacity = (param) => {
if (canvas) {

const color = hexToRgba(param.color, param.bushOpacity)
const rgb = color.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d\.]+))?\)$/)
if (rgb && rgb.length === 5) {
  state.brushInfo.color = `rgba(${rgb[1]}, ${rgb[2]}, ${rgb[3]}, ${param.bushOpacity})`
  // canvas.freeDrawingBrush.color = `rgba(${rgb[1]}, ${rgb[2]}, ${rgb[3]}, ${param.bushOpacity})`
}

}
}
//16进制颜色值转rgb值
const hexToRgba = (hex, alpha = 1) => {
// 检查十六进制颜色值是否有效
if (!/^#[0-9A-F]{6}$/i.test(hex)) {

throw new Error('Invalid HEX color.')

}

// 提取红色、绿色和蓝色分量
const bigint = parseInt(hex.slice(1), 16)
const r = (bigint >> 16) & 255
const g = (bigint >> 8) & 255
const b = bigint & 255

// 返回RGBA格式的颜色字符串
return rgba(${r}, ${g}, ${b}, ${alpha})
}

const resizeCanvas = () => {
state._cw = canvasContainerRef.value!.offsetWidth
state._ch = canvasContainerRef.value!.offsetHeight
console.log('画布宽高', state._cw, state._ch)
if (canvas) {

canvas.setWidth(state._cw)
canvas.setHeight(state._ch)
canvas.renderAll()

}
}

// 左侧工具栏 控制 顶部工具栏 显示
const toolModes = (modeType) => {
state.parentModeType = modeType
if (modeType == 2) {

GenerateRecord.value!.changeActive(true)

} else {

return
// 王斌 这块先注释
if (hbTopToolHtml && hbTopToolHtml.value) {
  hbTopToolHtml.value!.closeBrush()
  drawFree({ status: 0 })
}

}
}
//渲染底图
const loadImageAndRect = (imgUrl) => {
console.log(imgUrl)
state.baseMapUrl = imgUrl
// 定义裁剪区域
canvas.clipPath = new fabric.Rect({

originX: 'center',
originY: 'center',
left: state._cw / 2,
top: state._ch / 2 - state.away_top,
width: state._w,
height: state._h

})

fabric.Image.fromURL(imgUrl, { crossOrigin: 'anonymous' }).then((img) => {

// 设置图片的位置和大小
img.set({
  id: 'hbImg',
  objectCaching: false,
  originX: 'center',
  originY: 'center',
  left: state._cw / 2,
  top: state._ch / 2 - state.away_top, // 图片的顶坐标
  width: state._w,
  height: state._h,
  selectable: false, // 禁用选择
  evented: false // 禁用事件
  // scaleX: 0.2, // 缩放比例
  // scaleY: 0.2,
})
// 添加图片到画布
canvas.add(img)
//img.moveTo(1)
canvas.moveObjectTo(img, 1)
// canvas.setActiveObject(img)

})
}

// 修改控制点
const modifyControls = (canvasDom) => {
/* canvasDom.setControlsVisibility({

// tl: false, // 移除左上角控制点
// tr: false, // 移除右上角控制点
// bl: false, // 移除左下角控制点
// br: false, // 移除右下角控制点
// mt: false, // 移除中上控制点
// mb: false, // 移除中下控制点
// ml: false, // 移除中左控制点
// mr: false, // 移除中右控制点
// mtr: false

}) */
// console.log('???', whiteBoardRect.controls)
// whiteBoardRect.controls = fabric.util.object.clone(fabric.Object.prototype.controls);
canvasDom.controls.mtr.cursorStyle = 'pointer'
canvasDom.controls.mtr.offsetY = -25
canvasDom.controls.mtr.withConnection = false

// 遍历所有控制点并替换渲染方法
Object.keys(canvasDom.controls).forEach((key) => {

const control = canvasDom.controls[key]
// 关键步骤:克隆原型链中的controls配置,切断共享引用

// control.render = diamondControlRenderer
control.name = key
if (key === 'mt' || key === 'mb' || key === 'ml' || key === 'mr' || key === 'mtr') {
  control.render = diamondControlRenderer
}

})
}
// 1. 自定义控制点渲染函数(带菱形和图标)
const diamondControlRenderer = function (ctx, left, top, styleOverride, fabricObject) {
const size = 12 // 控制点大小
const radius = 4 // 半圆半径(高度固定为size)
ctx.save()
ctx.translate(left, top) // 将原点移动到控制点中心
ctx.rotate(((fabricObject.angle || 0) * Math.PI) / 180) // 旋转控制点,使其与对象保持一致
// --- 绘制菱形 ---
if (this.name === 'mt' || this.name === 'mb' || this.name === 'ml' || this.name === 'mr') {

// --- 绘制胶囊形状 ---
ctx.beginPath()
if (this.name === 'mt' || this.name === 'mb') {
  // 左侧半圆
  ctx.arc(-size / 2, 0, radius, Math.PI / 2, (Math.PI * 3) / 2, false)
  // // 右侧半圆
  ctx.arc(size / 2, 0, radius, -Math.PI / 2, Math.PI / 2, false)
} else {
  // 上半圆(从π到0,逆时针)
  ctx.arc(0, -size / 2, radius, Math.PI, 0, false)
  // 下半圆(从0到π,逆时针)
  ctx.arc(0, size / 2, radius, 0, Math.PI, false)
}
ctx.closePath()
ctx.fillStyle = '#FFFFFF'
ctx.strokeStyle = '#FFFFFF'
ctx.lineWidth = 1
ctx.fill()
ctx.stroke()

}
// --- 绘制内部图标 ---
if (this.name == 'mtr') {

let size = 25
ctx.save()
ctx.translate(0, 0)
ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle + 0))
ctx.drawImage(cachedImage, -size / 2, -size / 2, size, size)
ctx.restore()

}
ctx.restore()
}

const back = () => {
if (route.path == '/hb/make') {

setStorage('sourceImageId', '')
let currentPosterId = getStorage('currentPosterId')
let params = {
  posterId: currentPosterId,
  step: 0
}
updatePoster(params).then((res) => {
  if (res && res.code == 200) {
    push({
      path: '/hb/generation'
    })
  }
})

}
}
/**
*left页面通过图片点击使用调用
*/
const userImage = (object) => {
fabric.Image.fromURL(object.imageUrl, { crossOrigin: 'anonymous' }).then(

(img) => {
  // 设置图片的位置和大小
  img.set({
    left: state._cw / 2, // 图片的左坐标
    top: state._ch / 2 - state.away_top, // 图片的顶坐标
    width: object.width,
    height: object.height,
    originX: 'center',
    originY: 'center',
    selectable: true, // 禁用选择
    evented: true, // 禁用事件
    data: {
      imageId: object.imageId,
      imageUrl: object.imageUrl,
      effect: 0
    },
    showIndex: canvas.getObjects().length
  })

  // 添加图片到画布
  canvas.add(img)
},
{ crossOrigin: 'anonymous' }

)
}
/**
*通过历史记录的图片点击添加
*/
const addDTImgToCanva = (item) => {
if (canvas && canvas.openDrawingMode) {

//自由绘制时不允许添加
return

}
console.log(item.object)
if (!item.object.imageUrl) return
state._w = item.object.width
state._h = item.object.height
canvas.clipPath.left = state._cw / 2
canvas.clipPath.top = state._ch / 2 - state.away_top
canvas.clipPath.width = state._w
canvas.clipPath.height = state._h
canvas.getObjects().forEach(function (obj) {

// 检查对象的某些属性,例如 type, id 或其他自定义属性
if (obj.type === 'image' && obj.id === 'hbImg') {
  console.log('找到了对象:', obj)
  obj
    .setSrc(item.object.imageUrl, {
      crossOrigin: 'anonymous',
      width: state._w,
      height: state._h
    })
    .then(() => {
      console.log('为什么进不来')

      canvas.renderAll()
    })
  // setTimeout(() => {
  //   canvas.renderAll();
  //   canvas.requestRenderAll();
  //  },100)
}

})

//更新缓存数据
let hbMessage = getStorage('hbMessage')
state.baseMapUrl = item.object.imageUrl
hbMessage.imageUrl = item.object.imageUrl
hbMessage.imageId = item.object.imageId
hbMessage.backgroundImageSize.width = item.object.width
hbMessage.backgroundImageSize.height = item.object.height
setStorage('hbMessage', hbMessage)
state.baseMap.minWidth = state._w = item.object.width
state.baseMap.changeW = state._w //+ 200

state.baseMap.minHeight = state._h = item.object.height
state.baseMap.changeH = state._h //+ 100
// loadImageAndRect(result.data.backgroundImageUrl)
setStorage('sourceImageId', item.object.imageId)
setStorage('sourceImageUrl', item.object.imageUrl)
let currentPosterId = getStorage('currentPosterId')
let params = {

posterId: currentPosterId,
backgroundImageId: item.object.imageId,
backgroundImageUrl: item.object.imageUrl

}
updatePoster(params)
}
/**
*通过历史记录的素材点击添加
*/
const addImgToCanva = (item) => {
console.log(canvas.getObjects().length)
if (canvas && canvas.openDrawingMode) {

//自由绘制时不允许添加
return

}
fabric.Image.fromURL(item.object.imageUrl, { crossOrigin: 'anonymous' }).then(

(img) => {
  // 设置图片的位置和大小
  img.set({
    originX: 'center',
    originY: 'center',
    left: state._cw / 2, // 图片的左坐标
    top: state._ch / 2 - state.away_top, // 图片的顶坐标
    width: item.object.width,
    height: item.object.height,
    selectable: true, // 禁用选择
    evented: true, // 禁用事件
    data: {
      imageId: item.object.imageId,
      imageUrl: item.object.imageUrl,
      effect: 0
    },
    showIndex: canvas.getObjects().length,
    cornerStyle: 'circle', // 控制点样式
    cornerColor: 'white', // 控制点颜色
    transparentCorners: false // 控制点不透明
  })
  modifyControls(img)
  // 添加图片到画布
  canvas.add(img)
},
{ crossOrigin: 'anonymous' }

)
canvas.renderAll()
}

/**
*抠图后添加图片
*/
const mattingAddImgToCanva = (item) => {
console.log(item)
fabric.Image.fromURL(item.imageUrl, { crossOrigin: 'anonymous' }).then(

(img) => {
  // 设置图片的位置和大小
  img.set({
    originX: 'center',
    originY: 'center',
    left: state._cw / 2, // 图片的左坐标
    top: state._ch / 2 - state.away_top, // 图片的顶坐标
    width: item.width,
    height: item.height,
    selectable: true, // 禁用选择
    evented: true, // 禁用事件
    data: {
      imageId: item.imageId,
      imageUrl: item.imageUrl,
      effect: 0
    },
    showIndex: canvas.getObjects().length
  })

  // 添加图片到画布
  canvas.add(img)
},
{ crossOrigin: 'anonymous' }

)
canvas.renderAll()
}
/**

  • 针对背景裁剪
    */

const tailorBoxRef = ref<HTMLElement>()
const cropBgCanvasImg = () => {
state.cropBg = true
state.centerDialogVisible = true
state.imagePath = state.baseMapUrl
state.oldInpimgW = state._w
state.oldInpimgH = state._h
setTimeout(() => {

state.boxWidth = tailorBoxRef.value!.offsetWidth
state.boxHeight = tailorBoxRef.value!.offsetHeight

}, 100)
}

/**

  • 针对图片裁剪
    */

const cropCanvasImg = () => {
let objects = canvas.getActiveObjects()

if (objects.length > 1) {

return

}
let object = objects[0]
if (object.type != 'image') {

return

}
console.log(object.data)
state.chooseCanvasObject = object
state.centerDialogVisible = true
state.imagePath = object.data.imageUrl
state.oldInpimgW = object.width
state.oldInpimgH = object.height
setTimeout(() => {

state.boxWidth = tailorBoxRef.value!.offsetWidth
state.boxHeight = tailorBoxRef.value!.offsetHeight

}, 100)
}
/**

  • 下载素材
    */

const downloadMaterial = async () => {
let objects = canvas.getActiveObjects()

if (objects.length > 1) {

return

}
let object = objects[0]
if (object.type != 'image') {

return

}
try {

// 发起请求获取图片数据
const response = await fetch(object.data.imageUrl)
if (!response.ok) {
  throw new Error(`HTTP error! status: ${response.status}`)
}
// 读取响应体为 Blob 对象
const blob = await response.blob()
// 创建一个临时的 URL 对象
const url = URL.createObjectURL(blob)
// 创建一个 <a> 元素
const a = document.createElement('a')
a.href = url
// 设置下载的文件名
// a.download = 'downloaded_image.png'
a.download = object.data.imageId + '.png'
// 模拟点击 <a> 元素来触发下载
a.click()
// 释放临时的 URL 对象
URL.revokeObjectURL(url)

} catch (error) {

ElMessage.error('下载图片时出错')

}
}
/**

  • 针对图片 抠图
    */

const mattingCanvasImg = () => {
let objects = canvas.getActiveObjects()

if (objects.length > 1) {

return

}
let object = objects[0]
if (object.type != 'image') {

return

}
state.mattingDialogVisible = true
requestAnimationFrame(() => {

clearDrawing()

})

let _w = object.width > 1920 ? 1800 : 900
let _h = object.width > 1920 ? 720 : 360

let bWidth = object.width
let bHeight = object.height

if (bWidth / _w > bHeight / _h) {

state.imageWidth = bWidth / (bWidth / _w)
state.imageHeight = bHeight / (bWidth / _w)

} else if (bWidth / _w < bHeight / _h) {

state.imageWidth = bWidth / (bHeight / _h)
state.imageHeight = bHeight / (bHeight / _h)

}

// state.imageHeight = object.height;
// state.imageWidth = object.width;

//这里开始初始化canvas
requestAnimationFrame(() => {

//父调子
initMyCanvas(object)

})
}
const initMyCanvas = (object) => {
state.iamgeInitUrl = object.data.imageUrl
state.imageInitId = object.data.imageId
let canvas_ = document.getElementById('myCanvas') as HTMLCanvasElement
let container = document.getElementById('container') as HTMLCanvasElement
state.initCtx = canvas_.getContext('2d')

const canvasWidth = container.clientWidth - 32
const canvasHeight = container.clientHeight - 450

canvas_.width = state.imageWidth || canvasWidth
canvas_.height = state.imageHeight || canvasHeight

canvas_.addEventListener('click', (event: MouseEvent) => {

// 计算鼠标点击位置相对于canvas的坐标
const rect = canvas_.getBoundingClientRect()
const x = event.clientX - rect.left
const y = event.clientY - rect.top

// 添加新点到数组
state.points.push({ x, y })

// 清除画布并重新绘制所有点
state.initCtx.clearRect(0, 0, canvas_.width, canvas_.height)
state.points.forEach((point) => {
  state.initCtx.beginPath()
  state.initCtx.arc(point.x, point.y, 5, 0, 2 * Math.PI)
  state.initCtx.fillStyle = 'white'
  state.initCtx.fill()
})

})
}

//生成图片
const createObjSeg = () => {
if (state.points.length < 1) {

return ElMessage.warning('请先绘制')

}
// state.loading = true
const canvas = document.getElementById('myCanvas') as HTMLCanvasElement
//将canvas内容转换为Blob对象
canvas.toBlob(async (blob: Blob) => {

if (!blob) {
  ElMessage('上传失败')
  // state.loading = false
  return
}
// 使用Blob对象创建File对象
const file = new File([blob], 'image.png', { type: 'img/png' })
const formData = new FormData()
formData.append('file', file)
//上传绘制的图片
/*  const url =
  import.meta.env.VITE_API_BASE_PATH === '/api'
    ? 'http://localhost:5000/api/scene/upload'
    : `${import.meta.env.VITE_API_BASE_PATH}/scene/upload` */
const url = window.location.origin + '/api/v2/upload/images'
let res = await fetch(url, {
  method: 'POST', // 或者使用 'post'(不区分大小写)
  body: formData
})
  .then((response) => {
    return response.json()
  })
  .catch(() => {
    ElMessage('上传失败')
    // state.loading = false
  })
  .finally(() => {})

if (res.code == 200) {
  state.gloLoading = true
  state.loadingText = '抠图中...'
  //上传成功后生成图片
  matting({
    sourceImageId: state.imageInitId, //必须原图
    maskImageId: res.data.imageId // 必需 打点遮罩图
  })
    .then((segRes) => {
      if (segRes.code == 200) {
        // state.imageInitId = ''
        // state.iamgeInitUrl = ''
        // state.mattingDialogVisible = false;
        state.mattingObjectUrl = segRes.data.imageUrl
        state.mattingDialogVisible_Second = true
        state.mattingGenerate = segRes.data
        // refImageUrl(segRes.data);
        clearDrawing()
      } else {
        ElMessage('生成失败')
      }
    })
    .catch(() => {
      ElMessage('生成失败')
    })
    .finally(() => {
      // state.loading = false
      state.gloLoading = false
    })
} else {
  // state.loading = false
}

})
}

const refMatting = () => {
refImageUrl(state.mattingGenerate)
state.mattingDialogVisible = false
state.mattingDialogVisible_Second = false
}

//清除绘制
const clearDrawing = () => {
state.points = []
const canvas = document.getElementById('myCanvas') as HTMLCanvasElement
canvas.width = canvas.width
}

const base64 = ref('')
const cropperExpose = ref<InstanceType<typeof ImageCropping>>()
const getBase64 = () => {
state.gloLoading = true
state.loadingText = '导出下载中...'
base64.value = unref(cropperExpose)?.cropperExpose?.getCroppedCanvas()?.toDataURL() ?? ''

const formData = new FormData()
let blod = base64ToBlob(base64.value, 'image/png')
console.log(URL.createObjectURL(blod))
const file = new File([blod], 'fileName', { type: 'image/png' })

getResolutionFromImage(file, async (width: number, height: number) => {

//  当图片大小超过2048*2048,进行压缩
if (width > 2048 || height > 2048) {
  const compressionFile = await compressImage(file)
  // 计算压缩后的图片大小
  const { newWidth, newHeight } = calculateScaledDimensions(width, height)
  fileCue(formData, compressionFile, newWidth, newHeight)
} else if (width < 256 || height < 256) {
  ElMessage.error('图片分辨率不能小于256*256')
  state.gloLoading = false
} else if ((width >= 256 && width <= 2048) || (height >= 256 && height <= 2048)) {
  formData.append('file', file) // 将文件添加到FormData对象中
  fileUpload(formData)
}

})
}

// 文件接口
const fileUpload = (formData: any) => {
const url = window.location.origin + '/api/v2/upload/images'
fetch(url, {

method: 'POST', // 或者使用 'post'(不区分大小写)
body: formData

})

.then((response) => {
  if (!response.ok) {
    ElMessage.error('网络响应不是ok状态')
  }
  return response.json() // 如果需要,可以返回解析后的JSON数据
})
.then((data) => {
  console.log('我上传成功了')
  state.centerDialogVisible = false
  state.imagePath = ''
  state.baseMapUrl = data.data.imageUrl
  if (state.cropBg) {
    let hbMessage = getStorage('hbMessage')
    hbMessage.imageUrl = data.data.imageUrl
    hbMessage.imageId = data.data.imageId
    hbMessage.backgroundImageSize.width = data.data.width
    hbMessage.backgroundImageSize.height = data.data.height
    setStorage('hbMessage', hbMessage)
    setStorage('sourceImageId', data.data.imageId)
    setStorage('sourceImageUrl', data.data.imageUrl)

    state.cropBg = false
    state.baseMap.minWidth = state._w = data.data.width
    state.baseMap.minHeight = state._h = data.data.height
    canvas.clipPath.left = state._cw / 2
    canvas.clipPath.top = state._ch / 2 - state.away_top
    canvas.clipPath.width = state._w
    canvas.clipPath.height = state._h
    canvas.getObjects().forEach(function (obj) {
      // 检查对象的某些属性,例如 type, id 或其他自定义属性
      if (obj.type === 'rect' && obj.id === 'hsc') {
        obj.left = state._cw / 2
        obj.top = state._ch / 2 - state.away_top
        obj.width = state._w
        obj.height = state._h
        canvas.renderAll()
      }
      if (obj.type === 'image' && obj.id === 'hbImg') {
        obj.setSrc(data.data.imageUrl, { crossOrigin: 'anonymous' }).then(() => {
          obj.left = state._cw / 2
          obj.top = state._ch / 2 - state.away_top
          obj.width = state._w
          obj.height = state._h
          canvas.renderAll()
        })
      }
    })

    let currentPosterId = getStorage('currentPosterId')
    let params = {
      posterId: currentPosterId,
      backgroundImageId: data.data.imageId,
      backgroundImageUrl: data.data.imageUrl
    }
    updatePoster(params)

    let param_ = {
      posterId: currentPosterId,
      type: 1,
      imageList: [data.data.imageId]
    }

    addGenerateRecords(param_).then((res) => {
      if (res.code === 200) {
        GenerateRecord.value!.refGenerImg()
      }
    })
  } else {
    refImageUrl(data.data)
  }
})
.catch(() => {
  ElMessage.error('上传失败')
})
.finally(() => (state.gloLoading = false))

}

/**

  • 更新图片组件的显示内容

*/
const refImageUrl = (data) => {
console.log(data)
let object = canvas.getActiveObject()

canvas.discardActiveObject()

if (object.type != 'image') {

return

}
console.log(object.data)
let objectData = object.data
if (objectData == null) {

object.set('data', {
  imageUrl: data.imageUrl,
  imageId: data.imageId
})

} else {

objectData.imageUrl = data.imageUrl
objectData.imageId = data.imageId
object.set('data', objectData)

}

state.imagePath = data.imageId

object.setSrc(data.imageUrl, { crossOrigin: 'anonymous' }).then(() => {

console.log('123')
canvas.setActiveObject(object)
canvas.requestRenderAll()

})

let currentPosterId = getStorage('currentPosterId')
let param_ = {

posterId: currentPosterId,
type: 2,
imageList: [data.imageId]

}

addGenerateRecords(param_).then((res) => {

if (res.code === 200) {
  GenerateRecord.value!.refGenerList()
}

})
}

const fileCue = (formData: any, compressionFile: any, newWidth: number, newHeight: number) => {
ElMessageBox.confirm(该图片超过2K,将等比缩放图片压缩至${newWidth}*${newHeight}, '提示', {

confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'info'

})

.then(() => {
  formData.append('file', compressionFile) // 将文件添加到FormData对象中
  fileUpload(formData)
})
.catch(() => {
  ElMessage({
    type: 'info',
    message: '取消上传'
  })
})

}

// 图片压缩
const compressImage = (file: File) => {
return new Promise((resolve, reject) => {

new Compressor(file, {
  // 压缩质量,0-1之间。数字越小,压缩比越高
  quality: 0.6,
  width: 2048,
  height: 2048,
  success(result) {
    // 默认返回result是blob类型的文件,可以转成file并返回,交给afterRead钩子进行上传
    let newFile = new File([result], file.name, { type: file.type })
    resolve(newFile)
  },
  error(err) {
    reject(err)
  }
})

})
}

// 图片分辨率
const getResolutionFromImage = (file, callback) => {
var reader = new FileReader()
reader.onload = function (e) {

var img = new Image()
img.onload = function () {
  callback(img.width, img.height)
}
img.src = e.target?.result as string // 设置 img.src

}
reader.readAsDataURL(file)
}
const openPoster = async (id, oldId) => {
//切换场景前先保存一次
if (oldId != null) {

await saveData(oldId)

}

// 获取海报资产信息
getPosterInfo({ posterId: id }).then((result) => {

if (result && result.code == 200) {
  canvas?.clear()
  state.generateName = result.data.name
  setStorage('currentPosterName', result.data.name)
  setStorage('hbMessage', result.data)
  setStorage('sourceImageId', result.data.backgroundImageId)
  setStorage('sourceImageUrl', result.data.backgroundImageUrl)
  state.baseMap.minWidth = state._w = result.data.backgroundImageSize.width
  state.baseMap.minHeight = state._h = result.data.backgroundImageSize.height
  loadImageAndRect(result.data.backgroundImageUrl)
  nextTick(() => {
    // state.draggableResizable = true
    generateListHtml.value!.updateGenerateName({ name: state.generateName })

    requestAnimationFrame(() => {
      Promise.all(fonts.map((font) => font.load())).then((loadedFonts) => {
        // 将所有加载的字体添加到 `document.fonts`
        loadedFonts.forEach((font) => document.fonts.add(font))
        document.fonts.ready.then(() => {
          initTextObject(JSON.parse(result.data.metaData) || [])
        })
      })
    })
  })
}

})
}

/**

  • base64转bold
    */

const base64ToBlob = (base64, mimeType) => {
// 提取纯 Base64 数据(去掉前缀)
const base64Data = base64.split(',')[1]

// 将 Base64 字符串转换为二进制数据
const byteCharacters = atob(base64Data) // 解码 Base64
const byteNumbers = new Array(byteCharacters.length)
for (let i = 0; i < byteCharacters.length; i++) {

byteNumbers[i] = byteCharacters.charCodeAt(i) // 转换为字节数组

}

// 创建 Blob 对象
const byteArray = new Uint8Array(byteNumbers)
return new Blob([byteArray], { type: mimeType })
}
//修改海报名称
const updateName = (param) => {
let currentPosterId = getStorage('currentPosterId')
if (currentPosterId) {

state.generateName = param.name
setStorage('currentPosterName', state.generateName)
let params = {
  posterId: currentPosterId,
  name: param.name
}
renamePosters(params)
queryData()

}
}
/**

  • logo上传成功后的事件
    */

const uploadImage = (data) => {
console.log('进来了')
let ids: any = []
ids.push(data.imageId)
let currentPosterId = getStorage('currentPosterId')
let param_ = {

posterId: currentPosterId,
type: 2,
imageList: ids

}

addGenerateRecords(param_).then((res) => {

if (res.code === 200) {
  GenerateRecord.value!.refGenerList()
}

})
fabric.Image.fromURL(data.imageUrl, { crossOrigin: 'anonymous' }).then(

(img) => {
  // 设置图片的位置和大小
  img.set({
    left: state._cw / 2, // 图片的左坐标
    top: state._ch / 2 - state.away_top, // 图片的顶坐标
    width: data.width,
    height: data.height,
    selectable: true, // 禁用选择
    evented: true, // 禁用事件
    data: {
      imageId: data.imageId,
      imageUrl: data.imageUrl,
      effect: 0
    },
    originX: 'center',
    originY: 'center',
    showIndex: canvas.getObjects().length
  })
  modifyControls(img)
  // 添加图片到画布
  canvas.add(img)
},
{ crossOrigin: 'anonymous' }

)
canvas.renderAll()
}

/**

  • 获取添加的文件的option
    */

const getTextOption = () => {
let index = canvas.getObjects().length
console.log(index)
return {

left: state._cw / 2, // 文本框的 X 坐标
top: state._ch / 2 - state.away_top, // 文本框的 Y 坐标
width: 300, // 文本框的宽度
fontSize: 42, // 字体大小
borderColor: 'white', // 边框颜色
fill: 'white', // 文本颜色
hasControls: true, // 允许调整大小和旋转
hasBorders: true, // 显示边框
textAlign: 'left', // 文本对齐方式
// editingBorderColor: 'red',
fontFamily: 'huiwenfangsong',
splitByGrapheme: true,
cornerStyle: 'circle', // 控制点样式
cornerColor: 'white', // 控制点颜色
transparentCorners: false, // 控制点不透明
lockScalingFlip: false, // 允许翻转
lockUniScaling: false, // 允许非等比缩放
selectable: true, // 确保对象可选择
evented: true, // 确保对象可交互
data: {
  effect: 0
},
shadow: {},
stroke: '',
originX: 'center',
originY: 'center',
showIndex: index

}
}

/**
*

  • @param type 艺术字
    */

const generateText = (type) => {
let options = getTextOption()
const placeholderText = '请输入文字...'
if (type == 0) {

options.fontFamily = 'Slidefu-Regular'
options.fill = '#FFFFFF'

} else if (type == 1) {

options.fontFamily = 'FeiboZhengdianBody'
options.fill = '#FFFFFF'

} else if (type == 2) {

options.fontFamily = 'Optimal-Title'
options.fill = '#FFFFFF'
options.shadow = {
  color: '#0DE41C', // 阴影颜色
  blur: 5, // 模糊半径
  offsetX: 2, // 水平偏移
  offsetY: 2 // 垂直偏移
}

} else if (type == 3) {

options.fontFamily = 'SmileySans-Oblique'
options.fill = new fabric.Gradient({
  type: 'linear',
  gradientUnits: 'pixels', // 或 'percentage'
  coords: { x1: 0, y1: 0, x2: 0, y2: 50 }, // 水平渐变
  colorStops: [
    { offset: 0, color: '#EFFEC9' },
    { offset: 1, color: '#6FFEE4' }
  ]
})

} else if (type == 4) {

options.fontFamily = 'Fragrant-comfortable'
options.fill = '#FFFFFF'
options.stroke = '#FDDD28'
options.shadow = {}

}

let textbox = new fabric.Textbox(placeholderText, options)
textbox.setControlVisible('mt', false)
textbox.setControlVisible('mb', false)
textbox.selectionStart = textbox.text.length
textbox.selectionEnd = textbox.text.length
modifyControls(textbox)
canvas.add(textbox)
canvas.setActiveObject(textbox)
textbox.enterEditing()
// 重新渲染画布
canvas.renderAll()
}

const exportImage = throttle(
function () {

console.log('11111')
state.gloLoading = true
state.loadingText = '导出下载中...'
let objects = canvas.getObjects()

let activity = canvas.getActiveObjects()

canvas.discardActiveObject()
let type = 0

for (var i = 0; i < objects.length; i++) {
  if (objects[i].data != null && objects[i].data.effect != null) {
    let effect = objects[i].data.effect
    if (objects[i].type == 'image') {
      //图片
      console.log(effect)

      if (effect == 1) {
        type = 1
        //淡入淡出
        imgFadeEffec(objects[i])
      }
      if (effect == 2) {
        type = 1
        //旋转
        imgRotate(objects[i])
      }
      if (effect == 3) {
        type = 1
        //缩放
        imgScale(objects[i])
      }

      if (effect == 4) {
        type = 1
        //翻转
        imgFlip(objects[i])
      }
      if (effect == 5) {
        type = 1
        //抖动
        imgShake(objects[i])
      }
    } else if (textObject.includes(objects[i].type)) {
      //文字
      if (effect == 1) {
        type = 1
        fadeEffec(objects[i])
      }
      if (effect == 2) {
        type = 1
        colorFillFun(objects[i])
      }

      if (effect == 3) {
        type = 1
        rotateText(objects[i])
      }

      if (effect == 4) {
        type = 1
        scale(objects[i])
      }

      if (effect == 5) {
        type = 1
        movePosition(objects[i])
      }
    }
  }
}
if (type == 1) {
  capturerFun(activity)
} else {
  captureImg(activity)
}

},
1000,
{ trailing: false }
)

//撤销画笔
const revoke = () => {
state.points.pop()
const canvas = document.getElementById('myCanvas') as HTMLCanvasElement
// 清除画布并重新绘制所有点
state.initCtx.clearRect(0, 0, canvas.width, canvas.height)
state.points.forEach((point) => {

state.initCtx.beginPath()
state.initCtx.arc(point.x, point.y, 5, 0, 2 * Math.PI)
state.initCtx.fillStyle = '#ffffff'
state.initCtx.fill()

})
}

/**

  • 关闭裁剪
    */

const closeHeading = () => {
state.centerDialogVisible = false
state.cropBg = false
}

/**
*

  • @param param 点击立即生成调用
    */

const onGenerates = async (param) => {
try {

// if (param.imgaeId == null) {
//   param.imageId = '1'
// }

generateMaterials(param)
  .then((res) => {
    if (res.code === 200) {
      leftToolHtml.value!.refImageList(res.data)

      let ids: any = []
      res.data.forEach((element) => {
        ids.push(element.imageId)
      })

      let currentPosterId = getStorage('currentPosterId')
      let param_ = {
        posterId: currentPosterId,
        type: 2,
        imageList: ids
      }

      addGenerateRecords(param_).then((res) => {
        if (res.code === 200) {
          GenerateRecord.value!.refGenerList()
        }
      })
    }
  })
  .finally(() => {
    hbCreateRef.value!.resetLoading()
  })

} catch {}
}
</script>
<template>
<div class="hb-module" id="hb-module" ref="canvasContainerRef">

<div class="to-back cursor-pointer" v-show="state.parentResultType==0">
  <div @click="back">
    <el-icon>
      <ArrowLeftBold />
    </el-icon>
    <span class="ml-5px mb-2px">返回</span>
  </div>
</div>

<div class="canvas-container">
  <canvas id="canvasContainer" ref="canvasRef"></canvas>
  <!-- 顶部工具栏 -->
  <section class="absolute-top20-left-50">
    <hbTopTool ref="hbTopToolHtml" @addFonts="addText" @setDirection="setDirection" @setColors="updateTextColor" @setBrushColor="updateBrushColor" @setBrushSize="updateBrushSize"
      @setBrushOpacity="updateBrushOpacity" @setFontBold="updateTextFontBold" @setFontSize="updateTextFontSize" @setFontPosition="updateTextFontPosition" @setFontEffect="setFontEffect"
      @imgDynamicEffect="imgDynamicEffect" @changeFontFamily="updateFontFamily" :modeType="state.parentModeType" @draw="drawFree" @undo="brushUndo" @clear="brushClear" @cropImg="cropCanvasImg"
      @cropBgImg="cropBgCanvasImg" @mattingCanvasImg="mattingCanvasImg" @downloadMaterial="downloadMaterial" @uploadImage="uploadImage" @enlargeMap="enlargeMap"
      v-show="state.parentModeType!=0&&(!state.brushComplete||state.parentModeType!=2)">
    </hbTopTool>
  </section>
  <!-- 左侧工具栏 -->
  <section class="absolute-left20-left-50" v-show="state.parentResultType==0">
    <leftTool ref="leftToolHtml" @toolMode="toolModes" @userImage="userImage"></leftTool>
  </section>
  <!-- 右侧历史记录 -->
  <section class="hc-re">
    <generateRecord ref="GenerateRecord" @addImgToCanvas="addImgToCanva" @addDTImgToCanvas="addDTImgToCanva" v-show="state.parentResultType==0"></generateRecord>
    <!-- 右下侧缩放 -->
    <section class="hc-zoom" :msg="state.zoom">
      <proportionalValue :zoom="state.zoom" v-show="state.parentResultType==0"></proportionalValue>
    </section>
  </section>

</div>

<section class="absolute-bottom20-left-50">
  <!-- 底部工具栏  -->
  <hbBottomTool :modeType="state.parentModeType" v-if="state.parentModeType!=0" :resultType="state.parentResultType" @clearCommit="clearCommit" @reClear="reClear" :drawnPaths="state.drawnPaths"
    :brushStatus="state.brushStatus" @reEdit="reEdit" @complete="complete" @generateText="generateText">
  </hbBottomTool>
  <!-- 底部工具栏 素材 生成 -->
  <hbCreate :btnText="state.btnText" v-if="state.parentModeType==3" @onGenerate="onGenerates" ref="hbCreateRef" :contentPlaceholder="state.contentPlaceholder"></hbCreate>
</section>
<generateList :generateName="state.generateName" :lastPosters="state.lastPosters" @openPosters="openPoster" @updateName="updateName" ref="generateListHtml" v-show="state.parentResultType==0">
</generateList>

<!-- 底图裁剪和素材裁剪 -->
<div v-if="state.centerDialogVisible" class="glo-loading">
  <div class="dialog-draggable-resizable" v-loading="state.gloLoading" element-loading-custom-class="htm-custom-loading" element-loading-spinner="el-loading-spinner"
    element-loading-background="rgba(0, 0, 0, 0.6)" :element-loading-text="state.kuotuTextloading">
    <div class="heading">
      <span class="heading__title">裁剪</span>
      <span class="heading__close" @click="closeHeading"></span>
    </div>

    <div class="caijian" ref="tailorBoxRef" v-loading="state.gloLoading" element-loading-custom-class="htm-custom-loading" element-loading-spinner="el-loading-spinner"
      element-loading-background="rgba(0, 0, 0, 0.6)" element-loading-text="裁剪中...">
      <ImageCropping ref="cropperExpose" :image-url="state.imagePath" :boxWidth="state.boxWidth" :boxHeight="state.boxHeight" :oldInpimgW="state.oldInpimgW" :oldInpimgH="state.oldInpimgH" />
    </div>

    <div class="dialog-footer2">
      <span class="h-btn btn-cancel" @click="state.centerDialogVisible = false">
        取消
      </span>
      <span class="h-btn btn-confirm" @click="getBase64">
        确认
      </span>
    </div>
  </div>
</div>

<!-- 抠图弹窗 -->
<div v-if="state.mattingDialogVisible" class="glo-loading">
  <div class="dialog-draggable-resizable">
    <div class="heading">
      <span class="heading__title">抠图</span>
      <span class="heading__close" @click="state.mattingDialogVisible=false"></span>

      <div class="btn-options">
        <el-tooltip effect="dark" content="撤销打点" placement="bottom">
          <div class="btn-img">
            <span class="revoke-piont" @click="revoke"></span>
          </div>
        </el-tooltip>
        <el-tooltip effect="dark" content="清空打点" placement="bottom">
          <div class="btn-img">
            <span class="clear-piont" @click="clearDrawing"></span>
          </div>
        </el-tooltip>
      </div>
    </div>

    <div ref="mattingRef" class="caijian image-matting">
      <div id="container" class="image" :style="{'background': `url(${state.iamgeInitUrl}) center center no-repeat`,width:state.imageWidth+'px',height:state.imageHeight+'px'}">
        <canvas class="myCanvas" id="myCanvas" :style="{ width: state.imageWidth + 'px', height: state.imageHeight + 'px' }" :msg="state.imageWidth + '??' + state.imageHeight"></canvas>
      </div>
    </div>

    <div class="dialog-footer2">
      <span class="h-btn btn-cancel" @click="state.mattingDialogVisible = false">
        取消
      </span>
      <span class="h-btn btn-confirm" @click="createObjSeg">
        确认
      </span>
    </div>
  </div>
</div>

<!--  <el-dialog v-model="state.mattingDialogVisible" title="抠图" destroy-on-close center width="65%">
  <div ref="mattingRef" class="image-matting" style="height:65vh;">
    <div id="container" class="image" :style="{'background': `url(${state.iamgeInitUrl}) center center no-repeat`,width:state.imageWidth+'px',height:state.imageHeight+'px'}">
      <canvas class="myCanvas" id="myCanvas" :style="{ width: state.imageWidth + 'px', height: state.imageHeight + 'px' }" :msg="state.imageWidth + '??' + state.imageHeight"></canvas>
    </div>
  </div>

  <template #footer>
    <div class="dialog-footer">
      <el-button @click="state.mattingDialogVisible = false">取消</el-button>
      <el-button type="primary" @click="createObjSeg">
        确认
      </el-button>
      <el-button @click="revoke">
        撤销打点
      </el-button>
      <el-button @click="clearDrawing">
        清空打点
      </el-button>
    </div>
  </template>
</el-dialog> -->

<!-- 抠图 是否应用? -->
<div v-if="state.mattingDialogVisible_Second" class="glo-loading">
  <div class="dialog-draggable-resizable">
    <div class="heading">
      <span class="heading__title">是否应用?</span>
      <span class="heading__close" @click="state.mattingDialogVisible_Second=false"></span>
    </div>

    <div class="caijian image-matting">
      <div id="container" class="image" :style="{ 'background-image': `url(${state.mattingObjectUrl})`,width:state.imageWidth+'px',height:state.imageHeight+'px'}">
        <canvas class="myCanvas" id="myCanvas" :style="{ width: state.imageWidth + 'px', height: state.imageHeight + 'px' }" :msg="state.imageWidth + '??' + state.imageHeight"></canvas>
      </div>
    </div>

    <div class="dialog-footer2">
      <span class="h-btn btn-cancel" @click="state.mattingDialogVisible_Second = false">
        撤销
      </span>
      <span class="h-btn btn-confirm" @click="refMatting">
        确认
      </span>
    </div>
  </div>
</div>

<!-- <el-dialog v-model="state.mattingDialogVisible_Second" title="是否应用?" destroy-on-close center width="65%">
  <div class="image-matting" style="height:65vh;">
    <div id="container" class="image" :style="{ 'background-image': `url(${state.mattingObjectUrl})`,width:state.imageWidth+'px',height:state.imageHeight+'px'}">
      <canvas class="myCanvas" id="myCanvas" :style="{ width: state.imageWidth + 'px', height: state.imageHeight + 'px' }" :msg="state.imageWidth + '??' + state.imageHeight"></canvas>
    </div>
  </div>

  <template #footer>
    <div class="dialog-footer">
      <el-button @click="state.mattingDialogVisible_Second = false">撤销</el-button>
      <el-button type="primary" @click="refMatting">
        确认
      </el-button>
    </div>
  </template>
</el-dialog> -->
<!-- 扩图 -->
<div v-if="state.dialogdraggableResizable" class="glo-loading">
  <div class="dialog-draggable-resizable" v-loading="state.loading2" element-loading-custom-class="htm-custom-loading" element-loading-spinner="el-loading-spinner"
    element-loading-background="rgba(0, 0, 0, 0.6)" :element-loading-text="state.kuotuTextloading">
    <div class="heading">
      <span class="heading__title">{{state.kuotuText}}</span>
      <span class="heading__close" @click="state.dialogdraggableResizable=false;state.contrastImgStatus = false;state.kuotuText = '扩图'"></span>
    </div>

    <div class="parent-draggable-resizable" ref="parentDragResizRef">
      <EnlargeSceneMatch v-show="!state.contrastImgStatus" ref="enlargeSceneMatchRef"></EnlargeSceneMatch>
      <!-- 扩图对比 -->
      <contrastImg2 v-show="state.contrastImgStatus" ref="contrastImgRef2"></contrastImg2>
    </div>
    <div class="h-bottom-resizable" v-show="!state.contrastImgStatus">
      <div class="img-size">
        <div class="scale-mode">
          <div class="proportion" :class="{'proportion-bg':index==state.toolbarActive}" v-for="(item,index) in state.toolbar" :key="index" @click="switchingRatio(item,index)">
            <span class="px-img2" :class="[`bl-${item.id}`]"></span> {{item.name}}
          </div>
          <el-button type="primary" :disabled="false" :loading="state.loading" @click="baseMapGeneration">立即生成</el-button>
        </div>
      </div>
    </div>
    <div class="dialog-footer2" v-show="state.contrastImgStatus">
      <span class="h-btn btn-cancel" @click="state.contrastImgStatus = false;state.kuotuText = '扩图'">
        取消
      </span>
      <span class="h-btn btn-confirm" @click="playGenerate">
        确认
      </span>
    </div>
  </div>
</div>

<div class="glo-loading" v-if="state.gloLoading" v-loading="state.gloLoading" element-loading-custom-class="htm-custom-loading" element-loading-spinner="el-loading-spinner"
  element-loading-background="rgba(0, 0, 0, 0.7)" :element-loading-text="state.loadingText">
</div>
<!-- 动画透明遮罩 -->
<div :msg="state.gloLoading2" class="glo-loading2" v-if="state.gloLoading2">
</div>

<div :msg="state.parentResultType+'?'+state.guiding3" class="guiding-prompt3" v-show="state.parentResultType==1&&state.guiding3==1" @click="setguiding3">
  <div class="guiding-prompt2" id="guiding3">
    <div class="triangle-tip"></div>
    <div class="centen">
      <span class="txt1">
        涂抹消除或修改
      </span>
      <span class="txt2">
        将鼠标悬停在图片上,按住左键即可像握笔一样随心涂抹 ,可以涂抹图片中不需要的部分
      </span>
    </div>
  </div>
</div>
<div class="guiding-prompt3" v-show="state.parentResultType==3&&state.guiding4==1" @click="setguiding4">
  <div class="guiding-prompt2" id="guiding4">
    <div class="triangle-tip"></div>
    <div class="centen">
      <span class="txt1">
        涂抹消除或修改
      </span>
      <span class="txt2">
        将鼠标悬停在图片上,按住左键即可像握笔一样随心涂抹 ,可以涂抹图片中不需要的部分
      </span>
    </div>
  </div>
</div>

</div>
</template>
<style lang="less" scoped>

  • {
    user-select: none;
    }
    .hb-module {
    width: 100%;
    height: calc(100vh - 1px - var(--logo-height));
    position: relative;
    background: #0f1115;
    overflow: hidden !important;
    .canvas-container {
    background: #0f1115;
    position: relative;
    }

    canvasContainer {

    overflow: inherit !important;
    border: 0 !important;
    }
    .hc-re {
    position: absolute;
    top: 20px;
    right: 20px;
    z-index: 8;
    height: calc(100% - 20px);
    min-height: calc(260px + 48px + 40px + 35px);
    }
    .hc-zoom {
    position: absolute;
    right: 20px;
    bottom: 20px;
    z-index: 8;
    // bottom: 0px;
    height: 35px;
    }
    .to-back {
    position: absolute;
    left: 10px;
    top: 10px;
    z-index: 8;
    div {

    width: 74px;
    height: 40px;
    background: #1d2129;
    border-radius: 6px;
    display: flex;
    align-items: center;
    justify-content: center;

    }
    }
    .image-matting {
    display: flex;
    align-items: center;
    width: 100%;
    position: relative;
    justify-content: center;
    .image {

    width: 100%;
    height: 500px;
    background-size: 100% 100% !important;

    }
    }
    }
    .dialog-draggable-resizable {
    width: 80%;
    height: 85%;
    padding: 20px 20px 0 20px;
    position: fixed;
    left: 50%;
    top: 50%;
    z-index: 9999;
    transform: translate(-50%, -50%);
    background: #2a2e33;
    border: 1px solid #484d53;
    border-radius: 20px;
    .heading {
    display: flex;
    justify-content: space-between;
    align-items: center;
    color: #fff;
    font-size: 16px;
    height: 28px;
    margin-bottom: 20px;
    .heading__close {

    background: url('../../assets/img/close3.png') no-repeat center;
    display: flex;
    width: 28px;
    height: 28px;
    background-size: 100% 100% !important;
    cursor: pointer;
    opacity: 0.7;
    &:hover {
      opacity: 1;
    }

    }
    .btn-options {

    position: absolute;
    z-index: 99;
    left: 50%;
    transform: translate(-50%, 0);
    display: flex;
    height: 40px;
    background: #1d2129;
    border-radius: 6px;
    display: flex;
    align-items: center;
    box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.25);
    padding: 4px;
    .btn-img {
      width: 32px;
      height: 32px;
      border-radius: 4px;
      &:hover {
        background: #323942;
      }
      span {
        cursor: pointer;
        display: flex;
        width: 32px;
        height: 32px;
        background-size: 20px 20px !important;
      }
      span + span {
        margin-left: 8px;
      }
      .revoke-piont {
        background: url('../../assets/img/cs.png') no-repeat center;
      }
      .clear-piont {
        background: url('../../assets/img/qkhb.png') no-repeat center;
      }
    }

    }
    }
    .caijian {
    width: 100%;
    // 28+20+92
    height: calc(100% - 140px);
    position: relative;
    }
    // 28+40+90
    .parent-draggable-resizable {
    width: 100%;
    height: calc(100% - 158px);
    position: relative;
    }
    .h-bottom-resizable {
    position: absolute;
    bottom: 20px;
    left: 50%;
    transform: translate(-50%, 0);
    .img-size {

    width: 664px;
    height: 90px;
    // background: #1d2129;
    border-radius: 10px;
    padding: 16px;
    .scale-mode {
      display: flex;
      .proportion {
        width: 64px;
        height: 58px;
        background: #404853;
        border-radius: 10px;
        margin-right: 8px;
        font-size: 12px;
        font-family: PingFang SC, PingFang SC-400;
        font-weight: 400;
        color: #ffffff;
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        cursor: pointer;
        .px-img2 {
          display: flex;
          width: 20px;
          height: 20px;
          background-size: 100% 100% !important;
        }
        .bl-1 {
          background: url('../../assets/img/zyms.png') no-repeat center;
        }
        .bl-2 {
          background: url('../../assets/img/bl.png') no-repeat center;
        }
        .bl-3 {
          background: url('../../assets/img/1v1.png') no-repeat center;
        }
        .bl-4 {
          // 9:16
          background: url('../../assets/img/bl-2.png') no-repeat center;
        }
        .bl-5 {
          // 16:9
          background: url('../../assets/img/bl-3.png') no-repeat center;
        }
        .bl-6 {
          background: url('../../assets/img/bl-4.png') no-repeat center;
        }
        .bl-7 {
          background: url('../../assets/img/bl-5.png') no-repeat center;
        }
      }
      .proportion-bg {
        background: #696e76 !important;
      }
      .el-button {
        width: 128px;
        height: 58px;
        line-height: 58px;
        background: linear-gradient(90deg, #0066ff, #00b2ff 100%);
        border-radius: 10px;
        font-size: 14px;
      }
    }

    }
    }
    .dialog-footer2 {
    position: absolute;
    bottom: 20px;
    left: 50%;
    transform: translate(-50%, 0);
    display: flex;
    justify-content: center;
    align-items: center;
    .h-btn {

    width: 72px;
    height: 40px;
    border-radius: 6px;
    font-size: 14px;
    color: #ffffff;
    display: flex;
    justify-content: center;
    align-items: center;
    cursor: pointer;

    }
    .h-btn + .h-btn {

    margin-left: 8px;

    }
    .btn-cancel {

    background: #394147;
    &:hover {
      background: #485157;
    }

    }
    .btn-confirm {

    background: #0066ff;
    box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.25);
    &:hover {
      background: #247bff;
    }

    }
    }
    :deep(.htm-custom-loading) {
    border-radius: 20px;
    }
    }

</style>
<style lang="less">
.my-handle-class {

position: absolute;
z-index: 99;
background-color: #fff;
border: none;
border-radius: 50%;
height: 14px;
width: 14px;
-webkit-transition: all 300ms linear;
-ms-transition: all 300ms linear;
transition: all 300ms linear;

}
.my-handle-class-tl {

top: -5px;
left: -7px;
cursor: nw-resize;

}

.my-handle-class-tm {

top: -5px;
cursor: n-resize;
border-radius: 4px;
width: 20px;
height: 8px;
left: 50%;
transform: translate(-50%, 0px);

}

.my-handle-class-tr {

top: -5px;
right: -7px;
cursor: ne-resize;

}

.my-handle-class-ml {

top: 50%;
left: -5px;
cursor: w-resize;
border-radius: 4px;
width: 8px;
height: 20px;
top: 50%;
transform: translate(0px, -50%);

}

.my-handle-class-mr {

top: 50%;
right: -5px;
cursor: w-resize;
border-radius: 4px;
width: 8px;
height: 20px;
top: 50%;
transform: translate(0px, -50%);

}

.my-handle-class-bl {

bottom: -5px;
left: -7px;
cursor: sw-resize;

}

.my-handle-class-bm {

bottom: -5px;
cursor: n-resize;
border-radius: 4px;
width: 20px;
height: 8px;
left: 50%;
transform: translate(-50%, 0px);

}

.my-handle-class-br {

bottom: -5px;
right: -7px;
cursor: se-resize;

}
.vdr {

position: absolute;
border: 1px solid #0066ff;
box-sizing: border-box;

}
.glo-loading {

position: fixed !important;
width: 100%;
height: 100%;
top: 0;
left: 0;
z-index: 99;

}
.glo-loading2 {

position: fixed !important;
width: 100%;
height: 100%;
top: 0;
left: 0;
z-index: 99;
cursor: not-allowed;

}
.guiding-prompt3 {

position: fixed; /* 切换为固定定位,脱离父级约束 */
left: 0;
top: 0;
right: 0;
bottom: 0; /* 四边撑满视口 */
width: 100%;
height: 100%;
z-index: 9999; /* 确保遮罩层位于页面最顶层 */
.guiding-prompt2 {
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
}

}
</style>


煌大河ゞ
18 声望2 粉丝