<template>
  <div id="app">
    <div
      class="container"
      v-loading="loading"
      element-loading-background="rgba(0, 0, 0, 0.8)"
    >
      <div class="app-container">
        <div class="action-bar">
          <div
            class="action-bar-item spicon"
            @click="editFun"
            data-driver-step="edit-button"
          >
            <el-tooltip
              class="item"
              effect="dark"
              :content="isEdit ? '取消编辑' : '编辑'"
              placement="top"
            >
              <img v-if="isEdit" src="@/assets/edit-yes.png" alt="" />
              <img v-else src="@/assets/edit-no.png" alt="" />
            </el-tooltip>
          </div>

          <div class="action-bar-item spicon" @click="resetView">
            <el-tooltip
              class="item"
              effect="dark"
              content="重置视图"
              placement="top"
            >
              <img src="@/assets/bg-reset.png" alt="" />
            </el-tooltip>
          </div>
        </div>
        <!-- <div class="current-icon-info" v-if="currentPointX && currentPointY">
            <span>X: {{ currentPointX.toFixed(0) }}</span>
            <span>Y: {{ currentPointY.toFixed(0) }}</span>
          </div> -->

        <div class="canvas-container">
          <div
            class="tabbar-content"
            v-show="showFlag"
            data-driver-step="device-type-list"
          >
            <div
              class="tabbar-item"
              v-for="(item, index) in typesDictionary"
              :class="item.isSelected ? 'show' : 'hide'"
              @click="handleTabbar(item)"
              :data-driver-step="index === 0 ? 'device-type-item' : undefined"
            >
              <div class="tabbar-item-img">
                <img :src="icons[getIcon(item.model_id)]?.src" alt="" />
              </div>
              <div>{{ item.model_name }}</div>
            </div>
          </div>
          <div class="canvas-wrapper">
            <!-- 底层Canvas：仅绘制背景图（静态内容） -->
            <canvas ref="bgCanvasRef" class="bg-canvas"></canvas>
            <!-- 上层Canvas：绘制图表、标记点等动态内容 -->
            <canvas
              ref="canvasRef"
              @mousedown="startDrag"
              @mousemove="onMouseMove"
              @mouseup="mouseUp"
              @mouseleave="mouseLeave"
              @wheel.prevent="handleWheel"
              @dblclick="handleCanvasClick"
              :class="{ 'smooth-transition': isAnimating }"
              :style="`cursor: ${cursorStyle}`"
              class="main-canvas"
            ></canvas>
          </div>
        </div>
      </div>
      <!-- <div class="charts-content">
          <div ref="chartRef" class="line-chart"></div>
        </div> -->
    </div>

    <div class="progress-container spslider">
      <div class="bar-content bar-contentsp slider-content">
        <div
          class="slider-item"
          :class="item.area === selectAreaId ? 'is-select-area' : ''"
          v-for="(item, index) in region"
          @click="handleProgressChange2(item)"
        >
          {{ item.name }}
        </div>
      </div>
    </div>

    <template v-for="(item, index) in dialogList">
      <template v-if="item.target == 'device'">
        <dialogEle
          :modelValue="item.show"
          :id="item.id"
          :AreaId="item.AreaId"
          :TypeId="item.TypeId"
          :TypeName="item.TypeName"
          :dialogIndex="index"
          :zIndex="item.index"
          :extra="item.extra"
          @up:zindex="upDialogZindex"
          @close:dialog="closeDialog"
          @link:openCameraDialog="openCameraDialog"
        ></dialogEle>
      </template>
      <template v-if="item.target == 'camera'">
        <videoEle
          :info="item.info"
          :dialogIndex="index"
          :zIndex="item.index"
          :modelValue="item.show"
          @up:zindex="upDialogZindex"
          @close:dialog="closeDialog"
        ></videoEle>
      </template>
    </template>

    <tableEle
      :modelValue="tableModelValue"
      @update:modelValue="updateTableModelValue"
    ></tableEle>
  </div>
</template>
    
  
  <script setup>
import { throttle } from "lodash";
import { getArea } from "@/api/area.js";
import { getDeviceType, getDevices, updateDevice } from "@/api/device.js";
import { ElMessage, ElNotification } from "element-plus";
import dialogEle from "./../components/dialogEle.vue";
import { ref, onMounted, onUnmounted, reactive, nextTick, provide, watch } from "vue";
import { useRoute } from "vue-router";
import { shiyan_typesDictionary } from "../../utils/equipmentType"; //设备类型字典
import videoEle from "./../components/videoEle.vue";
import tableEle from "./../components/tableEle.vue";
import * as echarts from "echarts";
import { registerPagePrepare, unregisterPagePrepare } from "@/utils/userGuide";

// 通过环境变量动态加载资源
const assetsVersion = import.meta.env.VITE_ASSETS_VERSION || "shiyan";
const bg0101 = new URL(
  `../../assets/${assetsVersion}/0101.png`,
  import.meta.url
).href;
const bg0102 = new URL(
  `../../assets/${assetsVersion}/0102.png`,
  import.meta.url
).href;
const bg0201 = new URL(
  `../../assets/${assetsVersion}/0201.png`,
  import.meta.url
).href;
const bg0202 = new URL(
  `../../assets/${assetsVersion}/0202.png`,
  import.meta.url
).href;
const bg0301 = new URL(
  `../../assets/${assetsVersion}/0301.png`,
  import.meta.url
).href;
const bg0302 = new URL(
  `../../assets/${assetsVersion}/0302.png`,
  import.meta.url
).href;
const bg0401 = new URL(
  `../../assets/${assetsVersion}/0401.png`,
  import.meta.url
).href;
const bg0402 = new URL(
  `../../assets/${assetsVersion}/0402.png`,
  import.meta.url
).href;
const ground = new URL(
  `../../assets/${assetsVersion}/ground.png`,
  import.meta.url
).href;

// 2. 存储图片路径列表和转换后的ImageBitmap
const imagePaths = [
  bg0101,
  bg0102,
  bg0201,
  bg0202,
  bg0301,
  bg0302,
  bg0401,
  bg0402,
  ground,
]; // 9张图的路径数组
const imageBitmaps = ref([]); // 存储最终的ImageBitmap对象
const error = ref(""); // 错误信息

let fixArea = [
  "0101",
  "0102",
  "0201",
  "0202",
  "0301",
  "0302",
  "0401",
  "0402",
  "ground",
];

// 优化：添加缩放状态管理
const isZooming = ref(false);
let zoomEndTimer = null;

// 节流处理：每16ms最多重绘一次（上层动态内容）
const throttledRedraw = throttle((item) => {
  draw();
}, 16); // 16ms ~= 60fps

// 优化：背景重绘节流，缩放时使用更低频率
const throttledReBGdraw = throttle((item) => {
  if (!isZooming.value) {
    drawBackground();
  }
}, 16); // 16ms ~= 60fps

// 优化：缩放时的背景重绘（更低频率，降低质量）
const throttledReBGdrawDuringZoom = throttle((item) => {
  drawBackground();
}, 50); // 50ms，缩放时降低频率

const tableModelValue = ref(false);
// 新增：底层背景Canvas引用
const bgCanvasRef = ref(null);
// 上层主Canvas引用（原canvasRef）
const canvasRef = ref(null);
// 新增：底层背景Canvas上下文
const bgCtx = ref(null);
// 上层主Canvas上下文（原ctx）
const ctx = ref(null);
const img = new Image();

// 优化：离屏Canvas用于缓存背景（避免重复绘制）
let offscreenCanvas = null;
let offscreenCtx = null;
let cachedScale = null;
let cachedOffsetX = null;
let cachedOffsetY = null;

let _typesDictionary = reactive(shiyan_typesDictionary);
const typesDictionary = ref([]);

// 恢复points定义 - 点标记数据 设备数据
let points = reactive([]);

// 状态管理（保持不变）
const scale = ref(1);
const offsetX = ref(0);
const offsetY = ref(0);
const startX = ref(0);
const startY = ref(0);
const dragging = ref(null);
const isDragging = ref(false);
const mouseX = ref(0);
const mouseY = ref(0);
const pointSize = ref(3);
const iconSize = ref(5);
const selectedPointId = ref(null);
const isAnimating = ref(false);
const loading = ref(false);
const startPointX = ref(0);
const startPointY = ref(0);
const startDragX = ref(0);
const startDragY = ref(0);
const progress = ref(0);
const isEdit = ref(false);
const showFlag = ref(true);
const selectAreaId = ref(1);
const cursorStyle = ref("");
const activeReferenceLine = ref(null);
let currentPoint = reactive({});
// let currentPointX = ref(0);
// let currentPointY = ref(0);
let dialogList = reactive([]);
const deviceList = ref([]);

let StandardC = 948; //设置标准系数  初始化数据电脑屏幕的高度
let BL = ref(0); //标准系数 / 当前屏幕高度 计算出的系数比例 保证在各高度设备上展示正常

