定制化制定海报功能

96 阅读15分钟
  1. 预设海报模板,可以选择任一模板,在模板基础上添加自定义的内容
  2. 支持上传图片素材,用户可以拖拽图片至画布中
  3. 支持添加文本至画布,设置文本相关样式(字体颜色,字体大小,粗体,斜体等)
  4. 输入x,y坐标,设置元素在画布中的位置
  5. 添加文本对齐方式(左对齐,居中,右对齐),仅支持文本,不支持图片
  6. 优化----实现图片在鼠标松开时直接出现在最终位置且效果更加丝滑,见代码二:

代码一:

<template>
  <div class="warp-card">
    <div class="certificate-editor">
      <!-- 左侧工具栏 -->
      <div class="toolbar">
        <!-- 模板选择 -->
        <div class="tool-section">
          <h3>
            <el-icon
              title="Select a template and customize the content based on the existing template"
              ><InfoFilled
            /></el-icon>
            Default Template
          </h3>
          <div class="template-list" v-if="templates && templates.length">
            <div
              v-for="(template, index) in templates"
              :key="index"
              class="template-item"
              @click="applyTemplate(template)"
            >
              <el-image
                :src="template.logo"
                fit="contain"
                style="border-bottom: 1px solid #dcdfe6"
              />
              <div class="template-name">{{ template.name }}</div>

              <div
                class="delete-icon"
                @click.stop="deleteItem(index, template)"
              >
                x
              </div>
            </div>
          </div>
        </div>

        <div class="tool-section">
          <h3>
            <el-icon
              title="Users can customize and upload the image materials they need"
              ><InfoFilled
            /></el-icon>
            Image Tools
          </h3>
          <div class="image-grid" v-if="images && images.length">
            <div
              v-for="(img, index) in images"
              :key="index"
              class="image-item"
              draggable="true"
              @dragstart="onImageDragStart($event, img)"
            >
              <el-image
                :src="img.src"
                fit="contain"
                :preview-src-list="[img.src]"
                :initial-index="0"
                preview-teleported
                title="Click to Preview the Image"
              >
                <!-- <template #error>
                  <div class="image-slot">
                    <el-icon><icon-picture /></el-icon>
                  </div>
                </template> -->
              </el-image>

              <div class="delete-icon2" @click.stop="deleteImgItem(img)">x</div>
            </div>
          </div>

          <el-upload
            :http-request="handleUploadFile"
            style="margin-top: 10px"
            class="upload-image"
            action="/upload"
            :on-success="handleUploadSuccess"
            :show-file-list="false"
            accept="image/*"
            :before-upload="beforeUpload"
          >
            <el-button type="primary" :loading="upLoading">Add Image</el-button>
            <template #tip>
              <div class="el-upload__tip">
                Click the add image button to begin upload [.jpg, .jpeg, .gif,
                .png]
              </div>
            </template>
          </el-upload>
        </div>

        <div class="tool-section">
          <h3>
            <el-icon
              title="Add Text You can add a text and set the font color, font style, and font size for the text"
              ><InfoFilled
            /></el-icon>
            Text Tools
          </h3>
          <el-form :model="textStyle" label-width="80px">
            <el-form-item label="sizeStyle">
              <div class="size-buttons">
                <el-button
                  @click="setTextSize('h1')"
                  link
                  size="small"
                  class="btn"
                  >H1</el-button
                >
                <el-button
                  @click="setTextSize('h2')"
                  link
                  size="small"
                  class="btn"
                  >H2</el-button
                >
                <el-button
                  @click="setTextSize('h3')"
                  link
                  size="small"
                  class="btn"
                  >H3</el-button
                >
                <el-button
                  @click="setTextSize('h4')"
                  link
                  size="small"
                  class="btn"
                  >H4</el-button
                >
                <el-button
                  @click="setTextSize('h5')"
                  link
                  size="small"
                  class="btn"
                  >H5</el-button
                >
                <el-button
                  @click="setTextSize('h6')"
                  link
                  size="small"
                  class="btn"
                  >H6</el-button
                >
              </div>
            </el-form-item>

            <el-form-item label="fontSize">
              <el-input-number
                v-model="textStyle.fontSize"
                :min="12"
                :max="72"
                @change="updateTextStyle"
              />
            </el-form-item>

            <!-- <el-form-item label="字体">
              <el-select v-model="textStyle.fontFamily" @change="updateTextStyle">
                <el-option label="微软雅黑" value="Microsoft YaHei" />
                <el-option label="宋体" value="SimSun" />
                <el-option label="黑体" value="SimHei" />
              </el-select>
            </el-form-item> -->
            <el-form-item
              label="fontStyle"
              style="display: flex; justify-content: center"
            >
              <!-- 方法一: -->
              <!-- <el-switch
                v-model="textStyle.bold"
                active-text="加粗"
                @change="updateTextStyle"
              />
              <el-switch
                v-model="textStyle.italic"
                active-text="斜体"
                @change="updateTextStyle"
              /> -->

              <!-- 方法二: -->
              <div style="display: flex; align-items: center">
                <el-button @click="toggleBold" link type="info">
                  <img
                    src="~@/assets/font-b.png"
                    alt="icon"
                    width="20"
                    height="20"
                    title="bold"
                /></el-button>
                <el-button @click="toggleItalic" link type="info"
                  ><img
                    src="~@/assets/font-I.png"
                    alt="icon"
                    width="20"
                    height="20"
                    title="italic"
                /></el-button>
              </div>
            </el-form-item>

            <el-form-item label="color">
              <el-color-picker
                v-model="textStyle.color"
                @change="updateTextStyle"
                show-alpha
                :predefine="predefineColors"
              />
            </el-form-item>
          </el-form>
          <!-- <div class="tool-section">
            <h3>Text Box</h3>
            <div class="size-buttons">
              <el-button @click="setTextSize('h1')" link size="small"
                >H1</el-button
              >
              <el-button @click="setTextSize('h2')" link size="small"
                >H2</el-button
              >
              <el-button @click="setTextSize('h3')" link size="small"
                >H3</el-button
              >
              <el-button @click="setTextSize('h4')" link size="small"
                >H4</el-button
              >
              <el-button @click="setTextSize('h5')" link size="small"
                >H5</el-button
              >
              <el-button @click="setTextSize('h6')" link size="small"
                >H6</el-button
              >
            </div>
          </div> -->
          <el-button type="primary" @click="addText" class="add-text-btn">
            Add Text
          </el-button>

          <div class="tool-section">
            <h3>
              <el-icon
                title="Enter the x and y coordinates respectively and click move to adjust the position of the element in the canvas"
                ><InfoFilled
              /></el-icon>
              Set Coordinates
            </h3>
            <div>
              X:
              <el-input
                v-model="positionX"
                :title="positionX"
                placeholder="X 坐标"
                style="width: 60px"
                clearable
              />

              Y:
              <el-input
                v-model="positionY"
                :title="positionY"
                placeholder="Y 坐标"
                style="width: 60px"
                clearable
              />
              <el-button
                @click="moveElementToPosition"
                type="primary"
                style="margin-left: 5px"
                >Move</el-button
              >
            </div>
          </div>

          <div class="tool-section">
            <h3>
              <el-icon
                title="Only left-justified, center-justified, and right-justified text is supported"
                ><InfoFilled
              /></el-icon>
              Text Alignment
            </h3>
            <el-button @click="alignText('left')">Left</el-button>
            <el-button @click="alignText('center')">Center</el-button>
            <el-button @click="alignText('right')">Right</el-button>
          </div>
        </div>

        <!-- <div class="tool-section">
          <h3>画布设置</h3>
          <div class="background-settings">
            <el-upload
              class="background-uploader"
              :show-file-list="false"
              :on-success="handleBackgroundSuccess"
              :before-upload="beforeBackgroundUpload"
              action="/api/upload"
            >
              <el-image
                v-if="canvasBackground"
                :src="canvasBackground"
                class="background-preview"
              />
              <el-icon v-else class="background-uploader-icon"><Plus /></el-icon>
            </el-upload>
            <div class="background-actions">
              <el-button size="small" @click="resetBackground">恢复默认背景</el-button>
            </div>
          </div>
        </div> -->
      </div>

      <!-- 右侧画布区域 -->
      <div class="right-content">
        <div class="canvas-area">
          <div
            ref="canvasRef"
            class="canvas"
            @dragover.prevent
            @drop="onDrop"
            @click="onCanvasClick"
            :style="{
              backgroundImage: `url(${canvasBackground})`,
              backgroundSize: '100% 100%',
              backgroundPosition: 'center',
              width: '100%',
              height: '800px',
            }"
          >
            <div
              v-for="(element, index) in elements"
              :key="index"
              class="canvas-element"
              :class="{ selected: selectedIndex === index }"
              :style="getElementStyle(element)"
              @click.stop="selectElement(index)"
              @mousedown="startDragging($event, index)"
              @mousemove="onDragging"
              @mouseup="stopDragging"
              @mouseleave="stopDragging"
            >
              <template v-if="element.type === 'image'">
                <div class="element-wrapper">
                  <el-image :src="element.src" fit="contain" />
                  <div class="element-actions" v-if="selectedIndex === index">
                    <el-button
                      type="danger"
                      size="small"
                      circle
                      icon="Delete"
                      @click.stop="deleteElement(index)"
                    />

                    <!-- 添加调整图层顺序的按钮 -->
                    <!-- <el-button
                    type="primary"
                    size="small"
                    circle
                    icon="ArrowUp"
                    @click.stop="moveLayerUp(index)"
                    :disabled="index === elements.length - 1"
                  />
                  <el-button
                    type="primary"
                    size="small"
                    circle
                    icon="ArrowDown"
                    @click.stop="moveLayerDown(index)"
                    :disabled="index === 0"
                  /> -->
                  </div>

                  <div
                    v-if="selectedIndex === index"
                    class="resize-handle"
                    @mousedown.stop="startResizing($event, index)"
                  >
                    <el-icon><Rank /></el-icon>
                  </div>
                </div>
              </template>

              <template v-else-if="element.type === 'text'">
                <div class="element-wrapper">
                  <div
                    class="text-element"
                    :style="getTextStyle(element)"
                    @dblclick="startEditing(index)"
                  >
                    <el-input
                      v-if="editingIndex === index"
                      v-model="tempTextContent"
                      @blur="handleBlur(index)"
                      type="textarea"
                      autosize
                    />
                    <span v-else>{{ element.content }}</span>
                  </div>
                  <div class="element-actions" v-if="selectedIndex === index">
                    <el-button
                      type="danger"
                      size="small"
                      circle
                      icon="Delete"
                      @click.stop="deleteElement(index)"
                    />

                    <!-- 添加调整图层顺序的按钮 -->
                    <!-- <el-button
                    type="primary"
                    size="small"
                    circle
                    icon="ArrowUp"
                    @click.stop="moveLayerUp(index)"
                    :disabled="index === elements.length - 1"
                  />
                  <el-button
                    type="primary"
                    size="small"
                    circle
                    icon="ArrowDown"
                    @click.stop="moveLayerDown(index)"
                    :disabled="index === 0"
                  /> -->
                  </div>
                  <div
                    v-if="selectedIndex === index"
                    class="resize-handle"
                    @mousedown.stop="startResizing($event, index)"
                  >
                    <el-icon><Rank /></el-icon>
                  </div>
                </div>
              </template>
            </div>
          </div>
        </div>
        <div class="canvas-actions">
          <!-- 调整重叠图层的顺序 -->
          <el-button
            @click="moveLayerUp"
            :disabled="isLayerAtTop()"
            title="If two or more layers overlap, click move up to adjust the layer to the top and move down to adjust the layer to the bottom"
            >Move Up</el-button
          >
          <el-button
            @click="moveLayerDown"
            :disabled="isLayerAtBottom()"
            title="If two or more layers overlap, click move up to adjust the layer to the top and move down to adjust the layer to the bottom"
            >Move Down</el-button
          >

          <!-- <el-button @click="undo">Undo</el-button>
          <el-button @click="redo">Redo</el-button>
          <el-button @click="reset">Reset</el-button> -->

          <!-- <el-button type="primary" :disabled="!canGenerate">
            Generate Certificate
          </el-button> -->

          <el-button
            type="primary"
            @click="generateImage"
            :disabled="!canGenerate"
            title="Click Generate Certificate to generate a.png image of all the elements in the canvas for easy viewing"
          >
            Generate Certificate
          </el-button>

          <el-button
            @click="saveCurrentTemplate"
            :disabled="!canGenerate"
            title="Click save as template to save all the elements in the canvas as a new template, which is easy to use next time"
            >Save As Template</el-button
          >
        </div>
      </div>
    </div>
  </div>

  <!-- 保存模板时上传封面图片----先注释  -->
  <!-- <div>
    <el-dialog v-model="showModal" title="Tips" width="500">
      <el-form>
        <el-form-item label="Template Name">
          <el-input
            v-model="templateName"
            placeholder="Please enter a template name"
          ></el-input>
        </el-form-item>
        <el-form-item label="Thumbnail">
          <el-upload
            action="/upload"
            :on-success="handleThumbnailUploadSuccess"
            :show-file-list="false"
          >
            <el-button type="primary">Upload Thumbnail</el-button>
          </el-upload>
        </el-form-item>
      </el-form>

      <template #footer>
        <el-button @click="showModal = false">Cancel</el-button>
        <el-button type="primary" @click="handleSaveTemplate"
          >Confirm</el-button
        >
      </template>
    </el-dialog>
  </div> -->
