<template>
  <div class="labeling-page-container">
    <!-- 顶部操作栏：新增全屏按钮 -->
    <div class="labeling-header">
      <div class="header-left">
        <!-- 标注工具按钮组 -->
        <el-button-group>
          <el-button icon="el-icon-arrow-left" @click="prevImage" title="上一张" :disabled="imageList.length <= 1"></el-button>
          <el-button icon="el-icon-arrow-right" @click="nextImage" title="下一张" :disabled="imageList.length <= 1"></el-button>
          <!-- 标注类型切换 -->
          <el-button
            icon="el-icon-plus"
            :class="{ active: drawType === 'rect' }"
            @click="setDrawType('rect')"
            title="矩形标注"
            v-if="labelingTemplate === '3'"

          >
            矩形标注
          </el-button>
          <el-button
            icon="el-icon-plus"
            :class="{ active: drawType === 'polygon' }"
            @click="setDrawType('polygon')"
            title="多边形标注"
            v-if="labelingTemplate === '4'"

          >
            多边形标注
          </el-button>
          <el-button
            icon="el-icon-plus"
            :class="{ active: drawType === 'circle' }"
            @click="setDrawType('circle')"
            title="圆形标注"
            v-if="labelingTemplate === '5'"

          >
            圆形标注
          </el-button>
<!--          <el-button icon="el-icon-delete" @click="deleteSelected" title="删除选中"></el-button>-->
          <!-- 撤销/恢复 -->
          <el-button icon="el-icon-refresh-left" @click="undo" title="撤销上一步" :disabled="undoStack.length === 0"></el-button>
          <el-button icon="el-icon-refresh-right" @click="redo" title="恢复上一步" :disabled="redoStack.length === 0"></el-button>
          <!-- 缩放 -->
          <el-button icon="el-icon-zoom-in" @click="zoomIn" title="放大"></el-button>
          <el-button icon="el-icon-zoom-out" @click="zoomOut" title="缩小"></el-button>
          <!--          <el-button icon="el-icon-full-screen" @click="toggleFullScreen" title="页面内全屏" :class="{ active: isFullScreen }"></el-button>-->
          <!--          <el-button icon="el-icon-refresh" @click="resetZoom" title="重置视图"></el-button>-->
        </el-button-group>
      </div>

      <div class="header-right">
        <!-- 无效数据标记 -->
        <el-checkbox v-model="isInvalidData" label="标为无效数据"></el-checkbox>
        <!-- 保存按钮组 -->
        <el-button type="primary" @click="saveLabel" class="save-btn">保存</el-button>
        <el-button type="success" @click="saveAndContinue" class="save-btn">保存并继续</el-button>
      </div>
    </div>

    <!-- 主体内容区：适配全屏状态 -->
    <div class="labeling-main-wrapper" :class="{ fullscreen: isFullScreen }">
      <div class="labeling-main" :class="{ fullscreen: isFullScreen }">
        <!-- 左侧图片标注区：优化图片填充逻辑 -->
        <div class="labeling-image-area">
          <!-- 图片加载中占位 -->
          <div v-if="imageLoading" class="image-loading">
            <el-skeleton :rows="10" animated />
          </div>
          <div v-else ref="labelContainer" class="image-container" @mousedown="handleContainerMouseDown" @wheel.prevent="handleWheelZoom">
            <div class="image-wrapper" :style="{ transform: 'none', transformOrigin: 'center center' }">
              <img
                ref="labelImage"
                :src="currentImage.filePath"
                alt="标注图片"
                class="label-image"
                @load="onImageLoad"
                @error="onImageError"
                @mousedown.prevent
                style="display: none;"
              />
            </div>
          </div>
          <!-- 图片分页信息 -->
          <div class="image-pagination" v-if="!imageLoading">
            第 {{ currentImageIndex + 1 }} 张/共 {{ imageList.length }} 张 | 缩放比例：{{ Math.round(zoomScale * 100) }}%
            <!-- 标注提示 -->
            <span v-if="drawType" class="draw-tip">
              <template v-if="drawType === 'rect'">矩形标注：按住鼠标拖动绘制</template>
              <template v-if="drawType === 'polygon'">多边形标注：点击添加点（已选{{ polygonPoints.length }}个点），双击完成/右键取消</template>
              <template v-if="drawType === 'circle'">圆形标注：按住鼠标拖动绘制（起点为圆心，拖动为半径）</template>
            </span>
            <!-- 未选择标签提示（先标注后选标签场景） -->
            <span v-if="tempShapeId && !selectedLabelId" class="draw-tip warning">
              请为该标注选择标签！
            </span>
          </div>
        </div>

        <!-- 右侧标签栏：全屏时隐藏 -->
        <div class="labeling-sidebar" v-show="!isFullScreen">
          <div class="sidebar-title">标签栏</div>
          <!-- 加载状态提示 -->
          <div v-if="labelLoading" class="label-loading">
            <el-skeleton :rows="5" animated />
          </div>
          <!-- 标签列表容器（独立滚动，防止内容溢出） -->
          <div v-else class="sidebar-content">
            <div class="label-group" v-for="(group, index) in labelGroups" :key="group.id || index">
              <div class="label-group-header" @click="group.expanded = !group.expanded">
                <i class="el-icon" :class="group.expanded ? 'el-icon-arrow-down' : 'el-icon-arrow-right'"></i>
                <span>{{ group.name }} ({{ group.labels.length }})</span>
                <span class="label-count">{{ group.labels.length }}/{{ group.total || group.labels.length }}</span>
              </div>
              <!-- 标签列表 -->
              <div class="label-list" v-show="group.expanded">
                <div
                  class="label-item"
                  v-for="(label, labelIndex) in group.labels"
                  :key="label.id || labelIndex"
                  :class="{ active: label.id === selectedLabelId }"
                  @click="selectLabel(label)"
                >
                  <span class="label-name">#{{ labelIndex + 1 }} {{ label.name }} </span>
<!--                  <span class="shape-count" v-if="label.shapeIds && label.shapeIds.length">-->
<!--                    ({{ label.shapeIds.length }}个标注)-->
<!--                  </span>-->
                </div>
              </div>
            </div>
            <!-- 无数据提示 -->
            <div v-if="labelGroups.length === 0" class="no-label-data">
              <el-empty description="暂无标签数据"></el-empty>
            </div>
          </div>
        </div>
      </div>
    </div>

    <!-- 添加/编辑标签弹窗 -->
    <el-dialog title="添加标签" :visible.sync="addLabelDialogVisible" width="400px">
      <el-form :model="newLabelForm" label-width="80px" :rules="labelFormRules" ref="labelFormRef">
        <el-form-item label="标签分组" prop="groupId">
          <el-select v-model="newLabelForm.groupId" placeholder="选择标签分组" disabled="labelGroups.length === 0">
            <el-option
              v-for="group in labelGroups"
              :key="group.id"
              :label="group.name"
              :value="group.id"
            ></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="标签名称" prop="name">
          <el-input v-model="newLabelForm.name" placeholder="输入标签名称"></el-input>
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button @click="addLabelDialogVisible = false">取消</el-button>
        <el-button type="primary" @click="confirmAddLabel">确定</el-button>
      </div>
    </el-dialog>

    <!-- 为临时标注选择标签弹窗 -->
    <el-dialog title="为标注选择标签" :visible.sync="selectLabelForShapeDialog" width="400px">
      <div class="shape-label-select">
        <div class="label-group" v-for="(group, index) in labelGroups" :key="group.id || index">
          <div class="label-group-header">{{ group.name }}</div>
          <div class="label-list">
            <div
              class="label-item"
              v-for="(label, labelIndex) in group.labels"
              :key="label.id || labelIndex"
              @click="confirmShapeLabel(label)"
            >
              <span class="label-name">{{ label.name }}</span>
            </div>
          </div>
        </div>
      </div>
    </el-dialog>
  </div>
</template>

<script>
import * as AILabelLib from 'ailabel'
// 1. 引入接口请求方法
import {
  getLabelingTaskLabelTreeAPI,  // 根据标注任务ID获取标签树
  getLabelPicturesAPI,           // 获取标注图片列表
  saveLabelingDataAPI
} from '@/api/data/labeling'
import {decryptWithSM4} from "@/utils/sm"; // 请根据实际路径调整

