<template>
  <div class="Screen cm-layout">
    <!-- 新增：原生模态框 -->
    <div class="native-modal" :class="{ active: showUnListModal }">
      <div class="modal-mask" @click="closeModal" />
      <div class="modal-content">
        <div class="modal-header">
          <h3>离线设备列表</h3>
          <button class="modal-close" @click="closeModal">×</button>
        </div>
        <div id="unListContainer" class="modal-body">
          <!-- 动态渲染的列表将插入此处 -->
        </div>
      </div>
    </div>
    <div class="a_0">
      <!-- <cesium
				ref="CesiumMap"
				:mapinfo="mapinfo"
				:onload="mapOnload"
				@click="mapClick"
			/> -->
    </div>
    <div class="b_0">
      <header>
        <div class="b1_a">
          <h2>{{ title }}</h2>
          <div class="b1_a1">
            <span id="nowTime" class="font_digit" />
          </div>
          <div class="b1_a2 logo" />
        </div>
      </header>
      <section class="b_2">
        <div class="b_2a">
          <div class="b2_a">
            <div class="b2_a1">
              <div v-for="(item, i) in area.b2_a1" :key="item.id || i" class="view-item">
                <div class="view-flx">
                  <Carousel :ref="'view_' + item.id" />
                </div>
              </div>
            </div>
            <div id="b2_a2" class="b2_a2">
              <div v-for="(item, i) in area.b2a_2a" :key="item.id || i" class="b2a_2a view-item">
                <div class="view-flx">
                  <Center-Views :ref="'view_' + item.id" />
                </div>
              </div>
              <div class="b2a_2b">
                <div v-for="(item, i) in area.b2a_2b" :key="item.id || i" class="view-item">
                  <div class="view-flx">
                    <Carousel :ref="'view_' + item.id" />
                  </div>
                </div>
              </div>
            </div>
            <div class="b2_a3">
              <div v-for="(item, i) in area.b2_a3" :key="item.id || i" class="view-item">
                <div class="view-flx">
                  <Carousel :ref="'view_' + item.id" />
                </div>
              </div>
            </div>
          </div>
        </div>
      </section>
    </div>
  </div>
</template>

<script>
import { Tools } from '@/assets/js/common.js'
import { reqApi, Config } from '@/assets/js/httpApi.js'
import { Highchart } from '@/assets/js/chartTemplates.js'
// import cesium from '@/components/Cesium';
import Carousel from '@/components/Carousel'
import CenterViews from '@/components/CenterViews'
import Cookies from 'js-cookie'