</template>

<script setup>
import {
  ref,
  computed,
  onMounted,
  onBeforeUnmount,
  nextTick,
  watch,
} from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { Picture as IconPicture } from "@element-plus/icons-vue";
import { Delete, Plus, Rank } from "@element-plus/icons-vue";
// import * as html2canvas from "html2canvas";
import html2canvas from "html2canvas"; // 从 node_modules 中引入
import {
  getTemplatesDefault,
  getTemplatesSave,
  getTemplates,
  getImageUpload,
  getImages,
  deleteTemplates,
  deleteImages,
} from "@/api/poster/index";

import {
  journalHeadImg,
  bgImg,
  bgImg2,
  leftTopImg,
  rightTopImg,
  leftBottomImg,
  rightBottomImg,
  // journalHeadImg1,
  share1,
  share2,
  share3,
  share4,
  share6,
  // Round,
} from "@/views/sectionManagement/ts/img";

import { useRouter, useRoute } from "vue-router";
const route = useRoute();
const id = route.query.id;

// 预设模板数据
// const templates = ref([
//   {
//     name: "Editorial Board Example",
//     logo: journalHeadImg, // 期刊模板封面图
//     background: bgImg,
//     elements: [
//       // 先注释
//       // {
//       //   type: 'image',
//       //   src: '/journal-bg.png',  // 期刊背景图
//       //   isBackground: true,
//       //   style: {
//       //     left: '0',
//       //     top: '0',
//       //     // width: '100%',
//       //     // height: '100%',
//       //     zIndex: -1
//       //   }
//       // },
//       // 左上角图标
//       {
//         type: "image",
//         src: leftTopImg,
//         style: {
//           left: "60px",
//           top: "80px",
//           // width: '80px',
//           // height: '80px'
//           width: "300px",
//           height: "100px",
//         },
//       },
//       // 右上角期刊图片
//       {
//         type: "image",
//         src: rightTopImg,
//         style: {
//           // 这里先使用占位符,后续会替换为具体值
//           left: "placeholderLeft",
//           top: "80px",
//           width: "300px",
//           height: "100px",
//         },
//       },
//       // 右上角期刊名
//       // {
//       //   type: 'text',
//       //   content: '期刊名称',
//       //   style: {
//       //     right: '30px',
//       //     top: '200px',
//       //     width: '120px',
//       //     fontSize: '16px',
//       //     color: '#333333',
//       //     fontFamily: 'Microsoft YaHei',
//       //     textAlign: 'center'
//       //   }
//       // },
//       // 中间内容区域
//       {
//         type: "text",
//         content: "在此输入正文内容",
//         style: {
//           left: "50%",
//           top: "50%",
//           transform: "translate(-50%, -50%)",
//           // width: '60%',
//           fontSize: "18px",
//           color: "#333333",
//           fontFamily: "Microsoft YaHei",
//           textAlign: "left",
//           lineHeight: "1.8",
//           whiteSpace: "pre-wrap",
//         },
//       },
//       // 左下角电子签名
//       {
//         type: "image",
//         src: leftBottomImg,
//         style: {
//           left: "60px",
//           // 这里先使用占位符,后续会替换为具体值
//           top: "placeholderTop",
//           // left: "60px",
//           // top: "680px", // 800 - 120  // 假设画布高度为 800px
//           //  width: '150px',
//           // height: '60px'
//           width: "300px",
//           height: "100px",
//         },
//       },
//       // 右下角logo
//       {
//         type: "image",
//         src: rightBottomImg,
//         style: {
//           // 这里先使用占位符,后续会替换为具体值
//           left: "placeholderLeft",
//           // 这里先使用占位符,后续会替换为具体值
//           top: "placeholderTop",
//           width: "150px",
//           height: "140px",
//         },
//       },
//     ],
//   },
//   {
//     name: "Reviewer Example",
//     logo: journalHeadImg,
//     background: bgImg,
//     elements: [
//       {
//         type: "image",
//         src: leftTopImg,
//         style: {
//           // left: "0px",
//           // top: "0px",
//           // width: "100%",
//           left: "60px",
//           top: "80px",
//           // width: '80px',
//           // height: '80px'
//           width: "300px",
//           height: "100px",
//         },
//       },
//       {
//         type: "text",
//         content: "荣誉证书",
//         style: {
//           left: "50%",
//           top: "30%",
//           transform: "translate(-50%, -50%)",
//           fontSize: "36px",
//           color: "#000000",
//           fontFamily: "Microsoft YaHei",
//           fontWeight: "bold",
//         },
//       },
//       {
//         type: "text",
//         content: "兹证明\n[姓名]\n在[项目名称]中表现优异\n特发此证",
//         style: {
//           left: "50%",
//           top: "50%",
//           transform: "translate(-50%, -50%)",
//           fontSize: "24px",
//           color: "#333333",
//           fontFamily: "Microsoft YaHei",
//           textAlign: "center",
//           whiteSpace: "pre-wrap",
//         },
//       },
//     ],
//   },
//   {
//     name: "Guest Editor Example",
//     logo: journalHeadImg, // 海报模板封面图
//     background: bgImg2,
//     elements: [
//       // 左上角
//       {
//         type: "image",
//         src: rightTopImg,
//         style: {
//           left: "60px",
//           top: "80px",
//           width: "300px",
//           height: "100px",
//         },
//       },
//       // 右上角
//       {
//         type: "image",
//         src: Round,
//         style: {
//           // 这里先使用占位符,后续会替换为具体值
//           left: "placeholderLeft",
//           top: "80px",
//           width: "100px",
//           height: "100px",
//         },
//       },

//       {
//         type: "text",
//         content: "CALL FOR PAPERS",
//         style: {
//           left: "50%",
//           top: "20%",
//           transform: "translate(-50%, -50%)",
//           fontSize: "32px",
//           color: "#000",
//           fontFamily: "Microsoft YaHei",
//           fontWeight: "bold",
//         },
//       },
//     ],
//   },
//   {
//     name: "Acceptance Example",
//     logo: journalHeadImg, // 海报模板封面图
//     background: journalHeadImg1,
//     elements: [
//       {
//         type: "image",
//         src: leftTopImg,
//         style: {
//           left: "60px",
//           top: "80px",
//           // width: '80px',
//           // height: '80px'
//           width: "300px",
//           height: "100px",
//         },
//       },
//       {
//         type: "text",
//         content: "活动主题22",
//         style: {
//           left: "50%",
//           top: "20%",
//           transform: "translate(-50%, -50%)",
//           fontSize: "32px",
//           color: "#000",
//           fontFamily: "Microsoft YaHei",
//           fontWeight: "bold",
//         },
//       },
//     ],
//   },
// ]);