// 从配置文件中读取区域配置，如果没有则使用默认值
const region = ref(
  window.ShiYanRegionConfig || [
    { area: 1, x: 0, y: 450, name: "1栋1单元", code: '0101' },
    { area: 2, x: 0, y: 240, name: "1栋2单元", code: '0102' },
    { area: 3, x: 450, y: 410, name: "2栋1单元", code: '0201' },
    { area: 4, x: 450, y: 200, name: "2栋2单元", code: '0202' },
    { area: 5, x: 1100, y: 430, name: "3栋1单元", code: '0301' },
    { area: 6, x: 1100, y: 220, name: "3栋2单元", code: '0302' },
    { area: 7, x: 100, y: 0, name: "4栋1单元", code: '0401' },
    { area: 8, x: 800, y: 0, name: "4栋2单元", code: '0402' },
    { area: 9, x: 200, y: 600, name: "广场", code: 'ground' },
  ]
);

// 背景图信息（保持不变）
const imgAspectRatio = ref(1);
const imgWidth = ref(0);
const imgHeight = ref(0);

// 鼠标移动检测相关变量（保持不变）
const lastMousePosition = ref({ x: 0, y: 0 });

//悬浮展示设备 摄像头基本信息相关（保持不变）
const activeMarker = ref({ target: "" });

const SELECT_ACTION_TYPE = ref("");

const icons = {
  yyht: new Image(),
  eyht: new Image(),
  wenshidu: new Image(),
  yangqi: new Image(),
  yewei: new Image(),
  sll: new Image(),
  shuiwen: new Image(),
  sgxwd: new Image(),
  youwei: new Image(),
  youqi: new Image(),
  fengliang: new Image(),
  fengji: new Image(),
  ddff: new Image(),
  sb: new Image(),
  yb: new Image(),
  znzm: new Image(),
  xfzj: new Image(),
  pingjun: new Image(),
  ktjz: new Image(),
  xfjwjz: new Image(),
  cyfdjz: new Image(),
  xfbjzj: new Image(),
  mj: new Image(),
  spjk: new Image(),
  znyb: new Image(),
  weiji: new Image(),
  zhiliuping: new Image(),
  byqwky: new Image(),
  wyc: new Image(),
};

icons.yyht.src = new URL(
  "../../assets/planIcon/yyht.svg",
  import.meta.url
).href;
icons.eyht.src = new URL(
  "../../assets/planIcon/eyht.svg",
  import.meta.url
).href;
icons.wenshidu.src = new URL(
  "../../assets/planIcon/wenshidu.svg",
  import.meta.url
).href;
icons.yangqi.src = new URL(
  "../../assets/planIcon/yangqi.svg",
  import.meta.url
).href;
icons.yewei.src = new URL(
  "../../assets/planIcon/yewei.svg",
  import.meta.url
).href;
icons.wyc.src = new URL("../../assets/planIcon/wyc.svg", import.meta.url).href;
icons.sll.src = new URL("../../assets/planIcon/sll.svg", import.meta.url).href;
icons.shuiwen.src = new URL(
  "../../assets/planIcon/shuiwen.svg",
  import.meta.url
).href;
icons.sgxwd.src = new URL(
  "../../assets/planIcon/sgxwd.svg",
  import.meta.url
).href;
icons.youwei.src = new URL(
  "../../assets/planIcon/youwei.svg",
  import.meta.url
).href;
icons.youqi.src = new URL(
  "../../assets/planIcon/youqi.svg",
  import.meta.url
).href;
icons.fengliang.src = new URL(
  "../../assets/planIcon/fengliang.svg",
  import.meta.url
).href;
icons.fengji.src = new URL(
  "../../assets/planIcon/fengji.svg",
  import.meta.url
).href;
icons.ddff.src = new URL(
  "../../assets/planIcon/ddff.svg",
  import.meta.url
).href;
icons.sb.src = new URL("../../assets/planIcon/sb.svg", import.meta.url).href;
icons.yb.src = new URL("../../assets/planIcon/yb.svg", import.meta.url).href;
icons.znzm.src = new URL(
  "../../assets/planIcon/znzm.svg",
  import.meta.url
).href;
icons.xfzj.src = new URL(
  "../../assets/planIcon/xfzj.svg",
  import.meta.url
).href;
icons.pingjun.src = new URL(
  "../../assets/planIcon/pingjun.svg",
  import.meta.url
).href;
icons.ktjz.src = new URL(
  "../../assets/planIcon/ktjz.svg",
  import.meta.url
).href;
icons.xfjwjz.src = new URL(
  "../../assets/planIcon/xfjwjz.svg",
  import.meta.url
).href;
icons.cyfdjz.src = new URL(
  "../../assets/planIcon/cyfdjz.svg",
  import.meta.url
).href;
icons.xfbjzj.src = new URL(
  "../../assets/planIcon/xfbjzj.svg",
  import.meta.url
).href;
icons.mj.src = new URL("../../assets/planIcon/mj.svg", import.meta.url).href;
icons.spjk.src = new URL(
  "../../assets/planIcon/spjk.svg",
  import.meta.url
).href;
icons.znyb.src = new URL(
  "../../assets/planIcon/znyb.svg",
  import.meta.url
).href;
icons.weiji.src = new URL(
  "../../assets/planIcon/weiji.svg",
  import.meta.url
).href;
icons.zhiliuping.src = new URL(
  "../../assets/planIcon/zhiliuping.svg",
  import.meta.url
).href;
icons.byqwky.src = new URL(
  "../../assets/planIcon/byqwky.svg",
  import.meta.url
).href;

let referenceLines = []; //吸附区域y坐标
// let referenceLines = [355, 400, 460, 505]; //吸附区域y坐标
let snapThreshold = 15; //出现吸附区域的阈值

const updateTableModelValue = (value) => {
  tableModelValue.value = value;
};

// 图表引用和实例
const chartRef = ref(null);
let chartInstance = null;

// 状态变量
const darkMode = ref(true);
const chartData = ref({
  xAxis: [
    "1月",
    "2月",
    "3月",
    "4月",
    "5月",
    "6月",
    "7月",
    "8月",
    "9月",
    "10月",
    "11月",
    "12月",
  ],
  series: [
    {
      name: "产品A",
      data: [120, 132, 101, 134, 90, 230, 210, 230, 180, 230, 210, 250],
      color: "#42b983", // 柔和的绿色
    },
    {
      name: "产品B",
      data: [220, 182, 191, 234, 290, 330, 310, 330, 380, 330, 310, 350],
      color: "#3498db", // 柔和的蓝色
    },
    {
      name: "产品C",
      data: [150, 232, 201, 154, 190, 330, 410, 330, 380, 430, 410, 450],
      color: "#f39c12", // 柔和的橙色
    },
  ],
});

// 初始化图表
const initChart = () => {
  // 销毁已有实例
  if (chartInstance) {
    chartInstance.dispose();
  }

  // 创建新实例
  chartInstance = echarts.init(chartRef.value);

  // 设置图表选项
  const option = {
    backgroundColor: darkMode.value
      ? "rgba(30, 30, 30, 0.7)"
      : "rgba(255, 255, 255, 0.7)",
    tooltip: {
      trigger: "axis",
      backgroundColor: darkMode.value
        ? "rgba(40, 40, 40, 0.9)"
        : "rgba(255, 255, 255, 0.9)",
      borderColor: darkMode.value ? "#555" : "#ddd",
      textStyle: {
        color: darkMode.value ? "#fff" : "#333",
      },
      padding: 10,
      borderRadius: 6,
    },
    legend: {
      data: chartData.value.series.map((item) => item.name),
      top: 10,
      textStyle: {
        color: darkMode.value ? "#eee" : "#555",
      },
    },
    grid: {
      left: "3%",
      right: "4%",
      bottom: "3%",
      containLabel: true,
    },
    xAxis: {
      type: "category",
      boundaryGap: false,
      data: chartData.value.xAxis,
      axisLine: {
        lineStyle: {
          color: darkMode.value ? "#555" : "#ddd",
        },
      },
      axisLabel: {
        color: darkMode.value ? "#bbb" : "#666",
      },
      splitLine: {
        show: true,
        lineStyle: {
          color: darkMode.value
            ? "rgba(255, 255, 255, 0.05)"
            : "rgba(0, 0, 0, 0.05)",
        },
      },
    },
    yAxis: {
      type: "value",
      axisLine: {
        lineStyle: {
          color: darkMode.value ? "#555" : "#ddd",
        },
      },
      axisLabel: {
        color: darkMode.value ? "#bbb" : "#666",
      },
      splitLine: {
        show: true,
        lineStyle: {
          color: darkMode.value
            ? "rgba(255, 255, 255, 0.05)"
            : "rgba(0, 0, 0, 0.05)",
        },
      },
    },
    series: chartData.value.series.map((item) => ({
      name: item.name,
      type: "line",
      data: item.data,
      symbol: "circle",
      symbolSize: 6,
      emphasis: {
        symbolSize: 8,
      },
      lineStyle: {
        width: 2,
        color: item.color,
      },
      itemStyle: {
        color: item.color,
        borderWidth: 2,
        borderColor: darkMode.value ? "#333" : "#fff",
      },
      areaStyle: {
        color: {
          type: "linear",
          x: 0,
          y: 0,
          x2: 0,
          y2: 1,
          colorStops: [
            {
              offset: 0,
              color: item.color + "80", // 透明度80%
            },
            {
              offset: 1,
              color: item.color + "00", // 透明度0%
            },
          ],
        },
      },
    })),
  };

  // 设置选项
  chartInstance.setOption(option);
};