export default {
  name: 'LabelingAdd',
  data() {
    return {
      labelDrawConfig: {
        baseLineWidth: 3,
        minLineWidth: 1,
        maxLineWidth: 6,
        textFontSize: 16,
        textPadding: 3,
        shapeMainColor: {
          rect: '#00ff00',     // 矩形填充主色（可随便改，比如#2f4050）
          polygon: '#0099ff',  // 多边形填充主色
          circle: '#ff9900'    // 圆形填充主色
        },
        selectedColor: '#ff0000', // 选中时所有形状的填充/描边主色
        bgOpacity: 0.4, // 文字背景透明度（原有）
        shapeFillOpacity: 0.4, // 图形自身填充透明度（0~1，越小越透明）
        textColor: "#ffffff"
      },
      shapeColorMap: {
        rect: '#00ff00',     // 矩形 - 绿色
        polygon: '#0099ff',  // 多边形 - 蓝色
        circle: '#ff9900'    // 圆形 - 橙色
      },
      // 图片列表（替换死数据，从接口获取）
      imageList: [],
      currentImageIndex: 0,
      currentImage: {},
      imageLoading: false, // 图片加载状态
      isInvalidData: false,
      selectedLabelId: '',
      // 标签组数据
      labelGroups: [],
      labelLoading: false,
      addLabelDialogVisible: false,
      selectLabelForShapeDialog: false, // 为临时标注选标签弹窗
      newLabelForm: { groupId: '', name: '' },
      labelFormRules: { // 标签表单校验规则
        groupId: [{ required: true, message: '请选择标签分组', trigger: 'change' }],
        name: [{ required: true, message: '请输入标签名称', trigger: 'blur' }]
      },
      labelFormRef: '', // 标签表单ref
      ailabelInstance: null,
      zoomScale: 1.0, // 缩放比例
      drawType: '', // 标注类型：''(无)、'rect'(矩形)、'polygon'(多边形)、'circle'(圆形)
      editLabelId: '',
      imageLoaded: false,
      // 核心修改：标签与标注的映射关系（支持一个标签对应多个标注）
      shapeLabelMap: {}, // shapeId -> labelId
      labelShapeMap: {}, // labelId -> [shapeId1, shapeId2...]
      polygonPoints: [], // 多边形标注的点集合
      // 撤销/恢复栈
      undoStack: [], // 撤销栈：存储每一步操作前的状态
      redoStack: [], // 恢复栈：存储撤销的操作状态
      // 临时绘制状态
      tempDrawData: {
        isDrawing: false,
        startX: 0,
        startY: 0,
        tempShape: null
      },
      tempShapeId: '', // 临时标注ID（先标注后选标签场景）
      // 全屏状态
      isFullScreen: false,
      labelingTaskId: this.convertToNumber(decryptWithSM4(this.$route.query.labelingTaskId || '')),
      labelingTemplate: decryptWithSM4(this.$route.query.labelingTemplate || '')
    }
  },
  mounted() {
    // 先加载图片列表，再加载标签树
    this.loadTreeData().then(() => {
      if (this.imageList.length > 0) {
        this.currentImage = this.imageList[this.currentImageIndex]
      }
    })
    this.loadLabelTreeData()
    console.log('AILabel库导出内容：', AILabelLib)
  },
  methods: {

    resetLabelState() {
      // 1. 重置组件层核心标注状态（关键遗留数据）
      this.selectedLabelId = ''; // 清空标签选中状态
      this.drawType = ''; // 清空绘制类型，取消按钮active样式
      this.polygonPoints = []; // 清空多边形临时点（组件层）
      this.tempShapeId = ''; // 清空临时标注ID
      this.isInvalidData = false; // 重置无效数据标记（核心遗漏）
      this.tempDrawData = { // 重置临时绘制数据
        isDrawing: false,
        startX: 0,
        startY: 0,
        tempShape: null
      };

      // 2. 重置标注工具层状态（避免工具内部残留）
      if (this.ailabelInstance) {
        // 清空工具层多边形临时点
        if (this.ailabelInstance.polygonPoints) {
          this.ailabelInstance.polygonPoints = [];
        }
        // 重置工具层绘制状态
        if (this.ailabelInstance.tempDrawData) {
          this.ailabelInstance.tempDrawData = {
            isDrawing: false,
            startX: 0,
            startY: 0,
            tempShape: null
          };
        }
        // 清空工具层临时画布（避免绘制残留）
        if (this.ailabelInstance.tempCtx) {
          this.ailabelInstance.tempCtx.clearRect(
            0, 0,
            this.ailabelInstance.tempCanvas.width,
            this.ailabelInstance.tempCanvas.height
          );
        }
        // 取消工具层的形状选中状态
        this.ailabelInstance.selectedShape = null;
        // 同步重绘主画布，刷新视觉
        this.ailabelInstance.redrawMainCanvas?.();
      }

      // 3. 关闭所有弹窗（避免弹窗残留）
      this.addLabelDialogVisible = false;
      this.selectLabelForShapeDialog = false;
    },

    getConvexHull(points) {
      if (points.length <= 3) return [...points]; // 3个点以内无需处理，直接返回

      // 步骤1：找到最下方（y最小）的点，y相同则选x最小的点（作为凸包起点）
      const startPoint = points.reduce((min, p) =>
          p.y < min.y || (p.y === min.y && p.x < min.x) ? p : min,
        points[0]
      );

      // 步骤2：计算所有点相对于起点的极角，按极角逆时针排序（极角相同则按距离起点由近到远）
      const sortedPoints = [...points].sort((a, b) => {
        const cross = this.crossProduct(startPoint, a, b);
        if (cross !== 0) {
          return cross > 0 ? -1 : 1; // 极角小的排前面（逆时针）
        } else {
          // 极角相同，距离近的排前面
          return this.getDistance(startPoint, a) - this.getDistance(startPoint, b);
        }
      });

      // 步骤3：Graham扫描 - 构建凸包栈，剔除凹点（避免边交叉）
      const stack = [sortedPoints[0], sortedPoints[1]];
      for (let i = 2; i < sortedPoints.length; i++) {
        // 当栈内至少2个点，且当前点与栈顶两个点构成的边为凹边（叉乘≤0），弹出栈顶点
        while (
          stack.length >= 2 &&
          this.crossProduct(stack[stack.length - 2], stack[stack.length - 1], sortedPoints[i]) <= 0
          ) {
          stack.pop();
        }
        stack.push(sortedPoints[i]);
      }

      return stack; // 最终栈内的点即为凸包点集（无交叉）
    },

// 工具函数：计算向量叉乘 (p2-p1) × (p3-p1)
// 结果>0：p3在p1p2左侧；=0：三点共线；<0：p3在p1p2右侧
    crossProduct(p1, p2, p3) {
      return (p2.x - p1.x) * (p3.y - p1.y) - (p2.y - p1.y) * (p3.x - p1.x);
    },

// 工具函数：计算两点间欧式距离的平方（避免开方，提升性能）
    getDistance(p1, p2) {
      return (p2.x - p1.x) ** 2 + (p2.y - p1.y) ** 2;
    },

// 原有线段相交检测函数（可保留，后续若有其他需求可用）
    isSegmentsIntersect(p1, p2, p3, p4) {
      const cross = (a, b, c) => (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
      const s1 = cross(p1, p2, p3) * cross(p1, p2, p4);
      const s2 = cross(p3, p4, p1) * cross(p3, p4, p2);
      return s1 < 0 && s2 < 0;
    },

    // 检测标注是否超出图片边界
    isShapeOutOfImage(shape) {
      if (!this.$refs.labelImage) return true; // 图片未加载则默认越界

      // 获取图片原始尺寸
      const imgWidth = this.$refs.labelImage.naturalWidth;
      const imgHeight = this.$refs.labelImage.naturalHeight;

      // 不同形状的边界检测逻辑
      switch (shape.type) {
        case 'rect':
          // 矩形：检测四个顶点是否都在图片内
          return (
            shape.x < 0 ||
            shape.y < 0 ||
            shape.x + shape.width > imgWidth ||
            shape.y + shape.height > imgHeight
          );
        case 'circle':
          // 圆形：检测圆心+半径是否超出图片
          return (
            shape.x - shape.radius < 0 ||
            shape.x + shape.radius > imgWidth ||
            shape.y - shape.radius < 0 ||
            shape.y + shape.radius > imgHeight
          );
        case 'polygon':
          // 多边形：检测所有顶点是否都在图片内
          return shape.points.some(point =>
            point.x < 0 || point.x > imgWidth || point.y < 0 || point.y > imgHeight
          );
        default:
          return false;
      }
    },

    convertToNumber(value) {
      if (!value || value === '' || value === 'undefined' || value === 'null') {
        return '';
      }
      const num = Number(value);
      return isNaN(num) ? '' : num;
    },


    // 4. 新增：加载标签树数据
    async loadLabelTreeData() {
      try {
        this.labelLoading = true
        let res
        // 根据是否有标注任务ID选择不同接口
        if (this.labelingTaskId) {
          // 获取该任务关联的标签树
          res = await getLabelingTaskLabelTreeAPI({ labelingTaskId: this.labelingTaskId })
        }

        // 处理接口返回数据（适配原有labelGroups结构）
        // 假设接口返回格式：[{ id: 1, name: '餐余浪费', labels: [...], total: 15 }, ...]
        if (res.code === 200 && res.data) {
          this.labelGroups = res.data.map(group => ({
            ...group,
            expanded: true, // 默认展开所有分组
            // 确保labels字段存在且为数组，补充默认值
            labels: group.labels || [],
            // 确保total字段存在
            total: group.total || (group.labels ? group.labels.length : 0)
          }))

          // 初始化新增标签的默认分组
          if (this.labelGroups.length > 0) {
            this.newLabelForm.groupId = this.labelGroups[0].id
          }
        } else {
          this.$message.warning('标签数据加载失败：' + (res.msg || '接口返回异常'))
          this.labelGroups = []
        }
      } catch (error) {
        console.error('加载标签树失败：', error)
        this.$message.error('标签数据加载失败，请刷新重试')
        this.labelGroups = []
      } finally {
        this.labelLoading = false
      }
    },

    // 核心修改：加载图片列表（替换死数据）
    async loadTreeData() {
      try {
        this.imageLoading = true
        let res
        // 根据是否有标注任务ID选择不同接口
        if (this.labelingTaskId) {
          // 获取该任务关联的图片列表
          res = await getLabelPicturesAPI({ labelingTaskId: this.labelingTaskId })
          console.log('图片列表接口返回：', res)

          if (res.code === 200 && res.data && Array.isArray(res.data)) {
            console.log('res',res)
            this.imageList = res.data.map(item => ({
              id: item.labelingSampleId, // 标注样本ID
              datasetSampleId: item.datasetSampleId, // 数据集样本ID
              filePath: item.filePath, // 图片文件路径
              labels: [], // 该图片的标注数据
              isInvalid: false // 是否为无效数据
            }))
            // 如果有图片，初始化第一张
            if (this.imageList.length > 0) {
              this.currentImage = this.imageList[0]
            } else {
              this.$message.info('暂无待标注图片')
            }
          } else {
            this.$message.warning('图片数据加载失败：' + (res.msg || '接口返回异常'))
            this.imageList = []
          }
        }
      } catch (error) {
        console.error('加载图片失败：', error)
        this.$message.error('图片数据加载失败，请刷新重试')
        this.imageList = []
      } finally {
        this.imageLoading = false
      }
    },

    // 切换页面内全屏
    toggleFullScreen() {
      this.isFullScreen = !this.isFullScreen
      // 全屏时重置缩放为100%
      if (this.isFullScreen) {
        this.zoomScale = 1.0
        if (this.ailabelInstance?.setScale) {
          this.ailabelInstance.setScale(this.zoomScale)
        }
      }
      // 重新初始化标注工具以适配容器大小
      this.$nextTick(() => {
        this.initAILabel()
      })
    },

    // 容器鼠标按下事件
    handleContainerMouseDown(e) {
      e.preventDefault()
      // 多边形标注未绘制时，仅阻止默认行为，点的添加完全由工具层处理
      if (this.drawType === 'polygon' && !this.tempDrawData.isDrawing) {
        return; // 移除原有加逻辑，交给工具层
      }
    },

    // 记录操作状态（用于撤销）
    recordState() {
      // if (!this.ailabelInstance) return;
      // 深拷贝工具内部的shapes（保留所有专属属性：points/radius等）
      const deepCopyShapes = JSON.parse(JSON.stringify(this.ailabelInstance.getShapes() || []));
      // 完整快照：工具层+组件层所有状态，无遗漏
      const state = {
        // 标注工具核心状态
        shapes: deepCopyShapes,
        // 标签-标注映射关系
        shapeLabelMap: JSON.parse(JSON.stringify(this.shapeLabelMap)),
        labelShapeMap: JSON.parse(JSON.stringify(this.labelShapeMap)),
        // 组件层操作状态（撤销后同步回退）
        selectedLabelId: this.selectedLabelId,
        drawType: this.drawType,
        polygonPoints: JSON.parse(JSON.stringify(this.polygonPoints)),
        tempShapeId: this.tempShapeId,
        isInvalidData: this.isInvalidData
      };
      console.log('result',this.undoStack)
      this.undoStack.push(state);
      // 新操作后清空恢复栈（符合常规撤销逻辑）
      this.redoStack = [];
      // 限制撤销栈大小，防止内存溢出（可选，建议保留）
      if (this.undoStack.length > 50) this.undoStack.shift();
    },

    // 撤销上一步
    undo() {
      if (this.undoStack.length === 0) {
        this.$message.warning('无操作可撤销！');
        return;
      }

      try {
        // 1. 记录当前状态到恢复栈（用于redo）
        const currentState = {
          shapes: JSON.parse(JSON.stringify(this.ailabelInstance.getShapes() || [])),
          shapeLabelMap: JSON.parse(JSON.stringify(this.shapeLabelMap)),
          labelShapeMap: JSON.parse(JSON.stringify(this.labelShapeMap)),
          selectedLabelId: this.selectedLabelId,
          drawType: this.drawType,
          polygonPoints: JSON.parse(JSON.stringify(this.polygonPoints)),
          tempShapeId: this.tempShapeId,
          isInvalidData: this.isInvalidData
        };
        this.redoStack.push(currentState);

        // 2. 取出上一步快照并恢复
        const prevState = this.undoStack.pop();
        // 清空工具层所有状态（彻底重置，防止残留）
        this.ailabelInstance.clearShapes();
        this.ailabelInstance.cancelDrawing(); // 取消所有临时绘制
        this.ailabelInstance.selectedShape = null; // 清空选中状态

        // 3. 恢复组件层核心状态（同步回退操作状态）
        this.shapeLabelMap = prevState.shapeLabelMap;
        this.labelShapeMap = prevState.labelShapeMap;
        this.selectedLabelId = prevState.selectedLabelId;
        this.drawType = prevState.drawType;
        this.polygonPoints = prevState.polygonPoints;
        this.tempShapeId = prevState.tempShapeId;
        this.isInvalidData = prevState.isInvalidData;

        // 4. 重绘所有形状 - 核心：复用快照中的原始ID，保证标签映射不失效
        prevState.shapes.forEach(shape => {
          if (!shape.id) return; // 过滤无效形状
          switch (shape.type) {
            case 'rect':
              this.ailabelInstance.drawRect(shape.x, shape.y, shape.width, shape.height, shape.id);
              break;
            case 'polygon':
              // 确保多边形points有效
              if (shape.points && shape.points.length >= 3) {
                this.ailabelInstance.drawPolygon(shape.points, shape.id);
              }
              break;
            case 'circle':
              this.ailabelInstance.drawCircle(shape.x, shape.y, shape.radius, shape.id);
              break;
            default:
              break;
          }
          // 重新绑定标签数据到形状（恢复关联）
          this.ailabelInstance.setShapeData(shape.id, { labelId: this.shapeLabelMap[shape.id] });
        });

        // 5. 同步更新标签组的shapeIds（恢复标签计数）
        this.labelGroups.forEach(group => {
          group.labels.forEach(label => {
            label.shapeIds = this.labelShapeMap[label.id] || [];
          });
        });

        // 6. 重绘画布，确保视觉同步
        this.ailabelInstance.redrawMainCanvas();
        this.$message.success('已撤销上一步操作');
      } catch (e) {
        console.error('撤销操作失败：', e);
        this.$message.error('撤销失败，请重试！');
      }
    },

    // 恢复上一步
    redo() {
      if (this.redoStack.length === 0) {
        this.$message.warning('无操作可恢复！');
        return;
      }

      try {
        // 1. 【核心】深拷贝「恢复前的当前状态」存入undoStack，作为后续撤销的依据
        // 这一步已经完成了recordState的核心作用，无需额外调用
        const currentState = {
          shapes: JSON.parse(JSON.stringify(this.ailabelInstance.getShapes() || [])),
          shapeLabelMap: JSON.parse(JSON.stringify(this.shapeLabelMap)),
          labelShapeMap: JSON.parse(JSON.stringify(this.labelShapeMap)),
          selectedLabelId: this.selectedLabelId,
          drawType: this.drawType,
          polygonPoints: JSON.parse(JSON.stringify(this.polygonPoints)),
          tempShapeId: this.tempShapeId,
          isInvalidData: this.isInvalidData
        };
        this.undoStack.push(currentState);

        // 2. 取出恢复栈的快照并深拷贝，避免修改原栈数据（防止引用污染）
        const redoState = JSON.parse(JSON.stringify(this.redoStack.pop()));

        // 3. 清空工具层现有状态（彻底重置，防止残留绘制/选中状态）
        this.ailabelInstance.clearShapes();
        this.ailabelInstance.cancelDrawing();
        this.ailabelInstance.selectedShape = null;

        // 4. 恢复核心映射关系和组件层状态（全量同步，解决状态不一致）
        this.shapeLabelMap = redoState.shapeLabelMap;
        this.labelShapeMap = redoState.labelShapeMap;
        this.selectedLabelId = redoState.selectedLabelId;
        this.drawType = redoState.drawType;
        this.polygonPoints = redoState.polygonPoints;
        this.tempShapeId = redoState.tempShapeId;
        this.isInvalidData = redoState.isInvalidData;

        // 5. 重新绘制所有形状（核心：复用快照中的shape.id，保证标签-标注映射不失效）
        redoState.shapes.forEach(shape => {
          if (!shape.id) return; // 过滤无效形状
          let newShapeId = '';
          switch (shape.type) {
            case 'rect':
              newShapeId = this.ailabelInstance.drawRect(shape.x, shape.y, shape.width, shape.height, shape.id);
              break;
            case 'polygon':
              if (shape.points && shape.points.length >= 3) {
                newShapeId = this.ailabelInstance.drawPolygon(shape.points, shape.id);
              }
              break;
            case 'circle':
              newShapeId = this.ailabelInstance.drawCircle(shape.x, shape.y, shape.radius, shape.id);
              break;
            default:
              break;
          }
          // 重新绑定标签数据到形状，恢复映射关联
          if (newShapeId) {
            this.ailabelInstance.setShapeData(newShapeId, { labelId: this.shapeLabelMap[newShapeId] });
          }
        });

        // 6. 反向恢复labelGroups（不直接覆盖，解决模板渲染length报错）
        // 仅更新shapeIds，保留labelGroups原有响应式结构和expanded等属性
        this.labelGroups.forEach(group => {
          group.labels.forEach(label => {
            label.shapeIds = this.labelShapeMap[label.id] || [];
          });
        });

        // 7. 重绘画布+重置临时绘制状态（解决视觉残留，保证状态干净）
        this.ailabelInstance.redrawMainCanvas();
        this.tempDrawData = { isDrawing: false, startX: 0, startY: 0, tempShape: null };

        this.$message.success('已恢复上一步操作');
      } catch (e) {
        console.error('恢复操作失败：', e);
        this.$message.error('恢复失败，请重试！');
      }
    },

    onImageLoad() {
      this.imageLoaded = true
      this.calcFitScale()
      // 关键修复：先设置缩放，再强制触发重绘
      if (this.ailabelInstance?.setScale) {
        this.ailabelInstance.setScale(this.zoomScale)
      }
      this.initAILabel()
      this.$nextTick(() => {
        if (this.ailabelInstance?.redrawMainCanvas) {
          this.ailabelInstance.redrawMainCanvas()
        }
        // this.recordState()
        this.ailabelInstance?.redrawMainCanvas()

      })
    },

    getDynamicLineWidth() {
      const { baseLineWidth, minLineWidth, maxLineWidth } = this.labelDrawConfig
      // 核心逻辑：缩放比例越小，线条越粗（反比例），并限制最大/最小值
      const dynamicWidth = baseLineWidth / this.zoomScale
      // 边界限制，防止线条过粗/过细
      return Math.max(minLineWidth, Math.min(maxLineWidth, dynamicWidth))
    },

    // 工具方法1：16进制颜色转RGBA（支持#fff/#ffffff格式，添加透明度）
    hexToRgba(hex, opacity) {
      // 去除#号
      const cleanHex = hex.replace('#', '');
      // 处理3位简写（如#fff→#ffffff）
      const fullHex = cleanHex.length === 3
        ? cleanHex.split('').map(c => c + c).join('')
        : cleanHex;
      // 解析r/g/b值
      const r = parseInt(fullHex.substring(0, 2), 16);
      const g = parseInt(fullHex.substring(2, 4), 16);
      const b = parseInt(fullHex.substring(4, 6), 16);
      // 返回rgba格式
      return `rgba(${r}, ${g}, ${b}, ${opacity})`;
    },

// 工具方法2：判断16进制颜色是否为浅色（基于亮度公式，亮度>0.5为浅色）
    isLightColor(hex) {
      const cleanHex = hex.replace('#', '');
      const fullHex = cleanHex.length === 3
        ? cleanHex.split('').map(c => c + c).join('')
        : cleanHex;
      const r = parseInt(fullHex.substring(0, 2), 16) / 255;
      const g = parseInt(fullHex.substring(2, 4), 16) / 255;
      const b = parseInt(fullHex.substring(4, 6), 16) / 255;
      // 计算相对亮度（国际标准公式，判断颜色深浅最准确）
      const luminance = (0.299 * r + 0.587 * g + 0.114 * b);
      return luminance > 0.5;
    },

    // 动态计算标签文字字号
    getDynamicFontSize() {
      const { textFontSize } = this.labelDrawConfig
      // 文字字号也做反比例适配，保证缩放后文字大小视觉一致
      const dynamicSize = textFontSize / this.zoomScale
      return Math.max(10, Math.min(24, dynamicSize)) // 限制字号8-20px
    },

    calcFitScale() {
      const container = this.$refs.labelContainer
      const image = this.$refs.labelImage
      if (!container || !image) return

      // 容器可视尺寸
      const containerW = container.clientWidth
      const containerH = container.clientHeight

      // 图片原始尺寸
      const imgW = image.naturalWidth
      const imgH = image.naturalHeight

      // 计算适配缩放比例（让图片完整显示，不超出容器）
      const scaleW = containerW / imgW
      const scaleH = containerH / imgH
      this.zoomScale = Math.min(scaleW, scaleH)
      // 限制最小/最大缩放（可选）
      this.zoomScale = Math.max(0.1, Math.min(2.0, this.zoomScale))
    },

    // 图片加载失败提示
    onImageError() {
      this.$message.error(`图片加载失败：${this.currentImage.filePath}`)
      this.imageLoaded = false
    },

    // 初始化标注工具（适配非构造函数方式）
    initAILabel() {
      const container = this.$refs.labelContainer
      const image = this.$refs.labelImage
      if (!container || !image || !this.imageLoaded || !this.currentImage.filePath) return

      this.destroyAILabel()

      try {
        let instance = null
        if (AILabelLib.create) {
          // 原有第三方工具逻辑不变
          instance = AILabelLib.create({
            el: container,
            image: image,
            config: {
              enableRect: true,
              enablePolygon: true,
              enableCircle: true,
              enableDrag: true,
              enableScale: true,
              style: {
                rect: { strokeStyle: '#00ff00', lineWidth: 2, fillStyle: 'rgba(0,255,0,0.1)' },
                polygon: { strokeStyle: '#0099ff', lineWidth: 2, fillStyle: 'rgba(0,153,255,0.1)' },
                circle: { strokeStyle: '#ff9900', lineWidth: 2, fillStyle: 'rgba(255,153,0,0.1)' }
              },
              fit: 'cover'
            }
          })
        } else if (AILabelLib.init) {
          instance = AILabelLib.init(container, image, { enableRect: true, enablePolygon: true, enableCircle: true })
        } else if (window.AILabel) {
          instance = window.AILabel({ el: container, image: image })
        } else {
          // 关键修改：创建自研工具时传入组件this
          instance = this.createSimpleLabelTool(container, image, this)
        }

        // 原有事件绑定、缩放设置等逻辑完全保留
        if (instance) {
          this.ailabelInstance = instance
          this.ailabelInstance.recordState = this.recordState.bind(this)
          if (instance.on) {
            instance.on('rectCreated', (rect) => {
              this.handleRectCreated(rect)
            })
            instance.on('polygonCreated', (polygon) => {
              this.handlePolygonCreated(polygon)
            })
            instance.on('circleCreated', (circle) => {
              this.handleCircleCreated(circle)
            })
            instance.on('select', (shape) => this.handleShapeSelected(shape))
            instance.on('delete', (shape) => {
              this.handleShapeDeleted(shape)
            })
          }
          if (instance.setScale) instance.setScale(this.zoomScale)
          this.loadLabelData()
        }
      } catch (e) {
        console.error('标注工具初始化失败：', e)
        this.$message.error(`初始化失败：${e.message}`)
      }
    },

    // 销毁标注实例
    destroyAILabel() {
      if (this.ailabelInstance) {
        try {
          this.ailabelInstance.destroy?.() || this.ailabelInstance.clear?.()
        } catch (e) {}
        this.ailabelInstance = null
        this.shapeLabelMap = {}
        this.labelShapeMap = {}
        this.polygonPoints = []
        this.tempDrawData = {
          isDrawing: false,
          startX: 0,
          startY: 0,
          tempShape: null
        }
        this.tempShapeId = ''
      }
    },

    // 自研简易标注工具（修复多边形多点绘制、图片填充、全屏）
    createSimpleLabelTool(container, image, instance) {
      const tool = {
        el: container,
        image,
        shapes: [],
        selectedShape: null,
        drawType: '', // 绘制类型：rect/polygon/circle
        tempDrawData: {
          isDrawing: false,
          startX: 0,
          startY: 0,
          tempShape: null
        },
        polygonPoints: [],
        mainCanvas: null,
        tempCanvas: null,
        mainCtx: null,
        tempCtx: null,
        zoomScale: 1.0,

        // 工具层 init 方法：新增挂载组件实例，用于后续同步polygonPoints
        init(instance) {
          // 挂载组件实例（关键：让工具层能访问组件层数据）
          this.instance = instance
          this.hexToRgba = instance.hexToRgba.bind(instance);
          this.isLightColor = instance.isLightColor.bind(instance);
          // 原有主画布/临时画布创建逻辑完全保留
          const mainCanvas = document.createElement('canvas')
          mainCanvas.style.cssText = 'position:absolute;top:0;left:0;pointer-events:none;z-index:10;width:100%;height:100%;'
          mainCanvas.width = container.offsetWidth
          mainCanvas.height = container.offsetHeight
          container.appendChild(mainCanvas)
          this.mainCanvas = mainCanvas
          this.mainCtx = mainCanvas.getContext('2d')

          const tempCanvas = document.createElement('canvas')
          tempCanvas.style.cssText = 'position:absolute;top:0;left:0;pointer-events:none;z-index:11;width:100%;height:100%;'
          tempCanvas.width = container.offsetWidth
          tempCanvas.height = container.offsetHeight
          container.appendChild(tempCanvas)
          this.tempCanvas = tempCanvas
          this.tempCtx = tempCanvas.getContext('2d')

          // 原有事件绑定、resize监听逻辑完全保留
          container.addEventListener('mousedown', (e) => this.onMouseDown(e))
          container.addEventListener('mousemove', (e) => this.onMouseMove(e))
          container.addEventListener('mouseup', (e) => this.onMouseUp(e))
          container.addEventListener('dblclick', (e) => this.onDoubleClick(e))
          container.addEventListener('contextmenu', (e) => {
            e.preventDefault()
            this.cancelDrawing()
          })
          window.addEventListener('resize', () => this.resizeCanvas())

          this.redrawMainCanvas()
          return this
        },

        // 调整画布大小（适配全屏/窗口变化）
        resizeCanvas() {
          this.mainCanvas.width = this.el.offsetWidth
          this.mainCanvas.height = this.el.offsetHeight
          this.tempCanvas.width = this.el.offsetWidth
          this.tempCanvas.height = this.el.offsetHeight
          this.redrawMainCanvas()
        },


        onMouseDown(e) {
          e.preventDefault()

          // 非多边形绘制：正常开始绘制
          if (this.drawType !== 'polygon') {
            // 原有逻辑不变，保留
            this.tempDrawData.isDrawing = true
            const rect = this.el.getBoundingClientRect()
            let imgX = 0, imgY = 0
            if (this.image && this.image.naturalWidth && this.image.naturalHeight) {
              imgX = rect.left + (rect.width - this.image.naturalWidth * this.zoomScale) / 2
              imgY = rect.top + (rect.height - this.image.naturalHeight * this.zoomScale) / 2
            }
            this.tempDrawData.startX = (e.clientX - imgX) / this.zoomScale
            this.tempDrawData.startY = (e.clientY - imgY) / this.zoomScale
            if (this.drawType === 'circle') {
              this.tempDrawData.tempShape = {
                x: this.tempDrawData.startX,
                y: this.tempDrawData.startY,
                radius: 0
              }
            }
          } // 自研工具 onMouseDown 中的多边形标点分支（替换原有代码）
          else {
            // 多边形标点：工具层统一加点，然后同步到组件层
            const rect = this.el.getBoundingClientRect()
            let imgX = 0, imgY = 0
            if (this.image && this.image.naturalWidth && this.image.naturalHeight) {
              imgX = rect.left + (rect.width - this.image.naturalWidth * this.zoomScale) / 2
              imgY = rect.top + (rect.height - this.image.naturalHeight * this.zoomScale) / 2
            }
            const x = (e.clientX - imgX) / this.zoomScale
            const y = (e.clientY - imgY) / this.zoomScale

            // ========== 新增：单点边界检测 ==========
            // 构造单点形状，复用现有边界检测方法
            const singlePoint = { x, y };
            const isPointOut = singlePoint.x < 0 || singlePoint.x > this.image.naturalWidth ||
              singlePoint.y < 0 || singlePoint.y > this.image.naturalHeight;
            if (isPointOut) {
              this.$message.error('当前点超出图片范围，此次标注无效，已清空所有点位！');
              // 1. 清空工具层多边形点
              this.polygonPoints = [];
              // 2. 同步清空组件层多边形点（双向绑定）
              this.instance?.$set(this.instance, 'polygonPoints', []);
              // 3. 清空临时画布的绘制痕迹
              this.tempCtx.clearRect(0, 0, this.tempCanvas.width, this.tempCanvas.height);
              return; // 终止后续加点逻辑
            }
            // =======================================

            // 工具层加点（单源）
            this.polygonPoints.push({ x, y })
            // 同步到组件层：解决点数显示问题
            this.instance?.$set(this.instance, 'polygonPoints', [...this.polygonPoints])
            this.drawTempShape()
          }
        },

        // 鼠标移动：仅在非多边形绘制时响应
        onMouseMove(e) {
          e.preventDefault()

          if (!this.tempDrawData.isDrawing || this.drawType === 'polygon') return

          const rect = this.el.getBoundingClientRect()
          // 修复：直接计算相对容器的坐标，再转换为相对图片的坐标
          let imgX = 0, imgY = 0
          if (this.image && this.image.naturalWidth && this.image.naturalHeight) {
            imgX = rect.left + (rect.width - this.image.naturalWidth * this.zoomScale) / 2
            imgY = rect.top + (rect.height - this.image.naturalHeight * this.zoomScale) / 2
          }
          const x = (e.clientX - imgX) / this.zoomScale
          const y = (e.clientY - imgY) / this.zoomScale

          // 清空临时画布
          this.tempCtx.clearRect(0, 0, this.tempCanvas.width, this.tempCanvas.height)

          // 根据类型绘制临时形状
          this.tempDrawData.tempShape = this.getTempShape(x, y)
          this.drawTempShape()
        },

        // 鼠标松开：完成非多边形绘制
        onMouseUp(e) {
          e.preventDefault()
          if (!this.tempDrawData.isDrawing || this.drawType === 'polygon') return

          const rect = this.el.getBoundingClientRect()
          let imgX = 0, imgY = 0
          if (this.image && this.image.naturalWidth && this.image.naturalHeight) {
            imgX = rect.left + (rect.width - this.image.naturalWidth * this.zoomScale) / 2
            imgY = rect.top + (rect.height - this.image.naturalHeight * this.zoomScale) / 2
          }
          const x = (e.clientX - imgX) / this.zoomScale
          const y = (e.clientY - imgY) / this.zoomScale

          // 生成最终形状
          const finalShape = this.getTempShape(x, y)
          if (!this.isValidShape(finalShape) || this.isShapeOutOfImage(finalShape)) {
            this.cancelDrawing(); // 清空临时绘制的形状/痕迹
            this.$message.error('标注超出图片边界，此次标注无效！');
            return; // 终止流程
          }
          this.recordState && this.recordState()

          finalShape.id = 'shape_' + Date.now()
          this.shapes.push(finalShape)
          // 触发创建事件
          if (finalShape.type === 'rect') {
            this.onRectCreated?.(finalShape)
          } else if (finalShape.type === 'circle') {
            this.onCircleCreated?.(finalShape)
          }
          // 重绘并重置状态
          this.redrawMainCanvas()
          this.cancelDrawing()
        },

        // 双击：完成多边形绘制
        onDoubleClick(e) {
          e.preventDefault()
          if (this.drawType !== 'polygon' || this.polygonPoints.length < 3) {
            // 少于3个点时清空，不记录状态
            if (this.polygonPoints.length > 0) {
              this.polygonPoints = []
              this.tempCtx.clearRect(0, 0, this.tempCanvas.width, this.tempCanvas.height)
              this.$message.error('多边形至少需要3个点')
            }
            return
          }
          // 生成多边形形状
          const shape = {
            id: 'shape_' + Date.now(),
            type: 'polygon',
            points: [...this.polygonPoints]
          }
          if (!this.isValidShape(shape) || this.isShapeOutOfImage(shape)) {
            this.polygonPoints = [];
            this.tempShapeId = '';
            this.$message.error('标注超出图片边界，此次标注无效！'); // 统一文案
            return
          }
          this.recordState && this.recordState()

          this.shapes.push(shape)
          this.onPolygonCreated?.(shape)
          this.redrawMainCanvas()
          // 重置状态
          this.polygonPoints = []
          this.tempCtx.clearRect(0, 0, this.tempCanvas.width, this.tempCanvas.height)
        },

        // 更新多边形点（对外暴露）
        updatePolygonPoints(points) {
          this.polygonPoints = points
          this.drawTempShape()
        },

        // 获取临时形状数据
        getTempShape(x, y) {
          switch (this.drawType) {
            case 'rect':
              return {
                type: 'rect',
                x: Math.min(this.tempDrawData.startX, x),
                y: Math.min(this.tempDrawData.startY, y),
                width: Math.abs(x - this.tempDrawData.startX),
                height: Math.abs(y - this.tempDrawData.startY)
              }
            case 'circle':
              const dx = x - this.tempDrawData.startX
              const dy = y - this.tempDrawData.startY
              const radius = Math.sqrt(dx * dx + dy * dy)
              return {
                type: 'circle',
                x: this.tempDrawData.startX,
                y: this.tempDrawData.startY,
                radius
              }
            case 'polygon':
              return {
                type: 'polygon',
                points: [...this.polygonPoints, { x, y }]
              }
            default:
              return null
          }
        },

        // 验证形状有效性
        isValidShape(shape) {
          if (!shape) return false
          switch (shape.type) {
            case 'rect':
              return shape.width > 5 && shape.height > 5 // 最小尺寸
            case 'circle':
              return shape.radius > 5 // 最小半径
            case 'polygon':
              return shape.points.length >= 3
            default:
              return true
          }
        },

        isShapeOutOfImage(shape) {
          if (!this.image) return true; // 图片未加载则默认越界
          // 获取图片原始尺寸
          const imgWidth = this.image.naturalWidth;
          const imgHeight = this.image.naturalHeight;
          // 不同形状的边界检测逻辑
          switch (shape.type) {
            case 'rect':
              return shape.x < 0 || shape.y < 0 || shape.x + shape.width > imgWidth || shape.y + shape.height > imgHeight;
            case 'circle':
              return shape.x - shape.radius < 0 || shape.x + shape.radius > imgWidth || shape.y - shape.radius < 0 || shape.y + shape.radius > imgHeight;
            case 'polygon':
              return shape.points.some(point => point.x < 0 || point.x > imgWidth || point.y < 0 || point.y > imgHeight);
            default:
              return false;
          }
        },

        // 绘制临时形状
        // 自研工具中的临时绘制方法：drawTempShape（修改线条粗细）
        drawTempShape() {
          this.tempCtx.clearRect(0, 0, this.tempCanvas.width, this.tempCanvas.height)
          this.tempCtx.save()

          // 原有平移+缩放逻辑（不变）
          this.tempCtx.translate(
            this.el.offsetWidth / 2,
            this.el.offsetHeight / 2
          )
          this.tempCtx.scale(this.zoomScale, this.zoomScale)
          if (this.image && this.image.naturalWidth && this.image.naturalHeight) {
            this.tempCtx.translate(
              -this.image.naturalWidth / 2,
              -this.image.naturalHeight / 2
            )
          }

          // ========== 新增：临时形状也使用动态线条粗细 ==========
          const dynamicLineWidth = this.instance.getDynamicLineWidth()
          this.tempCtx.lineWidth = dynamicLineWidth

          // 原有临时形状绘制逻辑（仅保留，无改动）
          if (this.drawType === 'rect' && this.tempDrawData.tempShape) {
            const s = this.tempDrawData.tempShape
            this.tempCtx.strokeStyle = '#00ff00'
            this.tempCtx.fillStyle = 'rgba(0,255,0,0.1)'
            this.tempCtx.strokeRect(s.x, s.y, s.width, s.height)
            this.tempCtx.fillRect(s.x, s.y, s.width, s.height)
          } else if (this.drawType === 'circle' && this.tempDrawData.tempShape) {
            const s = this.tempDrawData.tempShape
            this.tempCtx.strokeStyle = '#ff9900'
            this.tempCtx.fillStyle = 'rgba(255,153,0,0.1)'
            this.tempCtx.beginPath()
            this.tempCtx.arc(s.x, s.y, s.radius, 0, Math.PI * 2)
            this.tempCtx.closePath()
            this.tempCtx.fill()
            this.tempCtx.stroke()
          } else if (this.drawType === 'polygon' && this.polygonPoints.length > 0) {
            this.tempCtx.strokeStyle = '#0099ff'
            this.tempCtx.fillStyle = 'rgba(0,153,255,0.1)'
            this.tempCtx.beginPath()
            this.polygonPoints.forEach((point, idx) => {
              if (idx === 0) {
                this.tempCtx.moveTo(point.x, point.y)
              } else {
                this.tempCtx.lineTo(point.x, point.y)
              }
              this.tempCtx.fillRect(point.x - 3, point.y - 3, 6, 6)
            })
            this.tempCtx.stroke()
          }

          this.tempCtx.restore()
        },

        // 取消绘制
        cancelDrawing() {
          this.tempDrawData = {
            isDrawing: false,
            startX: 0,
            startY: 0,
            tempShape: null
          }
          // 彻底清空工具层多边形点
          this.polygonPoints = []
          // 同步清空组件层多边形点（解决右键取消后残留问题）
          this.instance?.$set(this.instance, 'polygonPoints', [])
          this.tempCtx.clearRect(0, 0, this.tempCanvas.width, this.tempCanvas.height)
        },

        // 设置绘制类型
        setDrawType(type) {
          this.cancelDrawing()
          this.drawType = type
        },

        // 设置缩放（适配居中缩放）
        setScale(scale) {
          this.zoomScale = scale
          this.resizeCanvas()
        },

        // 绘制形状方
        drawRect(x, y, w, h, id) {
          const shape = {
            id: id || 'shape_' + Date.now(), // 有传id则复用，无则生成新id
            type: 'rect',
            x, y,
            width: w,
            height: h
          };
          this.shapes.push(shape);
          this.redrawMainCanvas();
          return shape.id;
        },

        drawCircle(x, y, radius, id) {
          const shape = {
            id: id || 'shape_' + Date.now(),
            type: 'circle',
            x, y,
            radius
          };
          this.shapes.push(shape);
          this.redrawMainCanvas();
          return shape.id;
        },

        // 绘制多边形
        drawPolygon(points, id) {
          // 生成新形状
          const shape = {
            id: id || 'shape_' + Date.now(),
            type: 'polygon',
            points
          };
          if (!this.isValidShape(shape) || this.isShapeOutOfImage(shape)) {
            this.polygonPoints = []
            this.$message.error('重绘多边形超出图片边界，此次标注无效')
            return null // 返回null，让组件层识别重绘失败
          }

          // this.recordState && this.recordState()
          this.shapes.push(shape);
          this.redrawMainCanvas();
          return shape.id;
        },

        // 删除形状
        deleteShape(id) {
          this.shapes = this.shapes.filter(s => s.id !== id)
          this.redrawMainCanvas()
          this.onDelete?.(id)
        },

        // 选中形状
        selectShape(id) {
          this.selectedShape = this.shapes.find(s => s.id === id)
          this.redrawMainCanvas()
          this.onSelect?.(this.selectedShape)
        },

        // 清空形状
        clearShapes() {
          this.shapes = []
          this.mainCtx.clearRect(0, 0, this.mainCanvas.width, this.mainCanvas.height)
          this.tempCtx.clearRect(0, 0, this.tempCanvas.width, this.tempCanvas.height)
        },

        // 获取所有形状
        getShapes() {
          return [...this.shapes]
        },

        redrawMainCanvas() {
          this.mainCtx.clearRect(0, 0, this.mainCanvas.width, this.mainCanvas.height)
          this.mainCtx.save()

          // 1. 画布平移+缩放（原有逻辑不变）
          this.mainCtx.translate(
            this.el.offsetWidth / 2,
            this.el.offsetHeight / 2
          )
          this.mainCtx.scale(this.zoomScale, this.zoomScale)
          if (this.image && this.image.naturalWidth && this.image.naturalHeight) {
            this.mainCtx.translate(
              -this.image.naturalWidth / 2,
              -this.image.naturalHeight / 2
            )
            // 绘制图片（原有逻辑不变）
            this.mainCtx.drawImage(
              this.image,
              0, 0,
              this.image.naturalWidth,
              this.image.naturalHeight
            )
          }

          const dynamicLineWidth = this.instance.getDynamicLineWidth() // 组件层的动态计算方法
          const dynamicFontSize = this.instance.getDynamicFontSize()   // 组件层的动态字号方法
          const { textPadding } = this.instance.labelDrawConfig // 只解构需要的，避免冗余

          // 2. 绘制所有形状
          this.shapes.forEach(s => {
            // ========== 核心修复：只声明一次，包含所有需要的配置属性，解决重复声明报错 ==========
            const { shapeMainColor, selectedColor, shapeFillOpacity, bgOpacity } = this.instance.labelDrawConfig;
            const isSelected = this.selectedShape?.id === s.id
            // 选中统一用selectedColor，未选中用对应形状的主色
            const baseColor = isSelected ? selectedColor : shapeMainColor[s.type] || '#000'
            // 生成填充色（主色 + 配置的形状填充透明度）
            const fillColor = this.hexToRgba(baseColor, shapeFillOpacity)
            // 描边色直接用纯色主色（选中红色，未选中形状主色）
            const strokeColor = baseColor

            // ========== 核心修改2：动态设置线条宽度（原有逻辑保留） ==========
            this.mainCtx.lineWidth = dynamicLineWidth
            this.mainCtx.strokeStyle = strokeColor // 统一描边色
            this.mainCtx.fillStyle = fillColor     // 统一填充色（带透明度）

            // ========== 各形状绘制逻辑（仅保留路径，删除原有写死的style） ==========
            if (s.type === 'rect') {
              this.mainCtx.strokeRect(s.x, s.y, s.width, s.height)
              this.mainCtx.fillRect(s.x, s.y, s.width, s.height)
            } else if (s.type === 'circle') {
              this.mainCtx.beginPath()
              this.mainCtx.arc(s.x, s.y, s.radius, 0, Math.PI * 2)
              this.mainCtx.closePath()
              this.mainCtx.fill()
              this.mainCtx.stroke()
            } else if (s.type === 'polygon') {
              this.mainCtx.beginPath()
              s.points.forEach((point, idx) => {
                idx === 0 ? this.mainCtx.moveTo(point.x, point.y) : this.mainCtx.lineTo(point.x, point.y)
              })
              this.mainCtx.closePath() // 多边形必须闭合才能正常填充
              this.mainCtx.fill()
              this.mainCtx.stroke()
            }

            // 1. 获取当前标注绑定的标签ID和标签名
            const labelId = this.instance.shapeLabelMap[s.id] || ''
            const label = labelId ? this.instance.findLabelById(labelId) : null
            if (!label) return // 未绑定标签，不绘制文字

            // 计算不同形状的正中心坐标（原有居中逻辑，不变）
            let textX, textY
            if (s.type === 'rect') {
              textX = s.x + s.width / 2
              textY = s.y + s.height / 2
            } else if (s.type === 'circle') {
              textX = s.x
              textY = s.y
            } else if (s.type === 'polygon') {
              const totalX = s.points.reduce((sum, p) => sum + p.x, 0)
              const totalY = s.points.reduce((sum, p) => sum + p.y, 0)
              textX = totalX / s.points.length
              textY = totalY / s.points.length
            }

            // 2. 设置文字基础样式
            this.mainCtx.font = `${dynamicFontSize}px Microsoft YaHei`
            this.mainCtx.textAlign = 'center'    // 水平居中
            this.mainCtx.textBaseline = 'middle' // 垂直居中

            // ========== 移除重复的解构代码，直接复用顶部已声明的变量 ==========
            // 第一步：根据【是否选中】+【形状类型】获取背景主色
            const bgBaseColor = isSelected ? selectedColor : shapeMainColor[s.type] || '#000000';
            // 第二步：将16进制纯色转为【rgba带透明度】的背景色
            const rgbaBgColor = this.hexToRgba(bgBaseColor, bgOpacity);
            // 第三步：智能判断背景色深浅，自动切换文字颜色
            const textColor = this.isLightColor(bgBaseColor) ? '#000000' : '#ffffff';

            // 3. 绘制文字背景（适配中心定位）
            const textWidth = this.mainCtx.measureText(label.name).width
            const textHeight = dynamicFontSize
            this.mainCtx.fillStyle = rgbaBgColor; // 用动态生成的形状主色背景
            this.mainCtx.fillRect(
              textX - (textWidth + 2 * textPadding) / 2,
              textY - (textHeight + 2 * textPadding) / 2,
              textWidth + 2 * textPadding,
              textHeight + 2 * textPadding
            )

            // 4. 绘制标签文字（用智能判断后的文字颜色）
            this.mainCtx.fillStyle = textColor; // 自动切换的文字色（黑/白）
            this.mainCtx.fillText(label.name, textX, textY);
          })

          this.mainCtx.restore()
        },

        // 事件绑定
        on(event, cb) {
          if (event === 'rectCreated') this.onRectCreated = cb
          if (event === 'polygonCreated') this.onPolygonCreated = cb
          if (event === 'circleCreated') this.onCircleCreated = cb
          if (event === 'select') this.onSelect = cb
          if (event === 'delete') this.onDelete = cb
        },

        // 形状数据操作
        setShapeData(id, data) {
          const shape = this.shapes.find(s => s.id === id)
          if (shape) Object.assign(shape, data)
        },

        getShapeData(id) {
          return this.shapes.find(s => s.id === id) || {}
        },

        // 销毁
        destroy() {
          this.mainCanvas?.remove()
          this.tempCanvas?.remove()
          this.shapes = []
          window.removeEventListener('resize', this.resizeCanvas)
        }
      }
      return tool.init(instance)
    },

    // 滚轮缩放（适配居中缩放）
    handleWheelZoom(e) {
      e.preventDefault()
      const delta = e.deltaY > 0 ? -0.1 : 0.1
      this.zoomScale = Math.max(0.1, Math.min(2.0, this.zoomScale + delta))

      if (this.ailabelInstance?.setScale) {
        this.ailabelInstance.setScale(this.zoomScale)
      }
      this.ailabelInstance?.redrawMainCanvas()
    },

    // 设置绘制类型（对外暴露）
    setDrawType(type) {
      // 移除必须先选标签的限制，支持先标注后选标签
      if (!this.ailabelInstance) {
        this.$message.error('标注工具未就绪！')
        this.initAILabel()
        return
      }
      // 重置绘制状态
      this.drawType = type
      this.polygonPoints = []

      // 通知标注工具设置绘制类型
      if (this.ailabelInstance.setDrawType) {
        this.ailabelInstance.setDrawType(type)
      }

      // 提示信息
      const tips = {
        rect: '已切换为矩形标注，请在图片上按住鼠标拖动绘制',
        polygon: '已切换为多边形标注，点击添加点（至少3个），双击完成，右键取消',
        circle: '已切换为圆形标注，按住鼠标拖动绘制（起点为圆心，拖动为半径）'
      }
      this.$message.info(tips[type])
    },

    // 矩形创建回调
    handleRectCreated(rect) {
      if (this.isShapeOutOfImage(rect)) {
        // 1. 删除越界的标注
        this.ailabelInstance.deleteShape(rect.id);
        // 2. 重置临时状态
        this.polygonPoints = [];
        this.tempShapeId = '';
        // 3. 提示用户
        this.$message.error('标注范围超出图片边界，此次标注无效，请重新标注！');
        return; // 终止后续逻辑
      }


      this.tempShapeId = rect.id
      // 情况1：已选择标签（先选标签后标注）
      if (this.selectedLabelId) {
        this.bindShapeToLabel(rect.id, this.selectedLabelId)
        this.drawType = ''
        this.tempShapeId = ''
        this.$message.success('矩形标注创建成功！')
      }
      // 情况2：未选择标签（先标注后选标签）
      else {
        this.selectLabelForShapeDialog = true
      }
    },

    handlePolygonCreated(polygon) {

      const convexPoints = this.getConvexHull(polygon.points);
      // 过滤重复点
      const uniquePoints = convexPoints.filter((p, index) =>
        index === 0 || !(p.x === convexPoints[index-1].x && p.y === convexPoints[index-1].y)
      );
      // 确保转换后仍满足多边形最小点数
      if (uniquePoints.length < 3) {
        this.polygonPoints = [];
        this.tempShapeId = '';
        this.$message.error('多边形点集转换后无效，至少需要3个不重复的点！');
        return;
      }


      // 构造临时凸包多边形，用于边界检测
      const convexPolygon = { ...polygon, points: uniquePoints };
      if (this.isShapeOutOfImage(convexPolygon)) {
        // 1. 删除原交叉的多边形标注
        this.ailabelInstance.deleteShape(polygon.id);
        // 2. 重置临时状态
        this.polygonPoints = [];
        this.tempShapeId = '';
        // 3. 提示用户
        this.$message.error('标注范围超出图片边界，此次标注无效，请重新标注！');
        return; // 终止后续逻辑
      }

      // 删除原交叉多边形，用无交叉凸包点集重新绘制
      this.ailabelInstance.deleteShape(polygon.id); // 删除原交叉的多边形
      // 重新绘制无交叉的凸多边形，获取新的形状ID
      const newShapeId = this.ailabelInstance.drawPolygon?.(uniquePoints);
      if (!newShapeId) {
        this.polygonPoints = [];
        this.tempShapeId = '';
        this.$message.error('无交叉多边形绘制失败，请重试！');
        return;
      }
      console.log('转换后无交叉凸包点集：', uniquePoints)

      this.tempShapeId = newShapeId; // 临时ID替换为新的无交叉多边形ID
      // 情况1：已选择标签（先选标签后标注）
      if (this.selectedLabelId) {
        this.bindShapeToLabel(newShapeId, this.selectedLabelId); // 绑定标签到新多边形
        this.drawType = '';
        this.polygonPoints = [];
        this.tempShapeId = '';
        this.$message.success('多边形标注创建成功（已自动处理交叉边）！');
      }
      // 情况2：未选择标签（先标注后选标签）
      else {
        this.selectLabelForShapeDialog = true; // 弹出选标签弹窗
      }
    },

    // 圆形创建回调
    handleCircleCreated(circle) {
      if (this.isShapeOutOfImage(circle)) {
        // 1. 删除越界的标注
        this.ailabelInstance.deleteShape(circle.id);
        // 2. 重置临时状态
        this.polygonPoints = [];
        this.tempShapeId = '';
        // 3. 提示用户
        this.$message.error('标注范围超出图片边界，此次标注无效，请重新标注！');
        return; // 终止后续逻辑
      }

      this.tempShapeId = circle.id
      // 情况1：已选择标签（先选标签后标注）
      if (this.selectedLabelId) {
        this.bindShapeToLabel(circle.id, this.selectedLabelId)
        this.drawType = ''
        this.tempShapeId = ''
        this.$message.success('圆形标注创建成功！')
      }
      // 情况2：未选择标签（先标注后选标签）
      else {
        this.selectLabelForShapeDialog = true
      }
    },

    // 核心方法：绑定标注到标签（支持一个标签对应多个标注）
    bindShapeToLabel(shapeId, labelId) {
      // 1. 更新shape->label映射
      this.shapeLabelMap[shapeId] = labelId;

      // 2. 更新label->shapes映射
      if (!this.labelShapeMap[labelId]) {
        this.labelShapeMap[labelId] = [];
      }
      if (!this.labelShapeMap[labelId].includes(shapeId)) {
        this.labelShapeMap[labelId].push(shapeId);
      }

      // 3. 同步更新labelGroups中的shapeIds（修复标签计数）
      const label = this.findLabelById(labelId);
      if (label) {
        if (!label.shapeIds) label.shapeIds = [];
        if (!label.shapeIds.includes(shapeId)) {
          label.shapeIds.push(shapeId);
        }
      }

      // 4. 通知标注工具绑定数据
      this.ailabelInstance?.setShapeData?.(shapeId, { labelId });
    },

    // 为临时标注确认标签
    confirmShapeLabel(label) {
      if (this.tempShapeId) {
        this.bindShapeToLabel(this.tempShapeId, label.id)
        this.selectedLabelId = label.id
        this.polygonPoints = []
        this.tempShapeId = ''
        this.selectLabelForShapeDialog = false
        this.$message.success(`标注已绑定到标签【${label.name}】`)
      }
    },

    // 选中形状回调
    handleShapeSelected(shape) {
      const labelId = this.ailabelInstance?.getShapeData?.(shape.id)?.labelId || this.shapeLabelMap[shape.id]
      if (labelId) {
        this.selectedLabelId = labelId
      }
    },

    // 核心修改：删除形状回调（同步更新标签-标注映射）
    handleShapeDeleted(shape) {
      const labelId = this.ailabelInstance?.getShapeData?.(shape.id)?.labelId || this.shapeLabelMap[shape.id];
      if (labelId) {
        // 1. 删除shape->label映射
        delete this.shapeLabelMap[shape.id];

        // 2. 删除label->shapes映射中的该标注
        if (this.labelShapeMap[labelId]) {
          this.labelShapeMap[labelId] = this.labelShapeMap[labelId].filter(id => id !== shape.id);

          // 同步更新labelGroups中的shapeIds
          const label = this.findLabelById(labelId);
          if (label && label.shapeIds) {
            label.shapeIds = label.shapeIds.filter(id => id !== shape.id);
          }

          // 如果标签下没有标注了，删除该标签
          if (this.labelShapeMap[labelId].length === 0) {
            this.removeLabelById(labelId);
            delete this.labelShapeMap[labelId];
          }
        }
      }
    },

    // 废弃原drawRect方法，改用setDrawType
    drawRect() {
      this.setDrawType('rect')
    },

    // 核心修改：选择标签（高亮该标签下的所有标注）
    selectLabel(label) {
      this.selectedLabelId = label.id
      // 高亮该标签下的所有标注
      if (this.labelShapeMap[label.id] && this.labelShapeMap[label.id].length > 0 && this.ailabelInstance) {
        this.labelShapeMap[label.id].forEach(shapeId => {
          this.ailabelInstance.selectShape?.(shapeId) || this.ailabelInstance.highlightShape?.(shapeId)
        })
      }
    },

    // 根据标签ID找形状ID列表
    findShapeIdsByLabelId(labelId) {
      return this.labelShapeMap[labelId] || []
    },

    // 根据ID找标签
    findLabelById(labelId) {
      for (const group of this.labelGroups) {
        for (const label of group.labels) {
          if (label.id === labelId) return label
        }
      }
      return null
    },

    // 根据ID删除标签（同步删除关联的所有标注）
    removeLabelById(labelId) {
      // 1. 删除该标签下的所有标注
      if (this.labelShapeMap[labelId]) {
        this.labelShapeMap[labelId].forEach(shapeId => {
          this.ailabelInstance.deleteShape?.(shapeId) || this.ailabelInstance.removeShape?.(shapeId)
          delete this.shapeLabelMap[shapeId]
        })
        delete this.labelShapeMap[labelId]
      }

      // 2. 从标签组中删除标签
      this.labelGroups.forEach(group => {
        const idx = group.labels.findIndex(l => l.id === labelId)
        idx > -1 && group.labels.splice(idx, 1)
      })

      // 3. 清空选中状态
      if (this.selectedLabelId === labelId) {
        this.selectedLabelId = ''
      }
    },


    // 确认添加/编辑标签
    confirmAddLabel() {
      // 表单校验
      this.$refs.labelFormRef.validate((valid) => {
        if (!valid) return

        if (this.editLabelId) {
          // 编辑标签
          const label = this.findLabelById(this.editLabelId)
          if (label) {
            label.name = this.newLabelForm.name
            this.$message.success('编辑成功！')
          }
        } else {
          // 添加新标签
          this.recordState() // 记录添加前状态
          const group = this.labelGroups.find(g => g.id === this.newLabelForm.groupId)
          if (group) {
            const newLabelId = `label_${Date.now()}`
            group.labels.push({
              id: newLabelId,
              name: this.newLabelForm.name,
              type: 'rect', // 默认矩形
              rect: { x: 0, y: 0, width: 0, height: 0 },
              points: [],
              circle: { x: 0, y: 0, radius: 0 },
              shapeIds: [] // 标注ID列表
            })
            this.$message.success('添加成功！')

            // 如果有临时标注，自动绑定
            if (this.tempShapeId) {
              this.bindShapeToLabel(this.tempShapeId, newLabelId)
              this.selectedLabelId = newLabelId
              this.tempShapeId = ''
              this.selectLabelForShapeDialog = false
            }
          } else {
            this.$message.warning('请先选择标签分组！')
          }
        }
        this.addLabelDialogVisible = false
        this.editLabelId = ''
      })
    },

    // 删除选中标注
    deleteSelected() {
      if (!this.selectedLabelId) {
        this.$message.warning('请选择标签！')
        return
      }

      this.recordState() // 记录删除前状态
      // 删除该标签下的所有标注
      this.removeLabelById(this.selectedLabelId)
      this.drawType = ''
    },

    // 上一张图片
    prevImage() {
      if (this.currentImageIndex > 0) {
        this.switchImage(this.currentImageIndex - 1)
      } else {
        this.$message.info('已是第一张！')
      }
    },

    // 下一张图片
    nextImage() {
      if (this.currentImageIndex < this.imageList.length - 1) {
        this.switchImage(this.currentImageIndex + 1)
      } else {
        this.$message.info('已是最后一张！')
      }
    },

    // 切换图片
    async switchImage(index) {
      // 检查是否有未保存的标注
      const currentShapes = this.ailabelInstance?.getShapes?.() || [];
      if (currentShapes.length > 0 || this.isInvalidData) {
        try {
          // 先保存当前标注（无提示，静默保存）
          await this.saveLabel();
        } catch (err) {
          // 保存失败则提示用户
          const confirm = await this.$confirm('当前图片标注未保存，是否放弃修改并切换？', '提示', {
            type: 'warning'
          });
          if (!confirm) return;
        }
      }

      // 切换到新图片
      this.currentImageIndex = index;
      this.currentImage = this.imageList[index];
      this.imageLoaded = false;

      this.resetLabelState();

      // 重置撤销/恢复栈（原有逻辑保留）
      this.undoStack = [];
      this.redoStack = [];

      // 重新初始化标注工具（原有逻辑保留）
      this.$nextTick(() => {
        this.initAILabel();
      });
    },

    // 保存当前图片的标注数据
    saveCurrentLabelData() {
      if (!this.currentImage) return

      // 收集标注数据（包含标签映射）
      const shapes = this.ailabelInstance?.getShapes?.().map(shape => ({
        id: shape.id,
        type: shape.type,
        x: shape.x,
        y: shape.y,
        width: shape.width,
        height: shape.height,
        radius: shape.radius,
        points: shape.points,
        labelId: this.shapeLabelMap[shape.id] || '' // 确保labelId存在（空字符串而非undefined）
      })) || []

      // 保存到当前图片对象（完整存储：标注+标签映射+标签组+无效标记）
      this.imageList[this.currentImageIndex] = {
        ...this.currentImage,
        labels: JSON.parse(JSON.stringify(this.labelGroups)), // 深拷贝避免引用问题
        shapes: shapes,
        shapeLabelMap: JSON.parse(JSON.stringify(this.shapeLabelMap)), // 存储标签-标注映射
        labelShapeMap: JSON.parse(JSON.stringify(this.labelShapeMap)), // 存储标注-标签映射
        isInvalid: this.isInvalidData
      }
    },
    // 加载标注数据
    loadLabelData() {
      if (!this.ailabelInstance || !this.currentImage) return

      // 清空现有状态
      this.ailabelInstance.clearShapes?.() || this.ailabelInstance.reset?.()
      this.shapeLabelMap = {}
      this.labelShapeMap = {}

      // 恢复映射关系（关键修复）
      if (this.currentImage.shapeLabelMap) {
        this.shapeLabelMap = JSON.parse(JSON.stringify(this.currentImage.shapeLabelMap))
      }
      if (this.currentImage.labelShapeMap) {
        this.labelShapeMap = JSON.parse(JSON.stringify(this.currentImage.labelShapeMap))
      }

      // 重新绘制标注
      const shapes = this.currentImage.shapes || []
      shapes.forEach(shapeData => {
        try {
          let shapeId = ''
          if (shapeData.type === 'rect' && shapeData.x !== undefined) {
            shapeId = this.ailabelInstance.drawRect?.(shapeData.x, shapeData.y, shapeData.width, shapeData.height) || shapeData.id
          } else if (shapeData.type === 'polygon' && shapeData.points && shapeData.points.length >= 3) {
            shapeId = this.ailabelInstance.drawPolygon?.(shapeData.points) || shapeData.id
          } else if (shapeData.type === 'circle' && shapeData.radius !== undefined) {
            shapeId = this.ailabelInstance.drawCircle?.(shapeData.x, shapeData.y, shapeData.radius) || shapeData.id
          }

          // 恢复标签-标注映射
          if (shapeId && shapeData.labelId) {
            this.bindShapeToLabel(shapeId, shapeData.labelId)
            // 同步更新labelGroups中的shapeIds（修复计数）
            const label = this.findLabelById(shapeData.labelId)
            if (label && !label.shapeIds) label.shapeIds = []
            if (label && !label.shapeIds.includes(shapeId)) {
              label.shapeIds.push(shapeId)
            }
          }
        } catch (e) {
          console.warn('绘制标注失败：', e)
        }
      })
    },

    // 放大图片
    zoomIn() {
      this.zoomScale = Math.min(2.0, this.zoomScale + 0.1)
      if (this.ailabelInstance?.setScale) {
        this.ailabelInstance.setScale(this.zoomScale)
      }
      this.ailabelInstance?.redrawMainCanvas()

    },

    // 缩小图片
    zoomOut() {
      this.zoomScale = Math.max(0.1, this.zoomScale - 0.1)
      if (this.ailabelInstance?.setScale) {
        this.ailabelInstance.setScale(this.zoomScale)
      }

      this.ailabelInstance?.redrawMainCanvas()

    },

    // 重置缩放
    resetZoom() {
      this.zoomScale = 1.0
      if (this.ailabelInstance?.setScale) {
        this.ailabelInstance.setScale(this.zoomScale)
      }
      this.ailabelInstance?.redrawMainCanvas()

    },

    async saveLabel() {
      // 1. 前置参数校验（核心必填项）
      if (!this.ailabelInstance) {
        this.$message.error('标注工具未就绪！');
        return Promise.reject(new Error('标注工具未就绪'));
      }
      if (!this.labelingTaskId) {
        this.$message.error('标注任务ID不能为空！');
        return Promise.reject(new Error('标注任务ID缺失'));
      }

      // 2. 获取所有标注形状
      const shapes = this.ailabelInstance.getShapes?.() || [];

      if (shapes.length === 0) {
        if (!this.isInvalidData) {
          // 无标注且未标记为无效 → 提示用户确认
          const confirm = await this.$confirm('暂无标注数据，是否标记为无效数据后保存？', '提示', {
            type: 'warning'
          });
          if (confirm) {
            this.isInvalidData = true;
          } else {
            return Promise.resolve();
          }
        } else {
          // 无标注但标记为无效 → 正常保存
          this.$message.info('将标记为无效数据并保存');
        }
      }

      // 4. 构造标注数据列表（严格匹配后端DTO字段规范）
      const labelingDataList = [];
      // 提取当前图片的labelingSampleId
      console.log("currentImage",this.currentImage)
      const currentLabelingSampleId = this.currentImage.id;

      for (const shape of shapes) {
        const labelId = this.shapeLabelMap[shape.id] || '';

        // 校验：非无效数据的标注必须绑定标签
        if (!labelId && !this.isInvalidData) {
          this.$message.error(`标注形状未绑定标签，无法保存！`);
          return Promise.reject(new Error('标注未绑定标签'));
        }

        const labelingColor = this.shapeColorMap[shape.type];

        const label = this.findLabelById(labelId);
        labelingDataList.push({
          labelingSampleId: currentLabelingSampleId,
          labelId: labelId,
          labelingColor: labelingColor,
          labelingCoordinate: JSON.stringify({
            x: Number(shape.x) || 0,
            y: Number(shape.y) || 0,
            width: Number(shape.width) || 0,
            height: Number(shape.height) || 0,
            radius: Number(shape.radius) || 0,
            points: (shape.points || []).map(p => ({
              x: Number(p.x) || 0,
              y: Number(p.y) || 0
            }))
          }),
          boxType: shape.type === 'rect' ? "1" : (shape.type === 'polygon' ? "2" : "3"),
          textContent: ''
        });
      }

      // 5. 构造最终保存参数（完全匹配后端LabelingSaveDto）
      const saveParams = {
        labelingTaskId: this.labelingTaskId,
        labelingSampleId: currentLabelingSampleId,
        datasetSampleId: this.currentImage.datasetSampleId || '',
        // 核心修复：字段名改为 isInvalidData，值改为字符串（匹配后端DTO）
        isInvalidData: this.isInvalidData ? "0" : "1",
        labelingDataList: labelingDataList
      };

      // 调试输出：确认参数是否正确（开发阶段使用，上线可删除）
      console.log('保存参数：', JSON.stringify(saveParams, null, 2));

      // 6. 调用接口保存
      const loadingInstance = this.$loading({ text: '保存中...' });
      try {
        const res = await saveLabelingDataAPI(saveParams);
        if (res.code === 200) {
          this.$message.success('标注保存成功！');
          // 保存后更新本地数据（深拷贝避免引用问题）
          this.saveCurrentLabelData();
          // 重置撤销/恢复栈（保存后视为新起点）
          this.undoStack = [];
          this.redoStack = [];
          this.recordState(); // 重新记录初始状态
          return Promise.resolve(res);
        } else {
          const errorMsg = '保存失败：' + (res.msg || '接口返回异常');
          this.$message.error(errorMsg);
          return Promise.reject(new Error(errorMsg));
        }
      } catch (err) {
        console.error('保存标注接口调用失败：', err);
        this.$message.error('保存失败，请重试！');
        return Promise.reject(err);
      } finally {
        loadingInstance.close();
      }
    },

    // 保存并继续
    async saveAndContinue() {
      try {
        // 等待保存完成
        await this.saveLabel();
        this.isInvalidData = false;
        if (this.currentImageIndex < this.imageList.length - 1) {
          this.nextImage();
        } else {
          this.$message.info('已是最后一张图片！');
        }
      } catch (err) {
        this.$message.error('保存失败，无法继续');
        console.error(err);
      }
    }
  }
}
</script>

<style scoped lang="scss">
// 全局容器（防止整体溢出）
.labeling-page-container {
  width: 100%;
  height: 90vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background-color: #f5f7fa;
  box-sizing: border-box;
}

// 顶部操作栏
.labeling-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 10px 20px;
  background-color: #fff;
  border-bottom: 1px solid #e6e6e6;
  flex-shrink: 0;
  z-index: 100;

  .header-left {
    display: flex;
    align-items: center;
  }

  .header-right {
    display: flex;
    align-items: center;
    gap: 15px;

    .save-btn {
      margin-left: 10px;
    }
  }

  .el-button-group .el-button.active {
    background-color: #1890ff;
    color: #fff;
    border-color: #1890ff;
  }
}