const templates = ref([
  {
    name: "SI Proposal Example",
    logo: journalHeadImg, // 期刊模板封面图
    background: bgImg2,
    // elements: [
    //   // 左上角 ======================
    //   {
    //     type: "image",
    //     src: leftTopImg,
    //     style: {
    //       left: "60px",
    //       top: "80px",
    //       width: "300px",
    //       height: "100px",
    //     },
    //   },
    //   // 右上角 ======================
    //   {
    //     type: "image",
    //     src: rightTopImg,
    //     style: {
    //       left: "placeholderLeft",
    //       top: "80px",
    //       width: "100px",
    //       height: "100px",
    //     },
    //   },
    //   {
    //     type: "image",
    //     src: rightTopImg,
    //     style: {
    //       left: "placeholderLeftPlus100",
    //       top: "80px",
    //       width: "100px",
    //       height: "100px",
    //     },
    //   },
    //   // {
    //   //   type: "text",
    //   //   content: "2023\nIMPACT\nFACTOR",
    //   //   style: {
    //   //     left: "placeholderLeft",
    //   //     top: "120px",
    //   //     transform: "translate(-40%, -50%)",
    //   //     fontSize: "12px",
    //   //     color: "",
    //   //     fontWeight: "",
    //   //     textAlign: "left",
    //   //     lineHeight: "1.8",
    //   //     whiteSpace: "pre-wrap",
    //   //   },
    //   // },

    //   // 中间 ======================
    //   {
    //     type: "text",
    //     content: "CALL FOR PAPERS",
    //     style: {
    //       left: "50%",
    //       top: "20%",
    //       transform: "translate(-50%, -50%)",
    //       fontSize: "40px",
    //       color: "#36767d",
    //       fontWeight: "bold",
    //       textAlign: "left",
    //       lineHeight: "1.8",
    //       whiteSpace: "pre-wrap",
    //     },
    //   },
    //   {
    //     type: "text",
    //     content:
    //       "SI: Renewable Energy Commnuity(REC) Engineering towards\n Sustainable Development and Energy Poverty Reduction",
    //     style: {
    //       left: "50%",
    //       top: "30%",
    //       transform: "translate(-50%, -50%)",
    //       fontSize: "24px",
    //       color: "#000",
    //       fontWeight: "bold",
    //       textAlign: "center",
    //       lineHeight: "1.8",
    //       whiteSpace: "pre-wrap",
    //     },
    //   },
    //   {
    //     type: "text",
    //     content: "ISSN: 1546-2226(online)",
    //     style: {
    //       left: "70px",
    //       top: "160px",
    //       fontSize: "16px",
    //       color: "#000",
    //       fontWeight: "",
    //       textAlign: "left",
    //       lineHeight: "1.8",
    //       whiteSpace: "pre-wrap",
    //     },
    //   },
    //   {
    //     type: "text",
    //     content: "ISSN: 1546-2218(print)",
    //     style: {
    //       left: "70px",
    //       top: "180px",
    //       fontSize: "16px",
    //       color: "#000",
    //       fontWeight: "",
    //       textAlign: "left",
    //       lineHeight: "1.8",
    //       whiteSpace: "pre-wrap",
    //     },
    //   },
    //   {
    //     type: "text",
    //     content: "Guest Editors:",
    //     style: {
    //       left: "70px",
    //       top: "350px",
    //       fontSize: "20px",
    //       color: "#36767d",
    //       fontWeight: "bold",
    //       textAlign: "left",
    //       lineHeight: "1.8",
    //       whiteSpace: "pre-wrap",
    //     },
    //   },
    //   {
    //     type: "text",
    //     content: "Dr. Mariacristina Roscia, University of Bergamo",
    //     style: {
    //       left: "70px",
    //       top: "380px",
    //       fontSize: "20px",
    //       color: "#000",
    //       fontWeight: "",
    //       textAlign: "left",
    //       lineHeight: "1.8",
    //       whiteSpace: "pre-wrap",
    //     },
    //   },
    //   {
    //     type: "text",
    //     content: "Dr. Korhan Kayisli, Gazi University",
    //     style: {
    //       left: "70px",
    //       top: "410px",
    //       fontSize: "20px",
    //       color: "#000",
    //       fontWeight: "",
    //       textAlign: "left",
    //       lineHeight: "1.8",
    //       whiteSpace: "pre-wrap",
    //     },
    //   },
    //   {
    //     type: "text",
    //     content: "Submission Deadline:",
    //     style: {
    //       left: "70px",
    //       top: "440px",
    //       fontSize: "20px",
    //       color: "#36767d",
    //       fontWeight: "bold",
    //       textAlign: "left",
    //       lineHeight: "1.8",
    //       whiteSpace: "pre-wrap",
    //     },
    //   },
    //   {
    //     type: "text",
    //     content: "01 October 2025",
    //     style: {
    //       left: "70px",
    //       top: "470px",
    //       fontSize: "20px",
    //       color: "#000",
    //       fontWeight: "",
    //       textAlign: "left",
    //       lineHeight: "1.8",
    //       whiteSpace: "pre-wrap",
    //     },
    //   },

    //   // 左下角 ======================
    //   {
    //     type: "text",
    //     content: "https://www.techscience.com/journal/CMC",
    //     style: {
    //       left: "60px",
    //       top: "750px",
    //       fontSize: "20px",
    //       color: "#FFF",
    //       fontWeight: "",
    //       textAlign: "left",
    //       lineHeight: "1.8",
    //       whiteSpace: "pre-wrap",
    //     },
    //   },
    //   // 右下角 ======================
    //   // {
    //   //   type: "image",
    //   //   src: rightBottomImg,
    //   //   style: {
    //   //     left: "placeholderLeft",
    //   //     top: "placeholderTop",
    //   //     width: "150px",
    //   //     height: "140px",
    //   //   },
    //   // },
    // ],
    elements: "",
  },
]);

// 图片库数据
const images = ref([
  { src: share1 },
  { src: share2 },
  { src: share3 },
  { src: share4 },
  { src: share6 },
  { src: leftTopImg },
  { src: leftBottomImg },
  { src: rightBottomImg },
  { src: rightTopImg },
]);

// 文本样式
const textStyle = ref({
  fontSize: 16,
  color: "#000000",
  fontFamily: "Microsoft YaHei",
  bold: false,
  italic: false,
});

// 画布元素
const elements = ref([]);
const selectedIndex = ref(-1);
const editingIndex = ref(-1);
const canvasRef = ref(null);
const textInput = ref(null);

const isDragging = ref(false);
const dragStartPos = ref({ x: 0, y: 0 });
const elementStartPos = ref({ x: 0, y: 0 });
const draggingIndex = ref(-1);

// 判断是否可以生成证书
const canGenerate = computed(() => {
  return elements.value.length > 0;
});

// 应用模板
const applyTemplate = (template) => {
  // console.log("应用", template.elements);
  elements.value = template.elements ? JSON.parse(template.elements) : [];
  canvasBackground.value = template.background;
  ElMessage.success("Template application successful");
};

const deleteItem = (index, row) => {
  ElMessageBox.confirm(
    "Are you sure you want to delete this template?",
    "Tips",
    {
      confirmButtonText: "Confirm",
      cancelButtonText: "Cancel",
      type: "warning",
    }
  )
    .then(() => {
      // templates.value.splice(index, 1);
      if (row.isDefault)
        return ElMessage({
          type: "error",
          message: "The default template cannot be deleted",
        });

      deleteTemplates({
        id: row.id,
      })
        .then((res) => {
          if (res.code == 2000) {
            ElMessage({
              type: "success",
              message: res.message,
            });
            getTempDatas();
          } else {
            ElMessage.error(res.message);
          }
        })
        .catch((err) => {
          ElMessage({
            type: "error",
            message: err.message,
          });
        });
    })
    .catch(() => {
      ElMessage({
        type: "info",
        message: "Canceled",
      });
    });
};

const deleteImgItem = (row) => {
  ElMessageBox.confirm(
    "Are you sure you want to delete this picture?",
    "Tips",
    {
      confirmButtonText: "Confirm",
      cancelButtonText: "Cancel",
      type: "warning",
    }
  )
    .then(() => {
      deleteImages({
        id: row.id,
      })
        .then((res) => {
          if (res.code == 2000) {
            ElMessage({
              type: "success",
              message: res.message,
            });
            getImageDatas();
          } else {
            ElMessage.error(res.message);
          }
        })
        .catch((err) => {
          ElMessage({
            type: "error",
            message: err.message,
          });
        });
    })
    .catch(() => {
      ElMessage({
        type: "info",
        message: "Canceled",
      });
    });
};

// 删除元素
const deleteElement = (index) => {
  elements.value.splice(index, 1);
  selectedIndex.value = -1;
  editingIndex.value = -1;
};

// 拖拽相关
const onImageDragStart = (event, image) => {
  event.dataTransfer.setData(
    "application/json",
    JSON.stringify({
      type: "image",
      src: image.src,
    })
  );
};

const onDrop = (event) => {
  event.preventDefault();
  const rect = canvasRef.value.getBoundingClientRect();
  const x = event.clientX - rect.left;
  const y = event.clientY - rect.top;

  try {
    const data = event.dataTransfer.getData("application/json");
    if (!data) return;

    const parsedData = JSON.parse(data);
    if (parsedData.type === "image") {
      elements.value.push({
        type: "image",
        src: parsedData.src,
        style: {
          left: `${x}px`,
          top: `${y}px`,
          width: "200px",
        },
      });
    }
  } catch (error) {
    console.log("Drop error:", error);
  }
};

// 开始拖拽元素
// const startDragging = (event, index) => {
//   // 只有在非编辑状态下才允许拖拽
//   if (editingIndex.value === -1) {
//     isDragging.value = true;
//     draggingIndex.value = index;
//     dragStartPos.value = {
//       x: event.clientX,
//       y: event.clientY,
//     };
//     const element = elements.value[index];
//     const left = element.style.left;
//     const top = element.style.top;

//     // 处理百分比位置转换为像素
//     if (left && left.includes("%")) {
//       const canvasWidth = canvasRef.value.offsetWidth;
//       const leftPercent = parseFloat(left);
//       elementStartPos.value.x = (canvasWidth * leftPercent) / 100;
//     } else {
//       elementStartPos.value.x = parseFloat(left);
//     }

//     if (top && top.includes("%")) {
//       const canvasHeight = canvasRef.value.offsetHeight;
//       const topPercent = parseFloat(top);
//       elementStartPos.value.y = (canvasHeight * topPercent) / 100;
//     } else {
//       elementStartPos.value.y = parseFloat(top);
//     }

//     // 处理transform偏移
//     if (
//       element.style.transform &&
//       element.style.transform.includes("translate")
//     ) {
//       const el = event.currentTarget;
//       const rect = el.getBoundingClientRect();
//       elementStartPos.value = {
//         x: rect.left - canvasRef.value.getBoundingClientRect().left,
//         y: rect.top - canvasRef.value.getBoundingClientRect().top,
//       };
//     }
//   }
// };

