<template>
  <div
    class="trace-dashboard-page"
    :style="{ width: `${style.width}px`, height: `${style.height}px`, transform: style.transform }"
  >
    <header class="dashboard-header">
      <div class="brand">
        <img class="brand-logo" :src="clientLogo" alt="客户标识" />
        <!-- <div class="brand-copy">
          <span>德州 大陆架</span>
          <strong>生产追溯中心</strong>
        </div> -->
      </div>
      <div class="title-wrap">
        <img class="title-frame" src="../assets/dashboard/title-frame.png" alt="" aria-hidden="true" />
        <h1>井下工具核心零部件精密加工示范线看板</h1>
      </div>
      <div class="clock">
        <span>{{ currentDate }}</span>
        <strong>{{ currentTime }}</strong>
      </div>
    </header>

    <main class="dashboard-body">
      <section
        v-for="card in summaryCards"
        :key="card.key"
        class="metric-card"
        :class="card.tone"
      >
        <div class="metric-icon" v-html="card.icon"></div>
        <div class="metric-copy">
          <span>{{ card.label }}</span>
          <strong :class="{ 'is-long-value': card.longValue }">{{ card.value }}<small>{{ card.unit }}</small></strong>
        </div>
        <i></i>
      </section>

      <section class="panel order-panel">
        <div class="panel-head">
          <span class="panel-index">01</span>
          <h2>订单执行概览</h2>
          <b>ORDER</b>
        </div>
        <div class="current-order">
          <div>
            <span>当前计划</span>
            <strong>{{ summary.currentPlan || '--' }}</strong>
          </div>
          <div>
            <span>物料</span>
            <strong>{{ summary.currentMaterialName || summary.currentMaterialNo || '--' }}</strong>
          </div>
          <div>
            <span>计划数量</span>
            <strong>{{ formatNumber(summary.currentPlanQty) }}</strong>
          </div>
        </div>
        <div ref="orderChartRef" class="order-chart"></div>
        <div class="task-list">
          <div
            v-for="item in taskRows"
            :key="item.planNo + item.processName"
            class="task-row"
          >
            <div class="task-main">
              <span>{{ item.planNo }}</span>
              <strong>{{ item.materialName || item.materialNo || '未指定物料' }}</strong>
            </div>
            <div class="task-progress">
              <i><em :style="{ width: `${clampProgress(item.progress)}%` }"></em></i>
              <span>{{ clampProgress(item.progress).toFixed(1) }}%</span>
            </div>
          </div>
        </div>
      </section>

      <section class="panel trend-panel">
        <div class="panel-head">
          <span class="panel-index">02</span>
          <h2>近七日生产趋势</h2>
          <b>PASS</b>
        </div>
        <div ref="passTrendChartRef" class="chart-fill"></div>
      </section>

      <section class="flow-panel">
        <div class="flow-backdrop">
          <img class="machine-img" src="../assets/image/MachineTool.png" alt="" />
          <img class="robot-img" src="../assets/image/Robot.png" alt="" />
        </div>
        <div class="flow-head">
          <span>PROCESS FLOW</span>
          <strong>工序流转矩阵</strong>
          <em>订单下发 / 生产 / 打印 / 报工</em>
        </div>
        <div class="flow-stage">
          <div class="flow-line"></div>
          <div class="station-grid">
            <article
              v-for="(station, index) in stationRows"
              :key="station.stationNo"
              class="station-card"
              :class="statusClass(station.statusText)"
            >
              <div class="station-beacon"></div>
              <div class="station-order">{{ String(index + 1).padStart(2, '0') }}</div>
              <div class="station-title">
                <span>{{ station.stationNo }}</span>
                <strong>{{ station.stationName }}</strong>
              </div>
              <div class="station-state">{{ station.statusText || '待机' }}</div>
              <dl>
                <div>
                  <dt>生产</dt>
                  <dd>{{ formatNumber(station.passCount) }}</dd>
                </div>
                <div>
                  <dt>停站</dt>
                  <dd>{{ formatDuration(station.stopSeconds) }}</dd>
                </div>
              </dl>
            </article>
          </div>
        </div>
      </section>

      <section class="panel event-panel">
        <div class="panel-head">
          <span class="panel-index">03</span>
          <h2>最新生产事件</h2>
          <b>EVENT</b>
        </div>
        <div class="event-list">
          <article
            v-for="event in eventRows"
            :key="event.eventTime + event.title + event.type"
            class="event-item"
            :class="event.level === '异常' ? 'is-alert' : ''"
          >
            <div class="event-type">{{ event.type }}</div>
            <div class="event-content">
              <strong>{{ event.title }}</strong>
              <span>{{ event.subTitle || event.planNo || event.stationNo || '--' }}</span>
            </div>
            <time>{{ formatTimeOnly(event.eventTime) }}</time>
          </article>
        </div>
      </section>

      <section class="panel alarm-panel">
        <div class="panel-head">
          <span class="panel-index">04</span>
          <h2>异常摘要</h2>
          <b>ALARM</b>
        </div>
        <div ref="alarmChartRef" class="chart-fill"></div>
      </section>

      <section class="panel print-panel">
        <div class="panel-head">
          <span class="panel-index">05</span>
          <h2>追溯打印</h2>
          <b>TRACE</b>
        </div>
        <div class="print-list">
          <article
            v-for="item in printDisplayRows"
            :key="item.traceCode + item.printTime"
            class="print-item"
          >
            <span>{{ item.traceCode || '--' }}</span>
            <strong>{{ item.planNo || '未绑定计划' }}</strong>
            <em>{{ formatTimeOnly(item.printTime) }}</em>
          </article>
        </div>
      </section>

      <section class="panel detail-panel">
        <div class="panel-head">
          <span class="panel-index">06</span>
          <h2>生产过程明细</h2>
          <b>DETAIL</b>
        </div>
        <div class="trace-table datav-table">
          <dv-scroll-board :config="passDetailBoardConfig" />
        </div>
      </section>
    </main>
  </div>
