<template>
  <view class="page-container">
    <!-- 简洁标签栏 -->
    <view class="tab-container">
      <view
        v-for="item in tabData"
        :key="item.value"
        @click="currentTab = item.value"
        class="tab-item"
        :class="{ 'tab-active': currentTab === item.value }"
      >
        <text class="tab-label">{{ item.text }}</text>
        <view class="tab-indicator" v-if="currentTab === item.value"></view>
      </view>
    </view>

    <!-- 刷新按钮 -->
    <view class="refresh-btn-fixed" @click="refreshAll">
      <text class="refresh-icon" :class="{ rotating: isRefreshing }">⟳</text>
    </view>

    <!-- Tab1：实时监控 -->
    <scroll-view
      scroll-y
      class="scroll-container"
      v-if="currentTab === 1"
    >
      <!-- 顶部状态栏 -->
      <view class="status-bar">
        <view class="status-item">
          <text class="status-label">网络状态</text>
          <text class="status-value" :class="isConnected ? 'status-通畅' : 'status-异常'">{{ isConnected ? '已连接' : '未连接' }}</text>
        </view>
        <view class="status-divider"></view>
        <view class="status-item">
          <text class="status-label">更新</text>
          <text class="status-value status-time">{{ lastUpdateTime }}</text>
        </view>
        <view class="status-divider"></view>
        <view class="status-item">
          <text class="status-label">设备数</text>
          <text class="status-value">{{ deviceDefs.length }}</text>
        </view>
      </view>

      <!-- 带钢位置和速度模块 -->
      <view class="section">
        <view class="section-title">带钢状态</view>
        <view class="strip-status-grid">
          <!-- 带钢位置卡片 -->
          <view class="strip-status-card">
            <text class="strip-status-label">带钢位置</text>
            <text class="strip-status-value">{{ getRealtimeValueBySource('ENTRY', 'stripLocation') }}</text>
            <text class="strip-status-unit">m</text>
            <view class="progress-container">
              <view class="progress-bar">
                <view class="progress-fill" :style="{ width: stripLocationProgress + '%' }"></view>
              </view>
              <text class="progress-text">{{ stripLocationProgress.toFixed(1) }}%</text>
            </view>
          </view>
          <!-- 带钢速度卡片 -->
          <view class="strip-status-card">
            <text class="strip-status-label">带钢速度</text>
            <text class="strip-status-value">{{ getRealtimeValueBySource('ENTRY', 'stripSpeed') }}</text>
            <text class="strip-status-unit">m/min</text>
          </view>
        </view>
        <!-- 带钢速度折线图 -->
        <view class="chart-box">
          <qiun-data-charts type="line" :chartData="stripSpeedChartData" :opts="stripSpeedChartOpts" />
        </view>
      </view>

      <!-- 实时数据卡片 + 统计图（按酸轧页风格，不按设备分块） -->
      <view class="section">
        <view class="section-title">入口段</view>
        <view class="metrics-grid-3">
          <view class="metric-box" v-for="it in entryMetrics" :key="'entry_' + it.field">
            <text class="metric-name">{{ it.label || getFieldLabel(it.field) }}</text>
            <text class="metric-value">{{ getRealtimeValueBySource('ENTRY', it.field) }}</text>
            <text class="metric-unit">{{ it.unit || getFieldUnit(it.field) }}</text>
          </view>
        </view>
        <view class="chart-box">
          <qiun-data-charts type="line" :chartData="toGroupLineChart('entry')" :opts="lineChartOpts" />
        </view>
      </view>

      <view class="section">
        <view class="section-title">退火炉段</view>
        <view class="metrics-grid-3">
          <view class="metric-box" v-for="it in furnaceMetrics" :key="'furnace_' + it.actualField">
            <text class="metric-name">{{ it.label || getFieldLabel(it.actualField) }}</text>
            <text class="metric-value">{{ getRealtimeValueBySource('FURNACE', it.actualField) }}</text>
            <text class="metric-unit">{{ it.unit || getFieldUnit(it.actualField) }}</text>
            <text class="metric-set-value" v-if="it.setField">
              设定: {{ getRealtimeValueBySource('FURNACE', it.setField) }}
            </text>
          </view>
        </view>
      </view>

      <view class="section">
        <view class="section-title">后处理/涂层段</view>
        <view class="metrics-grid-3">
          <view class="metric-box" v-for="it in coatMetrics" :key="'coat_' + it.field">
            <text class="metric-name">{{ it.label || getFieldLabel(it.field) }}</text>
            <text class="metric-value">{{ getRealtimeValueBySource('COAT', it.field) }}</text>
            <text class="metric-unit">{{ it.unit || getFieldUnit(it.field) }}</text>
          </view>
        </view>
        <view class="chart-box">
          <qiun-data-charts type="line" :chartData="toGroupLineChart('coat')" :opts="lineChartOpts" />
        </view>
      </view>

      <view class="section">
        <view class="section-title">出口段</view>
        <view class="metrics-grid-3">
          <view class="metric-box" v-for="it in exitMetrics" :key="'exit_' + it.field">
            <text class="metric-name">{{ it.label || getFieldLabel(it.field) }}</text>
            <text class="metric-value">{{ getRealtimeValueBySource('EXIT', it.field) }}</text>
            <text class="metric-unit">{{ it.unit || getFieldUnit(it.field) }}</text>
          </view>
        </view>
        <view class="chart-box">
          <qiun-data-charts type="line" :chartData="toGroupLineChart('exit')" :opts="lineChartOpts" />
        </view>
      </view>
    </scroll-view>

    <!-- 生产统计 / 实绩 -->
    <scroll-view scroll-y class="scroll-container" v-if="currentTab === 2">
    
      <view class="section">
        <view class="section-title">生产实绩汇总</view>
        <view class="overview-grid" v-if="reportSummary">
          <view class="overview-card">
            <text class="overview-label">钢卷总数</text>
            <text class="overview-value">{{ reportSummary.coilCount || 0 }}</text>
            <text class="overview-unit">卷</text>
          </view>
          <view class="overview-card">
            <text class="overview-label">总实际重量</text>
            <text class="overview-value">{{ formatNum(reportSummary.totalActualWeight) }}</text>
            <text class="overview-unit">t</text>
          </view>
          <view class="overview-card">
            <text class="overview-label">成材率</text>
            <text class="overview-value">
              {{ reportSummary.yieldRate != null ? (reportSummary.yieldRate * 100).toFixed(1) : '—' }}
            </text>
            <text class="overview-unit">%</text>
          </view>
        </view>
        <view class="chart-box" v-else>
          <text class="placeholder-text">暂无实绩汇总数据。</text>
        </view>
      </view>

      <view class="section">
        <view class="section-title">生产实绩明细</view>
        <view class="detail-cards" v-if="reportDetails && reportDetails.length">
          <view
            class="detail-card"
            v-for="(item, index) in reportDetails"
            :key="(item.exitMatId || '') + '_' + (item.onlineTime || index) + '_' + index"
          >
            <view class="detail-card-header">
              <text class="detail-card-title">成品卷号：{{ item.exitMatId }}</text>
            </view>
            <view class="detail-card-body">
              <view class="detail-row">
                <text class="detail-label">原料卷号</text>
                <text class="detail-value">{{ item.entryMatId || '—' }}</text>
              </view>
              <view class="detail-row">
                <text class="detail-label">班/组</text>
                <text class="detail-value">{{ item.groupNo || '—' }} / {{ item.shiftNo || '—' }}</text>
              </view>
              <view class="detail-row">
                <text class="detail-label">规格</text>
                <text class="detail-value">
                  {{ formatNum(item.exitThickness) }} × {{ formatNum(item.exitWidth) }}
                </text>
              </view>
              <view class="detail-row">
                <text class="detail-label">长度</text>
                <text class="detail-value">{{ formatNum(item.exitLength) }} m</text>
              </view>
              <view class="detail-row">
                <text class="detail-label">重量</text>
                <text class="detail-value">{{ formatNum(item.actualWeight) }} t</text>
              </view>
            </view>
          </view>
        </view>
        <view class="chart-box" v-else>
          <text class="placeholder-text">暂无生产明细记录。</text>
        </view>
      </view>
    </scroll-view>

    <!-- 停机统计 / 停机记录 -->
    <scroll-view scroll-y class="scroll-container" v-if="currentTab === 3">
      <view class="section">
        <view class="section-title">停机记录</view>
        <!-- 月份选择器 -->
        <view class="stoppage-filter">
          <picker mode="date" fields="month" :value="stoppageMonth" @change="onStoppageMonthChange">
            <view class="stoppage-month-btn">
              <text class="stoppage-month-text">{{ stoppageMonthLabel }}</text>
            </view>
          </picker>
        </view>
        <view class="stoppage-cards" v-if="stoppageList && stoppageList.length">
          <view
            class="stoppage-card"
            v-for="(item, index) in stoppageList"
            :key="item.stopid || index"
          >
            <view class="stoppage-card-header">
              <text class="stoppage-card-title">停机记录 #{{ index + 1 }}</text>
              <text class="stoppage-card-duration">{{ formatDuration(item.duration) }}</text>
            </view>
            <view class="stoppage-card-body">
              <view class="stoppage-row">
                <text class="stoppage-label">开始时间</text>
                <text class="stoppage-value">{{ item.startDate || '—' }}</text>
              </view>
              <view class="stoppage-row">
                <text class="stoppage-label">结束时间</text>
                <text class="stoppage-value">{{ item.endDate || '—' }}</text>
              </view>
              <view class="stoppage-row">
                <text class="stoppage-label">区域</text>
                <text class="stoppage-value">{{ item.area || '—' }}</text>
              </view>
              <view class="stoppage-row">
                <text class="stoppage-label">设备</text>
                <text class="stoppage-value">{{ item.seton || '—' }}</text>
              </view>
              <view class="stoppage-row" v-if="item.remark">
                <text class="stoppage-label">原因</text>
                <text class="stoppage-value stoppage-remark">{{ item.remark }}</text>
              </view>
            </view>
          </view>
        </view>
        <view class="chart-box" v-else>
          <text class="placeholder-text">暂无停机记录。</text>
        </view>
      </view>
    </scroll-view>

  </view>