// 在拖拽图片时出现卡顿的情况,可能是由于以下几个原因造成的:
// 图片加载问题:图片加载需要时间,如果图片较大或者网络较慢,可能会导致拖拽卡顿。
// 计算量过大:在拖拽过程中,可能存在大量的计算,如位置计算、边界检查等,这些计算可能会影响性能。
// DOM 操作频繁:频繁的 DOM 操作会导致浏览器重排和重绘,从而影响性能。
const startDragging = (event, index) => {
  if (editingIndex.value === -1) {
    isDragging.value = true;
    draggingIndex.value = index;
    dragStartPos.value = {
      x: event.clientX,
      y: event.clientY,
    };
    const element = elements.value[index];
    const left = element.style.left;
    const top = element.style.top;

    // 当前代码在处理拖拽时,只考虑了 left 和 top 属性,而没有对 bottom 和 right 属性进行相应的处理。
    // 当元素使用 bottom 和 right 定位时,计算元素的位置和偏移量的逻辑就会出错,从而导致拖拽失效。
    // 为了解决这个问题,我们需要对 startDragging 和 onDragging 函数进行修改,使其能够同时处理 left、top、bottom 和 right 属性。具体步骤如下:
    // 在 startDragging 函数中,获取元素的 left、top、bottom 和 right 属性,并将其转换为像素值。
    // 在 onDragging 函数中,根据元素的定位属性(left、top、bottom 或 right)来更新元素的位置。

    const right = element.style.right;
    const bottom = element.style.bottom;

    // 处理百分比位置转换为像素
    const canvasWidth = canvasRef.value.offsetWidth;
    const canvasHeight = canvasRef.value.offsetHeight;

    if (left?.includes("%")) {
      const leftPercent = parseFloat(left);
      elementStartPos.value.x = (canvasWidth * leftPercent) / 100;
    } else if (right?.includes("%")) {
      const rightPercent = parseFloat(right);
      elementStartPos.value.x =
        canvasWidth - (canvasWidth * rightPercent) / 100;
    } else {
      elementStartPos.value.x = left
        ? parseFloat(left)
        : canvasWidth - (right ? parseFloat(right) : 0);
    }

    if (top?.includes("%")) {
      const topPercent = parseFloat(top);
      elementStartPos.value.y = (canvasHeight * topPercent) / 100;
    } else if (bottom?.includes("%")) {
      const bottomPercent = parseFloat(bottom);
      elementStartPos.value.y =
        canvasHeight - (canvasHeight * bottomPercent) / 100;
    } else {
      elementStartPos.value.y = top
        ? parseFloat(top)
        : canvasHeight - (bottom ? parseFloat(bottom) : 0);
    }

    // 处理transform偏移
    if (
      element.style.transform &&
      element.style.transform.includes("translate")
    ) {
      const el = event.currentTarget;
      const rect = el.getBoundingClientRect();
      elementStartPos.value = {
        x: rect.left - canvasRef.value.getBoundingClientRect().left,
        y: rect.top - canvasRef.value.getBoundingClientRect().top,
      };
    }

    // 缓存 DOM 元素
    const canvas = canvasRef.value;
    const elementRef = document.querySelector(
      `.canvas-element:nth-child(${index + 1})`
    );

    // 在 onDragging 中使用缓存的 DOM 元素
    const onDragging = (event) => {
      if (!isDragging.value) return;

      const dx = event.clientX - dragStartPos.value.x;
      const dy = event.clientY - dragStartPos.value.y;

      const element = elements.value[draggingIndex.value];

      let newX = elementStartPos.value.x + dx;
      let newY = elementStartPos.value.y + dy;

      // 缓存画布和元素的尺寸
      const canvasRect = canvas.getBoundingClientRect();
      const elementRect = elementRef.getBoundingClientRect();
      const canvasWidth = canvasRect.width;
      const canvasHeight = canvasRect.height;
      const elementWidth = elementRect.width;
      const elementHeight = elementRect.height;

      // 限制范围
      newX = Math.max(0, Math.min(newX, canvasWidth - elementWidth));
      newY = Math.max(0, Math.min(newY, canvasHeight - elementHeight));

      // 统一使用 left 和 top 更新位置
      element.style.left = `${newX}px`;
      element.style.top = `${newY}px`;

      // 移除transform,避免位置计算问题
      element.style.transform = "";
      // 移除可能导致计算偏差的 right 和 bottom 属性
      element.style.right = "";
      element.style.bottom = "";
    };
  }
};

const resetConfig = () => {
  textStyle.value.fontSize = 16;
  textStyle.value.bold = false;
  textStyle.value.italic = false;
};

// 文本相关
const addText = () => {
  const canvasRect = canvasRef.value.getBoundingClientRect();
  const centerX = canvasRect.width / 2;
  const centerY = canvasRect.height / 2;

  resetConfig();

  // elements.value.push({
  //   type: 'text',
  //   content: '双击编辑文本',
  //   style: {
  //     position: 'absolute',
  //     left: `${centerX}px`,
  //     top: `${centerY}px`,
  //     transform: 'translate(-50%, -50%)',
  //     fontSize: `${textStyle.value.fontSize}px`,
  //     color: textStyle.value.color,
  //     fontFamily: textStyle.value.fontFamily,
  //     fontWeight: textStyle.value.bold ? 'bold' : 'normal',
  //     fontStyle: textStyle.value.italic ? 'italic' : 'normal',
  //     whiteSpace: 'pre-wrap',
  //     wordBreak: 'break-word',
  //     maxWidth: '500px'
  //   }
  // })

  console.log("elements", elements.value);

  elements.value.push({
    type: "text",
    content: "Double-click to edit text",
    style: {
      left: `${centerX}px`,
      top: `${centerY}px`,
      fontSize: "16px",
      color: "#000000",
      fontFamily: "Microsoft YaHei",
      fontWeight: "normal",
      fontStyle: "normal",
      whiteSpace: "pre-wrap",
      wordBreak: "break-word",
      maxWidth: "800px",
    },
  });

  // saveState(); // 保存状态
};

// 修改拖拽中的处理
const onDragging = (event) => {
  if (!isDragging.value) return;

  const dx = event.clientX - dragStartPos.value.x;
  const dy = event.clientY - dragStartPos.value.y;

  const element = elements.value[draggingIndex.value];

  // 确保元素不会拖出画布
  const canvasRect = canvasRef.value.getBoundingClientRect();
  const elementRect = event.target.getBoundingClientRect();

  let newX = elementStartPos.value.x + dx;
  let newY = elementStartPos.value.y + dy;

  // 限制范围
  newX = Math.max(0, Math.min(newX, canvasRect.width - elementRect.width));
  newY = Math.max(0, Math.min(newY, canvasRect.height - elementRect.height));

  element.style.left = `${newX}px`;
  element.style.top = `${newY}px`;
  // 移除transform,避免位置计算问题
  element.style.transform = "";

  checkOverlappingLayers();
};

// 停止拖拽
const stopDragging = () => {
  isDragging.value = false;
  draggingIndex.value = -1;
};

const textElementRefs = ref([]);
const savedRange = ref(null);

const tempTextContent = ref(""); // 临时变量用于存储编辑内容

// 文本相关
const startEditing = (index) => {
  editingIndex.value = index;
  tempTextContent.value = elements.value[index].content; // 初始化临时变量
};

const stopEditing = () => {
  editingIndex.value = -1;
  // 保存当前选区
  const sel = window.getSelection();
  if (sel.rangeCount > 0) {
    savedRange.value = sel.getRangeAt(0);
  }
};

const updateTextContent = (index, target) => {
  // 保存当前选区
  const sel = window.getSelection();
  if (sel.rangeCount > 0) {
    savedRange.value = sel.getRangeAt(0);
  }
  const newContent = target.textContent;
  elements.value[index].content = newContent;
  // 恢复选区
  if (savedRange.value) {
    sel.removeAllRanges();
    sel.addRange(savedRange.value);
  }
};

// 元素样式
const getElementStyle = (element) => {
  return {
    position: "absolute",
    ...element.style,
  };
};

const getTextStyle = (element) => {
  return {
    fontSize: `${element.style.fontSize}px`,
    color: element.style.color,
    fontFamily: element.style.fontFamily,
    fontWeight: element.style.fontWeight,
    fontStyle: element.style.fontStyle,
    whiteSpace: "pre-wrap",
    wordBreak: "break-word",
    maxWidth: "800px",
  };
};

// 选择元素
const selectElement = (index) => {
  selectedIndex.value = index;
};

const onCanvasClick = () => {
  selectedIndex.value = -1;
};

// 更新文本样式// 更新文本样式
const updateTextStyle = () => {
  if (
    selectedIndex.value !== -1 &&
    elements.value[selectedIndex.value].type === "text"
  ) {
    elements.value[selectedIndex.value].style = {
      ...elements.value[selectedIndex.value].style,
      // 使用当前 textStyle.value.fontSize 更新元素的 fontSize
      fontSize: `${textStyle.value.fontSize}px`,
      color: textStyle.value.color,
      fontFamily: textStyle.value.fontFamily,
      fontWeight: textStyle.value.bold ? "bold" : "normal",
      fontStyle: textStyle.value.italic ? "italic" : "normal",
    };
    // 强制更新文本元素的样式
    const textElement = document.querySelector(
      `.canvas-element:nth-child(${selectedIndex.value + 1}) .text-element`
    );
    if (textElement) {
      textElement.style.fontSize = `${textStyle.value.fontSize}px`;
    }
    const textSpan = textElement.querySelector("span");
    if (textSpan) {
      textSpan.style.fontSize = `${textStyle.value.fontSize}px`;
    }
    const textInput = textElement.querySelector("el-input");
    if (textInput) {
      textInput.style.fontSize = `${textStyle.value.fontSize}px`;
    }
  }
};

// 设置文本大小
const setTextSize = (size) => {
  if (
    selectedIndex.value !== -1 &&
    elements.value[selectedIndex.value].type === "text"
  ) {
    const newSize =
      size === "h1"
        ? 32
        : size === "h2"
        ? 24
        : size === "h3"
        ? 18
        : size === "h4"
        ? 16
        : size === "h5"
        ? 14
        : 12;
    // 更新 element.style 中的 fontSize
    elements.value[selectedIndex.value].style.fontSize = `${newSize}px`;
    // 更新 textStyle.value.fontSize
    textStyle.value.fontSize = newSize;
    // 强制更新文本元素的样式
    const textElement = document.querySelector(
      `.canvas-element:nth-child(${selectedIndex.value + 1}) .text-element`
    );
    if (textElement) {
      textElement.style.fontSize = `${newSize}px`;
    }
    const textSpan = textElement.querySelector("span");
    if (textSpan) {
      textSpan.style.fontSize = `${newSize}px`;
    }
    const textInput = textElement.querySelector("el-input");
    if (textInput) {
      textInput.style.fontSize = `${newSize}px`;
    }
  }
};

// 生成图片
const generateImage = async () => {
  try {
    const canvas = await html2canvas(canvasRef.value, {
      useCORS: true, // 允许跨域图片
      allowTaint: true, // 允许跨域图片
      scale: 2, // 提高输出质量
      logging: false,
      imageTimeout: 0, // 禁用图片加载超时
      onclone: (clonedDoc) => {
        // 获取克隆的画布元素
        const clonedCanvas = clonedDoc.querySelector(".canvas");
        if (clonedCanvas) {
          // 处理克隆画布中的所有图片元素
          const images = clonedCanvas.querySelectorAll(".el-image__inner");
          images.forEach((img) => {
            // 移除可能影响图片显示的样式限制
            img.style.maxWidth = "none";
            img.style.maxHeight = "none";
            img.style.width = img.parentElement.style.width;
            img.style.height = img.parentElement.style.height;
            img.style.objectFit = "contain";
          });
        }
      },
    });

    // 创建下载链接
    const link = document.createElement("a");
    link.download = "certificate.png";
    link.href = canvas.toDataURL("image/png", 1.0);
    link.click();
    ElMessage.success("successfully");
  } catch (error) {
    ElMessage.error("failed");
  }
};

// 背景图相关
const DEFAULT_BACKGROUND = bgImg;
// const canvasBackground = ref(DEFAULT_BACKGROUND);
const canvasBackground = ref(
  templates.value[0].background || DEFAULT_BACKGROUND
);

const handleBackgroundSuccess = (response) => {
  canvasBackground.value = response.url;
  ElMessage.success("背景图更新成功");
};