// style
// import '@/../static/js/Cesium/Widgets/widgets.css';
export default {
  components: { Carousel, CenterViews },
  data() {
    return {
      bianpoLinePoinArr: [], // 边坡线平均坐标点集合
      jingjieLinePointArr: [], // 警戒线平均坐标点集合

      area: {
        b2_a1: [],
        b2a_2b: [],
        b2_a3: [],
        views: []
      },
      title: '',
      // 原有数据保持不变
      showUnListModal: false, // 控制模态框显示
      currentUnList: [] // 当前离线设备列表
    }
  },
  mounted() {
    var that = this
    const roles = Cookies.get('roles')

    this.$nextTick(() => {
      this.pageApi = Config.getModuleInfo(this)

      // 时钟
      var nowTimeEl = this.$el.querySelector('#nowTime')
      var Loop1 = qf.Async.intervalLoop(
        1000,
        function() {
          var dateTime = Tools.Dates.format('yyyy年MM月dd日 HH:mm:ss')
          dateTime = dateTime.replace(/年|月|日/g, function(a) {
            return '<span>' + a + '</span>'
          })
          nowTimeEl.innerHTML = dateTime
          // Loop1.clear();
        },
        function() {
          var dateTime = Tools.Dates.format('yyyy年MM月dd日 HH:mm:ss')
          dateTime = dateTime.replace(/年|月|日/g, function(a) {
            return '<span>' + a + '</span>'
          })
          nowTimeEl.innerHTML = dateTime
        }
      )

      qf(
        function() {
          this.initData()
        }.bind(this)
      )

      // 报警弹窗
      ;(function reqAlarmsPopup() {
        that.pageApi.alterAbnormal('get', {}).then(res => {
          var list = res.body || []
          qf.Async.loopList.call(
            this,
            list,
            function(key, item, next) {
              var alarmDict = [
                '',
                { name: '红色报警', color: 'red' },
                { name: '橙色报警', color: 'orange' },
                { name: '黄色报警', color: '#FFC900' },
                { name: '蓝色报警', color: 'blue' }
              ]
              var alarmItem = alarmDict[item.alarmlevel]
              var name = item.equipname
              let divStr = `</div><div class="btn-groub" style="margin-top:5px;display:flex;justify-content:center;"><button data-id="0"  >解除报警</button><button data-id="1" style="margin-left:30px;">关闭报警</button><button data-id="2" style="margin-left:30px;">不再提示</button></div></div>`
              if (roles === '普通用户') {
                divStr = `</div><div class="btn-groub" style="margin-top:5px;display:flex;justify-content:center;"><button data-id="1" style="margin-left:30px;">关闭报警</button><button data-id="2" style="margin-left:30px;">不再提示</button></div></div>`
              }
              var Notify = new qf.UI.Notify({
                type: 'warning',
                duration: 10000,
                autoOpen: true,
                message:
                  '<div><div>' +
                  name +
                  ' <span style="color:' +
                  alarmItem.color +
                  '">' +
                  alarmItem.name +
                  '</span> ' +
                  item.evalue +
                  '(' +
                  item.unit +
                  ')</div><div>时间：' +
                  item.time +
                  divStr,
                onClose: function(v) {
                  setTimeout(function() {
                    next()
                  }, ~~(1 + Math.random() * 3) * 1000)
                },
                onClick: function(tag) {
                  if (tag.nodeName === 'BUTTON') {
                    var self = this
                    var id = tag.dataset.id
                    if (id === '0') {
                      // 解除报警
                      Notify.pause()
                      that.openRelieveAlarm(
                        { equipno: item.equipno },
                        function() {
                          Notify.close(true)
                          reqAlarmsPopup()
                        },
                        function() {
                          Notify.recover()
                        }
                      )
                    } else if (id === '1') {
                      // 关闭报警
                      Notify.pause()
                      qf.UI.popupLayer({
                        title: '提示',
                        className: 'confirm',
                        closeBtnColor: '#eee',
                        width: '350px',
                        html: '点击确认后，该条数据仍会处于报警状态，但是不会再提示，您确认要这么做吗？',
                        confirm: function(el) {
                          var parm = { id: item.id }
                          that.pageApi.alterAbnormal('put', parm).then(res => {
                            if (res.code === 200) {
                              Notify.close()
                            }
                          })
                        },
                        close: function() {
                          Notify.recover()
                        }
                      })
                    } else if (id === '2') {
                      // 不再提示
                      delete this.onClose
                      Notify.close()
                    }
                  }
                }
              })
            },
            function() {
              setTimeout(function() {
                reqAlarmsPopup()
              }, 15000)
            }
          )
        })
      }).bind(this)()
    })
  },
  methods: {
    // 创建区域列表
    initData(list) {
      var self = this
      this.pageApi.getViewsConfig().then(res => {
        if (res.code === 200) {
          var areas = res.body || []
          for (var area of areas) {
            this.area[area.areaid] = area.views
            this.area.views = this.area.views.concat(area.views)
          }

          // onload
          this.$nextTick(() => {
            new qf.Async.intervalLoop(600000, function() {
              // 加载数据
              self.loadViewData(self.area.views)
            })
          })
        }
      })

      // 标题
      reqApi.system.moduleConfig.reqScreenTitle('get').then(res => {
        var body = res.body || {}
        this.title = body.title || ''
      })
    },

    // 加载模块数据
    loadViewData(list) {
      // list = list.splice(0, 1);
      // 模块数据加工
      var viewDatas = {}
      for (var item of list) {
        var viewCode = 'view_' + item.id
        viewDatas[viewCode] = item
        var view = (this.$refs[viewCode] || [])[0]

        view &&
          view.createViews(item).then(panels => {
            this.moduleInfoDistribute(panels)
          })
      }
    },

    // 模块信息分发
    moduleInfoDistribute(panels) {
      Tools.asyncLoop.call(
        this,
        panels,
        function(key, panel, next) {
          var item = panel.item
          var param = {
            range: item.range,
            subitem: item.subitem,
            values: item.values,
            deviceid: item.deviceid
          }
          var type = panel.type
          if (type === 't_1') {
            // 干滩
            reqApi.common.requstEdge('get', item.requrl, param).then(res => {
              var data = res.body || {}
              var opts = {
                chartConfig: {}
              }
              Highchart.template.high(panel.el, data, opts)
            })
          } else if (type === 't_2') {
            // 库水位
            reqApi.common.requstEdge('get', item.requrl, param).then(res => {
              var data = res.body || {}
              var opts = {
                chartConfig: {
                  plotOptions: {
                    column: {
                      grouping: false,
                      shadow: false,
                      borderWidth: 0
                    }
                  }
                }
              }
              Highchart.template.high(panel.el, data, opts)
            })
          } else if (type === 't_3') {
            // 表面位移
            reqApi.common.requstEdge('get', item.requrl, param).then(res => {
              var data = res.body || {}
              var opts = {
                chartConfig: {
                  xAxis: {
                    labels: {
                      rotation: 0 // 设置轴标签旋转角度
                    }
                  }
                },
                warningLine: 'double'
              }
              Highchart.template.high(panel.el, data, opts)
            })
          } else if (type === 't_4') {
            // 内部位移
            reqApi.common.requstEdge('get', item.requrl, param).then(res => {
              var data = res.body || {}
              var chartType = param.values === '水平' ? 'bar' : 'column'
              var opts = {
                chartConfig: {
                  chart: {
                    type: chartType,
                    borderWidth: 0,
                    marginLeft: 55
                  }
                },
                warningLine: 'double'
              }
              Highchart.template.high(panel.el, data, opts)
            })
          } else if (type === 't_5') {
            // 浸润线
            reqApi.common.requstEdge('get', item.requrl, param).then(res => {
              var data = res.body || {}
              var opts = {
                chartConfig: {
                  chart: { type: 'bar', marginLeft: 55 },
                  xAxis: {
                    labels: {
                      rotation: 0 // 设置轴标签旋转角度
                    }
                  },
                  /*
								plotOptions: {
									series: {
										stacking: 'normal'
									}
								}, */
                  tooltip: {
                    formatter: function() {
                      return this.series.name + '：' + this.key + '<br/>' + Math.abs(this.y) + ' 米'
                    }
                  }
                },
                callback: function(key, extend, give) {
                  if (key === 'series' && give[key].constructor.name === 'Array') {
                    var mobj = extend[key]
                    var asobj = give[key]
                    var arr = mobj[1].data
                    var len = arr.length
                    for (var i = 0; i < len; i++) {
                      var val = arr[i]
                      arr[i] = 0 - val
                    }
                  }
                }
              }
              Highchart.template.high(panel.el, data, opts)
            })
          } else if (type === 't_6') {
            // 降雨量
            reqApi.common.requstEdge('get', item.requrl, param).then(res => {
              var data = res.body || {}
              var opts = {
                chartConfig: {
                  xAxis: [
                    {
                      labels: {
                        rotation: -25 // 设置轴标签旋转角度
                      }
                    }
                  ],
                  series: [
                    {
                      type: 'column',
                      color: '#1DF7F9',
                      borderRadius: '.05rem'
                    },
                    { type: 'column', color: '#FAE728', borderRadius: '.05rem' }
                  ]
                }
              }
              Highchart.template.high(panel.el, data, opts)
            })
          } else if (type === 't_7') {
            // 排流量
            reqApi.common.requstEdge('get', item.requrl, param).then(res => {
              var data = res.body || {}
              var opts = {
                chartConfig: {
                  chart: {
                    type: 'cylinder',
                    marginLeft: 10,
                    marginRight: -5,
                    options3d: {
                      enabled: true,
                      alpha: 10,
                      beta: 5, // 左右
                      depth: 50,
                      viewDistance: 0
                    }
                  },
                  plotOptions: {
                    series: {
                      depth: 25
                      // colorByPoint: true
                    }
                  },
                  series: [
                    { type: 'column', color: '#00B2FC', showInLegend: false }
                    // {type:'spline', color:'#FBC000', showInLegend:false, dashStyle:'Dash', lineWidth:1,}
                  ]
                }
              }
              Highchart.template.high(panel.el, data, opts)
            })
          } else if (type === 't_8') {
            // 水质
            reqApi.common.requstEdge('get', item.requrl, param).then(res => {
              res = {
                body: [
                  {
                    unit: 'ug/m³',
                    svalue: '12.00',
                    time: 1723248388000,
                    sensroname: '气象站东PM25',
                    sensorid: '37068520080801'
                  },
                  {
                    unit: 'ug/m³',
                    svalue: '12.00',
                    time: 1723248388000,
                    sensroname: '气象站东PM10',
                    sensorid: '37068520080802'
                  },
                  {
                    unit: 'db',
                    svalue: '52.20',
                    time: 1723248389000,
                    sensroname: '气象站东噪音',
                    sensorid: '37068520080803'
                  },
                  {
                    unit: '℃',
                    svalue: '29.25',
                    time: 1723248390000,
                    sensroname: '气象站东温度',
                    sensorid: '37068520080804'
                  },
                  {
                    unit: '%RH',
                    svalue: '82.81',
                    time: 1723248391000,
                    sensroname: '气象站东湿度',
                    sensorid: '37068520080805'
                  },
                  {
                    unit: 'm/s',
                    svalue: '2.80',
                    time: 1723248393000,
                    sensroname: '气象站东风速',
                    sensorid: '37068520080807'
                  },
                  {
                    unit: '',
                    svalue: '西南',
                    time: 1723248392000,
                    sensroname: '气象站东风向',
                    sensorid: '37068520080808'
                  }
                ],
                code: 200,
                msg: '操作成功'
              }

              var types = ['PM25', 'PM10', '噪音', '温度', '湿度', '风速', '风向']
              var data = res.body || {}
              var info = document.createElement('ul')
              info.className = 'info'
              // var len = data.length > 5 ? 5 : data.length, li;
              // 2024/1/9(周二) 去掉5个的限制
              var len = data.length
              var li
              for (var i = 0; i < len; i++) {
                var item = data[i]
                if (!(i % 2)) {
                  li = document.createElement('li')
                }
                var div = document.createElement('div')

                // 2024/8/10(周六) 为了缩减设备名称, 类型名称过长超出边界，添加对类型过滤
                var name = types.find(function(val, i) {
                  if (item.sensroname.includes(val)) {
                    return 1
                  }
                })

                if (i % 2) {
                  // div.innerHTML = '<font>'+item.sensroname+'</font><span>'+item.svalue+'</span>';
                  div.innerHTML = '<font>' + name + '</font><span>' + item.svalue + '</span>'
                } else {
                  // 左
                  // div.innerHTML = '<span>'+item.svalue+'</span><font>'+item.sensroname+'</font>';
                  div.innerHTML = '<span>' + item.svalue + '</span><font>' + name + '</font>'
                }
                li.appendChild(div)
                info.appendChild(li)
              }
              panel.el.innerHTML = ''
              panel.el.appendChild(info)
            })
          } else if (type === 't_9') {
            // 渗流量
            reqApi.common.requstEdge('get', item.requrl, param).then(res => {
              var data = res.body || {}
              var opts = {
                chartConfig: {
                  series: [
                    { type: 'column', color: '#00B2FC' }
                    // {type:'spline', color:'#FBC000', dashStyle:'ShortDot',}
                  ]
                }
              }
              Highchart.template.high(panel.el, data, opts)
            })
          } else if (type === 't_10') {
            // 设备信息
            reqApi.common.requstEdge('get', item.requrl, param).then(res => {
              var data = res.body || {}
              var that = this
              panel.info.sum = data.all
              panel.info.online = data.online
              panel.info.offline = data.unline

              // 离线设备点击事件
              this.currentUnList = data.unlineList // 存储数据
              this.addLastLiClickEvent()

              var opts = {
                chartConfig: {
                  chart: {
                    backgroundColor: 'transparent',
                    marginBottom: null,
                    marginLeft: null
                  },
                  title: {
                    floating: true,
                    text: '总数<br/>' + data.all,
                    verticalAlign: 'middle',
                    y: 22,
                    floating: true,
                    style: {
                      color: '#00f6ff'
                    }
                  },
                  tooltip: {
                    formatter: function(e) {
                      return this.key + '：' + this.y
                    }
                  },
                  plotOptions: {
                    pie: {
                      allowPointSelect: true,
                      borderWidth: 0,
                      minSize: 130,
                      // size:200,
                      cursor: 'pointer',
                      dataLabels: {
                        enabled: true,
                        distance: 10,
                        format: '<b>{point.name}</b>: {point.y}',
                        style: {
                          color: 'black'
                        }
                      }
                    }
                  },
                  series: [
                    {
                      type: 'pie',
                      innerSize: '70%',
                      name: '设备数量',
                      data: [
                        { name: '在线', y: data.online, color: '#48abe1' },
                        { name: '离线', y: data.unline, color: '#064f94' }
                      ]
                    }
                  ]
                },
                isSeriesData: true,
                type: item.type
              }
              Highchart.template.high(panel.el, data, opts)
            })
          } else if (type === 't_11') {
            // 实时数据
            reqApi.common.requstEdge('get', item.requrl, param).then(res => {
              var datas = res.body || {}
              var target = panel.el
              var scrolling = target.parentNode
              var dataList = []
              for (var item of datas) {
                var data = item.data
                var time = item.time
                if (data) {
                  for (var kitem of item.keys) {
                    dataList.push({
                      name: item.name + ' ' + kitem.name,
                      value: data[kitem.key],
                      unit: item.eqdanwei,
                      time: time
                    })
                  }
                }
              }

              // 创建列表
              var frag = document.createDocumentFragment()
              var ul = document.createElement('ul')
              ul.calssName = 'list'
              for (var item of dataList) {
                var li = document.createElement('li')
                li.innerHTML =
                  '<h5>' +
                  item.name +
                  '</h5><div><span>' +
                  item.value +
                  '</span><font>(' +
                  item.unit +
                  ')</font></div><font>' +
                  item.time +
                  '</font>'
                ul.appendChild(li)
              }
              target.firstElementChild.innerHTML = ''
              target.firstElementChild.appendChild(ul)
              qf.UI.scrollingPreview(scrolling)
            })
          } else if (type === 't_12') {
            // 报警信息
            reqApi.common.requstEdge('get', item.requrl, param).then(res => {
              var dataList = res.body || {}
              var target = panel.el
              var scrolling = target.parentNode

              // 创建列表
              var frag = document.createDocumentFragment()
              var ul = document.createElement('ul')
              ul.calssName = 'list'
              for (var item of dataList) {
                var li = document.createElement('li')
                var value = item.evalue + '(' + item.eqdanwei + ')'
                li.innerHTML =
                  '<h5><i class="level' +
                  item.alarmlevel +
                  '"></i>' +
                  item.equipname +
                  '</h5><span> ' +
                  value +
                  '</span><font>' +
                  item.time +
                  '</font>'
                ul.appendChild(li)
              }
              target.firstElementChild.innerHTML = ''
              target.firstElementChild.appendChild(ul)

              qf.UI.scrollingPreview(scrolling)
            })
          } else if (type === 't_13') {
            // 实时天气
            var city = panel.item.city_weather || 'weihai'
            var param = { c: 'code', a: 'getcode', id: 48, icon: 1, py: city }
            reqApi.getWeather(param).then(res => {
              var body = document.createElement('div')
              setTimeout(function() {
                body.innerHTML = res
                var styles = body.querySelectorAll('link')
                for (var style of styles) {
                  body.removeChild(style)
                }
                var scripts = body.querySelectorAll('script')
                for (var script of scripts) {
                  script.parentNode.removeChild(script)
                }

                // 查看图片
                var img = body.querySelector('#day_1 .img').firstElementChild
                img.src = 'http://plugin.tianqistatic.com/static/' + img.src.split('static/')[1]

                // 修剪dom
                var mobile05 = body.querySelector('#mobile05')
                var mleft = mobile05.firstElementChild
                var cityCtn = mleft.firstElementChild
                var cityEl = document.createElement('font')
                cityEl.innerText = '(' + cityCtn.firstElementChild.innerText + ')'
                mleft.nextElementSibling.firstElementChild.appendChild(cityEl)
                mobile05.removeChild(mleft)
                mobile05.removeChild(mobile05.lastElementChild)
                var childs = mobile05.children
                for (var child of childs) {
                  child.innerHTML = child.firstElementChild.innerHTML
                }
                panel.el.innerHTML = '<div class="weather" style="padding-top:0.2rem">' + body.innerHTML + '</div>'
              }, 300)
            })
          } else if (type === 't_14') {
            // 视频监控
            param.id = item.id
            reqApi.common.requstEdge('get', item.requrl, param).then(res => {
              var data = res.body || {}
              var vparam = {
                id: data.v_id,
                appKey: data.v_appkey,
                appSecret: data.v_appsecret,
                name: data.v_name,
                channel: data.v_channel,
                remark: data.v_remark
              }
              panel.setData(vparam)
            })
          } else if (type === 't_15') {
            // 监测点
            var initModule = () => {
              reqApi.common.requstEdge('get', item.requrl, param).then(res => {
                var data = res.body || {}
                panel.setData(data, Tools, this.pageApi, initModule)
              })
            }
            initModule()
            /* new qf.Async.intervalLoop(30000, function(){
						initModule();
					}, function(){
						initModule();
					}) */
          } else if (type === 't_16') {
            // 外坡比
            reqApi.common.requstEdge('get', item.requrl, param).then(res => {
              var data = res.body || {}
              var opts = {
                chartConfig: {
                  plotOptions: {
                    column: {
                      grouping: false,
                      shadow: false,
                      borderWidth: 0
                    }
                  }
                }
              }
              Highchart.template.high(panel.el, data, opts)
            })
          } else if (type === 't_17') {
            // 浸润线剖面
            reqApi.common.requstEdge('get', item.requrl, param).then(res => {
              var data = res.body || {}
              this.cuCharts(panel.el, data, {
                axisLineWidth: 2,
                axisColor: 'pink'
                // 边界限制现在默认启用
              })
            })
          }
          next()
        },
        function() {}
      )
    },
    // 新增：为 t_10 类下最后一个 li 添加点击事件
    addLastLiClickEvent() {
      // 定位类名为 t_10 的容器（根据实际 DOM 结构调整选择器）
      const t10Container = document.querySelector('.t_10')
      if (!t10Container) return

      // 获取容器内所有 li 元素
      const lis = t10Container.getElementsByTagName('li')
      if (lis.length === 0) return

      // 取最后一个 li 元素
      const lastLi = lis[lis.length - 1]

      // 添加点击事件
      lastLi.addEventListener('click', event => {
        // 点击事件逻辑（示例）
        this.showUnListModal = true // 显示模态框
        this.renderUnList()
        // 可在此处添加自定义业务逻辑（如弹框、数据请求等）
      })
    },
    // 新增：渲染离线设备列表
    renderUnList() {
      const container = document.getElementById('unListContainer')
      container.innerHTML = '' // 清空旧内容

      // 创建列表
      const ul = document.createElement('ul')
      ul.style.listStyle = 'none'
      ul.style.padding = '0 15px'

      this.currentUnList.forEach(item => {
        const li = document.createElement('li')
        li.style.padding = '8px 0'
        li.style.borderBottom = '1px solid #e5e5e5'
        li.innerHTML = `
					<span style="width: 40%; display: inline-block;">${item.equipname}</span>
				`
        ul.appendChild(li)
      })

      container.appendChild(ul)
    },

    // 新增：关闭模态框
    closeModal() {
      this.showUnListModal = false
    },
    cuCharts(ctn, data, opts) {
      //
      var cuCharts = function(ctn, data, opts) {
        var ctn = (this.ctn = typeof element === 'string' ? document.getElementById(ctn) : ctn)
        if (!ctn) return console.log('%c cuCharts Error Element is a invalid param! ', 'color:red')
        var self = this
        var setDpr = 1
        var dpr = window.devicePixelRatio || qf.envir.dpr
        self.dpr = dpr * setDpr
        self.Cache = {}
        !ctn.style.position && (ctn.style.position = 'relative')
        var canvas = (this.canvas = document.createElement('canvas'))
        canvas.style.cssText = 'position:absolute;height:100%;width:100%;'
        this.ctx = canvas.getContext('2d')
        ctn.appendChild(canvas)
        var defaults = (this.defaults = {
          altitude: 1000,
          damHeight: data.dissect.height || 100,
          damWidth: data.dissect.width || 100,
          axisLineWidth: 6,
          axisColor: 'red',
          margin: { top: 14, right: 0, bottom: 0, left: 18 }
        })
        var left = defaults.margin.left * this.dpr
        var top = defaults.margin.top * this.dpr
        var right = defaults.margin.right * this.dpr
        var bottom = defaults.margin.bottom * this.dpr
        this.ladders = data.steps || []
        this.devices = this.formatDeviceParam(data.devices)
        var ladderInfo = this.discernDamHeight(this.ladders)
        this.options = {
          damWidth: defaults.damWidth,
          damHeight: ladderInfo.damHeight,
          axisLineWidth: defaults.axisLineWidth || 6,
          stepAverageHeight: ladderInfo.averageHeight,
          initX: defaults.margin.left * dpr + defaults.axisLineWidth / 2,
          initY: defaults.margin.top * dpr,
          bottomDist: defaults.margin.bottom * dpr,
          rightDist: defaults.margin.right * dpr,
          waterArea: {
            startAngle: data.dissect.sdeg,
            radius: data.dissect.diameter
          },
          alarm: data.alarm,
          axisLineWidth: opts.axisLineWidth || defaults.axisLineWidth,
          axisColor: opts.axisColor || defaults.axisColor,
          enableBoundaryLimit: opts.enableBoundaryLimit !== false // 默认启用边界限制，除非明确设置为false
        }
        this.init()
      }
      cuCharts.prototype = {
        init: function() {
          this.setModule()
          this.onresize()
        },
        drawAxis: function(color, damHeight) {
          var lw = this.options.axisLineWidth
          var wHalf = lw / 2
          var initX = this.options.initX
          var endX = this.options.endX
          var endY = this.options.endY
          var axisYSX = initX - wHalf
          var axisXEY = endY + lw
          var axisYEY = endY + wHalf
          this.drawLiner({
            sx: axisYSX,
            sy: 0,
            ex: axisYSX,
            ey: axisXEY,
            width: lw,
            color: color
          })
          var fontY = ~~(this.options.damPxHeight / 2 + this.options.initY)
          var fontSize = 12
          var axisXText = '剖面高度' + ~~damHeight + '米'
          this.drawText({
            x: this.options.initY / 2 + 2,
            y: fontY,
            size: fontSize,
            width: fontSize * this.dpr,
            text: axisXText,
            color: '#fff',
            align: 'center',
            vertical: 'center'
          })
          this.drawLiner({
            sx: initX,
            sy: axisYEY,
            ex: endX,
            ey: axisYEY,
            width: lw,
            color: color
          })
        },
        drawSteps: function(ladders) {
          // 初始化边坡线采样点数组
          this.bianpoLinePoinArr = []

          ladders = ladders || [0]
          var initX = this.options.initX
          var initY = this.options.initY
          var cumulationX = initX
          var cumulationY = initY
          var damLength = this.options.endX
          var damDepth = this.options.endY
          var pxRetioY = this.options.pxRetioY
          var averageHeight = this.options.stepAverageHeight * pxRetioY
          var damPxWidth = this.options.damPxWidth
          var damPxHeight = this.options.damPxHeight
          var len = ladders.length
          var stepWidth = damPxWidth / (len + 0)
          var stepHeight = damPxHeight / (len + 0)
          var slopeRatio = damPxHeight / damPxWidth
          var maxStepDepth = { ratio: slopeRatio }
          this.Cache.ladders = []
          for (var i = 0; i < len; i++) {
            var item = ladders[i]
            var setWidth = item.width * this.dpr * this.options.pxRetioX
            var setHeight = item.height * pxRetioY || averageHeight
            var slope = item.slope || 0
            var aCoord = this.byAngleAndHeightToCoord(slope, setHeight)
            var fEndX, fEndY
            if (setWidth) {
              fEndX = cumulationX + setWidth
              stepWidth = (damLength - (cumulationX + setWidth + aCoord.x * this.dpr)) / (len - (i + 1))
            } else {
              fEndX = cumulationX + (stepWidth - aCoord.x * this.dpr)
            }
            if (setHeight) {
              fEndY = cumulationY + setHeight
              stepHeight = (damDepth - (cumulationY + setHeight)) / (len - (i + 1))
            } else {
              fEndY = cumulationY + stepHeight
            }
            var lEndX = fEndX + aCoord.x * this.dpr
            var lEndY = fEndY + aCoord.y * this.dpr
            var stepDepthRatio = (damDepth - ~~lEndY) / (damLength - lEndX)
            if (stepDepthRatio && stepDepthRatio < maxStepDepth.ratio && i < len - 1) {
              maxStepDepth = { ratio: stepDepthRatio, x: lEndX, y: lEndY }
            }
            // 绘制边坡的线
            this.drawLiner({
              sx: cumulationX,
              sy: cumulationY,
              ex: fEndX,
              ey: cumulationY,
              width: 2,
              color: '#8b8b8b'
            })
            // 新增：生成10个采样点并添加到边坡线数组（平坡）
            const stepPoints1 = this.getSampledPoints(cumulationX, cumulationY, fEndX, cumulationY, 20)
            this.bianpoLinePoinArr.push(...stepPoints1)

            this.drawLiner({
              sx: fEndX,
              sy: cumulationY,
              ex: lEndX,
              ey: lEndY,
              width: 2,
              color: '#8b8b8b'
            })

            // 新增：生成10个采样点并添加到边坡线数组（平坡间过渡连接线）

            const stepPoints2 = this.getSampledPoints(fEndX, cumulationY, lEndX, lEndY, 20)
            this.bianpoLinePoinArr.push(...stepPoints2)

            this.Cache.ladders[i] = {
              sx: cumulationX,
              sy: cumulationY,
              ex: fEndX,
              ey: cumulationY,
              slope: slope,
              slopeX: lEndX,
              slopeY: lEndY,
              xy: aCoord.xy
            }
            cumulationX = lEndX
            cumulationY = lEndY
          }
          this.drawLiner({
            sx: cumulationX,
            sy: cumulationY,
            ex: damLength,
            ey: damDepth,
            width: 2,
            color: '#8b8b8b'
          })

          // 新增：生成10个采样点并添加到边坡线数组（最后平坡收尾线）

          const stepPoints3 = this.getSampledPoints(cumulationX, cumulationY, damLength, damDepth, 20)
          this.bianpoLinePoinArr.push(...stepPoints3)

          console.log('bianpoLinePoinArr', JSON.stringify(this.bianpoLinePoinArr))

          var damHeight = (cumulationY - this.options.initY) / pxRetioY
          return (maxStepDepth.damHeight = damHeight), maxStepDepth
        },
        drawDevice: function(list) {
          var lineW = 4 * this.dpr
          var deviceW = 6 * this.dpr
          var deviceH = 3 * this.dpr
          var initX = this.options.initX
          var endX = this.options.endX
          var endY = this.options.endY
          var ladders = this.Cache.ladders || []
          var fontSize = 11
          var fontTopDev = fontSize * this.dpr + 3
          this.options.deviceCoords = []
          for (var device of list) {
            var holeX = ~~(endX - device.x * this.options.pxRetioX)
            var depthText = '埋深' + device.depth + '米'
            var holeDistY = device.holeDepth * this.options.pxRetioY
            var deviceY = device.value * this.options.pxRetioY
            var ladderSY = this.xPixelToStepsYPixel(holeX)
            this.drawText({
              x: holeX,
              y: ladderSY - fontTopDev,
              size: fontSize,
              text: depthText,
              color: 'red',
              align: 'center'
            })
            var holeEndY = ladderSY + holeDistY
            holeEndY = holeEndY > endY ? endY : holeEndY
            this.drawLiner({
              sx: holeX,
              sy: ladderSY,
              ex: holeX,
              ey: holeEndY + device.holeBaseElevation, // 控制穿孔线的长度
              width: lineW,
              color: 'grey'
            })
            var deviceLY = ladderSY + deviceY
            var deviceEY = deviceLY + deviceH / 2
            var deviceSY = deviceEY - deviceH
            this.drawLiner({
              sx: holeX,
              sy: deviceSY + device.holeBaseElevation,
              ex: holeX,
              ey: deviceEY + device.holeBaseElevation,
              width: deviceW,
              color: '#3403CB'
            })
            // 水位值的x,y坐标
            this.options.deviceCoords.push({
              x: holeX,
              y: deviceLY + device.holeBaseElevation,
              stepY: ladderSY
            })
            var valFont = 11
            var valueSY = ~~(deviceSY - valFont * this.dpr - 6)
            var text = '水位值' + device.waterValue + '米'

            this.drawText({
              x: holeX,
              y: valueSY + device.holeBaseElevation,
              size: valFont,
              text: text,
              color: 'white',
              align: 'center'
            })
          }

          // 连接所有水位值，使用曲线连接，只在首尾延长
          var deviceCoords = this.options.deviceCoords || []

          // 新增：如果水位值坐标在边坡边界外面，则不参与连线（按点级别过滤）
          if (this.options.enableBoundaryLimit && this.bianpoLinePoinArr && this.bianpoLinePoinArr.length > 1) {
            deviceCoords = deviceCoords.filter(function(pt) {
              return !this.checkIfPointOutsideSlope(pt)
            }.bind(this))
          }

          // 按X坐标从左到右排序（因为设备X坐标是 endX - device.x 计算的，可能需要排序）
          deviceCoords.sort(function(a, b) {
            return a.x - b.x
          })

          // 新增：若过滤后不足2个点，则不绘制连接线
          if (deviceCoords.length < 2) {
            return
          }

          if (deviceCoords.length >= 2) {
            // 计算延伸距离（可以根据需要调整）
            var extensionLength = 30 * this.dpr // 延伸30像素

            // 准备曲线点数组
            var curvePoints = []
            var needCurveConnection = false // 标记是否需要曲线连接

            // 处理左侧延伸点
            var firstDevice = deviceCoords[0]
            var secondDevice = deviceCoords[1]
            var chartMinX = this.options.initX
            var chartMaxX = this.options.endX
            var chartMinY = this.options.initY
            var chartMaxY = this.options.endY
            var shrinkDistance = 15 * this.dpr // 安全距离 15px

            // 计算左侧延伸点
            if (firstDevice && secondDevice && Math.abs(firstDevice.x - secondDevice.x) > 1) {
              var dx1 = secondDevice.x - firstDevice.x
              var dy1 = secondDevice.y - firstDevice.y
              var length1 = Math.sqrt(dx1 * dx1 + dy1 * dy1)
              var unitX1 = dx1 / length1
              var unitY1 = dy1 / length1

              var startX = firstDevice.x - unitX1 * extensionLength
              var startY = firstDevice.y - unitY1 * extensionLength

              // 限制起点坐标
              startX = Math.max(chartMinX, Math.min(chartMaxX, startX))
              startY = Math.max(chartMinY, Math.min(chartMaxY, startY))

              // 如果启用边界限制，找到延伸线与边坡线的交点（按y最小选择）
              if (this.options.enableBoundaryLimit && this.bianpoLinePoinArr.length > 0) {
                var leftIntersections = []
                for (var m = 0; m < this.bianpoLinePoinArr.length - 1; m++) {
                  var slopePoint1 = this.bianpoLinePoinArr[m]
                  var slopePoint2 = this.bianpoLinePoinArr[m + 1]
                  var interL = this.getLineIntersection(
                    firstDevice.x, firstDevice.y, startX, startY,
                    slopePoint1.x, slopePoint1.y, slopePoint2.x, slopePoint2.y
                  )
                  if (interL && interL.t >= 0 && interL.t <= 1 && interL.u >= 0 && interL.u <= 1) {
                    leftIntersections.push(interL)
                  }
                }

                if (leftIntersections.length > 0) {
                  // 选择y最小（更靠上）的交点，若并列取t最小
                  var closestLeft = leftIntersections.reduce(function(best, cur) {
                    if (!best) return cur
                    if (cur.y < best.y - 1e-6) return cur
                    if (Math.abs(cur.y - best.y) <= 1e-6) return (cur.t < best.t ? cur : best)
                    return best
                  }, null)
                  var leftDx = firstDevice.x - closestLeft.x
                  var leftDy = firstDevice.y - closestLeft.y
                  var leftLength = Math.sqrt(leftDx * leftDx + leftDy * leftDy)
                  if (leftLength > 0) {
                    var leftUnitX = leftDx / leftLength
                    var leftUnitY = leftDy / leftLength
                    startX = closestLeft.x + leftUnitX * shrinkDistance
                    startY = closestLeft.y + leftUnitY * shrinkDistance
                    needCurveConnection = true // 标记需要曲线连接
                  } else {
                    startX = closestLeft.x
                    startY = closestLeft.y
                  }
                }
              }

              curvePoints.push({ x: startX, y: startY })
            }

            // 添加所有设备点
            curvePoints.push(...deviceCoords)

            // 处理右侧延伸点
            var lastDevice = deviceCoords[deviceCoords.length - 1]
            var secondLastDevice = deviceCoords[deviceCoords.length - 2]

            // 计算右侧延伸点
            if (lastDevice && secondLastDevice && Math.abs(lastDevice.x - secondLastDevice.x) > 1) {
              var dx2 = lastDevice.x - secondLastDevice.x
              var dy2 = lastDevice.y - secondLastDevice.y
              var length2 = Math.sqrt(dx2 * dx2 + dy2 * dy2)
              var unitX2 = dx2 / length2
              var unitY2 = dy2 / length2

              var rightEndX = lastDevice.x + unitX2 * extensionLength
              var rightEndY = lastDevice.y + unitY2 * extensionLength

              // 限制终点坐标
              rightEndX = Math.max(chartMinX, Math.min(chartMaxX, rightEndX))
              rightEndY = Math.max(chartMinY, Math.min(chartMaxY, rightEndY))

              // 如果启用边界限制，找到延伸线与边坡线的交点（按y最小选择）
              if (this.options.enableBoundaryLimit && this.bianpoLinePoinArr.length > 0) {
                var rightIntersections = []
                for (var n = 0; n < this.bianpoLinePoinArr.length - 1; n++) {
                  var slopePoint1 = this.bianpoLinePoinArr[n]
                  var slopePoint2 = this.bianpoLinePoinArr[n + 1]
                  var interR = this.getLineIntersection(
                    lastDevice.x, lastDevice.y, rightEndX, rightEndY,
                    slopePoint1.x, slopePoint1.y, slopePoint2.x, slopePoint2.y
                  )
                  if (interR && interR.t >= 0 && interR.t <= 1 && interR.u >= 0 && interR.u <= 1) {
                    rightIntersections.push(interR)
                  }
                }

                if (rightIntersections.length > 0) {
                  var closestRight = rightIntersections.reduce(function(best, cur) {
                    if (!best) return cur
                    if (cur.y < best.y - 1e-6) return cur
                    if (Math.abs(cur.y - best.y) <= 1e-6) return (cur.t < best.t ? cur : best)
                    return best
                  }, null)
                  var rightDx = lastDevice.x - closestRight.x
                  var rightDy = lastDevice.y - closestRight.y
                  var rightLength = Math.sqrt(rightDx * rightDx + rightDy * rightDy)
                  if (rightLength > 0) {
                    var rightUnitX = rightDx / rightLength
                    var rightUnitY = rightDy / rightLength
                    rightEndX = closestRight.x + rightUnitX * shrinkDistance
                    rightEndY = closestRight.y + rightUnitY * shrinkDistance
                    needCurveConnection = true // 标记需要曲线连接
                  } else {
                    rightEndX = closestRight.x
                    rightEndY = closestRight.y
                  }
                }
              }

              curvePoints.push({ x: rightEndX, y: rightEndY })
            }

            // 两点之閴的直线是否穿过边坡，穿过则用曲线，其余用直线；两端延长线用直线
            if (curvePoints.length >= 2) {
              var ctx = this.ctx
              ctx.lineWidth = 2
              ctx.strokeStyle = 'blue'

              ctx.beginPath()
              ctx.moveTo(curvePoints[0].x, curvePoints[0].y)
              var lastCtrl = null

              for (var k = 1; k < curvePoints.length; k++) {
                var pt1 = curvePoints[k - 1]
                var pt2 = curvePoints[k]

                // 判断是否是两端的延长线
                var isEdgeExtension = (k === 1) || (k === curvePoints.length - 1)

                if (isEdgeExtension && k === curvePoints.length - 1 && k >= 2) {
                  // 末段延伸：依据上一段切线自然延申（非近直线）
                  var prev = curvePoints[k - 2]
                  var tvx = pt1.x - prev.x; var tvy = pt1.y - prev.y
                  var tlen = Math.sqrt(tvx * tvx + tvy * tvy) || 1
                  tvx /= tlen; tvy /= tlen
                  var segLenR = Math.hypot(pt2.x - pt1.x, pt2.y - pt1.y)
                  var dR = Math.min(28 * this.dpr, segLenR * 0.6)
                  var cp2 = { x: pt1.x + tvx * dR, y: pt1.y + tvy * dR }
                  if (this.doesQuadraticCurveCrossBoundary(pt1, cp2, pt2)) {
                    var safeCp = this.getControlPointForCurve(pt1, pt2)
                    ctx.quadraticCurveTo(safeCp.x, safeCp.y, pt2.x, pt2.y)
                  } else {
                    ctx.quadraticCurveTo(cp2.x, cp2.y, pt2.x, pt2.y)
                  }
                } else if (isEdgeExtension && k === 1 && curvePoints.length >= 3) {
                  // 首段延伸：在第一个设备点保持切线连续
                  var next = curvePoints[2] || pt2
                  var dirx = next.x - pt2.x; var diry = next.y - pt2.y
                  var dirLen = Math.sqrt(dirx * dirx + diry * diry) || 1
                  dirx /= dirLen; diry /= dirLen
                  var segLenL = Math.hypot(pt2.x - pt1.x, pt2.y - pt1.y)
                  var dL = Math.min(28 * this.dpr, segLenL * 0.6)
                  var cpL = { x: pt2.x - dirx * dL, y: pt2.y - diry * dL }
                  if (this.doesQuadraticCurveCrossBoundary(pt1, cpL, pt2)) {
                    var safeCpL = this.getControlPointForCurve(pt1, pt2)
                    ctx.quadraticCurveTo(safeCpL.x, safeCpL.y, pt2.x, pt2.y)
                  } else {
                    ctx.quadraticCurveTo(cpL.x, cpL.y, pt2.x, pt2.y)
                  }
                } else {
                  // 其他段：原直/曲线判断
                  var lineSegmentCrosses = this.isLineSegmentCrossesBoundary(pt1, pt2)
                  if (lineSegmentCrosses) {
                    var controlPt = this.getControlPointForCurve(pt1, pt2)
                    ctx.quadraticCurveTo(controlPt.x, controlPt.y, pt2.x, pt2.y)
                  } else {
                    ctx.lineTo(pt2.x, pt2.y)
                  }
                }
              }

              ctx.stroke()
            }
          }
        },
        drawAlarmLine: function(alarms, Steps) {
          // 隐藏报警线 - 直接返回，不绘制任何报警线
          return

          // var alarmValues = alarms.value
          // var alarmLevel = alarms.alarmLevel
          // var deviceCoords = this.options.deviceCoords || []
          // var deviceSX = deviceCoords[0].x
          // var deviceSY = deviceCoords[0].y

          // var initX = this.options.initX
          // var initY = this.options.initY
          // var endY = this.options.endY
          // var endX = this.options.endX
          // var alarmLevelNum = ['red', 'orange', 'yellow', 'blue'] || Object.keys(alarmValues)
          // var alarmLine = {}

          // // 显示所有报警线，而不是只显示当前级别和相邻级别
          // for (var i = 0; i < alarmLevelNum.length; i++) {
          //   var levelColor = alarmLevelNum[i]
          //   if (alarmValues[levelColor]) {
          //     alarmLine[levelColor] = alarmValues[levelColor]
          //   }
          // }
          // var alarmRealParam = this.getAlarmRealParam(Object.values(alarmLine)) || Steps.ratio
          // var deviceSlopeRatio = alarmRealParam.ratio
          // // 定义报警线样式配置
          // var alarmLineStyles = {
          //   red: { width: 3, dashPattern: [] }, // 实线，较粗
          //   orange: { width: 2, dashPattern: [5, 5] }, // 虚线
          //   yellow: { width: 2, dashPattern: [2, 3] }, // 点线
          //   blue: { width: 2, dashPattern: [8, 3, 2, 3] } // 点划线
          // }

          // console.log('alarmLine对象:', alarmLine)
          // console.log('alarmValues对象:', alarmValues)

          // // 清除之前绘制的报警线（如果有的话）
          // // this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)

          // for (var key in alarmLine) {
          //   var value = alarmValues[key]
          //   console.log(`处理报警线: ${key}, 值: ${value}`)
          //   if (key && value) {
          //     var ladderSY = this.xPixelToStepsYPixel(deviceSX) - initX + 6
          //     var lineSY = value * this.options.pxRetioY + ladderSY - (deviceSX - initX) * deviceSlopeRatio
          //     var lineEX = (endY - lineSY) / deviceSlopeRatio

          //     // 获取当前报警线的样式配置
          //     var lineStyle = alarmLineStyles[key] || { width: 2, dashPattern: [] }

          //     /**
          //      * 新增逻辑处理绘制警戒线，超过边坡外的则不显示
          //         2025年8月30日14:38:22
          //      */
          //     const { ctx, alarmLevel, xPixelToStepsYPixel } = this
          //     // 获取边坡线的x坐标数组（确保顺序一致）
          //     const slopeXCoords = this.bianpoLinePoinArr.map(point => point.x)
          //     const pointCount = slopeXCoords.length

          //     // 警戒线的起始和终止坐标(用于分配平均坐标点集合)
          //     const startPoint = { x: initX, y: lineSY + initY }
          //     const endPoint = { x: lineEX, y: endY }

          //     // 清空警戒线数组
          //     this.jingjieLinePointArr = []

          //     // 线性插值计算每个x对应的y值
          //     slopeXCoords.forEach(x => {
          //       // 计算当前x在起始点和终止点之间的比例
          //       const t = (x - startPoint.x) / (endPoint.x - startPoint.x)
          //       // 线性插值计算y值
          //       const y = startPoint.y + t * (endPoint.y - startPoint.y)
          //       // 存入警戒线数组
          //       this.jingjieLinePointArr.push({ x, y })
          //     })

          //     // 获取边坡线的X坐标范围
          //     const slopeStartX = this.bianpoLinePoinArr[0].x
          //     const slopeEndX = this.bianpoLinePoinArr[this.bianpoLinePoinArr.length - 1].x

          //     // 报警线的原始坐标
          //     const originalStartX = startPoint.x
          //     const originalEndX = endPoint.x
          //     const originalStartY = startPoint.y
          //     const originalEndY = endPoint.y

          //     // 计算报警线的斜率
          //     const slope = (originalEndY - originalStartY) / (originalEndX - originalStartX)

          //     // 找到报警线与边坡线的所有交点
          //     const allIntersections = []

          //     // 遍历边坡线的每个线段，找到与报警线的交点
          //     for (let i = 0; i < this.bianpoLinePoinArr.length - 1; i++) {
          //       const slopePoint1 = this.bianpoLinePoinArr[i]
          //       const slopePoint2 = this.bianpoLinePoinArr[i + 1]

          //       // 计算报警线与当前边坡线段的交点
          //       const intersection = this.getLineIntersection(
          //         originalStartX,
          //         originalStartY,
          //         originalEndX,
          //         originalEndY, // 报警线
          //         slopePoint1.x,
          //         slopePoint1.y,
          //         slopePoint2.x,
          //         slopePoint2.y // 边坡线段
          //       )

          //       if (intersection) {
          //         allIntersections.push(intersection)
          //         console.log(`${key}报警线找到交点:`, intersection.x, intersection.y, '在边坡线段', i)
          //       }
          //     }

          //     // 使用最左和最右的交点作为截取点
          //     let intersectionStartX = originalStartX
          //     let intersectionStartY = originalStartY
          //     let intersectionEndX = originalEndX
          //     let intersectionEndY = originalEndY

          //     if (allIntersections.length > 0) {
          //       // 按X坐标排序
          //       allIntersections.sort((a, b) => a.x - b.x)

          //       // 使用最左边的交点作为起点
          //       intersectionStartX = allIntersections[0].x
          //       intersectionStartY = allIntersections[0].y
          //       console.log(`${key}报警线新起点:`, intersectionStartX, intersectionStartY)

          //       // 使用最右边的交点作为终点
          //       const lastIntersection = allIntersections[allIntersections.length - 1]
          //       intersectionEndX = lastIntersection.x
          //       intersectionEndY = lastIntersection.y
          //       console.log(`${key}报警线新终点:`, intersectionEndX, intersectionEndY)
          //     } else {
          //       console.log(`${key}报警线未找到交点，使用原始范围`)
          //     }

          //     // 绘制调整后的报警线
          //     this.drawLiner({
          //       sx: intersectionStartX,
          //       sy: intersectionStartY,
          //       ex: intersectionEndX,
          //       ey: intersectionEndY,
          //       width: 2,
          //       color: key
          //     })

          //     console.log(`${key}报警线绘制成功 - 交点范围: ${intersectionStartX} to ${intersectionEndX}`)

          //     console.log(`${key}报警线分段绘制完成 - 边坡范围: ${slopeStartX} to ${slopeEndX}`)
          //     console.log(`${key}报警线原始范围: ${originalStartX} to ${originalEndX}`)

          //     console.log('警戒线的值point', JSON.stringify(this.jingjieLinePointArr))
          //     console.log('边坡X范围:', slopeStartX, 'to', slopeEndX)
          //     console.log('报警线X范围:', originalStartX, 'to', originalEndX)
          //     // this.drawLiner({
          //     //   sx: initX,
          //     //   sy: lineSY + initY,
          //     //   ex: lineEX,
          //     //   ey: endY,
          //     //   width: 2,
          //     //   color: key
          //     // });
          //   }
          // }
        },
        getCirHeightDev: function(r, x) {
          return r - Math.sqrt(Math.pow(r, 2) - Math.pow(x, 2))
        },
        drawWaterArea: function(depth) {
          depth = 100
          var startAngle = this.options.waterArea.startAngle
          var radiusMultiple = this.options.waterArea.radius
          var angleRatio = 90 / 0.5
          var initArc = startAngle / angleRatio
          var ctx = this.ctx
          var initX = this.options.initX
          var arcOrigin = 1.5
          var arcStart = arcOrigin + initArc
          var sAngle = (arcStart - arcOrigin) * angleRatio
          var r1 = this.options['damPxWidth'] * radiusMultiple
          var waterApexToAxisXDist = Math.sin(sAngle * 0.017453293) * r1
          var sAngleToTopSpotDist = r1 - Math.cos(sAngle * 0.017453293) * r1
          var depth = (depth || 30) * this.options.pxRetioY + sAngleToTopSpotDist
          var firstDeviceX = this.devices[0].x * this.options.pxRetioX
          var ladderSY = this.xPixelToStepsYPixel(firstDeviceX + initX) - initX + 6
          var deviceToAxisXDist = firstDeviceX + waterApexToAxisXDist
          var deviceToAxisYDist = Math.sqrt(r1 * r1 - deviceToAxisXDist * deviceToAxisXDist)
          var deviceAreaToApexDev = r1 - deviceToAxisYDist - sAngleToTopSpotDist - ladderSY
          var firstDeviceY = this.options.damPxHeight - this.devices[0].value * this.options.pxRetioY
          firstDeviceY += deviceAreaToApexDev
          var ry = r1 + this.options.endY - sAngleToTopSpotDist - firstDeviceY
          var lineToY = r1 - sAngleToTopSpotDist - firstDeviceY
          var eAngle = Math.acos(lineToY / r1) / 0.017453293
          var arcEnd = eAngle / angleRatio + arcOrigin
          ctx.beginPath()
          ctx.arc(initX - waterApexToAxisXDist, ry, r1, arcStart * Math.PI, arcEnd * Math.PI, false)
          ctx.lineTo(initX, ry - lineToY)
          ctx.fillStyle = '#00A2E8'
          ctx.fill()
        },
        byAngleAndHeightToCoord: function(angle, height) {
          var devX = Math.tan(angle * 0.017453293) * height
          var xy = height / Math.cos(angle * 0.017453293)
          var devY = height - devX
          var yRatio = height / devX
          var x = 0
          var y = 0
          if (angle > 50) {
            (x = height), (y = devY * yRatio)
          } else {
            (x = devX), (y = 0)
          }
          return { x: x, y: y, xy: xy }
        },
        discernDamHeight(list) {
          var not = 0
          var set = 0
          var sum = 0
          for (var item of list) {
            var height = item.height
            height ? (set++, (sum += height)) : not++
          }
          var damHeight = not ? this.defaults.damHeight : sum
          var averageHeight = (damHeight - sum) / not
          return { averageHeight: averageHeight, damHeight: damHeight }
        },
        setModule: function() {
          this.ctx.clearRect(0, 0, this.SumWidth, this.SumHeight)
          var canvas = this.canvas
          var SumWidth = (this.SumWidth = this.ctn.clientWidth * this.dpr - this.ctn.clientLeft - 0)
          var SumHeight = (this.SumHeight = this.ctn.clientHeight * this.dpr - this.ctn.clientTop - 0)
          canvas.width = SumWidth * this.dpr
          canvas.height = SumHeight * this.dpr
          canvas.style.cssText =
            'position:absolute;top:0;left:0;height:' +
            SumHeight +
            'px;width:' +
            SumWidth +
            'px;user-select: none;-webkit-tap-highlight-color:rgba(0,0,0,0);padding:0;margin:0;border-width:0;box-sizing: border-box;'
          var top = this.options.initY
          var left = this.options.initX
          var bottom = this.options.bottomDist
          var right = this.options.rightDist
          var damPxWidth = SumWidth - (right + left)
          var damPxHeight = SumHeight - (bottom + top + this.options.axisLineWidth)
          var endX = SumWidth - right
          var endY = SumHeight - bottom - this.options.axisLineWidth
          this.options['endX'] = endX
          this.options['endY'] = endY
          this.options['damPxWidth'] = damPxWidth
          this.options['damPxHeight'] = damPxHeight
          this.options['pxRetioX'] = damPxWidth / this.defaults.damWidth
          this.options['pxRetioY'] = damPxHeight / this.options.damHeight
          this.options['HWRation'] = (endY - top) / (endX - left)
          this.drawChart()
        },
        formatDeviceParam: function(devices) {
          var devices = devices || []
          var arr = []
          for (var item of devices) {
            arr.push({
              x: item.jrx_coord_x,
              holeDepth: item.jrx_trepanning,
              value: item.depth,
              depth: item.jrx_burial,
              waterValue: item.stage,
              holeBaseElevation: item.holeBaseElevation
            })
          }
          return arr
        },
        getAlarmRealParam: function(aValues) {
          var aval = aValues[0]
          var coords = this.options.deviceCoords || []
          var valY = aval * this.options.pxRetioY
          var sy = valY + coords[0].stepY
          var sx = coords[0].x
          var ey = 0
          var ex = 0
          var len = coords.length
          for (var i = 1; i < len; i++) {
            var item = coords[i]
            ;(ey = valY + item.stepY), (ex = item.x)
          }
          return { ratio: (ey - sy) / (ex - sx), sy: sy, ey: ey }
        },
        drawChart: function() {
          var Steps = this.drawSteps(this.ladders)
          this.drawAxis(this.options.axisColor, Steps.damHeight)
          var depth = 55
          this.drawWaterArea(depth)
          this.drawDevice(this.devices)
          this.drawAlarmLine(this.options.alarm, Steps)
        },
        onresize: function() {
          qf.resize(() => {
            this.setModule()
          })
        },
        updateData: function(data) {
          data = data || {}
          this.stations = data.stations || this.stations
          this.points = data.points || this.points
          this.draw(this.cacheX + this.destX, true)
        },
        drawLiner: function(o) {
          var ctx = this.ctx
          ctx.beginPath()
          ctx.moveTo(o.sx, o.sy)
          ctx.lineTo(o.ex, o.ey)
          ctx.lineWidth = o.width
          ctx.strokeStyle = o.color
          ctx.stroke()
        },
        // 新增：绘制曲线方法
        drawCurve: function(points, width, color) {
          if (!points || points.length < 2) return

          var ctx = this.ctx
          ctx.beginPath()
          ctx.lineWidth = width || 2
          ctx.strokeStyle = color || 'blue'

          // 如果只有两个点，绘制简单的曲线
          if (points.length === 2) {
            ctx.moveTo(points[0].x, points[0].y)

            // 计算控制点
            var cp1x = points[0].x + (points[1].x - points[0].x) / 3
            var cp1y = points[0].y
            var cp2x = points[0].x + 2 * (points[1].x - points[0].x) / 3
            var cp2y = points[1].y

            ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, points[1].x, points[1].y)
          } else if (points.length >= 3) {
            // 多个点，使用平滑的贝塞尔曲线连接
            ctx.moveTo(points[0].x, points[0].y)

            for (var i = 1; i < points.length; i++) {
              var prevPoint = points[i - 1]
              var currPoint = points[i]
              var nextPoint = i < points.length - 1 ? points[i + 1] : null

              if (nextPoint) {
                // 计算当前点的控制点
                var controlX = currPoint.x
                var controlY = currPoint.y

                // 使用二阶贝塞尔曲线绘制到中点
                var midX = (currPoint.x + nextPoint.x) / 2
                var midY = (currPoint.y + nextPoint.y) / 2
                ctx.quadraticCurveTo(controlX, controlY, midX, midY)
              } else {
                // 最后一个点，直接连接
                var prevPrevPoint = i > 1 ? points[i - 2] : prevPoint
                var cp1x = prevPoint.x + (currPoint.x - prevPrevPoint.x) / 4
                var cp1y = prevPoint.y + (currPoint.y - prevPrevPoint.y) / 4
                ctx.quadraticCurveTo(cp1x, cp1y, currPoint.x, currPoint.y)
              }
            }
          }

          ctx.stroke()
        },
        // 计算两条线段的交点（数值稳定，返回x,y并附带t/u，含共线重叠处理）
        getLineIntersection: function(x1, y1, x2, y2, x3, y3, x4, y4) {
          const eps = 1e-9
          const dx1 = x2 - x1; const dy1 = y2 - y1
          const dx2 = x4 - x3; const dy2 = y4 - y3
          const denom = dx1 * dy2 - dy1 * dx2

          // 平行或共线
          if (Math.abs(denom) < eps) {
            // 是否共线：向量(x3-x1,y3-y1)与dx1,dy1的叉积≈0
            const cross = (x3 - x1) * dy1 - (y3 - y1) * dx1
            if (Math.abs(cross) > eps) return null

            // 共线：计算重叠区间，并取重叠端点中y最小的一个
            const len1sq = dx1 * dx1 + dy1 * dy1
            if (len1sq < eps) return null // 退化为点
            const t3 = ((x3 - x1) * dx1 + (y3 - y1) * dy1) / len1sq
            const t4 = ((x4 - x1) * dx1 + (y4 - y1) * dy1) / len1sq
            const tmin = Math.max(0, Math.min(t3, t4))
            const tmax = Math.min(1, Math.max(t3, t4))
            if (tmax + eps < tmin) return null // 无重叠

            // 两个候选端点
            const Ax = x1 + tmin * dx1; const Ay = y1 + tmin * dy1
            const Bx = x1 + tmax * dx1; const By = y1 + tmax * dy1
            let t = tmin; let ix = Ax; let iy = Ay
            if (By < Ay - eps || (Math.abs(By - Ay) <= eps && tmax < tmin)) {
              t = tmax; ix = Bx; iy = By
            }

            // 对应第二段的u
            const len2sq = dx2 * dx2 + dy2 * dy2
            let u = 0
            if (len2sq > eps) {
              u = ((ix - x3) * dx2 + (iy - y3) * dy2) / len2sq
            }
            return { x: ix, y: iy, t, u, colinear: true }
          }

          // 一般情况
          const t = ((x3 - x1) * dy2 - (y3 - y1) * dx2) / denom
          const u = ((x3 - x1) * dy1 - (y3 - y1) * dx1) / denom
          if (t >= -eps && t <= 1 + eps && u >= -eps && u <= 1 + eps) {
            const ix = x1 + t * dx1
            const iy = y1 + t * dy1
            return { x: ix, y: iy, t, u, colinear: false }
          }
          return null
        },
        drawText: function(o) {
          var ctx = this.ctx
          var fontSize = (o.size || 14) * this.dpr
          var text = o.text || ''
          var textWidth = this.getTextWidth(text, fontSize)
          var width = o.width || textWidth
          var align = o.align || 'normal'
          ctx.font = 'normal ' + fontSize + "px 'Open Sans', 'PingFang SC', Helvetica, Arial, sans-serif"
          var rows = []
          if (width && ~~width < textWidth) {
            let str = ''
            const texts = text.split('')
            for (var word of texts) {
              var testStr = str + word
              var testWidth = ctx.measureText(testStr).width
              if (testWidth > width) {
                rows.push(str)
                str = word
              } else {
                if (testWidth === width) {
                  rows.push(testStr)
                  str = ''
                } else {
                  str = testStr
                }
              }
            }
            str && rows.push(str)
          } else {
            rows = text.split(/\r\n|\n/g)
          }
          var rlen = rows.length
          var sumHeight = (rlen + 1) * fontSize
          var x = o.x
          var y = o.vertical === 'center' ? o.y - sumHeight / 2 : o.y
          var color = o.randomColor ? this.getRandomColor() : o.color || 'red'
          var lsx = x
          var lsy = y
          for (var b = 0; b < rlen; b++) {
            if (align === 'center') {
              var textW = this.getTextWidth(rows[b], fontSize)
              lsx = x - textW / 2
            }
            lsy = (b + 1) * fontSize
            ctx.lineHeight = 1
            ctx.fillStyle = color
            ctx.fillText(rows[b], ~~lsx + 0.5, y + ~~lsy + 0.5)
          }
        },
        getTextWidth: function(text, fontSize) {
          return (this.ctx.measureText(text).width / parseInt(this.ctx.font)) * fontSize
        },
        drawImg: function(img, x, y, w, h) {
          x = x - w / 2
          y = y - h
          ctx.drawImage(img, x, y, w, h)
        },
        getRandomColor: function() {
          return '#' + Math.floor(Math.random() * 16777215).toString(16)
        },
        getEventTarget: function(x, y) {
          if (y > bottomReferY - imgA.height - 1 && y < bottomReferY + 1) {
            var arr = this.points
            var i = arr.length - 1
            var obj
            var halfW = imgA.width / 2 - 1
            while ((obj = arr[i])) {
              var ix = this.getDrawDoord(obj.x * 1)
              if (Math.abs(ix - x) < halfW) {
                return obj
              }
              i--
            }
          }
          if (y > bottomReferY - imgB.height - 1 && y < bottomReferY + 1) {
            var arr = this.stations
            var i = arr.length - 1
            var obj
            var halfW = imgA.width / 2 - 1
            while ((obj = arr[i])) {
              var ix = this.getDrawDoord(obj.x * 1)
              if (Math.abs(ix - x) < halfW) {
                return obj
              }
              i--
            }
          }
        },
        xPixelToStepsYPixel: function(x) {
          var ladders = this.Cache.ladders || []
          var ladderSY = 0
          for (var ladder of ladders) {
            if (x >= ladder.sx && x < ladder.slopeX) {
              if (x < ladder.ex) {
                ladderSY = ladder.sy
              } else {
                var slopeWidth = ladder.slopeX - ladder.ex
                var slopeHeight = ladder.slopeY - ladder.ey
                ladderSY = (x - ladder.ex) * (slopeHeight / slopeWidth) + ladder.ey
              }
            }
          }
          return ladderSY
        },
        // 新增：生成线段采样点
        getSampledPoints: function(sx, sy, ex, ey, numPoints) {
          const points = []
          const segmentCount = numPoints - 1
          for (let i = 0; i < numPoints; i++) {
            const t = i / segmentCount
            points.push({
              x: sx + t * (ex - sx),
              y: sy + t * (ey - sy)
            })
          }
          return points
        },
        // 新增：检查水位线是否在边坡线上方或外部
        checkIfWaterLineAboveSlope: function(currentDevice, nextDevice) {
          // 计算边坡X范围（取最小/最大，避免采样顺序影响）
          var minX = Infinity; var maxX = -Infinity
          for (var i = 0; i < this.bianpoLinePoinArr.length; i++) {
            var px = this.bianpoLinePoinArr[i].x
            if (px < minX) minX = px
            if (px > maxX) maxX = px
          }

          // 如果水位线的任一端点超出边坡线的X范围，认为在边坡外部
          if (currentDevice.x < minX || currentDevice.x > maxX ||
              nextDevice.x < minX || nextDevice.x > maxX) {
            console.log('水位线超出边坡X范围')
            return true
          }

          // 在水位线的几个关键点检查是否在边坡线上方
          var checkPoints = [
            { x: currentDevice.x, y: currentDevice.y },
            { x: nextDevice.x, y: nextDevice.y },
            { x: (currentDevice.x + nextDevice.x) / 2, y: (currentDevice.y + nextDevice.y) / 2 } // 中点
          ]

          var pointsAboveSlope = 0

          for (var i = 0; i < checkPoints.length; i++) {
            var point = checkPoints[i]
            var slopeYAtX = this.getSlopeYAtX(point.x)

            // 如果水位线的Y坐标小于边坡线的Y坐标，说明在上方（Y轴向下为正）
            if (slopeYAtX !== null && point.y < slopeYAtX) {
              pointsAboveSlope++
            }
          }

          // 如果大部分点都在边坡线上方，则认为整条水位线在边坡线上方
          return pointsAboveSlope >= 2
        },
        // 新增：获取指定X坐标处边坡线的Y坐标
        getSlopeYAtX: function(x) {
          if (!this.bianpoLinePoinArr || this.bianpoLinePoinArr.length < 2) {
            return null
          }

          // 找到包含该X坐标的边坡线段
          for (var i = 0; i < this.bianpoLinePoinArr.length - 1; i++) {
            var point1 = this.bianpoLinePoinArr[i]
            var point2 = this.bianpoLinePoinArr[i + 1]

            if (x >= Math.min(point1.x, point2.x) && x <= Math.max(point1.x, point2.x)) {
              // 线性插值计算Y坐标
              if (point2.x === point1.x) {
                return point1.y // 垂直线段
              }
              var t = (x - point1.x) / (point2.x - point1.x)
              return point1.y + t * (point2.y - point1.y)
            }
          }

          return null
        },
        // 修正：检查点是否在边坡外部（仅当超出边坡X范围，或在边坡线之上/图表底部之外时判为外部）
        checkIfPointOutsideSlope: function(point) {
          if (!this.bianpoLinePoinArr || this.bianpoLinePoinArr.length < 2) {
            return false // 无边坡数据时，视为不在外部，避免误删
          }

          // 计算边坡X范围（取最小/最大，避免采样顺序影响）
          var minX = Infinity; var maxX = -Infinity
          for (var i = 0; i < this.bianpoLinePoinArr.length; i++) {
            var px = this.bianpoLinePoinArr[i].x
            if (px < minX) minX = px
            if (px > maxX) maxX = px
          }
          var eps = 1e-6

          // X超出边坡范围：外部
          if (point.x < minX - eps || point.x > maxX + eps) {
            return true
          }

          // 取得该X处的边坡线Y（边坡上缘）
          var slopeYAtX = this.getSlopeYAtX(point.x)
          if (slopeYAtX === null) {
            return true
          }

          // 图表底部Y（允许在边坡线与底部之间为“内部”）
          var bottomY = this.options && this.options.endY != null ? this.options.endY : Number.POSITIVE_INFINITY

          // 在边坡线上方（更小的Y）：外部；低于图表底部：外部
          if (point.y < slopeYAtX - eps || point.y > bottomY + eps) {
            return true
          }

          // 其余均视为在边坡内部
          return false
        },
        // 新增：处理超过边坡的点，返回可见点和超过边坡的点
        processPointsBeyondSlope: function(points) {
          if (!points || points.length === 0) {
            return { visiblePoints: [], beyondPoints: [] }
          }

          var visiblePoints = []
          var beyondPoints = []
          var currentSegment = []
          var inBeyondSegment = false

          for (var i = 0; i < points.length; i++) {
            var point = points[i]
            var isOutside = this.checkIfPointOutsideSlope(point)

            if (!isOutside) {
              // 点在边坡内
              if (inBeyondSegment) {
                // 刚从超过边坡的区域回到边坡内，需要处理交点
                if (currentSegment.length > 0) {
                  // 找到与边坡线的交点
                  var intersection = this.findIntersectionWithSlope(currentSegment[currentSegment.length - 1], point)
                  if (intersection) {
                    // 将交点添加到两个段中
                    currentSegment.push(intersection)
                    beyondPoints.push(...currentSegment)

                    visiblePoints.push(intersection)
                    currentSegment = []
                  }
                }
                inBeyondSegment = false
              }
              visiblePoints.push(point)
            } else {
              // 点在边坡外
              if (!inBeyondSegment) {
                // 刚从边坡内进入超过边坡的区域，需要处理交点
                if (visiblePoints.length > 0) {
                  // 找到与边坡线的交点
                  var intersection = this.findIntersectionWithSlope(visiblePoints[visiblePoints.length - 1], point)
                  if (intersection) {
                    // 将交点添加到两个段中
                    visiblePoints.push(intersection)
                    currentSegment.push(intersection)
                  }
                }
                inBeyondSegment = true
              }
              currentSegment.push(point)
            }
          }

          // 处理最后一个段
          if (inBeyondSegment && currentSegment.length > 0) {
            beyondPoints.push(...currentSegment)
          }

          return { visiblePoints: visiblePoints, beyondPoints: beyondPoints }
        },
        // 新增：找到线段与边坡线的交点（取所有交点中y最小者，tie-break用t最小），返回segIndex
        findIntersectionWithSlope: function(point1, point2) {
          if (!this.bianpoLinePoinArr || this.bianpoLinePoinArr.length < 2) {
            return null
          }

          var best = null
          var bestY = Infinity
          var eps = 1e-6

          // 遍历边坡线的每个线段，收集交点
          for (var i = 0; i < this.bianpoLinePoinArr.length - 1; i++) {
            var slopePoint1 = this.bianpoLinePoinArr[i]
            var slopePoint2 = this.bianpoLinePoinArr[i + 1]
            var inter = this.getLineIntersection(
              point1.x, point1.y, point2.x, point2.y,
              slopePoint1.x, slopePoint1.y, slopePoint2.x, slopePoint2.y
            )

            if (inter && inter.t >= 0 && inter.t <= 1 && inter.u >= 0 && inter.u <= 1) {
              if (inter.y < bestY - eps || (Math.abs(inter.y - bestY) <= eps && (!best || inter.t < best.t))) {
                bestY = inter.y
                inter.segIndex = i
                best = inter
              }
            }
          }

          return best
        },
        // 新增：判断两点的直线连接是否会穿过边坡（优先精确判断，采样兜底）
        isLineSegmentCrossesBoundary: function(point1, point2) {
          if (!this.bianpoLinePoinArr || this.bianpoLinePoinArr.length < 2) {
            return false // 没有边界数据，默认不穿过
          }

          // 1) 精确判断与任何边坡线段是否相交
          var eps = 1e-6
          for (var i = 0; i < this.bianpoLinePoinArr.length - 1; i++) {
            var s1 = this.bianpoLinePoinArr[i]
            var s2 = this.bianpoLinePoinArr[i + 1]
            var inter = this.getLineIntersection(
              point1.x, point1.y, point2.x, point2.y,
              s1.x, s1.y, s2.x, s2.y
            )
            if (inter && inter.t >= -eps && inter.t <= 1 + eps && inter.u >= -eps && inter.u <= 1 + eps) {
              return true
            }
          }

          // 2) 采样兜底（处理数值贴边/共线等情况）
          var sampleCount = 50
          for (var s = 0; s <= sampleCount; s++) {
            var t = s / sampleCount
            var sampleX = point1.x + t * (point2.x - point1.x)
            var sampleY = point1.y + t * (point2.y - point1.y)
            var slopeYAtX = this.getSlopeYAtX(sampleX)
            if (slopeYAtX !== null && sampleY > slopeYAtX) {
              return true
            }
          }
          return false
        },
        // 新增：检测二阶贝塞尔曲线是否与边坡有交叉（按曲线段折线化后与边坡线段做精确相交判断）
        doesQuadraticCurveCrossBoundary: function(p0, cp, p1) {
          if (!this.bianpoLinePoinArr || this.bianpoLinePoinArr.length < 2) return false
          var samples = 64 // 折线化采样点数（>=3）
          var pts = []
          for (var s = 0; s <= samples; s++) {
            var t = s / samples
            var omt = 1 - t
            pts.push({
              x: omt * omt * p0.x + 2 * omt * t * cp.x + t * t * p1.x,
              y: omt * omt * p0.y + 2 * omt * t * cp.y + t * t * p1.y
            })
          }
          var eps = 1e-6
          // 与每个小折线段逐段精确相交检测（两点之间的交叉点判断）
          for (var i = 1; i < pts.length; i++) {
            var q0 = pts[i - 1]
            var q1 = pts[i]
            for (var j = 0; j < this.bianpoLinePoinArr.length - 1; j++) {
              var s0 = this.bianpoLinePoinArr[j]
              var s1 = this.bianpoLinePoinArr[j + 1]
              var inter = this.getLineIntersection(q0.x, q0.y, q1.x, q1.y, s0.x, s0.y, s1.x, s1.y)
              if (inter) {
                // 仅判定发生在两个折线段内的交点（排除端点接触）
                if (inter.t > eps && inter.t < 1 - eps && inter.u > eps && inter.u < 1 - eps) {
                  return true
                }
              }
            }
          }
          return false
        },
        // 根据线与边坡交点动态计算控制点；若一遍后仍有交叉，则把Y设为“比交点低10”，否则“低2”
        getControlPointForCurve: function(point1, point2) {
          // 找到线与边坡的交点
          var intersectionPt = this.findIntersectionWithSlope(point1, point2)

          var midX = (point1.x + point2.x) / 2
          var midY = (point1.y + point2.y) / 2

          // 无交点：控制点取中点
          if (!intersectionPt) {
            return { x: midX, y: midY }
          }

          // 基准Y：交点与两端点中的更大者（向下偏移）
          var baseRefY = Math.max(intersectionPt.y, Math.max(point1.y, point2.y))
          var smallOffset = 2 * this.dpr
          var bigOffset = 12 * this.dpr

          // 先尝试“低2像素”
          var cpSmall = { x: midX, y: Math.max(midY, baseRefY + smallOffset) }
          if (this.doesQuadraticCurveCrossBoundary(point1, cpSmall, point2)) {
            // 仍与边坡有交叉：改为“低10像素”
            return { x: midX, y: Math.max(midY, baseRefY + bigOffset) }
          }
          // 无交叉：使用“低2像素”
          return cpSmall
        }
      }
      return new cuCharts(ctn, data, opts)
    },

    openRelieveAlarm(params, fn, close) {
      var self = this
      qf.UI.popupLayer({
        title: '解除报警',
        className: 'screenAlart',
        closeBtnColor: '#eee',
        html: '<div class="item"><span>解除时长(小时)：</span><input data-name="jchours" value=12 placeholder="请输入解除时长"/></div><div class="item"><span>报警原因：</span><input data-name="reason" placeholder="报警原因"/></div><div class="item"><span>解除报警原因：</span><input data-name="jcreason" placeholder="解除报警原因"/></div>',
        confirm: function(el) {
          var inputs = el.querySelectorAll('input')
          for (var el of inputs) {
            var key = el.dataset.name
            var val = el.value
            if (key === 'jchours') {
              val = parseInt(val) || 12
            } else {
              val = val || '默认原因！'
            }
            params[key] = val
          }

          // 解除
          self.pageApi.relieveAlarm(params).then(res => {
            // if(res.code === 200){};
            fn && fn()
          })
        },
        close: function() {
          close && close()
        }
      })
    }
  }
}
</script>