</template>

<script setup>
import * as echarts from 'echarts'
import { computed, inject, nextTick, onMounted, onUnmounted, reactive, ref } from 'vue'

defineOptions({
  name: 'DaLuJiaTraceDashboard'
})

const CreateData = inject('CreateData')
const ExecDatabase = inject('ExecDatabase')
const clientLogo = '/public/ClientLogo.png'

const style = reactive({
  width: 1920,
  height: 1080,
  transform: 'scaleY(1) scaleX(1) translate(-50%, -50%)'
})

const currentDate = ref('')
const currentTime = ref('')
const orderChartRef = ref(null)
const passTrendChartRef = ref(null)
const alarmChartRef = ref(null)
const charts = {
  order: null,
  passTrend: null,
  alarm: null
}

let clockTimer = null
let refreshTimer = null
let resizeHandler = null

const summary = reactive(createEmptySummary())
const taskRows = ref([])
const passTrendRows = ref([])
const stationRows = ref([])
const eventRows = ref([])
const alarmRows = ref([])
const printRows = ref([])
const passDetailRows = ref([])

const icons = {
  plan: buildIcon('#42dfff', '#0867ff', '<path d="M22 18h28v36H22z"/><path d="M29 29h14M29 38h14M29 47h9M29 13h14l-3 7h-8z"/>'),
  pass: buildIcon('#36f0a3', '#087e5d', '<path d="M17 38h19"/><path d="M29 26l11 12-11 12"/><path d="M42 22h12v32H42"/>'),
  done: buildIcon('#8dfbff', '#1087ff', '<path d="M20 36l10 10 22-25"/><path d="M18 54h36"/>'),
  alarm: buildIcon('#ff8b63', '#df2f35', '<path d="M36 14 57 55H15z"/><path d="M36 28v12M36 48h.1"/>'),
  print: buildIcon('#ffd36b', '#de8f19', '<path d="M24 28V16h24v12"/><path d="M23 48h-6V31h38v17h-6"/><path d="M25 42h22v14H25z"/>'),
  station: buildIcon('#c7f3ff', '#3c8cff', '<path d="M18 23h36v24H18z"/><path d="M25 31h22M25 39h15M29 47v8M43 47v8M25 55h22"/>')
}

const summaryCards = computed(() => [
  { key: 'plan', label: '今日计划', value: formatNumber(summary.todayPlan), unit: '单', icon: icons.plan, tone: 'tone-blue' },
  { key: 'current', label: '当前计划', value: summary.currentPlan || '--', unit: '', icon: icons.station, tone: 'tone-cyan', longValue: true },
  { key: 'pass', label: '今日生产', value: formatNumber(summary.todayPass), unit: '次', icon: icons.pass, tone: 'tone-green' },
  { key: 'done', label: '今日完工', value: formatNumber(summary.todayDone), unit: '件', icon: icons.done, tone: 'tone-sky' },
  { key: 'alarm', label: '异常事件', value: formatNumber(summary.todayAlarm), unit: '条', icon: icons.alarm, tone: 'tone-red' },
  { key: 'print', label: '追溯打印', value: formatNumber(summary.todayPrint), unit: '次', icon: icons.print, tone: 'tone-gold' }
])

const printDisplayRows = computed(() => printRows.value.slice(0, 3))

const boardBaseConfig = {
  headerBGC: 'rgba(11, 77, 130, .88)',
  oddRowBGC: 'rgba(5, 28, 65, .44)',
  evenRowBGC: 'rgba(3, 20, 51, .38)',
  headerHeight: 36,
  waitTime: 1600,
  carousel: 'single',
  hoverPause: true,
  align: 'center',
  index: false
}

const passDetailBoardConfig = computed(() => ({
  ...boardBaseConfig,
  rowNum: 5,
  columnWidth: [150, 150, 220, 220, 250, 110, 100],
  header: ['时间', '工位', '计划号', '产品编码', '追溯/零件号', '停站', '结果'],
  data: extendRows(passDetailRows.value, 8).map(row => [
    formatDateTime(row.createTime),
    row.stationName || row.stationNo || '--',
    row.orderNo || '--',
    row.productCode || '--',
    row.partNo || '--',
    formatDuration(row.stopSeconds),
    resultText(row.result)
  ])
}))

onMounted(() => {
  refreshClock()
  startClock()
  setScale()
  resizeHandler = () => {
    setScale()
    resizeCharts()
  }
  window.addEventListener('resize', resizeHandler)
  nextTick(() => {
    initCharts()
    loadDashboardData()
  })
  refreshTimer = window.setInterval(loadDashboardData, 60 * 1000)
})

onUnmounted(() => {
  if (clockTimer) window.clearInterval(clockTimer)
  if (refreshTimer) window.clearInterval(refreshTimer)
  if (resizeHandler) window.removeEventListener('resize', resizeHandler)
  Object.keys(charts).forEach(key => {
    if (charts[key]) {
      charts[key].dispose()
      charts[key] = null
    }
  })
})