const beforeBackgroundUpload = (file) => {
  const isImage = file.type.startsWith("image/");
  const isLt2M = file.size / 1024 / 1024 < 2;

  if (!isImage) {
    ElMessage.error("只能上传图片文件!");
    return false;
  }
  if (!isLt2M) {
    ElMessage.error("图片大小不能超过 2MB!");
    return false;
  }
  return true;
};

const resetBackground = () => {
  canvasBackground.value = DEFAULT_BACKGROUND;
  ElMessage.success("已恢复默认背景");
};

// 调整大小相关状态
const isResizing = ref(false);
const resizingIndex = ref(-1);
const initialSize = ref({ width: 0, height: 0 });
const initialMousePos = ref({ x: 0, y: 0 });

const startResizing = (event, index) => {
  event.preventDefault();
  isResizing.value = true;
  resizingIndex.value = index;
  const element = elements.value[index];
  initialSize.value = {
    width: parseFloat(element.style.width),
    height: parseFloat(element.style.height),
  };
  initialMousePos.value = {
    x: event.clientX,
    y: event.clientY,
  };
  if (element.type === "text") {
    // 确保正确记录初始字体大小
    initialFontSize.value = parseFloat(element.style.fontSize) || 16;
    // 同步 textStyle 中的 fontSize
    textStyle.value.fontSize = initialFontSize.value;
  }
  document.addEventListener("mousemove", onResizing);
  document.addEventListener("mouseup", stopResizing);
};

const initialFontSize = ref(0);

// 添加一个标志变量,用于记录是否已经提示过最大字号限制
const hasReachedMaxSize = ref(false);

const onResizing = (event) => {
  if (!isResizing.value) return;

  const deltaX = event.clientX - initialMousePos.value.x;
  const deltaY = event.clientY - initialMousePos.value.y;
  const element = elements.value[resizingIndex.value];

  if (element.type === "image") {
    let newWidth = Math.max(50, initialSize.value.width + deltaX);
    let newHeight = Math.max(50, initialSize.value.height + deltaY);

    const canvas = canvasRef.value;
    const canvasRect = canvas.getBoundingClientRect();
    const canvasWidth = canvasRect.width;
    const canvasHeight = canvasRect.height;

    const elementLeft = parseFloat(element.style.left);
    const elementTop = parseFloat(element.style.top);

    newWidth = Math.min(newWidth, canvasWidth - elementLeft);
    newHeight = Math.min(newHeight, canvasHeight - elementTop);

    element.style = {
      ...element.style,
      width: `${newWidth}px`,
      height: `${newHeight}px`,
    };

    const imgElement = document.querySelector(
      `.canvas-element:nth-child(${resizingIndex.value + 1}) .el-image__inner`
    );
    if (imgElement) {
      imgElement.style.width = `${newWidth}px`;
      imgElement.style.height = `${newHeight}px`;
    }
  } else if (element.type === "text") {
    // 计算字体大小的变化量,这里简单以 deltaX 为例,你可以根据需要调整
    const fontSizeChange = deltaX * 0.1;
    let newFontSize = Math.max(12, initialFontSize.value + fontSizeChange);

    // 取整操作
    newFontSize = Math.round(newFontSize);

    // 检查字体大小是否超过 72
    // if (newFontSize > 72) {
    //   newFontSize = 72;
    //   ElMessage.warning("最大字号为 72,不能继续缩放了");
    // }

    // 检查字体大小是否超过 72
    if (newFontSize > 72) {
      newFontSize = 72;
      if (!hasReachedMaxSize.value) {
        ElMessage.warning("最大字号只能为 72,不能继续缩放了");
        hasReachedMaxSize.value = true; // 设置标志为已提示
      }
    } else {
      hasReachedMaxSize.value = false; // 当字体大小小于 72 时,重置标志
    }

    element.style = {
      ...element.style,
      fontSize: `${newFontSize}px`,
    };

    const textElement = document.querySelector(
      `.canvas-element:nth-child(${resizingIndex.value + 1}) .text-element`
    );
    if (textElement) {
      textElement.style.fontSize = `${newFontSize}px`;
    }
    // 更新文本元素的实际内容样式
    const textSpan = textElement.querySelector("span");
    if (textSpan) {
      textSpan.style.fontSize = `${newFontSize}px`;
    }
    const textInput = textElement.querySelector("el-input");
    if (textInput) {
      textInput.style.fontSize = `${newFontSize}px`;
    }
    // 同步更新 textStyle 中的 fontSize
    textStyle.value.fontSize = newFontSize;
  }
};

const stopResizing = () => {
  isResizing.value = false;
  resizingIndex.value = -1;
  document.removeEventListener("mousemove", onResizing);
  document.removeEventListener("mouseup", stopResizing);
};

const handleUploadSuccess = (response) => {
  // 处理上传成功的逻辑
};

const positionX = ref(100); // 用于存储 X 坐标
const positionY = ref(200); // 用于存储 Y 坐标

// // 根据坐标x,y更新图片,文本位置
// const moveElementToPosition = () => {
//   // 检查是否有选中的元素
//   if (selectedIndex.value === -1) {
//     ElMessage.error("请先选中一个元素");
//     return;
//   }

//   const x = parseFloat(positionX.value); // 获取输入的 X 坐标
//   const y = parseFloat(positionY.value); // 获取输入的 Y 坐标

//   // 验证输入的坐标是否有效
//   if (!isNaN(x) && !isNaN(y) && selectedIndex.value !== -1) {
//     const element = elements.value[selectedIndex.value];
//     element.style.left = `${x}px`; // 更新 X 位置
//     element.style.top = `${y}px`; // 更新 Y 位置
//   } else {
//     ElMessage.error("请输入有效的坐标"); // 提示用户输入有效坐标
//   }
// };

// 优化:如果坐标有效且在范围内,则更新元素的位置;否则,提示用户输入的坐标超出了画布范围。=======================================
const moveElementToPosition = () => {
  // 检查是否有选中的元素
  if (selectedIndex.value === -1) {
    ElMessage.error("请先选中一个元素");
    return;
  }

  const x = parseFloat(positionX.value); // 获取输入的 X 坐标
  const y = parseFloat(positionY.value); // 获取输入的 Y 坐标

  // 获取画布的宽度和高度
  const canvas = canvasRef.value;
  const canvasRect = canvas.getBoundingClientRect();
  const canvasWidth = canvasRect.width;
  const canvasHeight = canvasRect.height;

  // 获取选中元素的宽度和高度
  const element = elements.value[selectedIndex.value];
  const elementRef = document.querySelector(
    `.canvas-element:nth-child(${selectedIndex.value + 1})`
  );
  const elementRect = elementRef.getBoundingClientRect();
  const elementWidth = elementRect.width;
  const elementHeight = elementRect.height;

  // 验证输入的坐标是否有效且在画布范围内
  if (!isNaN(x) && !isNaN(y) && selectedIndex.value !== -1) {
    if (
      x >= 0 &&
      x + elementWidth <= canvasWidth &&
      y >= 0 &&
      y + elementHeight <= canvasHeight
    ) {
      element.style.left = `${x}px`; // 更新 X 位置
      element.style.top = `${y}px`; // 更新 Y 位置
    } else {
      ElMessage.error("该坐标超出了画布范围,请重新输入");
      return;
    }
  } else {
    ElMessage.error("请输入有效的坐标"); // 提示用户输入有效坐标
  }
};

// const alignText = (alignment) => {
//   if (selectedIndex.value !== -1) {
//     const element = elements.value[selectedIndex.value];
//     const canvasWidth = canvasRef.value.offsetWidth; // 获取画布宽度
//     const textWidth = calculateTextWidth(element.content); // 计算文本宽度

//     // 根据对齐方式设置文本位置
//     if (alignment === "left") {
//       element.style.left = "0"; // 靠左
//     } else if (alignment === "center") {
//       element.style.left = `${(canvasWidth - textWidth) / 2}px`; // 居中
//     } else if (alignment === "right") {
//       element.style.left = `${canvasWidth - textWidth - 100}px`; // 靠右,设置右边距
//     }

//     element.style.textAlign = alignment; // 更新文本对齐方式
//   }
// };

// 优化位置计算: =========================================================================================
// 左对齐:设置 left 为 0,right 为 auto。
// 居中对齐:计算文本宽度与画布宽度的差值,然后将差值的一半作为 left 值,right 为 auto。
// 右对齐:设置 right 为 0,left 为 auto,避免文本换行。
const alignText = (alignment) => {
  if (selectedIndex.value !== -1) {
    const element = elements.value[selectedIndex.value];
    if (element.type === "text") {
      const canvas = canvasRef.value;
      const canvasRect = canvas.getBoundingClientRect();
      const textElement = document.createElement("span");
      textElement.textContent = element.content;
      textElement.style.fontSize = element.style.fontSize;
      textElement.style.fontFamily = element.style.fontFamily;
      textElement.style.fontWeight = element.style.fontWeight;
      textElement.style.fontStyle = element.style.fontStyle;
      textElement.style.whiteSpace = "pre-wrap";
      textElement.style.wordBreak = "break-word";
      textElement.style.maxWidth = "800px";
      document.body.appendChild(textElement);
      const textWidth = textElement.offsetWidth;
      document.body.removeChild(textElement);

      if (alignment === "left") {
        element.style.left = "0";
      } else if (alignment === "center") {
        element.style.left = `${(canvasRect.width - textWidth) / 2}px`;
      } else if (alignment === "right") {
        element.style.left = `${canvasRect.width - textWidth}px`;
      }

      element.style.textAlign = alignment;
      // 移除可能导致计算偏差的 right 和 bottom 属性
      element.style.right = "";
      element.style.bottom = "";
    } else {
      return ElMessage.error("仅支持文本对齐");
    }
  }
};

// 计算文本宽度的辅助函数
const calculateTextWidth = (content) => {
  if (!Array.isArray(content)) {
    console.error("Content is not an array:", content);
    return 0; // 返回 0 或其他默认值
  }

  // 假设每个字符的平均宽度为 8px,您可以根据实际字体和大小进行调整
  const averageCharWidth = 8;
  const totalWidth = content.reduce((acc, char) => {
    if (typeof char.text === "string") {
      return acc + char.text.length * averageCharWidth; // 计算宽度
    }
    return acc; // 如果不是字符串,保持累加值不变
  }, 0);

  return totalWidth;
};

// const handleKeyDown = (event) => {
//   console.log("event.target.tagName ", event.target.tagName);
//   // 允许输入框的输入 //  (event.preventDefault会把输入框的输入事件也阻止了,需要排除掉)
//   if (event.target.tagName == "INPUT" || event.target.tagName == "TEXTAREA") {
//     return; // 如果是输入框,直接返回
//   }

//   // 阻止默认行为,防止页面滚动(加这个是为了只想操作画布中的元素,而不是这个屏幕的)
//   event.preventDefault();

//   if (selectedIndex.value !== -1) {
//     const element = elements.value[selectedIndex.value];
//     const step = 5; // 每次移动的像素步长