// 3. 加载单张图片并转换为ImageBitmap的工具函数
async function loadLocalImageBitmap(url) {
  const response = await fetch(url);
  if (!response.ok) {
    throw new Error(`图片加载失败: ${url}（状态码：${response.status}）`);
  }
  const blob = await response.blob();
  return await createImageBitmap(blob); // 转换为高性能ImageBitmap
}

// 4. 批量加载所有图片，全部完成后触发操作
const loadAllImages = async (images) => {
  loading.value = true;
  // 使用Promise.all等待9张图片全部转换完成
  const bitmaps = await Promise.all(
    images.map((path) => loadLocalImageBitmap(path))
  );
  imageBitmaps.value = bitmaps;
  error.value = "";
  return bitmaps; // 返回加载完成的ImageBitmap数组
};

const handleTabbar = (e) => {
  e.isSelected = !e.isSelected;
  selectedPointId.value = null;
  typesDictionary.value.forEach((d) => {
    if (d.model_id === e.model_id && d.isSelected) {
      d.isSelected = true;
    } else {
      d.isSelected = false;
    }
  });
  if (e.isSelected) {
    SELECT_ACTION_TYPE.value = e.model_id;
  } else {
    SELECT_ACTION_TYPE.value = "";
  }

  showFlag.value = false;
  nextTick(() => {
    showFlag.value = true;
  });
  throttledRedraw(); // 只重绘上层动态内容
};

const editFun = () => {
  isEdit.value = !isEdit.value;
  if (!isEdit.value) {
    throttledRedraw(); // 只重绘上层动态内容
  }
};

// 根据 code 从 region 配置中获取 label 的辅助函数
const getLabelByCode = (code) => {
  const matchedRegion = region.value.find((r) => r.code === code);
  return matchedRegion ? matchedRegion.name : '';
};

const rects = reactive([
  {
    code: "0401",
    y: 10,
    label: getLabelByCode("0401"),
    height: 220,
  },
  {
    code: "0402",
    y: 10,
    label: getLabelByCode("0402"),
    height: 220,
  },
]);

const rects1 = reactive([
  {
    code: "0102",
    y: 240,
    label: getLabelByCode("0102"),
  },
  {
    code: "0202",
    y: 240,
    label: getLabelByCode("0202"),
  },
  {
    code: "0302",
    y: 240,
    label: getLabelByCode("0302"),
  },
]);

const rects2 = reactive([
  {
    code: "0101",
    y: 430,
    label: getLabelByCode("0101"),
  },
  {
    code: "0201",
    y: 430,
    label: getLabelByCode("0201"),
  },
  {
    code: "0301",
    y: 430,
    label: getLabelByCode("0301"),
  },
]);

const rect_ground = reactive([
  {
    code: "ground",
    y: 620,
    label: getLabelByCode("ground"),
  },
]);

// 监听 region 变化，更新所有 rects 的 label
const updateRectsLabels = () => {
  rects.forEach((rect) => {
    rect.label = getLabelByCode(rect.code);
  });
  rects1.forEach((rect) => {
    rect.label = getLabelByCode(rect.code);
  });
  rects2.forEach((rect) => {
    rect.label = getLabelByCode(rect.code);
  });
  rect_ground.forEach((rect) => {
    rect.label = getLabelByCode(rect.code);
  });
};

// 监听 region 变化
watch(
  () => region.value,
  () => {
    updateRectsLabels();
  },
  { deep: true, immediate: true }
);

let s_w = 0.8;

const getDeviceTypeFun = async () => {
  let params = {
    page: 1,
    page_size: 100,
  };
  let res = await getDeviceType(params);
  if (res && res.code == 200 && res.data) {
    let list = res.data.device_types;
    typesDictionary.value = list;
  }
};

const getDevicesFun = async () => {
  let params = {
    page: 1,
    page_size: 1000,
  };
  let res = await getDevices(params);
  if (res && res.code == 200 && res.data) {
    let list = res.data.devices || [];

    // 1) 按 region_name 分组设备，用于计算同一区域内的索引
    const devicesByRegion = {};
    list.forEach((item) => {
      const regionName = item.region_name || "";
      if (!devicesByRegion[regionName]) {
        devicesByRegion[regionName] = [];
      }
      devicesByRegion[regionName].push(item);
    });

    deviceList.value = list.map((item) => {
      const regionName = item.region_name || "";
      const matchedRegion = region.value.find((r) => r.name === regionName);

      let finalX = item.x ?? 300;
      let finalY = item.y ?? 300;

      if (matchedRegion) {
        finalX = matchedRegion.x;
        finalY = matchedRegion.y + 50;

        const regionDevices = devicesByRegion[regionName] || [];
        const deviceIndex = regionDevices.findIndex((d) => {
          // 使用唯一标识来匹配，优先使用 DeviceId，如果没有则使用其他唯一字段
          return d.IotDevice?.id === item.IotDevice?.id;
        });

        // 根据索引逐次增加 x 坐标（第一个设备 x + 0，第二个 x + 50，第三个 x + 100...）
        if (deviceIndex >= 0) {
          finalX = matchedRegion.x + deviceIndex * 50;
        }
      }

      return {
        ...item,
        x:
          item.IotDevice.coordinate_x && item.IotDevice.coordinate_x != 0
            ? item.IotDevice.coordinate_x
            : finalX,
        y:
          item.IotDevice.coordinate_y && item.IotDevice.coordinate_y != 0
            ? item.IotDevice.coordinate_y
            : finalY,
      };
    });

    // 3) 将设备列表转成绘制需要的点数据结构（按区域分组）
    // 提取 points 中每一项的 IotAgreementHostPoint.id
    const extractPointIds = (points) => {
      if (!points || !Array.isArray(points)) return [];
      return points
        .map((point) => point?.IotAgreementHostPoint?.id)
        .filter((id) => id !== undefined && id !== null);
    };

    const mappedPoints = deviceList.value.map((item) => {
      // parent_region_id 作为区域分组标识
      const areaId = item.parent_region_id ?? 0;
      return {
        target: "device",
        AreaId: areaId,
        DeviceId: item.IotDevice.id,
        TypeId: item.IotDevice.device_type_id,
        TypeName: item.type_name,
        DeviceName: item.IotDevice.device_name,
        AreaName: item.region_name,
        x: item.x,
        y: item.y,
        IsOpen: item.IsOpen || 0,
        IsFault: item.IsFault || 0,
        IsAlarm: item.IsAlarm || 0,
        StateId: item.StateId || 0,
        CabinId: item.CabinId || 0,
        CabinName: item.CabinName || "",
        Station: item.Station || 0,
        Data: item.points || [],
        Points: extractPointIds(item.points)
      };
    });

    points = mappedPoints;
      
    throttledReBGdraw();
    throttledRedraw();
  }
};

const getAreaFun = async () => {
  let params = {
    page: 1,
    page_size: 10,
    area_type: 1,
  };
  let res = await getArea(params);
  if (res && res.code == 200) {
    let areaList_arr = [];
    let imagePaths = [];

    res.data.areas.forEach((d) => {
      if (fixArea.indexOf(d.area_code) != -1 && d.background) {
        areaList_arr.push({
          area_code: d.area_code,
          background: window.$FileBaseUrl + d.background,
        });
      }
    });

    areaList_arr.forEach((d) => {
      imagePaths.push(d.background);
    });

    const bitmaps = await loadAllImages(imagePaths);
    areaList_arr.forEach((d, i) => {
      rects.forEach((c) => {
        if (c.code == d.area_code) {
          c.colorBg = bitmaps[i];
        }
      });
      rects1.forEach((c) => {
        if (c.code == d.area_code) {
          c.colorBg = bitmaps[i];
        }
      });
      rects2.forEach((c) => {
        if (c.code == d.area_code) {
          c.colorBg = bitmaps[i];
        }
      });
      rect_ground.forEach((c) => {
        if (c.code == d.area_code) {
          c.colorBg = bitmaps[i];
        }
      });
    });

    initCanvas();
  } else {
    initCanvas();
  }
};

const drawColorBg = (ctx, img, x, y, w, h) => {
  if (img) {
    ctx.save();
    ctx.globalAlpha = 0.5;
    ctx.drawImage(img, x, y, w, h);
    ctx.globalAlpha = 1;
    ctx.restore();
  }
};

const drawText = (ctx, text, x, y) => {
  // 绘制文字（确保在最上层）
  ctx.save();
  ctx.font = "10px Arial";
  ctx.fillStyle = "#fff";
  ctx.textBaseline = "middle";
  ctx.fillText(text, x, y);
  ctx.restore();
};