async function loadDashboardData() {
  await Promise.allSettled([
    queryProcedure('大陆架看板_生产追溯_汇总查询', [], applySummary),
    queryProcedure('大陆架看板_生产追溯_任务查询', [['记录条数', 20]], applyTasks),
    queryProcedure('大陆架看板_生产追溯_过站趋势查询', [], applyPassTrend),
    queryProcedure('大陆架看板_生产追溯_工位流转查询', [], applyStations),
    queryProcedure('大陆架看板_生产追溯_事件查询', [['记录条数', 60]], applyEvents),
    queryProcedure('大陆架看板_生产追溯_报警摘要查询', [['记录条数', 10]], applyAlarms),
    queryProcedure('大陆架看板_生产追溯_打印查询', [['记录条数', 30]], applyPrints),
    queryProcedure('大陆架看板_生产追溯_过站明细查询', [['记录条数', 40]], applyPassDetails)
  ])
  nextTick(updateCharts)
}

function queryProcedure(procedureName, params, applyData) {
  if (typeof CreateData !== 'function' || typeof ExecDatabase !== 'function') {
    return Promise.resolve()
  }
  let payload
  try {
    payload = CreateData('11', procedureName, params)
  } catch (error) {
    return Promise.resolve()
  }
  return ExecDatabase(payload)
    .then(response => {
      const rows = getResponseRows(response)
      applyData(rows)
    })
    .catch(() => {})
}

function applySummary(rows) {
  const row = rows[0] || {}
  Object.assign(summary, {
    statDate: getField(row, ['统计日期'], ''),
    todayPlan: toNumber(getField(row, ['今日计划数'], 0)),
    currentPlan: getField(row, ['当前计划号'], ''),
    currentMaterialNo: getField(row, ['当前物料号'], ''),
    currentMaterialName: getField(row, ['当前物料名称'], ''),
    currentPlanQty: toNumber(getField(row, ['当前计划数量'], 0)),
    todayPass: toNumber(getField(row, ['今日过站数'], 0)),
    todayDone: toNumber(getField(row, ['今日完工数'], 0)),
    todayAlarm: toNumber(getField(row, ['今日异常数'], 0)),
    todayPrint: toNumber(getField(row, ['今日打印数'], 0)),
    stationCount: toNumber(getField(row, ['工位数'], 0))
  })
}

function applyTasks(rows) {
  const nextRows = rows.map(row => ({
    planNo: getField(row, ['计划号'], ''),
    materialNo: getField(row, ['物料号'], ''),
    materialName: getField(row, ['物料名称'], ''),
    qty: toNumber(getField(row, ['数量'], 0)),
    stationNo: getField(row, ['工位号'], ''),
    status: getField(row, ['状态'], ''),
    processName: getField(row, ['工序名称'], ''),
    passCount: toNumber(getField(row, ['过站数'], 0)),
    progress: toNumber(getField(row, ['进度'], 0))
  })).filter(row => hasAnyText(row.planNo, row.materialNo, row.materialName, row.stationNo, row.status, row.processName) || hasAnyPositiveNumber(row.qty, row.passCount, row.progress))

  taskRows.value = nextRows
}

function applyPassTrend(rows) {
  const nextRows = rows.map(row => ({
    date: getField(row, ['日期'], ''),
    passCount: toNumber(getField(row, ['过站数'], 0)),
    doneCount: toNumber(getField(row, ['完工数'], 0)),
    passOk: toNumber(getField(row, ['合格数'], 0)),
    avgStop: toNumber(getField(row, ['平均停站时长'], 0))
  }))

  passTrendRows.value = nextRows
}

function applyStations(rows) {
  const nextRows = rows.map(row => ({
    stationNo: getField(row, ['工位号'], ''),
    stationName: getField(row, ['工位名称'], ''),
    statusCode: toNumber(getField(row, ['状态代码'], 0)),
    statusText: getField(row, ['状态文本'], '待机'),
    orderNo: getField(row, ['工单号'], ''),
    inTime: getField(row, ['进站时间'], ''),
    outTime: getField(row, ['出站时间'], ''),
    stopSeconds: toNumber(getField(row, ['停站时长', '平均停站时长'], 0)),
    passCount: toNumber(getField(row, ['今日过站数'], 0)),
    lastOrder: getField(row, ['最近订单号'], ''),
    productCode: getField(row, ['最近产品编码'], ''),
    partNo: getField(row, ['最近零件号'], ''),
    lastPassTime: getField(row, ['最近过站时间'], ''),
    lastResult: toNumber(getField(row, ['最近合格标志'], 1))
  })).filter(row => ['OP1010', 'OP1020', 'OP1030'].includes(row.stationNo))
    .filter(row => hasAnyText(row.stationNo, row.stationName, row.orderNo, row.lastOrder, row.productCode, row.partNo, row.lastPassTime) || hasAnyPositiveNumber(row.passCount, row.stopSeconds))

  stationRows.value = nextRows
}

function applyEvents(rows) {
  const nextRows = rows.map(row => ({
    type: getField(row, ['事件类型'], ''),
    level: getField(row, ['事件级别'], ''),
    eventTime: getField(row, ['事件时间'], ''),
    stationNo: getField(row, ['工位号'], ''),
    planNo: getField(row, ['计划号'], ''),
    title: getField(row, ['主标题'], ''),
    subTitle: getField(row, ['副标题'], '')
  })).filter(row => hasAnyText(row.type, row.eventTime, row.stationNo, row.planNo, row.title, row.subTitle))

  eventRows.value = nextRows
}