</template>

<script>
import { listDeviceEnumAll } from '@/api/pocket/deviceEnum'
import { getDeviceFieldMetaAll } from '@/api/pocket/deviceFieldMeta'
import { listDeviceSnapshotLatest } from '@/api/pocket/deviceSnapshot'
import { getCurrentPlan, getCurrentProcess } from '@/api/business/dashboard'
import { getReportSummary, getReportDetails } from '@/api/business/report'
import { listStoppage } from '@/api/business/stoppage'
import { createMeasureSocket } from '@/utils/socketMeasure'

export default {
  data() {
    return {
      currentTab: 1,
      tabData: [
        { text: '实时监控', value: 1 },
        { text: '生产统计', value: 2 },
        { text: '停机统计', value: 3 }
      ],
      isRefreshing: false,
      isConnected: false,
      lastUpdateTime: '—',
      // 记录起始时间，用于计算运行时长
      startTime: null,
      // socket数据是否已补充到折线图（仅补充一次）
      socketDataAppended: false,

      // socket最新消息
      latestMeasure: null,

      // 设备定义（后端 DeviceEnum 全量）
      deviceDefs: [],

      // 字段元数据：fieldName -> {label, unit, description}
      fieldMeta: {},

      // 实时数据卡片定义（按酸轧页组织，不按设备分块）
      entryMetrics: [
        { field: 'stripLocation', label: '带钢位置', unit: 'm' },
        { field: 'stripSpeed', label: '带钢速度', unit: 'm/min' },
        { field: 'tensionPorBr1', label: '入口张力 POR-BR1', unit: 'daN' },
        { field: 'tensionBr1Br2', label: '张力 BR1-BR2', unit: 'daN' },
        { field: 'tensionBr2Br3', label: '张力 BR2-BR3', unit: 'daN' }
      ],
      furnaceMetrics: [
        { actualField: 'jcf1FurnaceTemperatureActual', setField: 'jcf1FurnaceTemperatureSet', label: 'JCF1炉温', unit: '℃' },
        { actualField: 'jcf2FurnaceTemperatureActual', setField: 'jcf2FurnaceTemperatureSet', label: 'JCF2炉温', unit: '℃' },
        { actualField: 'lbzFurnaceTemperatureActual', setField: 'lbzFurnaceTemperatureSet', label: 'LBZ炉温', unit: '℃' },
        { actualField: 'lthFurnaceTemperatureActual', setField: 'lthFurnaceTemperatureSet', label: 'LTH炉温', unit: '℃' },
        { actualField: 'nof1FurnaceTemperatureActual', setField: 'nof1FurnaceTemperatureSet', label: 'NOF1炉温', unit: '℃' },
        { actualField: 'nof2FurnaceTemperatureActual', setField: 'nof2FurnaceTemperatureSet', label: 'NOF2炉温', unit: '℃' },
        { actualField: 'nof3FurnaceTemperatureActual', setField: 'nof3FurnaceTemperatureSet', label: 'NOF3炉温', unit: '℃' },
        { actualField: 'nof4FurnaceTemperatureActual', setField: 'nof4FurnaceTemperatureSet', label: 'NOF4炉温', unit: '℃' },
        { actualField: 'nof5FurnaceTemperatureActual', setField: 'nof5FurnaceTemperatureSet', label: 'NOF5炉温', unit: '℃' },
        { actualField: 'phFurnaceTemperatureActual', setField: null, label: 'PH炉温', unit: '℃' },
        { actualField: 'rtf1FurnaceTemperatureActual', setField: 'rtf1FurnaceTemperatureSet', label: 'RTF1炉温', unit: '℃' },
        { actualField: 'rtf2FurnaceTemperatureActual', setField: 'rtf2FurnaceTemperatureSet', label: 'RTF2炉温', unit: '℃' },
        { actualField: 'sfFurnaceTemperatureActual', setField: 'sfFurnaceTemperatureSet', label: 'SF炉温', unit: '℃' },
        { actualField: 'tdsFurnaceTemperatureActual', setField: 'tdsFurnaceTemperatureSet', label: 'TDS炉温', unit: '℃' },
      ],
      coatMetrics: [
        { field: 'avrCoatingWeightTop', label: '上层平均涂层重量', unit: 'g/m²' },
        { field: 'avrCoatingWeightBottom', label: '下层平均涂层重量', unit: 'g/m²' },
        { field: 'airKnifePressure', label: '气刀压力', unit: '' },
        { field: 'stripSpeedTmExit', label: 'TM出口速度', unit: 'm/min' }
      ],
      exitMetrics: [
        { field: 'alkaliConcentration', label: '碱液浓度', unit: '' },
        { field: 'alkaliTemperature', label: '碱液温度', unit: '℃' },
        { field: 'celCapacity', label: '电池容量', unit: '' },
        { field: 'celLength', label: '电池长度', unit: 'm' },
        { field: 'cleaningCurrent', label: '清洗电流', unit: 'A' },
        { field: 'cleaningVoltage', label: '清洗电压', unit: 'V' },
        { field: 'dryingTemperature', label: '干燥温度', unit: '℃' },
        { field: 'entryCoilId', label: '入口卷号', unit: '' },
        { field: 'hotAirFlow', label: '热风流量', unit: '' },
        { field: 'hotAirPressure', label: '热风压力', unit: '' },
        { field: 'payOffReelNumber', label: '放卷卷号', unit: '' },
        { field: 'rinseConductivity', label: '冲洗电导率', unit: '' },
        { field: 'rinseTemperature', label: '冲洗温度', unit: '℃' },
        { field: 'stripLocation', label: '带钢位置', unit: 'm' },
        { field: 'stripSpeed', label: '带钢速度', unit: 'm/min' },
        { field: 'tensionBr1Br2', label: '张力 BR1-BR2', unit: 'daN' },
        { field: 'tensionBr2Br3', label: '张力 BR2-BR3', unit: 'daN' },
        { field: 'tensionPorBr1', label: '入口张力 POR-BR1', unit: 'daN' },
        { field: 'tensionBr8Br9', label: '张力 BR8-BR9', unit: 'daN' },
        { field: 'tensionBr9Tr', label: '张力 BR9-TR', unit: 'daN' },
        { field: 'speedExitSection', label: '出口速度', unit: 'm/min' },
        { field: 'coilLength', label: '钢卷长度', unit: 'm' }
      ],

      // 前端历史缓存（打开页面即可出趋势）
      chartMaxPoints: 60,
      // 带钢速度折线图最大点数（单独配置）
      stripSpeedMaxPoints: 30,
      chartSeries: {
        entry: { time: [], stripSpeed: [], tensionPorBr1: [], tensionBr1Br2: [], tensionBr2Br3: [] },
        furnace: { 
          time: [], 
          jcf1FurnaceTemperatureActual: [], jcf2FurnaceTemperatureActual: [],
          lbzFurnaceTemperatureActual: [], lthFurnaceTemperatureActual: [],
          nof1FurnaceTemperatureActual: [], nof2FurnaceTemperatureActual: [],
          nof3FurnaceTemperatureActual: [], nof4FurnaceTemperatureActual: [],
          nof5FurnaceTemperatureActual: [],
          phFurnaceTemperatureActual: [], 
          rtf1FurnaceTemperatureActual: [], rtf2FurnaceTemperatureActual: [],
          sfFurnaceTemperatureActual: [], tdsFurnaceTemperatureActual: [],
          potTemperature: [] 
        },
        coat: { time: [], avrCoatingWeightTop: [], avrCoatingWeightBottom: [], airKnifePressure: [], stripSpeedTmExit: [] },
        exit: { time: [], tensionBr8Br9: [], tensionBr9Tr: [], speedExitSection: [] },
        // 带钢速度实时折线图（持续刷新）
        stripSpeed: { time: [], stripSpeed: [] }
      },

      // 生产统计（实绩）
      currentPlan: null,
      currentProcess: null,
      reportSummary: null,
      reportDetails: [],
      reportLoading: false,

      // 停机记录
      stoppageList: [],
      stoppageLoading: false,
      stoppageMonth: '', // yyyy-MM

      // 组件销毁标志，用于防止异步操作在组件销毁后更新数据
      isDestroyed: false,

      lineChartOpts: {
        color: ['#0066cc', '#409eff', '#66b1ff', '#a0cfff', '#d9ecff', '#ecf5ff'],
        padding: [15, 15, 0, 15],
        enableScroll: false,
        legend: { show: true, position: 'top', fontSize: 10, lineHeight: 14, itemGap: 6 },
        dataLabel: false,
        dataPointShape: false,
        xAxis: { disableGrid: true, rotateLabel: true, itemCount: 5, labelCount: 5, fontSize: 10 },
        yAxis: {
          gridType: 'dash',
          dashLength: 4,
          gridColor: '#e4e7ed',
          showTitle: true,
          fontSize: 10,
          data: [{ title: '数值' }]
        },
        extra: { line: { type: 'curve', width: 2, activeType: 'hollow' } }
      },
      // 带钢速度折线图配置（Y轴固定为100-150，完全禁用动画）
      stripSpeedChartOpts: {
        color: ['#0066cc'],
        padding: [15, 15, 0, 15],
        enableScroll: false,
        legend: { show: true, position: 'top', fontSize: 10, lineHeight: 14, itemGap: 6 },
        dataLabel: false,
        dataPointShape: false,
        animation: false, // 禁用动画，直接显示到值上
        animationDuration: 0, // 动画时长为0，确保无动画效果
        xAxis: { disableGrid: true, rotateLabel: true, itemCount: 5, labelCount: 5, fontSize: 10 },
        yAxis: {
          gridType: 'dash',
          dashLength: 4,
          gridColor: '#e4e7ed',
          showTitle: true,
          fontSize: 10,
          data: [{ 
            title: '速度(m/min)',
            min: 80,
            max: 150
          }]
        },
        extra: { line: { type: 'curve', width: 2, activeType: 'hollow' } }
      },

      socketClient: null
    }
  },

  mounted() {
    if (this.isDestroyed) return
    // 记录起始时间
    this.startTime = new Date()
    this.initSocket()

    Promise.all([this.loadDeviceDefs(), this.loadFieldMeta(), this.loadHistoryForCharts()]).then(() => {
      // Promise完成后检查组件是否已销毁
      if (this.isDestroyed) return
    }).catch(() => {
      // 错误处理中也要检查
      if (this.isDestroyed) return
    })

    this.updateLastTime()
  },

  computed: {
    stoppageMonthLabel() {
      if (this.isDestroyed) return '选择月份'
      if (!this.stoppageMonth) return '选择月份'
      const parts = (this.stoppageMonth || '').split('-')
      if (parts.length < 2) return this.stoppageMonth
      const [y, m] = parts
      return `${y}年${m}月`
    },
    // 带钢位置进度（位置/3000）
    stripLocationProgress() {
      if (this.isDestroyed) return 0
      const m = this.latestMeasure || {}
      const entry = m.appMeasureEntryMessage || {}
      const location = entry.stripLocation !== null && entry.stripLocation !== undefined 
        ? parseFloat(entry.stripLocation) || 0 
        : 0
      const maxLocation = 3000
      const progress = (location / maxLocation) * 100
      return Math.min(Math.max(progress, 0), 100)
    },
    // 带钢速度折线图数据（使用计算属性，确保数据引用稳定）
    stripSpeedChartData() {
      if (this.isDestroyed) return { categories: [], series: [] }
      const g = this.chartSeries.stripSpeed
      if (!g) return { categories: [], series: [] }
      // 返回新对象，但保持数组引用稳定，避免触发重新初始化
      const timeArray = g.time || []
      const speedArray = g.stripSpeed || []
      return {
        categories: timeArray,
        series: [{
          name: '带钢速度',
          data: speedArray
        }]
      }
    }
  },

  watch: {
    currentTab(newVal) {
      if (this.isDestroyed) return
      if (newVal === 2) {
        this.loadReportData()
      } else if (newVal === 3) {
        // 默认当前月
        if (!this.stoppageMonth) {
          const now = new Date()
          const y = now.getFullYear()
          const m = String(now.getMonth() + 1).padStart(2, '0')
          this.stoppageMonth = `${y}-${m}`
        }
        this.loadStoppageData()
      }
    }
  },

  beforeDestroy() {
    // 先设置销毁标志，防止异步回调访问已销毁的组件
    this.isDestroyed = true
    
    // 设置闭包中的销毁标志（必须在关闭socket之前，且要立即执行）
    // 这会取消所有待执行的异步更新
    if (this._clearSocketFlag) {
      try {
        this._clearSocketFlag()
      } catch (e) {
        // ignore
      }
      // 清空引用，防止再次调用
      this._clearSocketFlag = null
    }
    
    // 清理定时器
    if (this._scrollTimer) {
      clearTimeout(this._scrollTimer)
      this._scrollTimer = null
    }
    
    // 关闭socket连接并清空回调（必须立即执行）
    if (this.socketClient) {
      try {
        // 先清空回调，防止关闭过程中触发回调
        if (this.socketClient.clearCallbacks) {
          this.socketClient.clearCallbacks()
        }
        // 立即关闭socket
        this.socketClient.close()
      } catch (e) {
        // ignore
      }
      // 立即清空引用
      this.socketClient = null
    }
    
    // 清空所有响应式数据引用，防止后续访问
    try {
      this.latestMeasure = null
      this.chartSeries = null
    } catch (e) {
      // ignore
    }
  },

  methods: {
    async loadDeviceDefs() {
      const res = await listDeviceEnumAll()
      if (this.isDestroyed) return
      this.deviceDefs = (res && res.data) || []
    },

    async loadFieldMeta() {
      const res = await getDeviceFieldMetaAll()
      if (this.isDestroyed) return
      this.fieldMeta = (res && res.data) || {}
    },

    // 加载历史快照，填充折线图的"历史部分"
    async loadHistoryForCharts() {
      try {
        // 使用设备快照作为折线图唯一数据源，避免每条 socket 消息都触发重绘
        // 每次加载前先清空本地缓存，防止重复累加
        // 注意：stripSpeed 数据不清空，保持实时更新连续性
        this.chartSeries = {
          entry: { time: [], stripSpeed: [], tensionPorBr1: [], tensionBr1Br2: [], tensionBr2Br3: [] },
          furnace: { 
            time: [], 
            jcf1FurnaceTemperatureActual: [], jcf2FurnaceTemperatureActual: [],
            lbzFurnaceTemperatureActual: [], lthFurnaceTemperatureActual: [],
            nof1FurnaceTemperatureActual: [], nof2FurnaceTemperatureActual: [],
            nof3FurnaceTemperatureActual: [], nof4FurnaceTemperatureActual: [],
            nof5FurnaceTemperatureActual: [],
            phFurnaceTemperatureActual: [], 
            rtf1FurnaceTemperatureActual: [], rtf2FurnaceTemperatureActual: [],
            sfFurnaceTemperatureActual: [], tdsFurnaceTemperatureActual: [],
            potTemperature: [] 
          },
          coat: { time: [], avrCoatingWeightTop: [], avrCoatingWeightBottom: [], airKnifePressure: [], stripSpeedTmExit: [] },
          exit: { time: [], tensionBr8Br9: [], tensionBr9Tr: [], speedExitSection: [] },
          // stripSpeed 保持现有数据，不清空，确保实时更新连续性
          stripSpeed: this.chartSeries.stripSpeed || { time: [], stripSpeed: [] }
        }

        const limit = this.chartMaxPoints
        const tasks = []

        const deviceMap = {
          entry: { deviceCode: 'POR1', fields: ['stripSpeed', 'tensionPorBr1', 'tensionBr1Br2', 'tensionBr2Br3'] },
          furnace: {
            deviceCode: 'FUR2',
            fields: [
              'jcf1FurnaceTemperatureActual', 'jcf2FurnaceTemperatureActual',
              'lbzFurnaceTemperatureActual', 'lthFurnaceTemperatureActual',
              'nof1FurnaceTemperatureActual', 'nof2FurnaceTemperatureActual',
              'nof3FurnaceTemperatureActual', 'nof4FurnaceTemperatureActual',
              'nof5FurnaceTemperatureActual',
              'phFurnaceTemperatureActual',
              'rtf1FurnaceTemperatureActual', 'rtf2FurnaceTemperatureActual',
              'sfFurnaceTemperatureActual', 'tdsFurnaceTemperatureActual',
              'potTemperature'
            ]
          },
          coat: {
            deviceCode: 'COAT',
            fields: ['avrCoatingWeightTop', 'avrCoatingWeightBottom', 'airKnifePressure', 'stripSpeedTmExit']
          },
          exit: { deviceCode: 'TR', fields: ['tensionBr8Br9', 'tensionBr9Tr', 'speedExitSection'] }
        }

        Object.keys(deviceMap).forEach((group) => {
          const cfg = deviceMap[group]
          tasks.push(
            listDeviceSnapshotLatest({ limit, deviceCode: cfg.deviceCode })
              .then((res) => {
                if (this.isDestroyed) return
                const rows = (res && res.data) || []
                // 按时间升序
                const list = rows.slice().reverse()
                list.forEach((row) => {
                  if (this.isDestroyed) return
                  const t = (row.createTime || '').slice(11, 19) // HH:mm:ss
                  let snap = {}
                  try {
                    snap = row.snapshotData ? JSON.parse(row.snapshotData) : {}
                  } catch (e) {
                    snap = {}
                  }
                  const values = {}
                  cfg.fields.forEach((f) => {
                    values[f] = snap[f]
                  })
                  this.pushSeries(group, t, values)
                })
              })
              .catch(() => {})
          )
        })

        await Promise.all(tasks)
      } catch (e) {
        // ignore
      }
    },

    initSocket() {
      // 使用闭包保存组件引用和销毁标志，避免直接访问 this
      const vm = this
      let isDestroyed = false
      // 保存所有待执行的异步操作ID，用于取消
      const pendingUpdates = []
      
      // 实时测量数据 WebSocket
      this.socketClient = createMeasureSocket({
        type: 'track_measure',
        onOpen: () => {
          if (isDestroyed) return
          try {
            vm.isConnected = true
          } catch (e) {
            // 组件已销毁，忽略错误
          }
        },
        onClose: () => {
          if (isDestroyed) return
          try {
            vm.isConnected = false
          } catch (e) {
            // 组件已销毁，忽略错误
          }
        },
        onError: () => {
          if (isDestroyed) return
          try {
            vm.isConnected = false
          } catch (e) {
            // 组件已销毁，忽略错误
          }
        },
        onMessage: (data) => {
          // 在函数最开始就检查，避免任何后续操作
          if (isDestroyed) return
          // 双重检查：如果组件实例不存在，直接返回
          if (!vm) {
            isDestroyed = true
            return
          }
          
          // 使用 try-catch 包裹整个函数，防止任何错误
          try {
            // 兼容字符串 / 已解析对象两种情况
            let payload = null
            if (typeof data === 'string') {
              payload = JSON.parse(data)
            } else if (data && typeof data === 'object') {
              if (typeof data.data === 'string') {
                payload = JSON.parse(data.data)
              } else {
                payload = data
              }
            }
            
            // 再次检查，防止在解析过程中组件被销毁
            if (!payload || isDestroyed || !vm) return
            
            // 使用 requestAnimationFrame 延迟更新
            // 如果组件已销毁，requestAnimationFrame 回调不会执行
            const updateFrame = () => {
              // 多重检查：确保组件仍然存在
              if (isDestroyed || !vm) return
              
              // 检查 Vue 实例状态 - 使用更安全的方式
              try {
                // 检查组件是否真的还存在且可用
                if (!vm || typeof vm !== 'object') {
                  isDestroyed = true
                  return
                }
                // 尝试访问 Vue 实例的内部属性来验证
                if (vm._isDestroyed === true || vm._isBeingDestroyed === true) {
                  isDestroyed = true
                  return
                }
              } catch (e) {
                isDestroyed = true
                return
              }
              
              // 安全地更新数据 - 使用 Object.defineProperty 或直接赋值
              try {
                // 直接赋值，但确保组件存在
                if (vm && !vm._isDestroyed && !vm._isBeingDestroyed) {
                  vm.latestMeasure = payload
                  if (!vm.socketDataAppended) {
                    vm.appendChartPoint(payload)
                    vm.socketDataAppended = true
                  }
                  vm.appendStripSpeedPoint(payload)
                  vm.updateLastTime()
                }
              } catch (e) {
                // 如果更新失败，设置销毁标志
                isDestroyed = true
              }
            }
            
            // 使用 requestAnimationFrame 或 setTimeout，并保存ID以便取消
            let frameId = null
            if (typeof requestAnimationFrame !== 'undefined') {
              frameId = requestAnimationFrame(updateFrame)
              pendingUpdates.push(frameId)
            } else {
              frameId = setTimeout(updateFrame, 0)
              pendingUpdates.push(frameId)
            }
          } catch (e) {
            // 解析错误或其他错误，静默处理
            if (!isDestroyed) {
              try {
                console.error('解析 track_measure 数据失败:', e)
              } catch (err) {
                // 忽略日志错误
              }
            }
          }
        }
      })

      // 保存清空标志的函数引用
      this._clearSocketFlag = () => {
        isDestroyed = true
        // 取消所有待执行的异步更新
        pendingUpdates.forEach(id => {
          try {
            if (typeof cancelAnimationFrame !== 'undefined') {
              cancelAnimationFrame(id)
            } else {
              clearTimeout(id)
            }
          } catch (e) {
            // ignore
          }
        })
        pendingUpdates.length = 0
      }

      this.socketClient.connect()
    },

    // 加载生产统计 / 实绩数据
    async loadReportData() {
      if (this.reportLoading) return
      this.reportLoading = true
      try {
        // 当前计划 & 当前工艺
        try {
          const planRes = await getCurrentPlan()
          if (this.isDestroyed) return
          this.currentPlan = (planRes && planRes.data) || null
        } catch (e) {
          if (this.isDestroyed) return
          this.currentPlan = null
        }
        try {
          const procRes = await getCurrentProcess()
          if (this.isDestroyed) return
          this.currentProcess = (procRes && procRes.data) || null
        } catch (e) {
          if (this.isDestroyed) return
          this.currentProcess = null
        }

        // 实绩汇总 & 明细：不传筛选条件，后端按默认时间范围处理
        let summaryData = null
        let detailsData = []
        
        try {
          const sumRes = await getReportSummary({})
          if (this.isDestroyed) return
          // 处理可能的多种返回结构：直接数据、{data: {...}}、{rows: [...], total: ...}
          if (sumRes) {
            if (sumRes.data && typeof sumRes.data === 'object' && !Array.isArray(sumRes.data)) {
              summaryData = sumRes.data
            } else {
              summaryData = sumRes
            }
          }
        } catch (e) {
          if (this.isDestroyed) return
          console.error('加载实绩汇总失败:', e)
        }
        
        try {
          const detRes = await getReportDetails({})
          if (this.isDestroyed) return
          // 处理可能的多种返回结构
          if (detRes) {
            if (Array.isArray(detRes)) {
              detailsData = detRes
            } else if (detRes.data && Array.isArray(detRes.data)) {
              detailsData = detRes.data
            } else if (detRes.rows && Array.isArray(detRes.rows)) {
              detailsData = detRes.rows
            }
          }
        } catch (e) {
          if (this.isDestroyed) return
          console.error('加载实绩明细失败:', e)
        }
        
        // 统一处理汇总数据：修正coilCount
        if (this.isDestroyed) return
        
        if (summaryData) {
          // 如果coilCount不存在或看起来像是页数（通常页数很小），尝试从其他字段获取
          if (summaryData.total !== undefined && 
              (summaryData.coilCount === undefined || 
               (summaryData.coilCount < 10 && summaryData.total > summaryData.coilCount))) {
            // 优先使用total字段
            summaryData.coilCount = summaryData.total
          } else if (detailsData.length > 0 && 
                     (summaryData.coilCount === undefined || summaryData.coilCount < detailsData.length)) {
            // 如果total不存在，使用明细数组长度作为总数
            summaryData.coilCount = detailsData.length
          }
          this.reportSummary = summaryData
        } else {
          this.reportSummary = null
        }
        
        this.reportDetails = detailsData
      } finally {
        if (!this.isDestroyed) {
          this.reportLoading = false
        }
      }
    },

    // 加载停机记录
    async loadStoppageData() {
      if (this.stoppageLoading) return
      this.stoppageLoading = true
      try {
        // 计算当月起止日期（yyyy-MM-dd）
        const month = this.stoppageMonth
        let startDate = ''
        let endDate = ''
        if (month) {
          const [y, m] = month.split('-').map((s) => Number(s))
          const first = new Date(y, m - 1, 1)
          const last = new Date(y, m, 0)
          const pad = (n) => String(n).padStart(2, '0')
          startDate = `${first.getFullYear()}-${pad(first.getMonth() + 1)}-${pad(first.getDate())}`
          endDate = `${last.getFullYear()}-${pad(last.getMonth() + 1)}-${pad(last.getDate())}`
        }
        const res = await listStoppage({ startDate, endDate })
        if (this.isDestroyed) return
        this.stoppageList = (res && res.data) || []
      } catch (e) {
        if (this.isDestroyed) return
        this.stoppageList = []
      } finally {
        if (!this.isDestroyed) {
          this.stoppageLoading = false
        }
      }
    },

    onStoppageMonthChange(e) {
      // e.detail.value 为 yyyy-MM-dd，这里截取前 7 位当作月份
      const v = e.detail && e.detail.value
      this.stoppageMonth = v ? v.slice(0, 7) : ''
      this.loadStoppageData()
    },

    getFieldLabel(field) {
      if (this.isDestroyed) return field || ''
      const meta = this.fieldMeta[field]
      return (meta && meta.label) || field
    },

    getFieldUnit(field) {
      if (this.isDestroyed) return ''
      const meta = this.fieldMeta[field]
      return (meta && meta.unit) || ''
    },


    // 根据 sourceType + 字段名取实时值（不按设备分块）
    getRealtimeValueBySource(sourceType, field) {
      if (this.isDestroyed) return '—'
      const m = this.latestMeasure || {}
      let msg = {}
      switch (sourceType) {
        case 'ENTRY':
          msg = m.appMeasureEntryMessage || {}
          break
        case 'FURNACE':
          msg = m.appMeasureFurnaceMessage || {}
          break
        case 'COAT':
          msg = m.appMeasureCoatMessage || {}
          break
        case 'EXIT':
          msg = m.appMeasureExitMessage || {}
          break
        default:
          msg = {}
      }
      const v = msg ? msg[field] : null
      if (typeof v === 'string') return v || '—'
      return this.formatValue(v)
    },

    // 追加一帧到前端历史缓存（用于统计折线图）
    appendChartPoint(payload) {
      if (this.isDestroyed) return
      const now = new Date()
      const hh = String(now.getHours()).padStart(2, '0')
      const mm = String(now.getMinutes()).padStart(2, '0')
      const ss = String(now.getSeconds()).padStart(2, '0')
      const t = `${hh}:${mm}:${ss}`

      const entry = payload.appMeasureEntryMessage || {}
      const furnace = payload.appMeasureFurnaceMessage || {}
      const coat = payload.appMeasureCoatMessage || {}
      const exit = payload.appMeasureExitMessage || {}

      this.pushSeries('entry', t, {
        stripSpeed: entry.stripSpeed,
        tensionPorBr1: entry.tensionPorBr1,
        tensionBr1Br2: entry.tensionBr1Br2,
        tensionBr2Br3: entry.tensionBr2Br3
      })

      this.pushSeries('furnace', t, {
        jcf1FurnaceTemperatureActual: furnace.jcf1FurnaceTemperatureActual,
        jcf2FurnaceTemperatureActual: furnace.jcf2FurnaceTemperatureActual,
        lbzFurnaceTemperatureActual: furnace.lbzFurnaceTemperatureActual,
        lthFurnaceTemperatureActual: furnace.lthFurnaceTemperatureActual,
        nof1FurnaceTemperatureActual: furnace.nof1FurnaceTemperatureActual,
        nof2FurnaceTemperatureActual: furnace.nof2FurnaceTemperatureActual,
        nof3FurnaceTemperatureActual: furnace.nof3FurnaceTemperatureActual,
        nof4FurnaceTemperatureActual: furnace.nof4FurnaceTemperatureActual,
        nof5FurnaceTemperatureActual: furnace.nof5FurnaceTemperatureActual,
        phFurnaceTemperatureActual: furnace.phFurnaceTemperatureActual,
        rtf1FurnaceTemperatureActual: furnace.rtf1FurnaceTemperatureActual,
        rtf2FurnaceTemperatureActual: furnace.rtf2FurnaceTemperatureActual,
        sfFurnaceTemperatureActual: furnace.sfFurnaceTemperatureActual,
        tdsFurnaceTemperatureActual: furnace.tdsFurnaceTemperatureActual,
        potTemperature: furnace.potTemperature
      })

      this.pushSeries('coat', t, {
        avrCoatingWeightTop: coat.avrCoatingWeightTop,
        avrCoatingWeightBottom: coat.avrCoatingWeightBottom,
        airKnifePressure: coat.airKnifePressure,
        stripSpeedTmExit: coat.stripSpeedTmExit
      })

      this.pushSeries('exit', t, {
        tensionBr8Br9: exit.tensionBr8Br9,
        tensionBr9Tr: exit.tensionBr9Tr,
        speedExitSection: exit.speedExitSection
      })
    },

    pushSeries(group, time, values) {
      if (this.isDestroyed) return
      const g = this.chartSeries[group]
      if (!g) return
      
      // 带钢速度折线图使用单独的最大点数限制
      const maxPoints = group === 'stripSpeed' ? this.stripSpeedMaxPoints : this.chartMaxPoints
      
      // 直接修改数组内容，保持数组引用不变，避免触发图表重新初始化
      g.time.push(time)
      Object.keys(values).forEach((k) => {
        if (!g[k]) return
        const raw = values[k]
        const num = raw === null || raw === undefined || raw === '' ? null : Number(raw)
        g[k].push(Number.isFinite(num) ? num : null)
      })
      
      // 如果超过最大点数，去掉最早的数据
      if (g.time.length > maxPoints) {
        g.time.shift()
        Object.keys(values).forEach((k) => {
          if (g[k] && g[k].length > maxPoints) {
            g[k].shift()
          }
        })
      }
    },

    // 生成多序列折线图数据
    toGroupLineChart(group) {
      if (this.isDestroyed) return { categories: [], series: [] }
      const g = this.chartSeries[group]
      if (!g) return { categories: [], series: [] }
      const seriesKeys = Object.keys(g).filter((k) => k !== 'time')
      return {
        categories: g.time || [],
        series: seriesKeys.map((k) => ({
          name: this.getFieldLabel(k),
          data: g[k] || []
        }))
      }
    },

    // 追加带钢速度点到折线图（持续刷新）
    appendStripSpeedPoint(payload) {
      if (this.isDestroyed) return
      const entry = payload.appMeasureEntryMessage || {}
      if (entry && entry.stripSpeed !== null && entry.stripSpeed !== undefined) {
        const now = new Date()
        const hh = String(now.getHours()).padStart(2, '0')
        const mm = String(now.getMinutes()).padStart(2, '0')
        const ss = String(now.getSeconds()).padStart(2, '0')
        const t = `${hh}:${mm}:${ss}`
        
        const speed = Number(entry.stripSpeed)
        if (Number.isFinite(speed)) {
          this.pushSeries('stripSpeed', t, { stripSpeed: speed })
        }
      }
    },

    formatNum(v) {
      if (v === null || v === undefined || Number.isNaN(Number(v))) return '—'
      return Number(v).toFixed(2)
    },

    // 不再按设备分块展示：pickSourceMsg/getRealtimeFieldValue 已弃用

    // 快捷导航（已移除）



    async refreshAll() {
      if (this.isRefreshing) return
      this.isRefreshing = true
      try {
        await Promise.all([this.loadDeviceDefs(), this.loadFieldMeta()])
        this.updateLastTime()
      } finally {
        if (!this.isDestroyed) {
          this.isRefreshing = false
        }
      }
    },

    updateLastTime() {

      
      // 如果没有起始时间，使用当前时间作为起始时间
      if (!this.startTime) {
        this.startTime = new Date()
      }
      
      const now = new Date()
      const diff = now - this.startTime // 时间差（毫秒）
      
      // 计算天数、小时数、分钟数
      const days = Math.floor(diff / (1000 * 60 * 60 * 24))
      const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60))
      const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60))
      
      // 格式化显示
      let timeStr = ''
      if (days > 0) {
        timeStr += `${days}天`
      }
      if (hours > 0 || days > 0) {
        timeStr += `${hours}小时`
      }
      timeStr += `${minutes}分钟`
      
      this.lastUpdateTime = timeStr || '0分钟'
    },

    formatTime(str) {
      if (!str) return ''
      const d = new Date(str)
      const h = String(d.getHours()).padStart(2, '0')
      const m = String(d.getMinutes()).padStart(2, '0')
      return `${h}:${m}`
    },

    // 格式化持续时间：将分钟数转换为"X天X小时X分钟"格式
    formatDuration(minutes) {
      if (minutes === null || minutes === undefined || minutes === '') return '—'
      const totalMinutes = Math.floor(Number(minutes))
      if (isNaN(totalMinutes) || totalMinutes < 0) return '—'
      
      const days = Math.floor(totalMinutes / (24 * 60))
      const hours = Math.floor((totalMinutes % (24 * 60)) / 60)
      const mins = Math.floor(totalMinutes % 60)
      
      let result = ''
      if (days > 0) {
        result += `${days}天`
      }
      if (hours > 0 || days > 0) {
        result += `${hours}小时`
      }
      if (mins > 0 || result === '') {
        result += `${mins}分钟`
      }
      
      return result || '0分钟'
    },

    formatValue(v) {
      if (v === null || v === undefined || v === '') return '—'
      const n = Number(v)
      if (Number.isNaN(n)) return '—'
      return n.toFixed(2)
    },

  }
}
</script>