// 优化：初始化离屏Canvas
const initOffscreenCanvas = () => {
  if (!bgCanvasRef.value) return;
  const canvasWidth = bgCanvasRef.value.width;
  const canvasHeight = bgCanvasRef.value.height;

  // 创建离屏Canvas，尺寸稍大以支持缓存
  offscreenCanvas = document.createElement("canvas");
  offscreenCanvas.width = canvasWidth;
  offscreenCanvas.height = canvasHeight;
  offscreenCtx = offscreenCanvas.getContext("2d");

  // 设置离屏Canvas的渲染质量
  offscreenCtx.imageSmoothingEnabled = true;
  offscreenCtx.imageSmoothingQuality = "high";
};

// 优化：绘制底层背景图（使用缓存优化）
const drawBackground = () => {
  if (!bgCtx.value || !img.complete) return;

  const canvasWidth = bgCanvasRef.value.width;
  const canvasHeight = bgCanvasRef.value.height;

  // 初始化离屏Canvas
  if (
    !offscreenCanvas ||
    offscreenCanvas.width !== canvasWidth ||
    offscreenCanvas.height !== canvasHeight
  ) {
    initOffscreenCanvas();
  }

  // 计算背景图尺寸（与原逻辑一致）
  imgHeight.value = canvasHeight;
  imgWidth.value = imgHeight.value * imgAspectRatio.value;

  // 检查是否需要重新绘制（缓存失效）
  const needRedraw =
    cachedScale !== scale.value ||
    cachedOffsetX !== offsetX.value ||
    cachedOffsetY !== offsetY.value ||
    !offscreenCanvas;

  if (needRedraw) {
    // 清除离屏画布
    offscreenCtx.clearRect(0, 0, canvasWidth, canvasHeight);

    // 保存离屏画布状态
    offscreenCtx.save();
    // 应用平移和缩放（与上层保持一致）
    offscreenCtx.translate(offsetX.value, offsetY.value);
    offscreenCtx.scale(scale.value, scale.value);

    // 在离屏Canvas上绘制背景
    drawRealBackgroundToCanvas(offscreenCtx);

    // 恢复离屏画布状态
    offscreenCtx.restore();

    // 更新缓存标记
    cachedScale = scale.value;
    cachedOffsetX = offsetX.value;
    cachedOffsetY = offsetY.value;
  }

  // 将离屏Canvas内容绘制到显示Canvas
  bgCtx.value.clearRect(0, 0, canvasWidth, canvasHeight);
  bgCtx.value.drawImage(offscreenCanvas, 0, 0);
};

// 优化：将背景绘制逻辑提取为独立函数，可在离屏Canvas上绘制
const drawRealBackgroundToCanvas = (targetCtx) => {
  targetCtx.strokeStyle = "white";
  targetCtx.lineWidth = 0.5;

  let _clientHeight = bgCanvasRef.value.clientHeight;
  const canvasWidth = _clientHeight * 2;

  function strokeRoundRect(ctx, x, y, width, height, radius) {
    if (radius <= 0) {
      ctx.strokeRect(x, y, width, height);
      return;
    }
    ctx.beginPath();
    ctx.moveTo(x + radius, y);
    ctx.arcTo(x + width, y, x + width, y + radius, radius);
    ctx.arcTo(x + width, y + height, x + width - radius, y + height, radius);
    ctx.arcTo(x, y + height, x, y + height - radius, radius);
    ctx.arcTo(x, y, x + radius, y, radius);
    ctx.closePath();
    ctx.stroke();
  }

  rects.forEach((d, i) => {
    const gap = (canvasWidth * (1 - s_w)) / 2;
    let d_imgWidth = (canvasWidth * s_w - 20) / rects.length;
    strokeRoundRect(
      targetCtx,
      gap + (d_imgWidth + 20) * i,
      d.y * BL.value,
      d_imgWidth,
      220 * BL.value,
      5
    );
    if (d.colorBg) {
      drawColorBg(
        targetCtx,
        d.colorBg,
        gap + (d_imgWidth + 20) * i,
        d.y * BL.value,
        d_imgWidth,
        220 * BL.value
      );
    }
    if (d.bg) {
      targetCtx.drawImage(
        d.bg,
        gap + (d_imgWidth + 20) * i,
        d.y * BL.value,
        d_imgWidth,
        220 * BL.value
      );
    }

    if (d.label) {
      const textMetrics = targetCtx.measureText(d.label).width;
      drawText(
        targetCtx,
        d.label,
        gap + d_imgWidth * i + 10 * i + textMetrics / 2,
        d.y * BL.value + 10
      );
    }
  });

  rects1.forEach((d, i) => {
    const gap = (canvasWidth * (1 - s_w)) / 2;
    let d_imgWidth = (canvasWidth * s_w - 40) / rects1.length;
    strokeRoundRect(
      targetCtx,
      gap + (d_imgWidth + 20) * i,
      d.y * BL.value,
      d_imgWidth,
      180 * BL.value,
      5
    );

    if (d.colorBg) {
      drawColorBg(
        targetCtx,
        d.colorBg,
        gap + d_imgWidth * i + 20 * i,
        d.y * BL.value,
        d_imgWidth,
        180 * BL.value
      );
    }

    if (d.bg) {
      targetCtx.drawImage(
        d.bg,
        gap + d_imgWidth * i + 20 * i,
        d.y * BL.value,
        d_imgWidth,
        180 * BL.value
      );
    }

    if (d.label) {
      const textMetrics = targetCtx.measureText(d.label).width;
      drawText(
        targetCtx,
        d.label,
        gap + d_imgWidth * i + 10 * i + textMetrics / 2,
        d.y * BL.value + 10
      );
    }
  });

  rects2.forEach((d, i) => {
    const gap = (canvasWidth * (1 - s_w)) / 2;
    let d_imgWidth = (canvasWidth * s_w - 40) / rects2.length;
    strokeRoundRect(
      targetCtx,
      gap + (d_imgWidth + 20) * i,
      d.y * BL.value,
      d_imgWidth,
      180 * BL.value,
      5
    );
    if (d.colorBg) {
      drawColorBg(
        targetCtx,
        d.colorBg,
        gap + d_imgWidth * i + 20 * i,
        d.y * BL.value,
        d_imgWidth,
        180 * BL.value
      );
    }

    if (d.bg) {
      targetCtx.drawImage(
        d.bg,
        gap + d_imgWidth * i + 20 * i,
        d.y * BL.value,
        d_imgWidth,
        180 * BL.value
      );
    }

    if (d.label) {
      const textMetrics = targetCtx.measureText(d.label).width;
      drawText(
        targetCtx,
        d.label,
        gap + d_imgWidth * i + 10 * i + textMetrics / 2,
        d.y * BL.value + 10
      );
    }
  });

  rect_ground.forEach((d, i) => {
    const gap = (canvasWidth * (1 - s_w)) / 2;
    let d_imgWidth = (canvasWidth * s_w) / rect_ground.length;
    strokeRoundRect(
      targetCtx,
      gap + (d_imgWidth + 20) * i,
      d.y * BL.value,
      d_imgWidth,
      180 * BL.value,
      5
    );
    if (d.colorBg) {
      drawColorBg(
        targetCtx,
        d.colorBg,
        gap + d_imgWidth * i + 20 * i,
        d.y * BL.value,
        d_imgWidth,
        180 * BL.value
      );
    }
    if (d.bg) {
      targetCtx.drawImage(
        d.bg,
        gap + d_imgWidth * i + 20 * i,
        d.y * BL.value,
        d_imgWidth,
        180 * BL.value
      );
    }

    if (d.label) {
      const textMetrics = targetCtx.measureText(d.label).width;
      drawText(
        targetCtx,
        d.label,
        gap + d_imgWidth * i + 10 * i + textMetrics / 2,
        d.y * BL.value + 10
      );
    }
  });
};

// 优化：初始化Canvas（同时初始化两层Canvas和离屏Canvas）
const initCanvas = () => {
  nextTick(() => {
    // 初始化上层主Canvas
    ctx.value = canvasRef.value.getContext("2d");
    // 优化：启用图像平滑以获得更好的缩放质量
    if (ctx.value) {
      ctx.value.imageSmoothingEnabled = true;
      ctx.value.imageSmoothingQuality = "high";
    }

    // 初始化底层背景Canvas
    bgCtx.value = bgCanvasRef.value.getContext("2d");
    // 优化：启用图像平滑
    if (bgCtx.value) {
      bgCtx.value.imageSmoothingEnabled = true;
      bgCtx.value.imageSmoothingQuality = "high";
    }

    // 初始化离屏Canvas
    initOffscreenCanvas();

    // 加载背景图（加载完成后绘制底层背景）
    loadImage();

    let _clientHeight = bgCanvasRef.value.clientHeight;
    const _w = _clientHeight * 2;
    let _left = (bgCanvasRef.value.clientWidth - _w) / 2;
    offsetX.value = _left;
  });
};