//     switch (event.key) {
//       case "ArrowUp":
//         element.style.top = `${parseFloat(element.style.top) - step}px`; // 向上移动
//         break;
//       case "ArrowDown":
//         element.style.top = `${parseFloat(element.style.top) + step}px`; // 向下移动
//         break;
//       case "ArrowLeft":
//         element.style.left = `${parseFloat(element.style.left) - step}px`; // 向左移动
//         break;
//       case "ArrowRight":
//         element.style.left = `${parseFloat(element.style.left) + step}px`; // 向右移动
//         break;
//     }
//   }
// };

// 优化上下左右箭头边界判断的准确性,避免移动到画布边缘时出现误判的问题
const handleKeyDown = (event) => {
  // console.log("event.target.tagName ", event.target.tagName);
  // 允许输入框的输入 (event.preventDefault会把输入框的输入事件也阻止了,需要排除掉)
  if (
    event.target.tagName === "INPUT" ||
    event.target.tagName === "TEXTAREA" ||
    event.target.tagName === "DIV"
  ) {
    return; // 如果是输入框,直接返回
  }

  // 阻止默认行为,防止页面滚动(加这个是为了只想操作画布中的元素,而不是这个屏幕的)
  event.preventDefault();

  if (selectedIndex.value !== -1) {
    const element = elements.value[selectedIndex.value];
    const step = 5; // 每次移动的像素步长
    const canvas = canvasRef.value;
    const canvasRect = canvas.getBoundingClientRect();
    const elementRef = document.querySelector(
      `.canvas-element:nth-child(${selectedIndex.value + 1})`
    );
    const elementRect = elementRef.getBoundingClientRect();

    let newLeft = parseFloat(element.style.left);
    let newTop = parseFloat(element.style.top);

    switch (event.key) {
      case "ArrowUp":
        newTop = newTop - step;
        if (newTop < 0) {
          ElMessage.error("元素不能移动到画布上方以外");
          return;
        }
        break;
      case "ArrowDown":
        newTop = newTop + step;
        if (newTop + elementRect.height > canvasRect.height) {
          ElMessage.error("元素不能移动到画布下方以外");
          return;
        }
        break;
      case "ArrowLeft":
        newLeft = newLeft - step;
        if (newLeft < 0) {
          ElMessage.error("元素不能移动到画布左方以外");
          return;
        }
        break;
      case "ArrowRight":
        newLeft = newLeft + step;
        if (newLeft + elementRect.width > canvasRect.width) {
          ElMessage.error("元素不能移动到画布右方以外");
          return;
        }
        break;
    }

    element.style.left = `${newLeft}px`;
    element.style.top = `${newTop}px`;
  }
};

// 1. 在拖拽图片时出现卡顿的情况=============》预加载图片
// 在数据初始化时预加载图片
const preloadImages = () => {
  const allImages = [
    ...images.value.map((img) => img.src),
    ...templates.value.flatMap(
      (template) =>
        template.elements &&
        template.elements
          .filter((el) => el.type === "image")
          .map((el) => el.src)
    ),
    canvasBackground.value,
  ];
  allImages.forEach((src) => {
    const img = new Image();
    img.src = src;
  });
};

const getImageDatas = async () => {
  // images.value = [];
  // 获取图片列表
  await getImages().then((res) => {
    if (res.code == 2000) {
      if (res.data && res.data.data) {
        images.value = res.data.data.custom;
      }
    }
  });
};

const getTempDatas = async () => {
  templates.value = [];
  // 获取默认模版数据、获取自定义模版列表
  await Promise.all([getTemplatesDefault({ sid: id }), getTemplates()])
    .then(([res1, res2]) => {
      const newDatas = [...res1.data.data, ...res2.data.data];
      for (const item of newDatas) {
        templates.value.push({
          name: item.name,
          // logo: item.logo,
          logo:
            newDatas && newDatas.length && newDatas[0]
              ? newDatas[0].logo
              : "/api/file/download/logo/CMC.png",
          background: item.background,
          elements: item.elements,
          id: item.id,
          isDefault: item.isDefault,
        });
      }
    })
    .catch((error) => {});
};

const getDataList = async () => {
  getImageDatas();
  getTempDatas();
};

onMounted(async () => {
  window.addEventListener("keydown", handleKeyDown); // 添加键盘事件监听

  await getDataList();

  // 初始化文本元素引用
  elements.value.forEach((_, index) => {
    textElementRefs.value[index] = null;
  });

  // 更新模板中元素的位置  -----------start

  // 加这段的原因:templates元素定位会使用right,bottom,会存在计算误差问题,这里定位我只用left,top,因此要计算在组件挂载时获取画布的宽度和高度,
  // 并用placeholderLeft,placeholderTop作为点位符来替换后续计算的真实值
  // const canvasWidth = canvasRef.value.offsetWidth;
  // const canvasHeight = canvasRef.value.offsetHeight;
  // templates.value.forEach((template) => {
  //   if (template.elements) {
  //     JSON.parse(template.elements).forEach((element) => {
  //       if (element.style.left === "placeholderLeft") {
  //         // 计算右上角和右下角元素的 left 值
  //         if (element.src === rightTopImg) {
  //           // element.style.left = `${canvasWidth - 120 - 300}px`;
  //           element.style.left = `${canvasWidth - 50 - 170}px`;
  //         } else if (element.src === rightBottomImg) {
  //           element.style.left = `${canvasWidth - 50 - 150}px`;
  //         }
  //       }
  //       if (element.style.top === "placeholderTop") {
  //         // 计算左下角和右下角元素的 top 值
  //         if (element.src === leftBottomImg) {
  //           element.style.top = `${canvasHeight - 120}px`;
  //         } else if (element.src === rightBottomImg) {
  //           element.style.top = `${canvasHeight - 60 - 140}px`;
  //         }
  //       }

  //       // 右上角放了两张图片
  //       if (element.style.left == "placeholderLeftPlus100") {
  //         element.style.left = `${canvasWidth - 50 - 60}px`;
  //       }
  //     });
  //   }
  // });

  // 更新模板中元素的位置  -----------end

  // 加载模板数据
  // const savedTemplates = localStorage.getItem("templates");
  // if (savedTemplates) {
  //   templates.value = JSON.parse(savedTemplates);
  // }

  // preloadImages();
});

onBeforeUnmount(() => {
  window.removeEventListener("keydown", handleKeyDown); // 移除键盘事件监听
});

// 加粗,斜体-----方法二
const toggleBold = () => {
  if (
    selectedIndex.value !== -1 &&
    elements.value[selectedIndex.value].type === "text"
  ) {
    const currentStyle = elements.value[selectedIndex.value].style.fontWeight; // 获取当前字体粗细
    elements.value[selectedIndex.value].style.fontWeight =
      currentStyle === "bold" ? "normal" : "bold"; // 切换加粗
    // 更新 textStyle.value.bold
    textStyle.value.bold =
      elements.value[selectedIndex.value].style.fontWeight === "bold";
  }
};
const toggleItalic = () => {
  if (
    selectedIndex.value !== -1 &&
    elements.value[selectedIndex.value].type === "text"
  ) {
    const currentStyle = elements.value[selectedIndex.value].style.fontStyle; // 获取当前字体样式
    elements.value[selectedIndex.value].style.fontStyle =
      currentStyle === "italic" ? "normal" : "italic"; // 切换斜体
    // 更新 textStyle.value.italic
    textStyle.value.italic =
      elements.value[selectedIndex.value].style.fontStyle === "italic";
  }
};

// ----------------------------------undo , redo 的功能

// const history = ref([]); // 用于保存历史状态
// const currentStep = ref(-1); // 当前状态指针

// const saveState = () => {
//   // 保存当前状态
//   if (currentStep.value < history.value.length - 1) {
//     history.value.splice(currentStep.value + 1); // 清除 redo 的历史
//   }
//   history.value.push(JSON.stringify(elements.value)); // 保存当前元素状态
//   currentStep.value++;
// };

// const undo = () => {
//   if (currentStep.value > 0) {
//     currentStep.value--;
//     elements.value = JSON.parse(history.value[currentStep.value]); // 恢复到上一个状态
//   }
// };

// const redo = () => {
//   if (currentStep.value < history.value.length - 1) {
//     currentStep.value++;
//     elements.value = JSON.parse(history.value[currentStep.value]); // 恢复到下一个状态
//   }
// };

const reset = () => {
  // 重置操作的逻辑
};

// const saveCurrentTemplate = () => {
//   // 获取当前时间
//   const now = new Date();
//   const year = now.getFullYear();
//   const month = String(now.getMonth() + 1).padStart(2, "0");
//   const day = String(now.getDate()).padStart(2, "0");
//   const hour = String(now.getHours()).padStart(2, "0");
//   const minute = String(now.getMinutes()).padStart(2, "0");
//   const second = String(now.getSeconds()).padStart(2, "0");
//   // 生成模板名称
//   const templateName = `${year}${month}${day}${hour}${minute}${second}`;

//   const newTemplate = {
//     // name: `新模板 ${Date.now()}`,
//     name: `新模板 ${templateName}`,
//     logo: "/path/to/thumbnail.png",
//     // background: "/cerificationFile/bg.png",
//     background: canvasBackground.value,
//     elements: elements.value,
//   };
//   templates.value.push(newTemplate);

//   ElMessage.success("保存模板成功");

//   console.log("保存的模板", templates.value);
//   localStorage.setItem("templates", JSON.stringify(templates.value));
// };

const predefineColors = ref([
  "#ff4500",
  "#ff8c00",
  "#ffd700",
  "#90ee90",
  "#00ced1",
  "#1e90ff",
  "#c71585",
  "rgba(255, 69, 0, 0.68)",
  "rgb(255, 120, 0)",
  "hsv(51, 100, 98)",
  "hsva(120, 40, 94, 0.5)",
  "hsl(181, 100%, 37%)",
  "hsla(209, 100%, 56%, 0.73)",
  "#c7158577",
]);

// =======================================保存模板时上传封面图片
const showModal = ref(false);
const templateName = ref("");
const customThumbnail = ref("");
const saveCurrentTemplate = () => {
  ElMessageBox.prompt("Please enter a template name", "Save template", {
    confirmButtonText: "Confirm",
    cancelButtonText: "Cancel",
    inputValidator: (value) => {
      if (value.trim() === "") {
        return "The template name cannot be empty";
      }
      return true;
    },
  })
    .then(({ value }) => {
      // 创建新模板对象
      const newTemplate = {
        name: value,
        // logo: "/path/to/thumbnail.png", // 这里可以根据需要设置默认缩略图
        logo: "/api/file/download/logo/CMC.png",
        background: canvasBackground.value,
        elements:
          elements.value && elements.value.length > 0
            ? JSON.stringify(elements.value)
            : "",
      };

      // 将新模板添加到 templates 数组中
      // templates.value.push(newTemplate);
      // localStorage.setItem("templates", JSON.stringify(templates.value));
      // ElMessage.success("save successfully.");

      getTemplatesSave(newTemplate).then((res) => {
        if (res.code == 2000) {
          ElMessage.success(res.message);
          getDataList();
        }
      });
    })
    .catch(() => {
      ElMessage.info("cancel.");
    });

  // showModal.value = true;
};
const handleThumbnailUploadSuccess = (response) => {
  customThumbnail.value = response.url;
  ElMessage.success("Thumbnail upload successful");
};
// const handleSaveTemplate = () => {
//   if (templateName.value.trim() === "") {
//     ElMessage.error("The template name cannot be empty");
//     return;
//   }
//   const newTemplate = {
//     name: templateName.value,
//     logo: customThumbnail.value || "",
//     background: canvasBackground.value,
//     elements: JSON.parse(JSON.stringify(elements.value)),
//   };
//   templates.value.push(newTemplate);
//   localStorage.setItem("templates", JSON.stringify(templates.value));
//   ElMessage.success("save successfully.");
//   showModal.value = false;
//   templateName.value = "";
//   customThumbnail.value = "";
// };