<style scoped lang="scss">
.page-container {
  min-height: 100vh;
  background: #f5f7fa;
}

.tab-container {
  display: flex;
  background: #fff;
  border-bottom: 2rpx solid #e4e7ed;
}

.tab-item {
  flex: 1;
  text-align: center;
  padding: 28rpx 0;
  position: relative;

  .tab-label {
    font-size: 28rpx;
    color: #606266;
    font-weight: 400;
  }

  &.tab-active {
    .tab-label {
      color: #0066cc;
      font-weight: 500;
    }
  }

  .tab-indicator {
    position: absolute;
    bottom: 0;
    left: 50%;
    transform: translateX(-50%);
    width: 60rpx;
    height: 3rpx;
    background: #0066cc;
  }
}

.refresh-btn-fixed {
  position: fixed;
  right: 32rpx;
  bottom: 120rpx;
  width: 96rpx;
  height: 96rpx;
  background: #0066cc;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 8rpx 20rpx rgba(0, 102, 204, 0.4);
  z-index: 999;

  &:active {
    opacity: 0.8;
    transform: scale(0.95);
  }
}

.refresh-icon {
  font-size: 48rpx;
  color: #fff;
  display: block;
  line-height: 1;

  &.rotating {
    animation: rotate 1s linear infinite;
  }
}