const getIcon = (id) => {
  let _d = shiyan_typesDictionary.filter((d) => {
    return d.value == id;
  });
  if (_d && _d[0]) {
    return _d[0].img;
  } else {
    return "";
  }
};

// 加载背景图（修改：加载完成后调用drawBackground）
const loadImage = async () => {
  // 等待所有图片加载完成后执行操作
  const bitmaps = await loadAllImages(imagePaths);
  if (bitmaps.length === 9) {
    loading.value = false;

    rects[0].bg = bitmaps[6];
    rects[1].bg = bitmaps[7];

    rects1[0].bg = bitmaps[1];
    rects1[1].bg = bitmaps[3];
    rects1[2].bg = bitmaps[5];

    rects2[0].bg = bitmaps[0];
    rects2[1].bg = bitmaps[2];
    rects2[2].bg = bitmaps[4];

    rect_ground[0].bg = bitmaps[8];

    nextTick(() => {
      resizeCanvas();
      // 首次绘制背景
      throttledReBGdraw();
    });
  }
};

// 优化：调整Canvas大小（重置缓存）
const resizeCanvas = () => {
  if (!canvasRef.value || !bgCanvasRef.value) return;

  // 同时设置两层Canvas的大小（保持一致）
  const clientWidth = canvasRef.value.clientWidth;
  const clientHeight = canvasRef.value.clientHeight;

  canvasRef.value.width = clientWidth;
  canvasRef.value.height = clientHeight;
  bgCanvasRef.value.width = clientWidth;
  bgCanvasRef.value.height = clientHeight;

  BL.value = clientHeight / StandardC;

  // 重置缓存，因为Canvas尺寸改变
  cachedScale = null;
  cachedOffsetX = null;
  cachedOffsetY = null;
  initOffscreenCanvas();

  // 窗口 resize 时需要重新绘制背景
  throttledReBGdraw();
  // 同时重绘上层内容
  throttledRedraw();
};

// 绘制tooltip（保持不变）
const drawTooltip = () => { 
  if (!activeMarker.value || !activeMarker.value.target) return;

  const ctxTooltip = ctx.value;
  const text =
    activeMarker.value.target === "camera"
      ? `${activeMarker.value.CamName} - ${activeMarker.value.CamAddress}`
      : `${activeMarker.value.DeviceName} - ${activeMarker.value.AreaName} - ${activeMarker.value.TypeName}`;

  const lines = text.split(" - ");
  const padding = 10;
  const lineHeight = 18;
  const maxWidth = 300;

  ctxTooltip.font = "14px Arial";
  let tooltipWidth = 0;
  lines.forEach((line) => {
    const width = ctxTooltip.measureText(line).width;
    if (width > tooltipWidth) tooltipWidth = Math.min(width, maxWidth);
  });
  tooltipWidth += padding * 2;

  const tooltipHeight = lines.length * lineHeight + padding * 2;

  const canvasX = activeMarker.value.x * BL.value * scale.value + offsetX.value;
  const canvasY = activeMarker.value.y * BL.value * scale.value + offsetY.value;

  let x = canvasX - tooltipWidth / 2;
  let y = canvasY - tooltipHeight - 6 * scale.value;

  const canvasWidth = canvasRef.value.width;

  if (x < 5) x = 5;
  if (x + tooltipWidth > canvasWidth - 5) x = canvasWidth - tooltipWidth - 5;

  if (y < 5) {
    y = canvasY + 20
  } else {
    y = canvasY - 90
  }

  ctxTooltip.save();
  ctxTooltip.fillStyle = "rgba(0, 20, 40, 0.95)";
  ctxTooltip.strokeStyle = "#4dabf7";
  ctxTooltip.lineWidth = 1;

  const radius = 10;
  ctxTooltip.beginPath();
  ctxTooltip.moveTo(x + radius, y);
  ctxTooltip.lineTo(x + tooltipWidth - radius, y);
  ctxTooltip.arcTo(x + tooltipWidth, y, x + tooltipWidth, y + radius, radius);
  ctxTooltip.lineTo(x + tooltipWidth, y + tooltipHeight - radius);
  ctxTooltip.arcTo(
    x + tooltipWidth,
    y + tooltipHeight,
    x + tooltipWidth - radius,
    y + tooltipHeight,
    radius
  );
  ctxTooltip.lineTo(x + radius, y + tooltipHeight);
  ctxTooltip.arcTo(x, y + tooltipHeight, x, y + tooltipHeight - radius, radius);
  ctxTooltip.lineTo(x, y + radius);
  ctxTooltip.arcTo(x, y, x + radius, y, radius);
  ctxTooltip.closePath();

  ctxTooltip.fill();
  ctxTooltip.stroke();

  ctxTooltip.fillStyle = "#e0f7ff";
  ctxTooltip.font = "14px Arial";
  ctxTooltip.textBaseline = "middle";

  lines.forEach((line, index) => {
    let currentLine = "";
    let currentY = y + padding + lineHeight / 2 + index * lineHeight;

    for (let i = 0; i < line.length; i++) {
      const testLine = currentLine + line[i];
      const metrics = ctxTooltip.measureText(testLine);

      if (metrics.width > maxWidth && i > 0) {
        ctxTooltip.fillText(currentLine, x + padding, currentY);
        currentLine = line[i];
        currentY += lineHeight;
      } else {
        currentLine = testLine;
      }
    }

    ctxTooltip.fillText(currentLine, x + padding, currentY);
  });

  ctxTooltip.restore();
};