function applyAlarms(rows) {
  const nextRows = rows.map(row => ({
    stationNo: getField(row, ['工位号'], ''),
    alarmText: getField(row, ['报警内容'], ''),
    alarmCount: toNumber(getField(row, ['报警次数'], 0)),
    lastTime: getField(row, ['最近报警时间'], '')
  })).filter(row => hasAnyText(row.stationNo, row.alarmText, row.lastTime) || hasAnyPositiveNumber(row.alarmCount))

  alarmRows.value = nextRows
}

function applyPrints(rows) {
  const nextRows = rows.map(row => ({
    planNo: getField(row, ['计划号'], ''),
    mode: getField(row, ['产品管理模式'], ''),
    traceCode: getField(row, ['追踪码单件'], ''),
    printTime: getField(row, ['打印时间'], ''),
    template: getField(row, ['打印模板'], '')
  })).filter(row => hasAnyText(row.planNo, row.mode, row.traceCode, row.printTime, row.template))

  printRows.value = nextRows
}

function applyPassDetails(rows) {
  const nextRows = rows.map(row => ({
    createTime: getField(row, ['创建时间'], ''),
    stationNo: getField(row, ['工位号'], ''),
    stationName: getField(row, ['工位名称'], ''),
    orderNo: getField(row, ['订单号'], ''),
    productCode: getField(row, ['产品编码'], ''),
    partNo: getField(row, ['零件号'], ''),
    inTime: getField(row, ['进站时间'], ''),
    outTime: getField(row, ['出站时间'], ''),
    stopSeconds: toNumber(getField(row, ['停站时长'], 0)),
    result: getField(row, ['结果'], '合格')
  })).filter(row => hasAnyText(row.createTime, row.stationNo, row.stationName, row.orderNo, row.productCode, row.partNo, row.inTime, row.outTime) || hasAnyPositiveNumber(row.stopSeconds))

  passDetailRows.value = nextRows
}

function createEmptySummary() {
  return {
    statDate: '',
    todayPlan: 0,
    currentPlan: '',
    currentMaterialNo: '',
    currentMaterialName: '',
    currentPlanQty: 0,
    todayPass: 0,
    todayDone: 0,
    todayAlarm: 0,
    todayPrint: 0,
    stationCount: 0
  }
}

function initCharts() {
  if (orderChartRef.value) charts.order = echarts.init(orderChartRef.value)
  if (passTrendChartRef.value) charts.passTrend = echarts.init(passTrendChartRef.value)
  if (alarmChartRef.value) charts.alarm = echarts.init(alarmChartRef.value)
  updateCharts()
}

function updateCharts() {
  drawOrderChart()
  drawPassTrendChart()
  drawAlarmChart()
}

function drawOrderChart() {
  if (!charts.order) return
  const done = Number(summary.todayDone || 0)
  const pass = Math.max(Number(summary.todayPass || 0) - done, 0)
  charts.order.setOption({
    backgroundColor: 'transparent',
    color: ['#35f2a4', '#28a9ff'],
    tooltip: { trigger: 'item' },
    legend: {
      bottom: 0,
      itemWidth: 8,
      itemHeight: 8,
      textStyle: { color: '#b9d9ff', fontSize: 12 }
    },
    series: [
      {
        type: 'pie',
        radius: ['54%', '72%'],
        center: ['50%', '46%'],
        avoidLabelOverlap: true,
        label: {
          color: '#e9f7ff',
          formatter: '{b}\n{c}'
        },
        labelLine: { lineStyle: { color: 'rgba(142, 220, 255, .5)' } },
        itemStyle: {
          borderColor: '#071932',
          borderWidth: 3
        },
        data: [
          { name: '完工', value: done },
          { name: '在制生产', value: pass }
        ]
      }
    ]
  })
}

function drawPassTrendChart() {
  if (!charts.passTrend) return
  const labels = passTrendRows.value.map(row => shortDate(row.date))
  const passValues = passTrendRows.value.map(row => row.passCount)
  const doneValues = passTrendRows.value.map(row => row.doneCount)
  charts.passTrend.setOption({
    backgroundColor: 'transparent',
    tooltip: { trigger: 'axis' },
    grid: { left: 44, right: 18, top: 32, bottom: 36 },
    legend: {
      top: 0,
      right: 8,
      itemWidth: 9,
      itemHeight: 9,
      textStyle: { color: '#b9d9ff' }
    },
    xAxis: {
      type: 'category',
      data: labels,
      axisTick: { show: false },
      axisLine: { lineStyle: { color: 'rgba(111, 188, 255, .35)' } },
      axisLabel: { color: '#b9d9ff', fontSize: 12 }
    },
    yAxis: {
      type: 'value',
      minInterval: 1,
      splitLine: { lineStyle: { color: 'rgba(79, 150, 222, .14)' } },
      axisLabel: { color: '#7fb4e8' }
    },
    series: [
      {
        name: '生产',
        type: 'bar',
        barWidth: 16,
        data: passValues,
        itemStyle: {
          borderRadius: [4, 4, 0, 0],
          color: new echarts.graphic.LinearGradient(0, 1, 0, 0, [
            { offset: 0, color: 'rgba(28, 132, 255, .22)' },
            { offset: 1, color: '#4ee8ff' }
          ])
        }
      },
      {
        name: '完工',
        type: 'line',
        smooth: true,
        symbolSize: 8,
        data: doneValues,
        lineStyle: { width: 3, color: '#ffe08a' },
        itemStyle: { color: '#ffe08a' },
        areaStyle: {
          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
            { offset: 0, color: 'rgba(255, 210, 100, .28)' },
            { offset: 1, color: 'rgba(255, 210, 100, 0)' }
          ])
        }
      }
    ]
  })
}