/* 快速导航菜单（固定在左下角，模仿 acidity.vue） */
.nav-menu-fixed {
  position: fixed;
  left: 32rpx;
  bottom: 120rpx;
  z-index: 998;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 12rpx;
}

.nav-toggle {
  width: 96rpx;
  height: 96rpx;
  background: #409eff;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 8rpx 20rpx rgba(64, 158, 255, 0.4);
  transition: all 0.3s ease;

  &:active {
    opacity: 0.8;
    transform: scale(0.95);
  }
}

.nav-toggle-icon {
  font-size: 48rpx;
  color: #fff;
  display: block;
  line-height: 1;
}

.nav-items {
  display: flex;
  flex-direction: column;
  gap: 8rpx;
  animation: slideUp 0.3s ease;
  max-height: 60vh;
  overflow: auto;
}

@keyframes slideUp {
  from {
    opacity: 0;
    transform: translateY(20rpx);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.nav-item {
  background: #fff;
  border: 2rpx solid #409eff;
  border-radius: 8rpx;
  padding: 16rpx 24rpx;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 4rpx 12rpx rgba(64, 158, 255, 0.2);
  transition: all 0.2s ease;

  &:active {
    background: #f0f9ff;
    transform: scale(0.95);
  }
}

.nav-label {
  font-size: 26rpx;
  color: #409eff;
  font-weight: 500;
  white-space: nowrap;
}

@keyframes rotate {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}

.scroll-container {
  height: calc(100vh - 96rpx);
  padding: 24rpx;
}

.placeholder-box {
  background: #fff;
  border: 1rpx dashed #dcdfe6;
  border-radius: 12rpx;
  padding: 28rpx 20rpx;
  margin-bottom: 18rpx;
}

.placeholder-text {
  font-size: 24rpx;
  color: #909399;
}

.status-bar {
  display: flex;
  align-items: center;
  background: #fff;
  padding: 24rpx 32rpx;
  margin-bottom: 24rpx;
  border-radius: 8rpx;
  border: 1rpx solid #e4e7ed;
}

.status-item {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 16rpx;
}

.status-label {
  font-size: 26rpx;
  color: #909399;
}

.status-value {
  font-size: 28rpx;
  font-weight: 500;
  color: #303133;

  &.status-通畅 {
    color: #67c23a;
  }

  &.status-异常 {
    color: #f56c6c;
  }

  &.status-time {
    color: #909399;
    font-size: 24rpx;
  }
}

.status-divider {
  width: 1rpx;
  height: 40rpx;
  background: #e4e7ed;
}

.section {
  margin-bottom: 24rpx;
}

.section-title {
  font-size: 30rpx;
  font-weight: 500;
  color: #303133;
  margin-bottom: 20rpx;
  padding-left: 16rpx;
  border-left: 4rpx solid #0066cc;
}

.strip-status-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16rpx;
  margin-bottom: 20rpx;
}

.strip-status-card {
  background: #fff;
  border: 1rpx solid #e4e7ed;
  border-radius: 8rpx;
  padding: 24rpx;
  display: flex;
  flex-direction: column;
  align-items: center;
}

.strip-status-label {
  font-size: 24rpx;
  color: #909399;
  margin-bottom: 12rpx;
}

.strip-status-value {
  font-size: 48rpx;
  font-weight: 600;
  color: #0066cc;
  line-height: 1;
  margin-bottom: 8rpx;
}

.strip-status-unit {
  font-size: 20rpx;
  color: #909399;
  margin-bottom: 16rpx;
}

.progress-container {
  width: 100%;
  margin-top: 8rpx;
}

.progress-bar {
  width: 100%;
  height: 8rpx;
  background: #e4e7ed;
  border-radius: 4rpx;
  overflow: hidden;
  margin-bottom: 8rpx;
}

.progress-fill {
  height: 100%;
  background: linear-gradient(90deg, #0066cc 0%, #409eff 100%);
  border-radius: 4rpx;
  transition: width 0.3s ease;
}

.progress-text {
  font-size: 20rpx;
  color: #606266;
  text-align: center;
  display: block;
}

.device-card {
  background: #fff;
  border: 1rpx solid #e4e7ed;
  border-radius: 12rpx;
  padding: 20rpx;
  margin-bottom: 18rpx;
}

.device-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 12rpx;
}

.device-header-left {
  display: flex;
  flex-direction: column;
}

.device-title {
  font-size: 30rpx;
  font-weight: 600;
  color: #303133;
}

.device-sub {
  font-size: 22rpx;
  color: #909399;
  margin-top: 6rpx;
}

.device-toggle {
  font-size: 24rpx;
  color: #0066cc;
  font-weight: 500;
}

.metrics-grid-3 {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16rpx;
}

.metric-box {
  background: #fff;
  border: 1rpx solid #e4e7ed;
  border-radius: 8rpx;
  padding: 18rpx 12rpx;
  text-align: center;
}

.metric-name {
  display: block;
  font-size: 22rpx;
  color: #909399;
  margin-bottom: 12rpx;
}

.metric-value {
  display: block;
  font-size: 44rpx;
  font-weight: 600;
  color: #0066cc;
  margin-bottom: 8rpx;
  line-height: 1;

  &.small {
    font-size: 34rpx;
  }
}

.metric-unit {
  display: block;
  font-size: 20rpx;
  color: #909399;
}

.metric-set-value {
  display: block;
  font-size: 20rpx;
  color: #909399;
  margin-top: 4rpx;
}

.device-actions {
  display: flex;
  justify-content: flex-end;
  margin-top: 14rpx;
}

.action-btn {
  padding: 12rpx 18rpx;
  border: 2rpx solid #0066cc;
  border-radius: 8rpx;
}

.action-text {
  font-size: 24rpx;
  color: #0066cc;
  font-weight: 500;
}

.history-tip {
  font-size: 24rpx;
  color: #909399;
  margin-bottom: 12rpx;
}

.history-strong {
  color: #303133;
  font-weight: 600;
}

.field-selector {
  display: flex;
  flex-wrap: wrap;
  gap: 12rpx;
  align-items: center;
  margin-bottom: 12rpx;
}

.field-chip {
  padding: 10rpx 14rpx;
  border-radius: 999rpx;
  border: 2rpx solid #dcdfe6;
  background: #fff;
}

.field-chip.active {
  border-color: #0066cc;
  background: #f0f9ff;
}

.chip-text {
  font-size: 22rpx;
  color: #606266;
}

.field-chip.active .chip-text {
  color: #0066cc;
  font-weight: 500;
}

.field-tip {
  font-size: 22rpx;
  color: #909399;
  margin-left: auto;
}

.chart-box {
  background: #fff;
  border: 1rpx solid #e4e7ed;
  border-radius: 8rpx;
  padding: 24rpx 16rpx;
}

.raw-box {
  background: #fff;
  border: 1rpx solid #e4e7ed;
  border-radius: 8rpx;
  padding: 24rpx 16rpx;
}

.overview-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16rpx;
}