// 防抖函数
const debounce = (func, delay) => {
  let timer = null;
  return function () {
    const context = this;
    const args = arguments;
    clearTimeout(timer);
    timer = setTimeout(() => {
      func.apply(context, args);
    }, delay);
  };
};

// 失焦处理函数
const handleBlur = (index) => {
  elements.value[index].content = tempTextContent.value; // 在失去焦点时赋值
  editingIndex.value = -1; // 结束编辑状态
};

/**
 * 自定义图片上传
 * @param options
 */

let upLoading = ref(false);
const handleUploadFile = async (options) => {
  upLoading.value = true;

  const { data, code, message } = await getImageUpload(options.file);

  if (code == 2000) {
    ElMessage({
      type: "success",
      message: message,
    });
    upLoading.value = false;

    getImageDatas();
  } else {
    ElMessage({
      type: "error",
      message: message,
    });
    upLoading.value = false;
  }
  // 保存文件的 ID 到文件对象的 id 属性中
  // options.file.id = data.content.fileId;
};

const beforeUpload = (file) => {
  const isImage = file.type.startsWith("image/"); // 文件类型必须是 image
  if (!isImage) {
    ElMessage.error("Upload image files only (jpg, png, jpeg, gif)");
    return false;
  }

  // 可选:限制文件大小(例如最大 5MB)
  const isLt5M = file.size / 1024 / 1024 < 5;
  if (!isLt5M) {
    ElMessage.error("Upload picture size cannot exceed 5MB!");
    return false;
  }

  return true;
};

// // 添加调整图层顺序的方法
// const moveLayerUp = (index) => {
//   if (index < elements.value.length - 1) {
//     const temp = elements.value[index];
//     elements.value[index] = elements.value[index + 1];
//     elements.value[index + 1] = temp;
//   }
// };

// const moveLayerDown = (index) => {
//   if (index > 0) {
//     const temp = elements.value[index];
//     elements.value[index] = elements.value[index - 1];
//     elements.value[index - 1] = temp;
//   }
// };

// 判断当前选中的图层是否是最上方
const isLayerAtTop = () => {
  return (
    // selectedIndex.value === elements.value.length - 1 ||
    // elements.value.length <= 0 ||
    !isDisabledLayerPos.value
  );
};

// 判断当前选中的图层是否是最下方
const isLayerAtBottom = () => {
  return (
    // selectedIndex.value === 0 ||
    // elements.value.length <= 0 ||
    !isDisabledLayerPos.value
  );
};

// 向上移动图层
const moveLayerUp = () => {
  if (!isLayerAtTop()) {
    const currentIndex = selectedIndex.value;
    const nextIndex = currentIndex + 1;
    const temp = elements.value[currentIndex];
    elements.value[currentIndex] = elements.value[nextIndex];
    elements.value[nextIndex] = temp;
    selectedIndex.value = nextIndex;
  }
};

// 向下移动图层
const moveLayerDown = () => {
  if (!isLayerAtBottom()) {
    const currentIndex = selectedIndex.value;
    const prevIndex = currentIndex - 1;
    const temp = elements.value[currentIndex];
    elements.value[currentIndex] = elements.value[prevIndex];
    elements.value[prevIndex] = temp;
    selectedIndex.value = prevIndex;
  }
};

// 假设这里有一个监听选中索引变化的逻辑
watch(selectedIndex, () => {
  checkOverlappingLayers();
});

// 加一个优化:判断出图层有重叠时,才可以move up, move down
const isDisabledLayerPos = ref(false);
// 判断两个矩形区域是否有交集
const isRectanglesIntersecting = (rect1, rect2) => {
  return (
    rect1.left < rect2.right &&
    rect1.right > rect2.left &&
    rect1.top < rect2.bottom &&
    rect1.bottom > rect2.top
  );
};

// 判断选中的图层身上是否有其他交集的图层
const checkOverlappingLayers = () => {
  if (selectedIndex.value === -1) return; // 如果没有选中的图层,直接返回

  const selectedElement = document.querySelector(
    `.canvas-element:nth-child(${selectedIndex.value + 1})`
  );
  if (!selectedElement) return;

  const selectedRect = selectedElement.getBoundingClientRect();

  for (let i = 0; i < elements.value.length; i++) {
    if (i === selectedIndex.value) continue; // 跳过选中的图层

    const otherElement = document.querySelector(
      `.canvas-element:nth-child(${i + 1})`
    );
    if (!otherElement) continue;

    const otherRect = otherElement.getBoundingClientRect();

    // console.log("重叠", isRectanglesIntersecting(selectedRect, otherRect));

    if (isRectanglesIntersecting(selectedRect, otherRect)) {
      // ElMessage.warning("选中的图层与其他图层有重叠!");
      isDisabledLayerPos.value = true;
      // return;
    } else {
      isDisabledLayerPos.value = false;
    }
  }
};

// ... existing code ...
</script>

<style lang="scss" scoped>
.certificate-editor {
  display: flex;
  height: 100vh;
  background-color: #f5f7fa;
}

.toolbar {
  width: 300px;
  padding: 0 20px;
  background-color: white;
  border-right: 1px solid #dcdfe6;
  overflow-y: auto;

  .tool-section {
    margin-bottom: 30px;

    h3 {
      font-size: 16px;
      color: #303133;
      margin-bottom: 15px;
    }
  }

  .template-list {
    display: grid;
    grid-template-columns: repeat(1, 1fr);
    gap: 15px;

    .template-item {
      position: relative;
      cursor: pointer;
      border: 1px solid #dcdfe6;
      border-radius: 4px;
      overflow: hidden;
      transition: all 0.3s;

      &:hover {
        border-color: #409eff;
        transform: translateY(-2px);
        box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
      }

      .el-image {
        width: 100%;
        height: 150px;
        object-fit: cover;
      }

      .template-name {
        padding: 8px;
        text-align: center;
        color: #606266;
        font-size: 14px;
      }
    }

    .template-item::before {
      z-index: 100;
      content: "";
      width: 0;
      height: 0;
      border: 40px solid transparent;
      // border-right: 60px solid #17b899;
      border-right: 60px solid #fc5531;
      transform: rotate(135deg);
      position: absolute;
      right: -61px;
      top: -61px;
      cursor: pointer;
      opacity: 0; /* 默认隐藏 */
      transition: opacity 0.3s ease;
      pointer-events: none; /* 避免隐藏时阻挡鼠标事件 */
    }

    .template-item:hover::before {
      opacity: 1; /* 鼠标移入时显示 */
      pointer-events: auto; /* 允许鼠标点击 */
    }

    // .template-item::after {
    //   z-index: 999;
    //   content: "x";
    //   width: 40px;
    //   height: 30px;
    //   color: #fff;
    //   // transform: rotate(45deg);
    //   position: absolute;
    //   right: -24px;
    //   top: -1px;
    //   font-weight: bold;
    //   letter-spacing: 2px;
    //   cursor: pointer;
    // }

    .delete-icon {
      z-index: 999;
      content: "x";
      width: 40px;
      height: 30px;
      color: #fff;
      // transform: rotate(45deg);
      position: absolute;
      right: -24px;
      top: -1px;
      font-weight: bold;
      letter-spacing: 2px;
      cursor: pointer;
      opacity: 0; /* 默认隐藏 */
      transition: opacity 0.3s ease;
      pointer-events: none;
    }

    .template-item:hover .delete-icon {
      opacity: 1; /* 鼠标移入时显示 */
      pointer-events: auto; /* 允许鼠标点击 */
    }
  }

  .image-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 8px;
    padding: 8px;
    background-color: #f5f7fa;
    border-radius: 4px;

    .image-item {
      cursor: move;
      border: 1px solid #dcdfe6;
      border-radius: 4px;
      overflow: hidden;
      aspect-ratio: 1;
      transition: all 0.3s;
      position: relative;

      &:hover {
        border-color: #409eff;
        transform: translateY(-2px);
        box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
      }

      // &::before {
      //   content: "";
      //   position: absolute;
      //   top: 0;
      //   left: 0;
      //   right: 0;
      //   bottom: 0;
      //   background: rgba(0, 0, 0, 0.03);
      //   opacity: 0;
      //   transition: opacity 0.3s;
      // }

      // &:hover::before {
      //   opacity: 1;
      // }

      .el-image {
        width: 100%;
        height: 100%;
        object-fit: cover;
      }
    }

    .image-item::before {
      z-index: 100;
      content: "";
      width: 0;
      height: 0;
      border: 35px solid transparent;
      border-right: 60px solid #fc5531;
      transform: rotate(135deg);
      position: absolute;
      right: -61px;
      top: -61px;
      cursor: pointer;
      opacity: 0; /* 默认隐藏 */
      transition: opacity 0.3s ease;
      pointer-events: none; /* 避免隐藏时阻挡鼠标事件 */
    }

    .image-item:hover::before {
      opacity: 1; /* 鼠标移入时显示 */
      pointer-events: auto; /* 允许鼠标点击 */
    }

    .delete-icon2 {
      z-index: 999;
      content: "x";
      font-weight: bold;
      letter-spacing: 2px;
      cursor: pointer;
      opacity: 0;
      position: absolute;
      right: 1px;
      top: -5px;
      transition: opacity 0.3s ease;
      color: #fff;
    }

    .image-item:hover .delete-icon2 {
      opacity: 1; /* 鼠标移入时显示 */
      pointer-events: auto; /* 允许鼠标点击 */
    }
  }

  .add-text-btn {
    width: 100%;
  }

  .background-settings {
    margin-top: 10px;

    .background-uploader {
      border: 1px dashed #d9d9d9;
      border-radius: 6px;
      cursor: pointer;
      position: relative;
      overflow: hidden;
      width: 100%;
      height: 120px;
      display: flex;
      justify-content: center;
      align-items: center;

      &:hover {
        border-color: var(--el-color-primary);
      }
    }

    .background-preview {
      width: 100%;
      height: 100%;
      object-fit: cover;
    }

    .background-uploader-icon {
      font-size: 28px;
      color: #8c939d;
    }

    .background-actions {
      margin-top: 10px;
      text-align: center;
    }
  }
}