// 绘制函数（修改：移除背景图绘制，只绘制动态内容）
const draw = () => {
  if (!ctx.value || !img.complete) return;

  const canvasWidth = canvasRef.value.width;
  const canvasHeight = canvasRef.value.height;

  BL.value = canvasHeight / StandardC;

  // 清除上层画布（只清除动态内容）
  ctx.value.clearRect(0, 0, canvasWidth, canvasHeight);

  // 保存上层画布状态
  ctx.value.save();
  // 应用平移和缩放（与底层保持一致）
  ctx.value.translate(offsetX.value, offsetY.value);
  ctx.value.scale(scale.value, scale.value);

  // 绘制参考线（编辑状态时）
  if (isEdit.value && activeReferenceLine.value !== null) {
    ctx.value.beginPath();
    ctx.value.lineTo(imgWidth.value, activeReferenceLine.value * BL.value);
    ctx.value.strokeStyle = "rgba(35, 139, 34, 0.9)";
    ctx.value.lineWidth = 1;
    ctx.value.stroke();

    ctx.value.beginPath();
    ctx.value.rect(
      0,
      (activeReferenceLine.value - snapThreshold) * BL.value,
      imgWidth.value,
      snapThreshold * 2 * BL.value
    );
    ctx.value.fillStyle = "rgba(35, 139, 34, 0.4)";
    ctx.value.fill();
  }

  // 绘制点标记（动态内容）
  points.forEach((point) => {
    if (SELECT_ACTION_TYPE.value && point.TypeId !== SELECT_ACTION_TYPE.value) {
      return;
    }

    const size = pointSize.value * scale.value * 0.2;
    const iSize = iconSize.value * scale.value * 0.2;

    ctx.value.beginPath();
    ctx.value.arc(
      point.x * BL.value,
      point.y * BL.value,
      (size * BL.value) / scale.value,
      0,
      Math.PI * 2
    );

    ctx.value.fillStyle = "#FFFFFF";
    ctx.value.fill();

    let _img = point.target == "device" ? getIcon(point.TypeId) : "camera";
    if (_img && icons[_img]) {
      ctx.value.drawImage(
        icons[_img],
        point.x * BL.value - (iSize * BL.value) / scale.value / 2,
        point.y * BL.value - (iSize * BL.value) / scale.value / 2,
        (iSize * BL.value) / scale.value,
        (iSize * BL.value) / scale.value
      );
    }

    // 辅助函数：绘制圆角矩形
    function roundedRect(ctx, x, y, width, height, radius, isStroke = false) {
      ctx.beginPath();
      ctx.moveTo(x + radius, y);
      ctx.lineTo(x + width - radius, y);
      ctx.arcTo(x + width, y, x + width, y + radius, radius);
      ctx.lineTo(x + width, y + height - radius);
      ctx.arcTo(x + width, y + height, x + width - radius, y + height, radius);
      ctx.lineTo(x + radius, y + height);
      ctx.arcTo(x, y + height, x, y + height - radius, radius);
      ctx.lineTo(x, y + radius);
      ctx.arcTo(x, y, x + radius, y, radius);
      ctx.closePath();

      if (isStroke) {
        ctx.stroke();
      } else {
        ctx.fill();
      }
    }

    // 添加文字传感器数值（带圆角边框和半透明背景）当缩放倍率大于20时出现
    if (
      point.TypeName.includes("传感器") === true &&
      point.Data &&
      point.Data.length > 0 && scale.value > 20
    ) {
      // 设置字体样式
      ctx.value.font = `0.5px Arial`;
      const textColor = "#FFF"; // 白色字体
      const borderColor = "rgba(100, 180, 255, 0.9)"; // 浅蓝色边框
      const bgColor = "rgba(100, 180, 255, 0.4)"; // 同色系半透明背景

      // 生成所有传感器数据文本数组
      const textLines = point.Data.map((item) => {
        const pointName = item.IotAgreementHostPoint.point_name || "";
        const unit = item.IotAgreementHostPoint.unit || "";
        let status =
          item.IotAgreementHostPoint.status !== undefined
            ? item.IotAgreementHostPoint.status
            : "";
        
        // 当 point.TypeId == 71 时，需要特殊计算
        if ((point.TypeId == 71 || point.TypeId == 69) && status !== "" && status !== undefined && status !== null) {
          const numValue = Number(status);
          if (numValue === 0) {
            status = 0;
          } else {
            // 实际数值 = (status - 400) / 1600 * 5，保留1位小数
            const calculatedValue = ((numValue - 400) / 1600) * 5;
            status = calculatedValue.toFixed(1);
          }
        }

        if(point.TypeId == 86 || point.TypeId == 84 || point.TypeId == 81) {
          const numValue = Number(status);
          if (numValue === 0) {
            status = 0;
          } else {
            status = numValue.toFixed(1);
          }
        }
        
        return `${pointName} : ${status} ${unit}`;
      });

      // 计算文字位置（图标右侧10px处，垂直居中对齐）
      const textX = point.x * BL.value;
      const textY = point.y * BL.value ; // 垂直居中调整
      const padding = 0.2; // 文字周围的内边距
      const borderRadius = 0.5; // 圆角半径
      const lineHeight = 0.7; // 行高
      const lineSpacing = 0.1; // 行间距

      // 测量所有文本的宽度，找出最宽的一行
      let maxWidth = 0;
      textLines.forEach((line) => {
        const textMetrics = ctx.value.measureText(line);
        if (textMetrics.width > maxWidth) {
          maxWidth = textMetrics.width;
        }
      });

      // 计算背景框的位置和大小
      const bgX = textX - padding - maxWidth / 2;
      const bgY = textY - 6.5; // 基于10px字体的位置调整
      const bgWidth = maxWidth + padding * 2;
      const bgHeight =
        textLines.length * (lineHeight + lineSpacing) -
        lineSpacing +
        padding * 2;

      // 绘制圆角背景
      ctx.value.fillStyle = bgColor;
      roundedRect(ctx.value, bgX, bgY + 7, bgWidth, bgHeight, borderRadius);

      // 绘制圆角边框
      ctx.value.strokeStyle = borderColor;
      ctx.value.lineWidth = 0.1;
      roundedRect(
        ctx.value,
        bgX,
        bgY + 7,
        bgWidth,
        bgHeight,
        borderRadius,
        true
      );

      // 绘制所有文字行（确保在最上层）
      ctx.value.fillStyle = textColor;
      textLines.forEach((line, index) => {
        const lineMetrics = ctx.value.measureText(line);
        const lineY =
          textY + 3 + padding + index * (lineHeight + lineSpacing) - 2;
        ctx.value.fillText(line, textX - lineMetrics.width / 2, lineY);
      });
    }

    // if (point.target == "device" && point.IsOpen) {
    //   ctx.value.strokeStyle = window.customConfigUrl.openColor;
    // } else if (point.target == "device" && point.IsAlarm) {
    //   ctx.value.strokeStyle = window.customConfigUrl.faultColor;
    // } else if (point.target == "device" && point.IsFault) {
    //   ctx.value.strokeStyle = window.customConfigUrl.faultColor;
    // }

    ctx.value.shadowBlur = 0;
  });

  // 恢复上层画布状态
  ctx.value.restore();

  // 绘制tooltip（上层最顶层）
  if (activeMarker.value && activeMarker.value.target) {
    drawTooltip();
    cursorStyle.value = "pointer";
  } else {
    cursorStyle.value = "";
  }
};

// 动画循环（保持不变）
const animate = () => {
  const time = Date.now() / 1000;
  throttledRedraw();
  requestAnimationFrame(animate);
};

// 鼠标移动处理（保持不变，只影响上层Canvas）
let mouseMoveTimeout = null;
const onMouseMove = (e) => {
  if (!BL.value) return;
  if (dragging.value === null) {
    if (mouseMoveTimeout) {
      cancelAnimationFrame(mouseMoveTimeout);
    }
    mouseMoveTimeout = requestAnimationFrame(() => {
      handleMouseMove(e);
    });
    return;
  }
  isDragging.value = true;
  const rect = canvasRef.value.getBoundingClientRect();
  mouseX.value = Math.floor(e.clientX - rect.left);
  mouseY.value = Math.floor(e.clientY - rect.top);
  const deltaX = mouseX.value - startDragX.value;
  const deltaY = mouseY.value - startDragY.value;
  
  if (dragging.value === "background") {
    offsetX.value = e.clientX - startX.value;
    offsetY.value = e.clientY - startY.value;
    // 拖动背景时，标记为非缩放状态，正常更新底层背景
    if (isZooming.value) {
      isZooming.value = false;
      if (zoomEndTimer) {
        clearTimeout(zoomEndTimer);
        zoomEndTimer = null;
      }
    }
    throttledReBGdraw();
  } else {
    const originalXDelta = deltaX / scale.value;
    const originalYDelta = deltaY / scale.value;

    const newOriginalX = startPointX.value + originalXDelta;
    const newOriginalY = startPointY.value + originalYDelta;
    currentPoint.x = newOriginalX / BL.value;
    currentPoint.y = newOriginalY / BL.value;

    // currentPointX.value = currentPoint.x;
    // currentPointY.value = currentPoint.y;

    let closestLine = null;
    let minDistance = Infinity;
    referenceLines.forEach((lineY) => {
      const distance = Math.abs(currentPoint.y - lineY);
      if (distance < snapThreshold && distance < minDistance) {
        minDistance = distance;
        closestLine = lineY;
      }
    });

    activeReferenceLine.value = closestLine;
    if (closestLine !== null) {
      const easingFactor = 0.2;
      currentPoint.y =
        currentPoint.y + (closestLine - currentPoint.y) * easingFactor;
      if (Math.abs(currentPoint.y - closestLine) < 10) {
        currentPoint.y = closestLine;
      }
    }
  }

  throttledRedraw(); // 只重绘上层动态内容
};

const handleMouseMove = (e) => {
  const rect = canvasRef.value.getBoundingClientRect();
  const x = e.clientX - rect.left;
  const y = e.clientY - rect.top;

  const originalX = (x - offsetX.value) / scale.value;
  const originalY = (y - offsetY.value) / scale.value;

  const mouseMoved =
    Math.abs(x - lastMousePosition.value.x) > 2 ||
    Math.abs(y - lastMousePosition.value.y) > 2;

  if (!mouseMoved && activeMarker.value.target) {
    return;
  }

  lastMousePosition.value = { x, y };

  let newActiveMarker = { target: "" };

  points.forEach((point) => {
    // 使用距离计算而不是路径检测，更精确且不受 scale 影响
    if (point.TypeId == SELECT_ACTION_TYPE.value || SELECT_ACTION_TYPE.value == "") {
      const pointX = point.x * BL.value;
      const pointY = point.y * BL.value;
      // 计算检测半径（与绘制时保持一致：pointSize * 0.2 * BL）
      // 绘制时半径是 (pointSize * scale * 0.2 * BL) / scale = pointSize * 0.2 * BL
      const detectRadius = pointSize.value * 0.2 * BL.value;
      const distance = Math.sqrt(
        Math.pow(originalX - pointX, 2) + Math.pow(originalY - pointY, 2)
      );
      if (distance <= detectRadius) {
        newActiveMarker = point;
      }
    }
  });

  if (
    newActiveMarker.target !== activeMarker.value.target ||
    newActiveMarker.DeviceId !== activeMarker.value.DeviceId
  ) {
    activeMarker.value = newActiveMarker;
    throttledRedraw(); // 只重绘上层动态内容
  }
};

const handleMouseOut = () => {
  if (activeMarker.value.target) {
    activeMarker.value = { target: "" };
    throttledRedraw(); // 只重绘上层动态内容
  }
};

// 开始拖拽
const startDrag = (e) => {
  const rect = canvasRef.value.getBoundingClientRect();
  const mouseX = e.clientX - rect.left;
  const mouseY = e.clientY - rect.top;

  const x = e.clientX - rect.left;
  const y = e.clientY - rect.top;
  const originalX = (x - offsetX.value) / scale.value;
  const originalY = (y - offsetY.value) / scale.value;
  
  if (isEdit.value) {
    points.forEach((point) => {
      if (point.TypeId == SELECT_ACTION_TYPE.value || SELECT_ACTION_TYPE.value == "") {
        const pointX = point.x * BL.value;
        const pointY = point.y * BL.value;
        // 计算检测半径（与绘制时保持一致：pointSize * 0.2 * BL）
        const detectRadius = pointSize.value * 0.2 * BL.value;
        const distance = Math.sqrt(
          Math.pow(originalX - pointX, 2) + Math.pow(originalY - pointY, 2)
        );
        if (distance <= detectRadius) {
          dragging.value = "point";
          currentPoint = point;
          startPointX.value = point.x * BL.value;
          startPointY.value = point.y * BL.value;
          startDragX.value = mouseX;
          startDragY.value = mouseY;
          return;
        }
      }
    });
  }

  if(dragging.value == "point") {
    return
  }

  dragging.value = "background";
  startX.value = e.clientX - offsetX.value;
  startY.value = e.clientY - offsetY.value;
};