.overview-card {
  background: #fff;
  border-radius: 12rpx;
  padding: 32rpx 24rpx;
  text-align: center;
  border: 1rpx solid #e4e7ed;
}

.overview-card:nth-child(2) {
  background: #fff;
}

.overview-card:nth-child(3) {
  background: #fff;
}

.overview-label {
  display: block;
  font-size: 24rpx;
  color: #909399;
  margin-bottom: 16rpx;
}

.overview-value {
  display: block;
  font-size: 48rpx;
  font-weight: 700;
  color: #303133;
  line-height: 1.2;
}

.overview-unit {
  display: block;
  font-size: 22rpx;
  color: #909399;
  margin-top: 8rpx;
}

.stoppage-filter {
  margin: 0 0 20rpx 0;
  display: flex;
  justify-content: flex-end;
}

.stoppage-month-btn {
  padding: 12rpx 24rpx;
  border-radius: 999rpx;
  border: 1rpx solid #dcdfe6;
  background: #fff;
}

.stoppage-month-text {
  font-size: 24rpx;
  color: #606266;
}

.status-distribution {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 16rpx;
}

.status-item-card {
  background: #fff;
  border: 1rpx solid #e4e7ed;
  border-radius: 12rpx;
  padding: 24rpx;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.status-type {
  font-size: 28rpx;
  color: #606266;
  font-weight: 500;
}

.status-count {
  font-size: 36rpx;
  font-weight: 600;
  color: #0066cc;
}

.stats-table {
  background: #fff;
  border: 1rpx solid #e4e7ed;
  border-radius: 8rpx;
  overflow: hidden;
}

.stats-header {
  display: grid;
  grid-template-columns: 2fr 1fr 1fr 1fr 1fr;
  background: #f5f7fa;
  padding: 20rpx 16rpx;
  border-bottom: 1rpx solid #e4e7ed;
}

.stats-row {
  display: grid;
  grid-template-columns: 2fr 1fr 1fr 1fr 1fr;
  padding: 20rpx 16rpx;
  border-bottom: 1rpx solid #e4e7ed;
}

.stats-row:last-child {
  border-bottom: none;
}

.stats-col {
  font-size: 24rpx;
  color: #606266;
  text-align: center;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.stats-header .stats-col {
  font-weight: 600;
  color: #303133;
}

.detail-cards {
  display: flex;
  flex-direction: column;
  gap: 16rpx;
}

.detail-card {
  background: #fff;
  border: 1rpx solid #e4e7ed;
  border-radius: 12rpx;
  overflow: hidden;
  transition: all 0.3s ease;
}

.detail-card:active {
  transform: scale(0.98);
  box-shadow: 0 4rpx 12rpx rgba(0, 102, 204, 0.1);
}

.detail-card-header {
  background: linear-gradient(135deg, #0066cc 0%, #409eff 100%);
  padding: 20rpx 24rpx;
  border-bottom: 1rpx solid #e4e7ed;
}

.detail-card-title {
  font-size: 28rpx;
  font-weight: 600;
  color: #fff;
}

.detail-card-body {
  padding: 24rpx;
  display: flex;
  flex-direction: column;
  gap: 16rpx;
}

.detail-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 12rpx 0;
  border-bottom: 1rpx solid #f5f7fa;
}

.detail-row:last-child {
  border-bottom: none;
}

.detail-label {
  font-size: 24rpx;
  color: #909399;
  font-weight: 400;
  min-width: 120rpx;
}

.detail-value {
  font-size: 26rpx;
  color: #303133;
  font-weight: 500;
  text-align: right;
  flex: 1;
}

.stoppage-cards {
  display: flex;
  flex-direction: column;
  gap: 16rpx;
}

.stoppage-card {
  background: #fff;
  border: 1rpx solid #e4e7ed;
  border-radius: 12rpx;
  overflow: hidden;
  transition: all 0.3s ease;
}

.stoppage-card:active {
  transform: scale(0.98);
  box-shadow: 0 4rpx 12rpx rgba(245, 108, 108, 0.1);
}

.stoppage-card-header {
  background: linear-gradient(135deg, #f56c6c 0%, #ff7875 100%);
  padding: 20rpx 24rpx;
  border-bottom: 1rpx solid #e4e7ed;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.stoppage-card-title {
  font-size: 28rpx;
  font-weight: 600;
  color: #fff;
}

.stoppage-card-duration {
  font-size: 26rpx;
  font-weight: 600;
  color: #fff;
  background: rgba(255, 255, 255, 0.2);
  padding: 6rpx 16rpx;
  border-radius: 20rpx;
}

.stoppage-card-body {
  padding: 24rpx;
  display: flex;
  flex-direction: column;
  gap: 16rpx;
}

.stoppage-row {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  padding: 12rpx 0;
  border-bottom: 1rpx solid #f5f7fa;
}

.stoppage-row:last-child {
  border-bottom: none;
}

.stoppage-label {
  font-size: 24rpx;
  color: #909399;
  font-weight: 400;
  min-width: 120rpx;
}

.stoppage-value {
  font-size: 26rpx;
  color: #303133;
  font-weight: 500;
  text-align: right;
  flex: 1;
}

.stoppage-remark {
  color: #606266;
  font-weight: 400;
  word-break: break-all;
  white-space: normal;
  text-align: left;
}
</style>