.right-content {
  display: flex;
  flex-direction: column;
  flex: 1;
  .canvas-area {
    // flex: 1;
    padding: 20px;
    // display: flex;
    // flex-direction: column;

    .canvas {
      position: relative;
      background-color: #fff;
      border: 1px solid #ddd;
      // margin: 20px;
      width: 300px;
      height: 210px;
      overflow: hidden;
      background-repeat: no-repeat;
    }
  }
  .canvas-actions {
    margin-top: 10px;
    text-align: center;

    .el-button {
      padding: 12px 30px;
      font-size: 16px;

      &.is-disabled {
        cursor: not-allowed;
        opacity: 0.6;
      }
    }
  }
}

.canvas-element {
  position: absolute;
  user-select: none;

  &.selected {
    outline: 2px solid var(--el-color-primary);
  }

  &:hover {
    cursor: move;
  }
}

.element-wrapper {
  position: relative;
  width: 100%;
  height: 100%;

  .element-actions {
    position: absolute;
    top: -20px;
    right: -20px;
    z-index: 100;
    display: flex;
    gap: 5px;
  }
}

.resize-handle {
  position: absolute;
  right: 0;
  bottom: 0;
  width: 20px;
  height: 20px;
  background-color: var(--el-color-primary);
  border: 2px solid #fff;
  border-radius: 4px;
  cursor: se-resize;
  z-index: 100;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
  display: flex;
  align-items: center;
  justify-content: center;
  transform: translate(50%, 50%);

  .el-icon {
    font-size: 12px;
    color: #fff;
    transform: rotate(45deg);
  }

  &:hover {
    transform: translate(50%, 50%) scale(1.1);
    transition: transform 0.2s;
  }
}

:deep .el-image__inner {
  // 移除或调整 CSS 中的最大尺寸限制:在 CSS 中,检
  // max-width 和 max-height 属性会阻止图片超过指定的尺寸。查是否有 max-width 和 max-height 属性限制了图片的尺寸。如果有,可以将其移除或设置为一个更大的值。

  // max-width: 300px !important;
  // max-height: 300px !important;
  // 移除最大宽度和最大高度限制
  max-width: none !important;
  max-height: none !important;
  width: 100% !important;
  height: 100% !important;
  object-fit: contain;
}

.size-buttons {
  .btn {
    padding: 0;
    margin: 0 4px;
  }
}

// 设置滚动条样式 ==============================
*::-webkit-scrollbar {
  width: 14px;
  height: 14px;
}
*::-webkit-scrollbar-button {
  width: 0;
  height: 0;
  display: none;
}
*::-webkit-scrollbar-corner {
  background-color: transparent;
}
*::-webkit-scrollbar-thumb {
  min-height: 12px;
  border: 4px solid transparent;
  background-clip: content-box;
  border-radius: 7px;
  background-color: #3c8dbc;
}
*::-webkit-scrollbar-thumb:hover {
  background-color: #a8bbcf;
}
*::-webkit-scrollbar-thumb:active {
  background-color: #87a2bd;
}
*::-webkit-scrollbar-track {
  background-color: transparent;
}
*::-webkit-scrollbar-track-piece {
  background-color: transparent;
}

// .image-slot {
//   display: flex;
//   justify-content: center;
//   align-items: center;
//   width: 100%;
//   height: 100%;
//   background: var(--el-fill-color-light);
//   color: var(--el-text-color-secondary);
//   font-size: 30px;
// }
// .image-slot .el-icon {
//   font-size: 30px;
// }
</style>

代码二:

  <div
              v-for="(element, index) in elements"
              :key="index"
              class="canvas-element"
              :class="{ selected: selectedIndex === index }"
              :style="getElementStyle(element)"
              @click.stop="selectElement(index)"
              @mousedown="
                element.type === 'image'
                  ? startImageDragging($event, index)
                  : startDragging($event, index)
              "
              @mousemove="
                element.type === 'image'
                  ? onImageDragging($event)
                  : onDragging($event)
              "
              @mouseup="
                element.type === 'image'
                  ? stopImageDragging($event)
                  : stopDragging()
              "
              @mouseleave="
                element.type === 'image'
                  ? stopImageDragging($event)
                  : stopDragging()
              "
            >
              <template v-if="element.type === 'image'">
                <div class="element-wrapper">
                  <el-image :src="element.src" fit="contain" />
                  <div class="element-actions" v-if="selectedIndex === index">
                    <el-button
                      type="danger"
                      size="small"
                      circle
                      icon="Delete"
                      @click.stop="deleteElement(index)"
                    />

                    <!-- 添加调整图层顺序的按钮 -->
                    <!-- <el-button
                    type="primary"
                    size="small"
                    circle
                    icon="ArrowUp"
                    @click.stop="moveLayerUp(index)"
                    :disabled="index === elements.length - 1"
                  />
                  <el-button
                    type="primary"
                    size="small"
                    circle
                    icon="ArrowDown"
                    @click.stop="moveLayerDown(index)"
                    :disabled="index === 0"
                  /> -->
                  </div>

                  <div
                    v-if="selectedIndex === index"
                    class="resize-handle"
                    @mousedown.stop="startResizing($event, index)"
                  >
                    <el-icon><Rank /></el-icon>
                  </div>
                </div>
              </template>

              <template v-else-if="element.type === 'text'">
                <div class="element-wrapper">
                  <div
                    class="text-element"
                    :style="getTextStyle(element)"
                    @dblclick="startEditing(index)"
                  >
                    <el-input
                      v-if="editingIndex === index"
                      v-model="tempTextContent"
                      @blur="handleBlur(index)"
                      type="textarea"
                      autosize
                    />
                    <span v-else>{{ element.content }}</span>
                  </div>
                  <div class="element-actions" v-if="selectedIndex === index">
                    <el-button
                      type="danger"
                      size="small"
                      circle
                      icon="Delete"
                      @click.stop="deleteElement(index)"
                    />

                    <!-- 添加调整图层顺序的按钮 -->
                    <!-- <el-button
                    type="primary"
                    size="small"
                    circle
                    icon="ArrowUp"
                    @click.stop="moveLayerUp(index)"
                    :disabled="index === elements.length - 1"
                  />
                  <el-button
                    type="primary"
                    size="small"
                    circle
                    icon="ArrowDown"
                    @click.stop="moveLayerDown(index)"
                    :disabled="index === 0"
                  /> -->
                  </div>
                  <div
                    v-if="selectedIndex === index"
                    class="resize-handle"
                    @mousedown.stop="startResizing($event, index)"
                  >
                    <el-icon><Rank /></el-icon>
                  </div>
                </div>
              </template>
            </div>
            
            


// ============================================  3.26优化   ============================================
// 实现图片在鼠标松开时直接出现在最终位置且效果更加丝滑
// startDragging 、onDragging 、stopDragging 这三个方法修改了
// 新增图片拖拽相关变量
const isImageDragging = ref(false); //   用于标记图片是否正在被拖拽
const imageDragStartPos = ref({ x: 0, y: 0 }); // 记录图片拖拽开始时的鼠标位置
const imageElementStartPos = ref({ x: 0, y: 0 }); // 记录图片拖拽开始时元素的位置
const imageDraggingIndex = ref(-1); // 记录正在被拖拽的图片元素的索引

// 开始拖拽图片元素: 处理图片开始拖拽的逻辑
const startImageDragging = (event, index) => {
  if (editingIndex.value === -1) {
    isImageDragging.value = true;
    imageDraggingIndex.value = index;
    imageDragStartPos.value = {
      x: event.clientX,
      y: event.clientY,
    };
    const element = elements.value[index];
    const left = element.style.left;
    const top = element.style.top;

    const right = element.style.right;
    const bottom = element.style.bottom;

    // 处理百分比位置转换为像素
    const canvasWidth = canvasRef.value.offsetWidth;
    const canvasHeight = canvasRef.value.offsetHeight;

    if (left?.includes("%")) {
      const leftPercent = parseFloat(left);
      imageElementStartPos.value.x = (canvasWidth * leftPercent) / 100;
    } else if (right?.includes("%")) {
      const rightPercent = parseFloat(right);
      imageElementStartPos.value.x =
        canvasWidth - (canvasWidth * rightPercent) / 100;
    } else {
      imageElementStartPos.value.x = left
        ? parseFloat(left)
        : canvasWidth - (right ? parseFloat(right) : 0);
    }

    if (top?.includes("%")) {
      const topPercent = parseFloat(top);
      imageElementStartPos.value.y = (canvasHeight * topPercent) / 100;
    } else if (bottom?.includes("%")) {
      const bottomPercent = parseFloat(bottom);
      imageElementStartPos.value.y =
        canvasHeight - (canvasHeight * bottomPercent) / 100;
    } else {
      imageElementStartPos.value.y = top
        ? parseFloat(top)
        : canvasHeight - (bottom ? parseFloat(bottom) : 0);
    }

    // 处理transform偏移
    if (
      element.style.transform &&
      element.style.transform.includes("translate")
    ) {
      const el = event.currentTarget;
      const rect = el.getBoundingClientRect();
      imageElementStartPos.value = {
        x: rect.left - canvasRef.value.getBoundingClientRect().left,
        y: rect.top - canvasRef.value.getBoundingClientRect().top,
      };
    }
  }
};

// 图片拖拽中的处理: 处理图片拖拽过程中的逻辑,这里不更新元素位置
const onImageDragging = (event) => {
  // 拖拽过程中不更新元素位置
  if (!isImageDragging.value) return;
};

// 停止拖拽图片: 处理图片停止拖拽的逻辑,计算并更新图片的最终位置
const stopImageDragging = (event) => {
  if (isImageDragging.value) {
    isImageDragging.value = false;
    const index = imageDraggingIndex.value;
    imageDraggingIndex.value = -1;

    const element = elements.value[index];
    const canvasRect = canvasRef.value.getBoundingClientRect();
    const elementRef = document.querySelector(
      `.canvas-element:nth-child(${index + 1})`
    );

    if (elementRef) {
      const mouseEndPos = {
        x: event.clientX,
        y: event.clientY,
      };
      const dx = mouseEndPos.x - imageDragStartPos.value.x;
      const dy = mouseEndPos.y - imageDragStartPos.value.y;

      let newX = imageElementStartPos.value.x + dx;
      let newY = imageElementStartPos.value.y + dy;

      const elementRect = elementRef.getBoundingClientRect();
      // 限制范围
      newX = Math.max(0, Math.min(newX, canvasRect.width - elementRect.width));
      newY = Math.max(
        0,
        Math.min(newY, canvasRect.height - elementRect.height)
      );

      element.style.left = `${newX}px`;
      element.style.top = `${newY}px`;
      // 移除transform,避免位置计算问题
      element.style.transform = "";
      // 移除可能导致计算偏差的 right 和 bottom 属性
      element.style.right = "";
      element.style.bottom = "";
    }
  }
};