function drawAlarmChart() {
  if (!charts.alarm) return
  const rows = alarmRows.value.slice(0, 6).reverse()
  charts.alarm.setOption({
    backgroundColor: 'transparent',
    tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
    grid: { left: 84, right: 20, top: 18, bottom: 22 },
    xAxis: {
      type: 'value',
      minInterval: 1,
      splitLine: { lineStyle: { color: 'rgba(79, 150, 222, .14)' } },
      axisLabel: { color: '#7fb4e8' }
    },
    yAxis: {
      type: 'category',
      data: rows.map(row => row.alarmText),
      axisTick: { show: false },
      axisLine: { show: false },
      axisLabel: { color: '#dcefff', fontSize: 12 }
    },
    series: [
      {
        type: 'bar',
        barWidth: 13,
        data: rows.map(row => row.alarmCount),
        label: { show: true, position: 'right', color: '#ffd7c9' },
        itemStyle: {
          borderRadius: [0, 8, 8, 0],
          color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
            { offset: 0, color: 'rgba(255, 78, 78, .24)' },
            { offset: 1, color: '#ff8c62' }
          ])
        }
      }
    ]
  })
}

function resizeCharts() {
  Object.keys(charts).forEach(key => {
    if (charts[key]) charts[key].resize()
  })
}

function refreshClock() {
  const now = new Date()
  currentDate.value = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`
  currentTime.value = `${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`
}

function startClock() {
  clockTimer = window.setInterval(refreshClock, 1000)
}

function setScale() {
  const scale = getScale()
  style.transform = `scaleY(${scale.y}) scaleX(${scale.x}) translate(-50%, -50%)`
}

function getScale() {
  return {
    x: window.innerWidth / style.width,
    y: window.innerHeight / style.height
  }
}

function getResponseRows(response) {
  if (!response) return []
  const data = response.data
  if (Array.isArray(data)) return data
  if (data && Array.isArray(data.result)) return data.result
  if (data && Array.isArray(data.data)) return data.data
  return []
}

function getField(row, names, fallback = '') {
  for (let i = 0; i < names.length; i += 1) {
    const value = row[names[i]]
    if (value !== undefined && value !== null && value !== '') return value
  }
  return fallback
}

function toNumber(value, fallback = 0) {
  const numberValue = Number(value)
  return Number.isFinite(numberValue) ? numberValue : Number(fallback || 0)
}

function hasAnyText(...values) {
  return values.some(value => value !== undefined && value !== null && String(value).trim() !== '')
}

function hasAnyPositiveNumber(...values) {
  return values.some(value => Number(value) > 0)
}

function extendRows(rows, minCount) {
  if (!Array.isArray(rows) || rows.length === 0) return []
  const nextRows = [...rows]
  while (nextRows.length < minCount) {
    nextRows.push(...rows)
  }
  return nextRows.slice(0, Math.max(rows.length, minCount))
}

function resultText(value) {
  const result = value || '合格'
  const className = result === '异常' ? 'result-bad' : 'result-ok'
  return `<span class="result-tag ${className}">${result}</span>`
}

function formatNumber(value) {
  return Number(value || 0).toLocaleString('zh-CN')
}

function formatDuration(value) {
  const seconds = Math.max(Number(value || 0), 0)
  if (seconds >= 3600) return `${(seconds / 3600).toFixed(1)}h`
  if (seconds >= 60) return `${Math.round(seconds / 60)}m`
  return `${Math.round(seconds)}s`
}

function formatDateTime(value) {
  if (!value) return '--'
  const text = String(value).replace('T', ' ')
  return text.length > 16 ? text.slice(5, 16) : text
}

function formatTimeOnly(value) {
  if (!value) return '--'
  const text = String(value).replace('T', ' ')
  return text.length >= 16 ? text.slice(11, 16) : text
}

function shortDate(value) {
  if (!value) return ''
  const text = String(value).replace('T', ' ')
  return text.length >= 10 ? text.slice(5, 10) : text
}

function pad(value) {
  return String(value).padStart(2, '0')
}

function clampProgress(value) {
  return Math.max(0, Math.min(100, Number(value || 0)))
}

function statusClass(status) {
  if (status === '运行') return 'is-running'
  if (status === '报警') return 'is-alarm'
  if (status === '离线') return 'is-offline'
  if (status === '暂停') return 'is-pause'
  return 'is-standby'
}

function buildIcon(from, to, body) {
  return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 72 72" fill="none">
    <defs>
      <linearGradient id="g-${from.replace('#', '')}" x1="0" y1="0" x2="1" y2="1">
        <stop offset="0" stop-color="${from}"/>
        <stop offset="1" stop-color="${to}"/>
      </linearGradient>
    </defs>
    <rect x="8" y="8" width="56" height="56" rx="14" fill="url(#g-${from.replace('#', '')})" opacity=".92"/>
    <rect x="14" y="14" width="44" height="18" rx="9" fill="#fff" opacity=".14"/>
    <g stroke="#fff" stroke-width="4" stroke-linecap="round" stroke-linejoin="round">${body}</g>
  </svg>`
}
</script>

<style scoped>
@font-face {
  font-family: ds-digit;
  src: url('../assets/font/DS-DIGI.TTF');
}

* {
  box-sizing: border-box;
}

.trace-dashboard-page {
  position: fixed;
  top: 50%;
  left: 50%;
  overflow: hidden;
  color: #e9f7ff;
  font-family: "Microsoft YaHei", Arial, sans-serif;
  background:
    radial-gradient(circle at 50% 38%, rgba(25, 132, 190, .24), transparent 34%),
    radial-gradient(circle at 8% 12%, rgba(52, 197, 255, .16), transparent 28%),
    linear-gradient(135deg, #041223 0%, #061c32 45%, #071629 100%);
  transform-origin: 0 0;
}

.trace-dashboard-page::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image:
    linear-gradient(rgba(94, 190, 255, .07) 1px, transparent 1px),
    linear-gradient(90deg, rgba(94, 190, 255, .06) 1px, transparent 1px);
  background-size: 48px 48px;
  mask-image: linear-gradient(to bottom, rgba(0,0,0,.78), rgba(0,0,0,.14));
}

.trace-dashboard-page::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  border: 1px solid rgba(101, 218, 255, .2);
  box-shadow: inset 0 0 80px rgba(11, 116, 190, .35);
}