// 主体内容外层容器（处理滚动）
.labeling-main-wrapper {
  height: 100%;
  overflow: hidden;
  padding: 15px;
  transition: all 0.3s ease;

  &.fullscreen {
    padding: 0;
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 99;
    background-color: #000;
  }
}

// 主体内容区
.labeling-main {
  display: flex;
  gap: 20px;
  height: 100%;
  min-height: 0;
  transition: all 0.3s ease;

  &.fullscreen {
    gap: 0;
  }

  // 左侧图片标注区
  .labeling-image-area {
    flex: 1;
    background-color: #fff;
    border-radius: 4px;
    padding: 15px;
    display: flex;
    flex-direction: column;
    transition: all 0.3s ease;

    .image-loading {
      flex: 1;
      display: flex;
      align-items: center;
      justify-content: center;
      padding: 20px;
    }

    .labeling-main.fullscreen & {
      flex: 1;
      padding: 0;
      background-color: #000;
      border-radius: 0;
      height: 100vh;
    }

    .image-container {
      flex: 1;
      position: relative;
      overflow: hidden;
      border: 1px solid #e6e6e6;
      border-radius: 4px;
      min-height: 400px;
      cursor: crosshair;
      display: flex;
      align-items: center;    // 垂直居中
      justify-content: center;// 水平居中
      background-color: #f0f0f0;
      padding: 0;
      box-sizing: border-box;

      .labeling-main.fullscreen & {
        border: none;
        border-radius: 0;
        min-height: 100%;
        background-color: #000;
      }

      .image-wrapper {
        position: relative;
        transition: transform 0.2s ease;
        max-width: 100%;
        max-height: 100%;

        .label-image {
          max-width: 100%;
          max-height: 100%;
          object-fit: contain;
          display: block;
          margin: 0 auto;
        }
      }
    }

    .image-pagination {
      margin-top: 10px;
      font-size: 14px;
      color: #666;
      display: flex;
      justify-content: space-between;
      align-items: center;
      transition: all 0.3s ease;

      .labeling-main.fullscreen & {
        position: absolute;
        bottom: 20px;
        left: 20px;
        right: 20px;
        background-color: rgba(0,0,0,0.5);
        color: #fff;
        padding: 8px 15px;
        border-radius: 4px;
        margin-top: 0;

        .draw-tip {
          color: #40a9ff;
        }

        .warning {
          color: #ff4d4f;
        }
      }

      .draw-tip {
        color: #1890ff;
        font-weight: 500;
      }

      .warning {
        color: #ff4d4f;
        font-weight: 500;
      }
    }
  }

  // 右侧标签栏
  .labeling-sidebar {
    width: 300px;
    background-color: #fff;
    border-radius: 4px;
    padding: 15px;
    display: flex;
    flex-direction: column;
    flex-shrink: 0;
    border: 1px solid #e6e6e6;

    .add-label-btn {
      margin-bottom: 10px;
      text-align: left;
      padding: 0;
      color: #1890ff;
    }

    .sidebar-title {
      font-size: 16px;
      font-weight: 600;
      margin-bottom: 15px;
      padding-bottom: 8px;
      border-bottom: 1px solid #e6e6e6;
    }

    .sidebar-content {
      flex: 1;
      overflow-y: auto;

      .label-group {
        margin-bottom: 15px;

        .label-group-header {
          display: flex;
          align-items: center;
          justify-content: space-between;
          cursor: pointer;
          padding: 8px 10px;
          background-color: #f5f7fa;
          border-radius: 4px;
          margin-bottom: 8px;

          .el-icon {
            margin-right: 8px;
            font-size: 12px;
          }

          .label-count {
            font-size: 12px;
            color: #999;
          }
        }

        .label-list {
          padding-left: 10px;

          .label-item {
            display: flex;
            align-items: center;
            justify-content: space-between;
            padding: 8px 10px;
            margin-bottom: 5px;
            border-radius: 4px;
            cursor: pointer;
            transition: background-color 0.2s;

            &:hover {
              background-color: #f5f7fa;
            }

            &.active {
              background-color: #e6f7ff;
              border: 1px solid #91d5ff;
            }

            .label-name {
              font-size: 14px;
              color: #333;
              flex: 1;
            }

            .shape-count {
              font-size: 12px;
              color: #999;
              margin-right: 8px;
            }

            .label-actions {
              display: flex;
              gap: 5px;
            }
          }
        }
      }
    }
  }
}

// 弹窗样式
::v-deep .el-dialog__body {
  padding: 20px;
}

::v-deep .el-form-item {
  margin-bottom: 15px;
}

// 标注选择标签弹窗样式
::v-deep .shape-label-select {
  .label-group-header {
    font-weight: 600;
    margin: 10px 0;
    color: #333;
  }

  .label-item {
    padding: 8px;
    border-radius: 4px;
    cursor: pointer;

    &:hover {
      background-color: #e6f7ff;
    }
  }
}

// 滚动条样式
::v-deep .el-scrollbar__bar {
  width: 6px;
  height: 6px;
}

::v-deep .el-scrollbar__thumb {
  background-color: #ccc;
  border-radius: 3px;
}
</style>