const mouseUp = () => {
  endDrag(1);
};

const mouseLeave = () => {
  endDrag(2);
};

const endDrag = (type) => {
  if (type == 1 && dragging.value == "point") {
    // 拖拽图标结束时 可以调用接口上传坐标信息
    updateDevice(currentPoint.DeviceId, {
      coordinate_x: currentPoint.x,
      coordinate_y: currentPoint.y,
    });
  }
  dragging.value = null;
  handleMouseOut();

  if (currentPoint.path) {
    currentPoint.path = new Path2D();
    currentPoint.path.arc(
      currentPoint.x * BL.value,
      currentPoint.y * BL.value,
      pointSize.value * BL.value,
      0,
      2 * Math.PI
    );
    currentPoint.path.closePath();
  }
};

const handleProgressChange2 = (e) => {
  selectAreaId.value = e.area;
  scale.value = 2;
  centerPoint(e);
};

// 优化：处理滚轮缩放（缩放过程中降低背景重绘频率）
const handleWheel = (e) => {
  e.preventDefault();
  const rect = canvasRef.value.getBoundingClientRect();
  const mouseX = e.clientX - rect.left;
  const mouseY = e.clientY - rect.top;
  const imgX = (mouseX - offsetX.value) / scale.value;
  const imgY = (mouseY - offsetY.value) / scale.value;
  const zoomIntensity = 1;
  const wheel = e.deltaY < 0 ? 1 : -1;
  const newScale = Math.max(
    0.75,
    Math.min(25, scale.value + wheel * zoomIntensity)
  );
  offsetX.value = mouseX - imgX * newScale;
  offsetY.value = mouseY - imgY * newScale;
  scale.value = newScale;

  // 标记正在缩放
  isZooming.value = true;

  // 清除之前的定时器
  if (zoomEndTimer) {
    clearTimeout(zoomEndTimer);
  }

  // 缩放过程中：使用低频率背景重绘，优先重绘上层内容
  throttledReBGdrawDuringZoom();
  throttledRedraw();

  // 缩放结束后，延迟一定时间再完整重绘背景
  zoomEndTimer = setTimeout(() => {
    isZooming.value = false;
    // 缩放结束后完整重绘背景
    drawBackground();
    throttledRedraw();
  }, 150); // 150ms后认为缩放结束
};

// 处理Canvas点击
const handleCanvasClick = (e) => {
  isDragging.value = false;
  const rect = canvasRef.value.getBoundingClientRect();
  const x = e.clientX - rect.left;
  const y = e.clientY - rect.top;

  const originalX = (x - offsetX.value) / scale.value;
  const originalY = (y - offsetY.value) / scale.value;

  console.log(
    `点击坐标: (${(originalX / BL.value).toFixed(1)}, ${(
      originalY / BL.value
    ).toFixed(1)})`
  );

  let clickedPoint = false;

  points.forEach((point) => {
    if (point.TypeId == SELECT_ACTION_TYPE.value || SELECT_ACTION_TYPE.value == "") {
      const pointX = point.x * BL.value;
      const pointY = point.y * BL.value;
      // 计算检测半径（与绘制时保持一致：pointSize * 0.2 * BL）
      const detectRadius = pointSize.value * 0.2 * BL.value;
      const distance = Math.sqrt(
        Math.pow(originalX - pointX, 2) + Math.pow(originalY - pointY, 2)
      );
      if (distance <= detectRadius) {
        clickedPoint = true;
        if (point.target == "camera") {
          selectPoint(point);
          selectedPointId.value = null;
        } else {
          // currentPointX.value = point.x;
          // currentPointY.value = point.y;
          selectPoint(point);
        }
      }
    }
  });

  if (!clickedPoint) {
    selectedPointId.value = null;
    throttledRedraw(); // 只重绘上层动态内容
  }
};

// 重置视图（修改：重置时更新底层背景）
const resetView = () => {
  scale.value = 1;
  offsetX.value = 0;
  offsetY.value = 0;
  selectedPointId.value = null;
  progress.value = 0;
  selectAreaId.value = 1;

  initCanvas();
};

// 注册引导前的准备工作（同时注册 /plan 和 /index 路径）
const prepareGuide = () => {
  resetView();

  // 确保设备类型列表可见
  if (!showFlag.value) {
    showFlag.value = true;
  }

  return nextTick();
};

registerPagePrepare("/shiyan-plan", prepareGuide);

// 选择点
const selectPoint = (data) => {
  let some = dialogList.filter((d) => {
    if (d.target === "camera") {
      return d.id == data.CamAddress;
    } else {
      return d.id == data.DeviceId;
    }
  });
  if (some && some.length) {
    some[0].show = true;
    return;
  }

  let max_index = 1100;
  dialogList.forEach((d) => {
    if (d.index > max_index) {
      max_index = d.index;
    }
  });

  dialogList.push({
    show: true,
    id: data.target == "device" ? data.DeviceId : data.CamAddress,
    TypeId: data.TypeId,
    AreaId: data.AreaId,
    TypeName: data.TypeName,
    index: max_index + 1,
    type: data.TypeId,
    extra: data,
    target: data.target,
    info: data.target == "device" ? "" : data,
  });
};

// 点居中显示（修改：居中时更新底层背景）
const centerPoint = (point) => {
  if (!point || !canvasRef.value) return;

  const targetOffsetX = 0 - point.x * BL.value * scale.value;
  const targetOffsetY = 0 - point.y * BL.value * scale.value;

  const startOffsetX = offsetX.value;
  const startOffsetY = offsetY.value;

  const duration = 800;
  const startTime = Date.now();

  isAnimating.value = true;

  const translateAnimate = () => {
    const elapsed = Date.now() - startTime;
    const progress = Math.min(elapsed / duration, 1);
    const ease = 1 - Math.pow(1 - progress, 3);

    offsetX.value = startOffsetX + (targetOffsetX - startOffsetX) * ease;
    offsetY.value = startOffsetY + (targetOffsetY - startOffsetY) * ease;

    // 动画过程中更新背景和上层内容
    // 动画时降低背景重绘频率以提高性能
    if (progress % 0.1 < 0.05) {
      // 每10%进度重绘一次背景
      throttledReBGdraw();
    }
    throttledRedraw();

    if (progress < 1) {
      requestAnimationFrame(translateAnimate);
    } else {
      isAnimating.value = false;
      // 动画结束后完整重绘
      drawBackground();
      throttledRedraw();

      setTimeout(() => {
        throttledRedraw();
      }, 1200);
    }
  };

  requestAnimationFrame(translateAnimate);
};

// 关闭对话框
const closeDialog = (e) => {
  dialogList[e].show = false;
  let hasshow = dialogList.filter((d) => {
    return d.show === true;
  });
  if (!hasshow || !hasshow.length) {
    dialogList.forEach((d, i) => {
      dialogList.splice(i, 1);
    });
  }
};

const openCameraDialog = (equipment) => {
  let point = points.filter((d) => {
    return d.target === "camera" && d.CamId == equipment.Remark6;
  });

  if (point && point[0]) {
    selectPoint(point[0]);
  }
};

const upDialogZindex = (e) => {
  let max_index = 0;
  dialogList.forEach((d) => {
    if (d.index > max_index) {
      max_index = d.index;
    }
  });
  dialogList[e].index = max_index + 1;
};

// MQTT 相关
const mqttClientRef = ref(null);
// 全局事件监听器
let pointValueUpdateHandler = null;

// 通过 provide 将 MQTT 实例提供给子组件
provide("mqttClient", mqttClientRef);