.dashboard-header {
  position: relative;
  z-index: 2;
  height: 94px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 34px 0;
}

.brand,
.clock {
  width: 360px;
}

.brand {
  display: flex;
  align-items: center;
  gap: 16px;
}

.brand-logo {
  width: 210px;
  height: 80px;
  /* object-fit: contain;*/
  /* object-position: left center;*/
  /* padding: 3px 6px; */
  /* background: rgba(255, 255, 255, .95);  */
  /* border-radius: 3px; */
  filter: drop-shadow(0 0 12px rgba(82, 209, 255, .28));
}

.brand-copy {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.brand-copy span,
.clock span {
  color: #75a9d7;
  font-size: 14px;
  letter-spacing: 0;
}

.brand-copy strong {
  color: #d7f7ff;
  font-size: 18px;
  font-weight: 600;
}

.title-wrap {
  position: relative;
  width: 760px;
  height: 72px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.title-frame {
  position: absolute;
  inset: 0;
  width: 200%;
  left: -50%;
  height: 100%;
  opacity: .92;
}

.title-wrap h1 {
  position: relative;
  margin: 0;
  color: #f4fbff;
  font-size: 34px;
  font-weight: 700;
  text-shadow: 0 0 16px rgba(82, 225, 255, .72);
}

.clock {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 4px;
}

.clock strong {
  font-family: ds-digit, "Microsoft YaHei", sans-serif;
  color: #f1fbff;
  font-size: 30px;
  font-weight: 500;
  text-shadow: 0 0 14px rgba(120, 228, 255, .58);
}

.dashboard-body {
  position: relative;
  z-index: 1;
  height: 986px;
  padding: 14px 28px 28px;
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  grid-template-rows: 116px 268px 278px 260px;
  gap: 16px;
}

.metric-card,
.panel,
.flow-panel {
  position: relative;
  overflow: hidden;
  border: 1px solid rgba(97, 203, 255, .26);
  background:
    linear-gradient(180deg, rgba(14, 64, 106, .58), rgba(3, 18, 42, .72)),
    rgba(5, 22, 48, .76);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .08),
    inset 0 0 28px rgba(38, 151, 230, .13),
    0 10px 32px rgba(0, 8, 24, .32);
}

.metric-card {
  border-radius: 8px;
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 18px;
}

.metric-card::before,
.panel::before,
.flow-panel::before {
  content: "";
  position: absolute;
  top: 0;
  left: 18px;
  right: 18px;
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(117, 238, 255, .9), transparent);
}

.metric-card i {
  position: absolute;
  right: 14px;
  bottom: 12px;
  width: 44px;
  height: 3px;
  border-radius: 999px;
  background: currentColor;
  opacity: .65;
}

.metric-icon {
  width: 62px;
  height: 62px;
  flex: 0 0 62px;
}

.metric-icon :deep(svg) {
  width: 100%;
  height: 100%;
  filter: drop-shadow(0 0 12px rgba(82, 214, 255, .36));
}

.metric-copy {
  min-width: 0;
}

.metric-copy span {
  display: block;
  color: #99c3e8;
  font-size: 15px;
}