<style lang="scss" scope>
/* 新增原生模态框样式 */
.t_10 {
  li:last-child {
    cursor: pointer;
  }
}
.native-modal {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: 9999;
  display: none;
}

.native-modal.active {
  display: block;
}

.modal-mask {
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.6);
}

.modal-content {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  background: #fff;
  border-radius: 8px;
  min-width: 600px;
  max-width: 80%;
  max-height: 80%;
  overflow-y: auto;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
}

.modal-header {
  padding: 15px 20px;
  border-bottom: 1px solid #e5e5e5;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.modal-header h3 {
  margin: 0;
  font-size: 18px;
  color: #333;
}

.modal-close {
  background: none;
  border: none;
  font-size: 24px;
  cursor: pointer;
  color: #666;
  padding: 0 8px;
}

.modal-body {
  padding: 20px;
  max-height: calc(100vh - 600px);
  overflow-y: auto;
}
@font-face {
  font-family: 'diget year';
  src: url('~@/assets/fonts/DS-Digital.ttf');
}
.font_digit {
  font-family: diget year;
}

.Screen {
  position: relative;
  height: 100%;
  width: 100%;
  overflow: hidden;
  background-color: #00164e;

  .a_0,
  .b_0 {
    position: absolute;
  }
  > .actv {
    display: block;
  }
  .a_0 {
    top: 0;
    left: 0;
    height: 100%;
    width: 100%;
    background: no-repeat center center url('~@/assets/images/layout/s_bg_min.png');
    background-size: 100% 100%;
  }
  .b_0 {
    display: flex;
    width: 100%;
    height: 100%;
    padding-top: 0.65rem;
    background-color: rgba(0, 0, 0, 0.15);
    header {
      position: absolute;
      top: 0;
      left: 0;
      height: 0.64rem;
      width: 100%;
      z-index: 5;
      display: flex;
      user-select: none;
      .b1_a {
        position: absolute;
        top: 0;
        left: 0;
        height: 0.64rem;
        width: 100%;
        background: no-repeat top left url('~@/assets/images/layout/s_hd_min.png');
        background-size: 100% auto;
        display: flex;
        justify-content: center;
        align-items: center;
        h2 {
          font-size: 0.32rem;
          line-height: 1;
          margin-top: -0.04rem;
          background-image: -webkit-linear-gradient(top, #fff, #d5f3fe, #7adbfe);
          -webkit-background-clip: text;
          -webkit-text-fill-color: transparent;
          letter-spacing: 0.02rem;
        }
        .b1_a1 {
          position: absolute;
          top: 0;
          right: 0;
          height: 100%;
          width: 3.5rem;
          display: flex;
          align-items: center;
          justify-content: left;
          .font_digit {
            margin-right: 0.2rem;
            font-size: 0.28rem;
            color: #2ef4ee;
            span {
              font-family: serif;
              font-size: 0.24rem;
              margin: 0 0.05rem;
            }
          }
        }
        .b1_a2 {
          position: absolute;
          top: 0;
          left: 0.2rem;
          height: 100%;
          display: flex;
          align-items: center;
          &:before {
            content: '';
            display: block;
            height: 0.35rem;
            width: 1.58rem;
            background: no-repeat top left url('~@/assets/images/logo_ks.png');
            background-size: 100% auto;
          }
        }
      }
    }

    .b_2 {
      flex: 1;
      position: relative;
      margin: 0 0.08rem;
      .b_2a {
        position: absolute;
        top: 0;
        left: 0;
        height: 100%;
        width: 100%;
        display: flex;
        flex-direction: column;

        .b2_a {
          flex: 1;
          display: flex;

          .b2_a1,
          .b2_a3 {
            flex: 1;
            > div {
              //background-color:pink;
              flex: 1;
            }
          }
          .b2_a1,
          .b2_a2,
          .b2_a3 {
            display: flex;
            flex-direction: column;
            > div {
              margin-bottom: 0.08rem;
            }
          }

          .b2_a2 {
            width: 8.84rem;
            margin: 0 0.08rem;
            > div:first-child {
              height: 7.52rem;
              /* background-color:yellow; */
              box-shadow: inset 0rem 0rem 0.15rem 0.24rem rgba(2, 43, 80, 0.8);
            }
            > div:last-child {
              //height:2.46rem;
              flex: 1;
              display: flex;
              //height:22.777vh;
              > div {
                flex: 1;
                margin-right: 0.08rem;
                //background-color:pink;
                &:last-child {
                  margin-right: 0;
                }
              }
            }

            .b2a2_a {
              flex: 1;
              position: relative;
              /* background:no-repeat center center url('~@/assets/images/layout/map_min.png');background-size:100% auto; */
              > div {
                display: none;
                position: absolute;
                top: 0;
                left: 0;
                height: 100%;
                width: 100%;
              }
              > div.show {
                display: block;
              }
              > div:first-child {
              }
            }
          }
          .b2_a3 {
          }

          .view-item {
            border: 1px solid #004a8b;
            position: relative;
            box-shadow: inset 0rem 0rem 0.15rem 0.04rem rgba(10, 106, 180, 0.8);
            &:before,
            &:after,
            .view-flx:before,
            .view-flx:after {
              content: '';
              position: absolute;
              height: 0.1rem;
              width: 0.1rem;
              z-index: 1;
              border-style: solid;
              border-color: #02f6ff;
            }
            &:before,
            &:after {
              top: -1px;
            }
            &:before {
              left: -1px;
              border-width: 0.02rem 0 0 0.02rem;
            }
            &:after {
              right: -1px;
              border-width: 0.02rem 0.02rem 0 0;
            }
            .view-flx {
              position: absolute;
              top: 0;
              left: 0;
              height: 100%;
              width: 100%;
              background-color: rgb(1, 19, 70, 0.3);
              &:before,
              &:after {
                bottom: -1px;
              }
              &:before {
                left: -1px;
                border-width: 0 0 0.02rem 0.02rem;
              }
              &:after {
                right: -1px;
                border-width: 0 0.02rem 0.02rem 0;
              }
            }
          }
        }

        .weather {
          font-size: 11px;
          .mleft {
            display: none;
          }
          .wtpath {
            display: none;
          }
          .mright {
            flex: 1;
            text-align: center;
          }
          img {
            display: block;
            width: 2vw !important;
            height: auto !important;
            margin: 0 auto;
          }
          ul {
            margin: 5px 0;
            color: #d0d2ff;
          }
          #mobile05 {
            display: flex;
            position: relative;
            padding-top: 5vh !important;
          }
          #mobile05 > div:last-child {
            display: none;
          }
          #mright_1 {
            position: absolute;
            top: 0;
            left: 0;
            height: auto;
            width: 100%;
            display: flex;
            ul {
              margin: 0;
              color: #03d3fe;
            }
            .wt {
              display: flex;
              align-items: center;
              margin-left: 140px;
            }
            .day {
              display: none;
            }
            span:last-child {
              display: block;
              position: absolute;
              top: 4px;
              left: 0px;
              height: auto;
              font-size: 26px;
            }
            font {
              margin: 6px 0 0 10px;
              color: #35ecfa;
            }
          }
        }
      }
    }
  }
}
</style>