// 初始化全局事件监听
const initPointValueListener = () => {
  // 使用全局的 MQTT 客户端（在 App.vue 中已初始化）
  if (window.mqttClient) {
    mqttClientRef.value = window.mqttClient;
  }

  // 监听全局点位更新事件
  pointValueUpdateHandler = (event) => {
    const { pointId, status } = event.detail;
    const _id = pointId;
    const data = status;

    // 循环 points 找出一项下面 Points 中包含 _id 的那一项
    const matchedPoint = points.find((point) => {
      if (!point.Points || !Array.isArray(point.Points)) {
        return false;
      }
      // 支持字符串和数字类型的 id 匹配
      let isMatched = point.Points.some(
        (pointId) => String(pointId) === String(_id)
      );
      
      if(isMatched) {
        point.Data.forEach((d)=> {
          if(d.IotAgreementHostPoint.id == _id) {
            d.status = data;
            // 同时更新 IotAgreementHostPoint 中的 status，确保模板能正确显示
            if (d.IotAgreementHostPoint) {
              d.IotAgreementHostPoint.status = data;
            }
          }
        })
        throttledRedraw(); // 只重绘上层动态内容
      }
    });

    if (matchedPoint) {
      matchedPoint.Data.forEach((d)=> {
        if(d.IotAgreementHostPoint.id === _id) {
          d.status = data;
          // 同时更新 IotAgreementHostPoint 中的 status，确保模板能正确显示
          if (d.IotAgreementHostPoint) {
            d.IotAgreementHostPoint.status = data;
          }
        }
      })
      
      // 同步更新 dialogList 中对应 dialogEle 的数据
      dialogList.forEach((dialogItem) => {
        if (dialogItem.target === "device" && dialogItem.id === matchedPoint.DeviceId) {
          // 更新 extra 中的数据
          if (dialogItem.extra && dialogItem.extra.Data) {
            dialogItem.extra.Data.forEach((d) => {
              if (d.IotAgreementHostPoint && d.IotAgreementHostPoint.id === _id) {
                d.status = data;
                // 同时更新 IotAgreementHostPoint 中的 status
                d.IotAgreementHostPoint.status = data;
              }
            });
          }
        }
      });
    }
  };
  
  window.addEventListener('pointValueUpdate', pointValueUpdateHandler);
  console.log("已注册全局点位更新事件监听器");
};

// 初始化
onMounted(() => {
  // getJson();

  initCanvas();
  getAreaFun();
  getDeviceTypeFun();
  getDevicesFun();
  // initChart();
  window.addEventListener("resize", resizeCanvas);

  // 初始化全局事件监听
  initPointValueListener();
});

onUnmounted(() => {
  if (mouseMoveTimeout) {
    cancelAnimationFrame(mouseMoveTimeout);
  }
  if (zoomEndTimer) {
    clearTimeout(zoomEndTimer);
  }
  window.removeEventListener("resize", resizeCanvas);
  // 清理离屏Canvas
  if (offscreenCanvas) {
    offscreenCanvas = null;
    offscreenCtx = null;
  }
  // 取消注册页面准备工作
  unregisterPagePrepare("/plan");
  unregisterPagePrepare("/shiyan-plan");

  // 清理全局事件监听器
  if (pointValueUpdateHandler) {
    try {
      window.removeEventListener('pointValueUpdate', pointValueUpdateHandler);
      pointValueUpdateHandler = null;
      console.log("已取消全局点位更新事件监听器");
    } catch (error) {
      console.error("取消全局事件监听器失败:", error);
    }
  }
});
</script>
  
  <style lang="less" scoped>
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  user-select: none;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
}
#app {
  position: relative;
}
body {
  font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
  background: linear-gradient(135deg, #1a2a6c, #b21f1f50, #1a2a6c);
  color: #fff;
  min-height: 100vh;
  overflow-x: hidden;
}

.container {
  width: 100%;
  height: calc(100vh - 84px);
  margin: 0;
}

.app-container {
  width: 100%;
  height: 100%;
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

.tabbar-content {
  position: absolute;
  left: 20px;
  top: 50%;
  transform: translateY(-50%);
  z-index: 99;
  display: flex;
  flex-wrap: wrap;
  font-size: 14px;
  flex-direction: column;
  background: rgba(0, 0, 0, 0.1);
  backdrop-filter: blur(4px);
  border-radius: 4px;
  padding: 10px;
}

.tabbar-item {
  margin-bottom: 2px;
  cursor: pointer;
  line-height: 20px;
  display: flex;
  padding: 2px 4px;
  border-radius: 4px;
  color: #fff;
  &-img {
    width: 20px;
    height: 20px;
    background: #fff;
    border-radius: 50%;
    margin-right: 4px;
    padding: 2px;
  }
  img {
    width: 16px;
    height: 16px;
  }
}

.tabbar-item.show {
  background: rgba(255, 255, 255, 0.5);
}

.action-bar {
  width: 100px;
  height: 40px;
  position: absolute;
  right: 30px;
  top: 30px;
  z-index: 99;
  display: flex;
  justify-content: space-between;
  &-item {
    width: 30px;
    height: 30px;
    cursor: pointer;
    border-radius: 50%;
    img {
      width: 30px;
      height: 30px;
    }
  }
}
.spicon {
  background: #fff;
  img {
    width: 20px;
    height: 20px;
    margin-left: 5px;
    margin-top: 5px;
  }
}
canvas {
  transform: translateZ(0); /* 触发GPU加速 */
  will-change: transform; /* 提示浏览器该元素可能会动画，提前优化 */
}
.canvas-container {
  flex: 1;
  min-width: 300px;
  height: 100%;
  background: rgba(0, 0, 0, 0.25);
  border-radius: 15px;
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
  backdrop-filter: blur(4px);
  border: 0;
  padding: 10px;
  box-sizing: border-box;
  position: relative;
}

.canvas-wrapper {
  position: relative;
  flex: 1;
  border-radius: 10px;
  overflow: hidden;
  background: #000;
  box-shadow: 0 10px 20px rgba(0, 0, 0, 0.4);
  height: 100%;
}

/* 新增：两层Canvas样式，确保完全重叠 */
.bg-canvas,
.main-canvas {
  display: block;
  width: 100%;
  height: 100%;
  position: absolute;
  top: 0;
  left: 0;
}
/* 底层背景Canvas层级更低，上层主Canvas层级更高（用于交互） */
.bg-canvas {
  z-index: 1;
  background: #222;
}
.main-canvas {
  z-index: 2;
  transition: transform 0.3s ease;
}

.reset-btn {
  margin: 0;
}

button {
  display: block;
  width: 100%;
  padding: 12px;
  background: linear-gradient(to right, #ff9966, #ff5e62);
  color: white;
  border: none;
  border-radius: 8px;
  font-size: 16px;
  font-weight: 600;
  cursor: pointer;
  transition: all 0.3s ease;
  margin-top: 10px;
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
}

button:active {
  transform: translateY(1px);
}

@keyframes center-point {
  0% {
    transform: scale(1);
  }
  50% {
    transform: scale(1.3);
  }
  100% {
    transform: scale(1);
  }
}

@media (max-width: 768px) {
  .app-container {
    flex-direction: column;
  }
}

.smooth-transition {
  transition: transform 0.8s cubic-bezier(0.22, 0.61, 0.36, 1);
}

::v-deep .el-dialog {
  backdrop-filter: blur(10px) !important;
  background: #fff8dc;
  padding: 0;
  border-radius: 6px;
  overflow: hidden;
  backdrop-filter: blur(4px);
}

::v-deep .el-dialog__header {
  background: transparent !important;
  box-shadow: none !important;
  text-align: left;
  line-height: 20px;
  padding: 0;
}
::v-deep .el-dialog__headerbtn {
  background: transparent !important;
  box-shadow: none !important;
  line-height: 20px;
  padding: 15px !important;
  height: 20px !important;
  width: 20px !important;
}

.dialog-footer {
  display: flex;
  justify-content: flex-end;
  padding: 15px;
}

.progress-container {
  width: 90%;
  height: 48px;
  background: rgba(255, 255, 255, 0.1);
  backdrop-filter: blur(4px);
  border-radius: 4px;
  position: absolute;
  bottom: 30px;
  left: 50%;
  transform: translateX(-50%);
  box-shadow: 0 4px 10px rgba(255, 255, 255, 0.1);
  display: flex;
  padding: 4px 0;
}
.spslider {
  bottom: 70px;
}
.bar-content {
  width: 380px;
  height: 40px;
  position: absolute;
  z-index: 1;
  top: 3px;
  left: 10px;
}

.progress-bar {
  width: 100%;
}

.charts-content {
  width: 800px;
  height: 240px;
  background: rgba(255, 255, 255, 0.1);
  backdrop-filter: blur(4px);
  border-radius: 4px;
  position: absolute;
  bottom: 30px;
  left: 50%;
  transform: translateX(-50%);
  box-shadow: 0 4px 10px rgba(255, 255, 255, 0.1);
  display: flex;
  .line-chart {
    width: 100%;
    height: 100%;
  }
}

/deep/ .el-slider__button {
  height: 10px !important;
  width: 10px !important;
}

.slider-content {
  width: 100%;
  display: flex;
  left: 0;
  justify-content: space-around;
}
.slider-item {
  width: 10%;
  background: #00000050;
  font-size: 14px;
  text-align: center;
  line-height: 40px;
  border-radius: 4px;
  cursor: pointer;
  color: #fff;
}
.is-select-area {
  background: rgba(255, 255, 255, 0.5);
}

.current-icon-info {
  position: absolute;
  bottom: 20px;
  left: 20px;
  background: rgba(255, 255, 255, 0.2);
  height: 30px;
  z-index: 9999;
  border-radius: 4px;
  padding: 0 10px;
  span {
    margin-right: 20px;
    line-height: 30px;
  }
}
</style>
  