.metric-copy strong {
  display: block;
  margin-top: 7px;
  color: #f4fdff;
  font-family: ds-digit, "Microsoft YaHei", sans-serif;
  font-size: 31px;
  line-height: 1;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.metric-copy strong.is-long-value {
  font-size: 25px;
  letter-spacing: 0;
  text-overflow: clip;
}

.metric-copy small {
  margin-left: 5px;
  color: #8fbfe5;
  font-family: "Microsoft YaHei", sans-serif;
  font-size: 14px;
}

.tone-blue { color: #39b8ff; }
.tone-cyan { color: #5ef0ff; }
.tone-green { color: #39eda6; }
.tone-sky { color: #a9f3ff; }
.tone-red { color: #ff835f; }
.tone-gold { color: #ffd16d; }

.panel,
.flow-panel {
  border-radius: 8px;
  padding: 16px;
}

.panel-head {
  height: 30px;
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 10px;
}

.panel-index {
  color: #58e9ff;
  font-family: ds-digit, "Microsoft YaHei", sans-serif;
  font-size: 20px;
}

.panel-head h2 {
  margin: 0;
  color: #edfaff;
  font-size: 18px;
  font-weight: 650;
}

.panel-head b {
  margin-left: auto;
  color: rgba(127, 196, 236, .34);
  font-size: 16px;
  font-weight: 700;
}

.order-panel {
  grid-column: 1 / 3;
  grid-row: 2 / 4;
}

.trend-panel {
  grid-column: 1 / 3;
  grid-row: 4 / 5;
}

.flow-panel {
  grid-column: 3 / 5;
  grid-row: 2 / 4;
}

.event-panel {
  grid-column: 5 / 7;
  grid-row: 2 / 3;
}

.alarm-panel {
  grid-column: 5 / 6;
  grid-row: 3 / 4;
}

.print-panel {
  grid-column: 6 / 7;
  grid-row: 3 / 4;
}

.detail-panel {
  grid-column: 3 / 7;
  grid-row: 4 / 5;
}

.current-order {
  display: grid;
  grid-template-columns: 1.25fr 1.15fr .7fr;
  gap: 10px;
  margin-bottom: 12px;
}

.current-order > div {
  min-width: 0;
  padding: 12px;
  border: 1px solid rgba(103, 217, 255, .18);
  background: rgba(7, 32, 65, .58);
}

.current-order span,
.station-title span,
.station-card dt {
  color: #86bce3;
  font-size: 12px;
}

.current-order strong {
  display: block;
  margin-top: 6px;
  color: #f3fbff;
  font-size: 18px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.order-chart {
  height: 210px;
}

.task-list {
  display: flex;
  flex-direction: column;
  gap: 9px;
  margin-top: 10px;
}

.task-row {
  display: grid;
  grid-template-columns: 1fr 150px;
  gap: 14px;
  padding: 10px 12px;
  border: 1px solid rgba(91, 185, 255, .13);
  background: rgba(5, 25, 52, .58);
}

.task-main {
  min-width: 0;
}

.task-main span {
  display: block;
  color: #57dbff;
  font-size: 14px;
}

.task-main strong {
  display: block;
  margin-top: 4px;
  color: #e8f5ff;
  font-size: 14px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.task-progress {
  display: flex;
  align-items: center;
  gap: 8px;
}

.task-progress i {
  flex: 1;
  height: 8px;
  border-radius: 999px;
  background: rgba(80, 132, 176, .2);
  overflow: hidden;
}

.task-progress em {
  display: block;
  height: 100%;
  border-radius: inherit;
  background: linear-gradient(90deg, #23dfa0, #62efff);
}

.task-progress span {
  width: 48px;
  color: #ccecff;
  font-family: ds-digit, "Microsoft YaHei", sans-serif;
  font-size: 17px;
  text-align: right;
}

.chart-fill {
  width: 100%;
  height: calc(100% - 40px);
}

.flow-panel {
  padding: 18px;
}

.flow-panel::after {
  content: "";
  position: absolute;
  left: 18px;
  right: 18px;
  top: 72px;
  bottom: 18px;
  pointer-events: none;
  background:
    radial-gradient(circle at 50% 36%, rgba(74, 202, 255, .12), transparent 42%),
    linear-gradient(90deg, rgba(4, 20, 45, .48), rgba(7, 38, 75, .2), rgba(4, 20, 45, .48)),
    linear-gradient(180deg, transparent, rgba(5, 18, 39, .42));
  border: 1px solid rgba(94, 210, 255, .08);
}

.flow-backdrop {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 0;
}

.machine-img {
  position: absolute;
  right: -26px;
  bottom: 34px;
  width: 300px;
  opacity: .22;
  filter: brightness(1.18) saturate(1.08) drop-shadow(0 0 24px rgba(98, 218, 255, .32));
}

.robot-img {
  position: absolute;
  left: -18px;
  bottom: 26px;
  height: 270px;
  opacity: .2;
  filter: brightness(1.16) saturate(1.08) drop-shadow(0 0 20px rgba(255, 210, 92, .3));
}

.flow-head {
  position: relative;
  z-index: 2;
  display: flex;
  align-items: baseline;
  gap: 12px;
}

.flow-head span {
  color: #59e8ff;
  font-size: 13px;
  font-weight: 700;
}

.flow-head strong {
  color: #f6fcff;
  font-size: 24px;
}

.flow-head em {
  margin-left: auto;
  color: #86bce3;
  font-style: normal;
  font-size: 13px;
}

.flow-stage {
  position: relative;
  z-index: 2;
  height: calc(100% - 44px);
  margin-top: 2px;
  display: flex;
  align-items: flex-start;
  padding: 46px 0 18px;
}

.flow-stage::before {
  content: "";
  position: absolute;
  left: 36px;
  right: 36px;
  top: 50%;
  height: 78px;
  transform: translateY(-50%);
  background: linear-gradient(90deg, transparent, rgba(29, 117, 185, .16), transparent);
  border-top: 1px solid rgba(83, 224, 255, .12);
  border-bottom: 1px solid rgba(83, 224, 255, .08);
}

.flow-line {
  position: absolute;
  left: 54px;
  right: 54px;
  top: 50%;
  height: 2px;
  background: linear-gradient(90deg, transparent, rgba(81, 230, 255, .72), rgba(255, 208, 91, .5), rgba(81, 230, 255, .72), transparent);
  box-shadow: 0 0 18px rgba(81, 230, 255, .45);
  transform: translateY(-50%);
}

.station-grid {
  position: relative;
  z-index: 2;
  width: 100%;
  height: 100%;
  display: grid;
  grid-template-columns: repeat(3, 132px);
  justify-content: center;
  align-items: center;
  gap: 44px;
}

.station-card {
  position: relative;
  min-width: 0;
  width: 132px;
  height: 238px;
  padding: 14px 16px 12px;
  border: 1px solid rgba(105, 215, 255, .28);
  background:
    linear-gradient(180deg, rgba(14, 74, 118, .9), rgba(5, 22, 48, .9));
  box-shadow:
    inset 0 0 18px rgba(55, 164, 236, .14),
    0 14px 26px rgba(0, 8, 24, .32);
}

.station-card::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    linear-gradient(90deg, rgba(88, 226, 255, .16), transparent 18%, transparent 82%, rgba(88, 226, 255, .1)),
    linear-gradient(180deg, rgba(255, 255, 255, .08), transparent 18%);
}

.station-beacon {
  content: "";
  position: absolute;
  left: 50%;
  top: -32px;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: currentColor;
  box-shadow: 0 0 16px currentColor;
  transform: translateX(-50%);
}

.station-beacon::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 10px;
  width: 1px;
  height: 28px;
  background: linear-gradient(currentColor, transparent);
  transform: translateX(-50%);
  opacity: .72;
}

.station-order {
  position: relative;
  color: rgba(127, 222, 255, .28);
  font-family: ds-digit, "Microsoft YaHei", sans-serif;
  font-size: 42px;
  line-height: .9;
}

.station-title {
  position: relative;
  min-height: 54px;
}

.station-title span {
  display: block;
  margin-top: 4px;
  color: #a2d8f6;
  font-size: 15px;
}

.station-title strong {
  display: block;
  margin-top: 5px;
  color: #f2fbff;
  font-size: 20px;
  line-height: 1.15;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: clip;
  word-break: keep-all;
}

.station-state {
  position: relative;
  display: inline-flex;
  min-width: 64px;
  height: 26px;
  align-items: center;
  justify-content: center;
  margin: 6px 0 8px;
  border: 1px solid currentColor;
  color: currentColor;
  background: color-mix(in srgb, currentColor 16%, transparent);
  font-size: 13px;
}

.station-card dl {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin: 0;
}

.station-card dl div {
  min-width: 0;
  padding: 4px 6px;
  display: flex;
  align-items: center;
  gap: 6px;
  background: rgba(4, 20, 45, .54);
}

.station-card dt {
  display: block;
  flex: 0 0 auto;
  white-space: nowrap;
}

.station-card dd {
  margin: 0;
  color: #f1fbff;
  font-family: ds-digit, "Microsoft YaHei", sans-serif;
  font-size: 18px;
  line-height: 1;
  white-space: nowrap;
}

.is-running { color: #35f2a4; }
.is-standby { color: #ffd16d; }
.is-alarm { color: #ff7d58; }
.is-offline { color: #8aa4b8; }
.is-pause { color: #57b9ff; }

.event-list {
  height: calc(100% - 40px);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  gap: 9px;
}

.print-list {
  height: calc(100% - 40px);
  overflow: hidden;
  display: grid;
  grid-template-rows: repeat(3, minmax(0, 1fr));
  gap: 8px;
}

.event-item {
  display: grid;
  grid-template-columns: 52px 1fr 48px;
  gap: 10px;
  align-items: center;
  height: 40px;
  padding: 5px 10px;
  border: 1px solid rgba(91, 185, 255, .13);
  background: rgba(5, 25, 52, .58);
}

.event-type {
  height: 24px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: #091a26;
  background: #5eeeff;
  font-size: 13px;
  font-weight: 700;
}

.event-item.is-alert .event-type {
  background: #ff855f;
}

.event-content {
  min-width: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

.event-content strong,
.print-item strong {
  display: block;
  color: #f4fbff;
  font-size: 14px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.event-content span {
  display: block;
  margin-top: 1px;
  color: #83b9dd;
  font-size: 12px;
  line-height: 1.1;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.event-item time,
.print-item em {
  color: #78dbff;
  font-family: ds-digit, "Microsoft YaHei", sans-serif;
  font-style: normal;
  font-size: 18px;
  text-align: right;
}

.event-item time {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  height: 100%;
  line-height: 1;
}

.print-item {
  min-height: 0;
  padding: 8px 12px 6px;
  border-left: 3px solid #ffd16d;
  background: rgba(5, 25, 52, .58);
  overflow: hidden;
}

.print-item span {
  display: block;
  color: #ffd16d;
  font-size: 14px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.print-item strong {
  /* margin-top: 5px; */
}

.print-item em {
  display: block;
  /* margin-top: 4px; */
  font-size: 16px;
  line-height: 1;
  text-align: left;
}

.trace-table {
  height: calc(100% - 40px);
  overflow: hidden;
}

.datav-table :deep(.dv-scroll-board) {
  width: 100%;
  height: 100%;
  color: #c9e5f7;
  font-size: 13px;
}

.datav-table :deep(.header) {
  color: #87ddff;
  font-size: 13px;
  font-weight: 700;
  background: rgba(14, 79, 127, .62) !important;
}

.datav-table :deep(.header-item),
.datav-table :deep(.ceil) {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 8px;
  border-bottom: 1px solid rgba(91, 185, 255, .12);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.datav-table :deep(.row-item) {
  color: #c9e5f7 !important;
  font-weight: 400 !important;
}

.datav-table :deep(.row-item .ceil) {
  color: #c9e5f7 !important;
  font-weight: 400 !important;
  text-align: center;
}

.datav-table :deep(.result-tag) {
  display: inline-flex;
  min-width: 44px;
  height: 22px;
  align-items: center;
  justify-content: center;
  padding: 0 8px;
  border-radius: 3px;
  color: #fff;
  font-size: 12px;
  font-weight: 700;
  line-height: 22px;
  box-shadow: inset 0 0 8px rgba(255, 255, 255, .14);
}

.datav-table :deep(.result-ok) {
  background: linear-gradient(180deg, #18d887, #07965f);
}

.datav-table :deep(.result-bad) {
  background: linear-gradient(180deg, #ff695f, #cf202a);
}
</style>
