<template>
  <div class="app-container">
    <div style="text-align:center; margin-bottom: 12px;">
      <div style="font-size: 24px; color: #ff9d00; font-weight: bold;">{{ opName }}: 操作平台</div>
    </div>
    <el-row :gutter="20">
      <el-col :span="12">
        <div class="workstation-panel">
          <div class="workstation-title" style="text-align: right;">
            <el-button :type="clickOpName === opNameList[0] ? 'success' : 'info'"
              style="width: 200px;height: 60px; font-size: 16px; font-weight: bold;"
              @click="selectOpNameFun(opNameList[0])">
              {{ opNameList[0] }}<br v-if="clickOpName === opNameList[0]">
              <div v-if="clickOpName === opNameList[0]" style="font-size: 12px;">[当前扫码位置]</div>
              <div v-else> ·</div>
            </el-button>
          </div>

          <div class="section-title">扫码上线验证信息</div>
          <el-form :inline="true" label-width="70px" label-position="right" class="upline-form" style="width: 100%;">
            <el-row :gutter="10">
              <el-col :span="12">
                <el-form-item label="扫码值" style="width: 100%;">
                  <el-input v-model="ws1Result.扫码值" readonly size="small" style="width: 100%;" />
                </el-form-item>
              </el-col>
              <el-col :span="12">
                <el-form-item label="序列号" style="width: 100%;">
                  <el-input v-model="ws1Result.扫码序列号" readonly size="small" style="width: 100%;" />
                </el-form-item>
              </el-col>
            </el-row>
            <el-row :gutter="10">
              <el-col :span="12">
                <el-form-item label="工单号" style="width: 100%;">
                  <el-input v-model="ws1Result.工单号" readonly size="small" style="width: 100%;" />
                </el-form-item>
              </el-col>
              <el-col :span="12">
                <el-form-item label="错误消息" style="width: 100%;">
                  <el-input v-model="ws1Result.错误消息" readonly size="small" style="width: 100%;color:red;" />
                </el-form-item>
              </el-col>
            </el-row>
            <div class="material-version-row">
              <div class="mv-version">
                <div class="top-label">程序版本</div>
                <el-input
                  v-model="ws1Result.程序版本"
                  readonly
                  size="small"
                  placeholder="双击选择"
                  class="version-input"
                  @dblclick.native="openVersionDialog(opNameList[0])" />
              </div>
              <div class="mv-material">
                <div class="top-label">物料编码</div>
                <el-input v-model="ws1Result.扫码物料编码" readonly size="small" />
              </div>
              <div class="mv-result">
                <div class="top-label">验证结果</div>
                <div class="result-display">
                  <span v-if="ws1Result.验证结果 == 1" class="result-pass">验证通过</span>
                  <span v-else-if="ws1Result.验证结果 == 2" class="result-fail">验证失败</span>
                  <span v-else-if="ws1Result.验证结果 == -1" class="result-error">解析错误</span>
                </div>
              </div>
            </div>
          </el-form>
          <div style="display: flex; align-items: center; margin: 8px 0;">
            <el-button type="primary" style="width: 200px;height: 50px; font-size: 14px;"
              @click="partLoadOpName(opNameList[0])" :disabled="isAutoPartLoad">手动请求上线</el-button>
            <el-button
              :loading="isResetSerialLoading"
              type="danger"
              style="width: 200px;height: 50px; margin-left: 30px; font-size: 14px;"
              @click="resetSerialNumber(opNameList[0])"
            >重置序列号</el-button>
            <div v-if="ws1PartLoadStatus.show" style="margin-left: 20px;">
              <span v-if="ws1PartLoadStatus.success" style="color: #67C23A; font-size: 16px;">
                操作成功！
              </span>
              <span v-else style="color: #F56C6C; font-size: 16px;">
                操作失败！ {{ ws1PartLoadStatus.message }}
              </span>
            </div>
          </div>
          <el-divider></el-divider>
          <div class="section-title">工件下线完成报工</div>
          <el-form :inline="true" label-width="90px" label-position="right" style="width: 100%;">
            <el-row :gutter="10">
              <el-col :span="12">
                <el-form-item label="物料编码" style="width: 100%;">
                  <el-input v-model="ws1OfflineData.物料编码" readonly size="small" style="width: 100%;" />
                </el-form-item>
              </el-col>
              <el-col :span="12">
                <el-form-item label="序列号" style="width: 100%;">
                  <el-input v-model="ws1OfflineData.序列号" readonly size="small" style="width: 100%;" />
                </el-form-item>
              </el-col>
            </el-row>
            <el-row :gutter="10">
              <el-col :span="12">
                <el-form-item label="工单号" style="width: 100%;">
                  <el-input v-model="ws1OfflineData.工单号" readonly size="small" style="width: 100%;" />
                </el-form-item>
              </el-col>
              <el-col :span="12">
                <el-form-item label="托盘号" style="width: 100%;">
                  <el-input v-model="ws1OfflineData.托盘号" readonly size="small" style="width: 100%;" />
                </el-form-item>
              </el-col>
            </el-row>
          </el-form>
          <div style="display: flex; align-items: center; margin: 8px 0;">
            <el-button type="warning" style="width: 200px;height: 50px; font-size: 14px;"
              @click="handleOfflineReport(opNameList[0])">工件下线完成报工</el-button>
            <div v-if="ws1OfflineStatus.show" style="margin-left: 20px;">
              <span v-if="ws1OfflineStatus.success" style="color: #67C23A; font-size: 16px;">
                操作成功！
              </span>
              <span v-else style="color: #F56C6C; font-size: 16px;">
                操作失败！ {{ ws1OfflineStatus.message }}
              </span>
            </div>
          </div>
          <el-divider></el-divider>
          <div class="section-title">读码写码清码操作</div>
          <div>
            <div class="status-bar">
              <span>写码成功 <span class="status-dot" :class="{ 'ok': ws1Status.write }" /></span>
              <span>读码成功 <span class="status-dot" :class="{ 'ok': ws1Status.read }" /></span>
              <span>清码成功 <span class="status-dot" :class="{ 'ok': ws1Status.clear }" /></span>
              <span>物料放置错误 <span class="status-dot" :class="{ 'err': ws1Status.materialError }" /></span>
            </div>
          </div>
          <el-form :inline="true" label-width="90px" label-position="right" style="width: 100%;">
            <el-row :gutter="10">
              <el-col :span="24">
                <el-form-item label="托盘物料编码" style="width: 100%;" class="ShortLabel">
                  <el-input v-model="ws1Tray.托盘物料编码" readonly size="small" style="width: 100%;" />
                </el-form-item>
              </el-col>
            </el-row>

            <el-row :gutter="10">
              <el-col :span="12">
                <el-form-item label="托盘号" style="width: 100%;">
                  <el-input v-model="ws1Tray.托盘号" readonly size="small" style="width: 100%;" />
                </el-form-item>
              </el-col>
              <el-col :span="12">
                <el-form-item label="托盘工件状态" style="width: 100%;">
                  <el-input v-model="ws1Tray.托盘工件状态" readonly size="small" style="width: 100%;" />
                </el-form-item>
              </el-col>
            </el-row>
            <el-row style="margin-top: 8px;">
              <el-col :span="24" style="display: flex;justify-content: space-evenly">
                <el-button style="width: 120px;height: 45px; font-size: 14px;" type="primary"
                  @click="handleWriteCode(opNameList[0])">写码</el-button>
                <el-button style="width: 120px;height: 45px;margin-left:10px; font-size: 14px;" type="success"
                  @click="handleReadCode(opNameList[0])">读码</el-button>
                <el-button style="width: 120px;height: 45px;margin-left:10px; font-size: 14px;" type="warning"
                  @click="handleClearCode(opNameList[0])">清码</el-button>
                <el-button style="width: 120px;height: 45px;margin-left:10px; font-size: 14px;" type="info"
                  @click="pollPlcOnce(opNameList[0])">刷新</el-button>
              </el-col>
            </el-row>
          </el-form>
        </div>
      </el-col>
      <el-col :span="12">
        <div class="workstation-panel">
          <div class="workstation-title" style="text-align: left;">
            <el-button :type="clickOpName === opNameList[1] ? 'success' : 'info'"
              style="width: 200px;height: 60px; font-size: 16px; font-weight: bold; margin-left: 20px;"
              @click="selectOpNameFun(opNameList[1])">
              {{ opNameList[1] }}<br v-if="clickOpName === opNameList[1]">
              <div v-if="clickOpName === opNameList[1]" style="font-size: 12px;">[当前扫码位置]</div>
              <div v-else> ·</div>
            </el-button>
          </div>

          <div class="section-title">扫码上线验证信息</div>
          <el-form :inline="true" label-width="70px" label-position="right" class="upline-form" style="width: 100%;">
            <el-row :gutter="10">
              <el-col :span="12">
                <el-form-item label="扫码值" style="width: 100%;">
                  <el-input v-model="ws2Result.扫码值" readonly size="small" style="width: 100%;" />
                </el-form-item>
              </el-col>
              <el-col :span="12">
                <el-form-item label="序列号" style="width: 100%;">
                  <el-input v-model="ws2Result.扫码序列号" readonly size="small" style="width: 100%;" />
                </el-form-item>
              </el-col>
            </el-row>
            <el-row :gutter="10">
              <el-col :span="12">
                <el-form-item label="工单号" style="width: 100%;">
                  <el-input v-model="ws2Result.工单号" readonly size="small" style="width: 100%;" />
                </el-form-item>
              </el-col>
              <el-col :span="12">
                <el-form-item label="错误消息" style="width: 100%;">
                  <el-input v-model="ws2Result.错误消息" readonly size="small" style="width: 100%;color:red;" />
                </el-form-item>
              </el-col>
            </el-row>
            <div class="material-version-row">
              <div class="mv-version">
                <div class="top-label">程序版本</div>
                <el-input
                  v-model="ws2Result.程序版本"
                  readonly
                  size="small"
                  placeholder="双击选择"
                  class="version-input"
                  @dblclick.native="openVersionDialog(opNameList[1])" />
              </div>
              <div class="mv-material">
                <div class="top-label">物料编码</div>
                <el-input v-model="ws2Result.扫码物料编码" readonly size="small" />
              </div>
              <div class="mv-result">
                <div class="top-label">验证结果</div>
                <div class="result-display">
                  <span v-if="ws2Result.验证结果 == 1" class="result-pass">验证通过</span>
                  <span v-else-if="ws2Result.验证结果 == 2" class="result-fail">验证失败</span>
                  <span v-else-if="ws2Result.验证结果 == -1" class="result-error">解析错误</span>
                </div>
              </div>
            </div>
          </el-form>
          <div style="display: flex; align-items: center; margin: 8px 0;">
            <el-button type="primary" style="width: 200px;height: 50px; font-size: 14px;"
              @click="partLoadOpName(opNameList[1])" :disabled="isAutoPartLoad">手动请求上线</el-button>
            <el-button
              :loading="isResetSerialLoading"
              type="danger"
              style="width: 200px;height: 50px; margin-left: 30px; font-size: 14px;"
              @click="resetSerialNumber(opNameList[1])"
            >重置序列号</el-button>
            <div v-if="ws2PartLoadStatus.show" style="margin-left: 20px;">
              <span v-if="ws2PartLoadStatus.success" style="color: #67C23A; font-size: 16px;">
                操作成功！
              </span>
              <span v-else style="color: #F56C6C; font-size: 16px;">
                操作失败！ {{ ws2PartLoadStatus.message }}
              </span>
            </div>
          </div>
          <el-divider></el-divider>
          <div class="section-title">工件下线完成报工</div>
          <el-form :inline="true" label-width="90px" label-position="right" style="width: 100%;">
            <el-row :gutter="10">
              <el-col :span="12">
                <el-form-item label="物料编码" style="width: 100%;">
                  <el-input v-model="ws2OfflineData.物料编码" readonly size="small" style="width: 100%;" />
                </el-form-item>
              </el-col>
              <el-col :span="12">
                <el-form-item label="序列号" style="width: 100%;">
                  <el-input v-model="ws2OfflineData.序列号" readonly size="small" style="width: 100%;" />
                </el-form-item>
              </el-col>
            </el-row>
            <el-row :gutter="10">
              <el-col :span="12">
                <el-form-item label="工单号" style="width: 100%;">
                  <el-input v-model="ws2OfflineData.工单号" readonly size="small" style="width: 100%;" />
                </el-form-item>
              </el-col>
              <el-col :span="12">
                <el-form-item label="托盘号" style="width: 100%;">
                  <el-input v-model="ws2OfflineData.托盘号" readonly size="small" style="width: 100%;" />
                </el-form-item>
              </el-col>
            </el-row>
          </el-form>
          <div style="display: flex; align-items: center; margin: 8px 0;">
            <el-button type="warning" style="width: 200px;height: 50px; font-size: 14px;"
              @click="handleOfflineReport(opNameList[1])">工件下线完成报工</el-button>
            <div v-if="ws2OfflineStatus.show" style="margin-left: 20px;">
              <span v-if="ws2OfflineStatus.success" style="color: #67C23A; font-size: 16px;">
                操作成功！
              </span>
              <span v-else style="color: #F56C6C; font-size: 16px;">
                操作失败！ {{ ws2OfflineStatus.message }}
              </span>
            </div>
          </div>
          <el-divider></el-divider>
          <div class="section-title">读码写码清码操作</div>
          <div class="status-bar">
            <span>写码成功 <span class="status-dot" :class="{ 'ok': ws2Status.write }" /></span>
            <span>读码成功 <span class="status-dot" :class="{ 'ok': ws2Status.read }" /></span>
            <span>清码成功 <span class="status-dot" :class="{ 'ok': ws2Status.clear }" /></span>
            <span>物料放置错误 <span class="status-dot" :class="{ 'err': ws2Status.materialError }" /></span>
          </div>
          <el-form :inline="true" label-width="90px" label-position="right" style="width: 100%;">
            <el-row :gutter="10">
              <el-col :span="24">
                <el-form-item label="托盘物料编码" style="width: 100%;" class="ShortLabel">
                  <el-input v-model="ws2Tray.托盘物料编码" readonly size="small" style="width: 100%;" />
                </el-form-item>
              </el-col>
            </el-row>
            <el-row :gutter="10">
              <el-col :span="12">
                <el-form-item label="托盘号" style="width: 100%;">
                  <el-input v-model="ws2Tray.托盘号" readonly size="small" style="width: 100%;" />
                </el-form-item>
              </el-col>
              <el-col :span="12">
                <el-form-item label="托盘工件状态" style="width: 100%;">
                  <el-input v-model="ws2Tray.托盘工件状态" readonly size="small" style="width: 100%;" />
                </el-form-item>
              </el-col>
            </el-row>
            <el-row style="margin-top: 8px;">
              <el-col :span="24" style="display: flex;justify-content: space-evenly">
                <el-button style="width: 120px;height: 45px; font-size: 14px;" type="primary"
                  @click="handleWriteCode(opNameList[1])">写码</el-button>
                <el-button style="width: 120px;height: 45px;margin-left:10px; font-size: 14px;" type="success"
                  @click="handleReadCode(opNameList[1])">读码</el-button>
                <el-button style="width: 120px;height: 45px;margin-left:10px; font-size: 14px;" type="warning"
                  @click="handleClearCode(opNameList[1])">清码</el-button>
                <el-button style="width: 120px;height: 45px;margin-left:10px; font-size: 14px;" type="info"
                  @click="pollPlcOnce(opNameList[1])">刷新</el-button>
              </el-col>
            </el-row>
          </el-form>
        </div>
      </el-col>
    </el-row>

    <el-dialog
      :visible.sync="versionDialog.visible"
      width="520px"
      :close-on-click-modal="false"
      append-to-body
      custom-class="version-dialog"
      :show-close="true">
      <div slot="title" class="vd-header">
        <div class="vd-title">选择程序版本</div>
        <div class="vd-subtitle">物料编码 · {{ versionDialog.partCode || '—' }}</div>
      </div>

      <div class="vd-body">
        <div class="vd-section-title">
          <span>历史程序版本</span>
          <span class="vd-count" v-if="versionDialog.historyList.length">共 {{ versionDialog.historyList.length }} 条</span>
        </div>

        <div v-if="versionDialog.historyList.length" class="vd-tag-list">
          <div
            v-for="item in versionDialog.historyList"
            :key="item"
            class="vd-tag"
            :class="{ 'is-active': versionDialog.selectedVersion === item }"
            @click="versionDialog.selectedVersion = item">
            {{ item }}
          </div>
        </div>
        <div v-else class="vd-empty">
          <div class="vd-empty-text">该物料暂无历史程序版本</div>
          <div class="vd-empty-hint">请在下方手动输入新版本号</div>
        </div>

        <div class="vd-section-title vd-section-title-spaced">手动输入版本号</div>
        <el-input
          v-model="versionDialog.selectedVersion"
          size="medium"
          maxlength="32"
          placeholder=""
          clearable />
      </div>

      <div slot="footer" class="vd-footer">
        <el-button @click="versionDialog.visible = false">取 消</el-button>
        <el-button type="primary" :disabled="!versionDialog.selectedVersion" @click="confirmVersionDialog">确 定</el-button>
      </div>
    </el-dialog>

    <el-dialog
      :visible.sync="loadConfirmDialog.visible"
      width="560px"
      :close-on-click-modal="false"
      append-to-body
      custom-class="load-confirm-dialog"
      :show-close="true">
      <div slot="title" class="lcd-header">
        <div class="lcd-title">上线信息确认</div>
        <div class="lcd-subtitle">请确认以下信息无误后再点击"确认上线"</div>
      </div>

      <div class="lcd-body">
        <div class="lcd-card lcd-card-primary">
          <div class="lcd-card-tag">关键信息</div>
          <div class="lcd-row-2col lcd-row-2col-key">
            <div class="lcd-field">
              <div class="lcd-label">程序版本</div>
              <div class="lcd-value lcd-value-strong lcd-value-right">{{ loadConfirmDialog.detail.程序版本 || '—' }}</div>
            </div>
            <div class="lcd-field">
              <div class="lcd-label">物料编码</div>
              <div class="lcd-value lcd-value-strong">{{ loadConfirmDialog.detail.扫码物料编码 || '—' }}</div>
            </div>
          </div>
          <div class="lcd-field lcd-field-program-no">
            <div class="lcd-label">程序号</div>
            <div class="lcd-value lcd-value-mono">{{ loadConfirmProgramNo || '—' }}</div>
          </div>
        </div>

        <div class="lcd-card">
          <div class="lcd-info-grid">
            <div class="lcd-info-item">
              <div class="lcd-info-label">工位号</div>
              <div class="lcd-info-value">{{ loadConfirmDialog.detail.工位号 || '—' }}</div>
            </div>
            <div class="lcd-info-item">
              <div class="lcd-info-label">序列号</div>
              <div class="lcd-info-value">{{ loadConfirmDialog.detail.扫码序列号 || '—' }}</div>
            </div>
            <div class="lcd-info-item lcd-info-item-full">
              <div class="lcd-info-label">工单号</div>
              <div class="lcd-info-value">{{ loadConfirmDialog.detail.工单号 || '—' }}</div>
            </div>
            <div class="lcd-info-item lcd-info-item-full">
              <div class="lcd-info-label">扫码值</div>
              <div class="lcd-info-value lcd-info-value-mono">{{ loadConfirmDialog.detail.扫码值 || '—' }}</div>
            </div>
          </div>
        </div>
      </div>

      <div slot="footer" class="lcd-footer">
        <el-button @click="loadConfirmDialog.visible = false">取 消</el-button>
        <el-button type="primary" :loading="loadConfirmDialog.loading" @click="confirmLoadDialog">确 认 上 线</el-button>
      </div>
    </el-dialog>
  </div>
</template>

<script>
import { mapGetters } from 'vuex'
import axios from 'axios'
import { mqttIp, mqttTopic, mqttInitRequestTopic, writePlcApi_BtnType, selectOpNameApi, multiReadPlcApi, partUnloadedApi } from '@/../static/config'

// ==================== 常量定义 ====================

/** 程序版本 PLC 字符串信号标识 */
const PROGRAM_VERSION_TAG_ID = '208640'

/** 程序版本下拉选项 */
/** 程序版本可选项（PLC 实际可写的字符值） */
const PROGRAM_VERSION_OPTIONS = []

/** 轮询读取的所有 PLC 信号标识 */
const POLL_TAG_IDS = ['200004', '200005', '200006', '200007', '200380', '203240', '200440']

/** 上次使用的程序版本 localStorage key 前缀（按工位独立） */
const LAST_VERSION_STORAGE_PREFIX = 'ZL_DJ_LastProgramVersion_'

/** 状态灯信号 → data 属性映射 */
const STATUS_TAG_MAP = {
  '200004': 'materialError', // 放置错误
  '200005': 'read', // 读码
  '200006': 'write', // 写码
  '200007': 'clear' // 清码
}

/** 托盘工件状态码 → 文本映射 */
const TRAY_STATUS_TEXT = {
  '0': '毛坯',
  '1': '已加工',
  '2': '返修件',
  '3': '空托'
}

/** 轮询间隔（毫秒） */
const POLL_INTERVAL = 1000

/** 轮询总次数（10秒 = 10次） */
const POLL_TOTAL_COUNT = 120

export default {
  data() {
    return {
      opNameType: '底架上线',
      opName: '',
      clickOpName: '',
      opNameList: [],
      mqtt: window.mqtt,
      // 双工位扫码验证数据
      ws1Result: { 扫码值: '', 工单号: '', 扫码物料编码: '', 扫码序列号: '', 错误消息: '', 验证结果: '', 程序版本: '' },
      ws2Result: { 扫码值: '', 工单号: '', 扫码物料编码: '', 扫码序列号: '', 错误消息: '', 验证结果: '', 程序版本: '' },
      // 是否已处理过首次扫码（首次：自动取最近一次版本，不弹窗；之后：弹窗手动选）
      ws1FirstVerifyHandled: false,
      ws2FirstVerifyHandled: false,
      programVersionOptions: PROGRAM_VERSION_OPTIONS,
      // 双工位托盘信息
      ws1Tray: { 托盘号: '', 托盘物料编码: '', 托盘工件状态: '' },
      ws2Tray: { 托盘号: '', 托盘物料编码: '', 托盘工件状态: '' },
      // 双工位状态灯
      ws1Status: { clear: false, write: false, read: false, materialError: false },
      ws2Status: { clear: false, write: false, read: false, materialError: false },
      // 工件下线报工数据
      ws1OfflineData: { 物料编码: '', 序列号: '', 工单号: '', 托盘号: '' },
      ws2OfflineData: { 物料编码: '', 序列号: '', 工单号: '', 托盘号: '' },
      // 报工状态
      ws1OfflineStatus: { success: false, message: '', show: false },
      ws2OfflineStatus: { success: false, message: '', show: false },
      // 上线状态
      ws1PartLoadStatus: { success: false, message: '', show: false },
      ws2PartLoadStatus: { success: false, message: '', show: false },
      isAutoPartLoad: false,
      isResetSerialLoading: false,
      // 轮询定时器（按工位分别存储，支持双工位同时轮询）
      pollTimers: {},
      // 历史程序版本选择弹窗
      versionDialog: {
        visible: false,
        opName: '',
        partCode: '',
        historyList: [],
        selectedVersion: ''
      },
      // 手动上线二次确认弹窗
      loadConfirmDialog: {
        visible: false,
        loading: false,
        opName: '',
        detail: {
          工位号: '', 扫码值: '', 工单号: '', 扫码序列号: '', 扫码物料编码: '', 程序版本: ''
        }
      },
    }
  },
  computed: {
    ...mapGetters(['id', 'token', 'name']),
    versionDialogOptions() {
      const merged = [...this.versionDialog.historyList]
      this.programVersionOptions.forEach(v => {
        if (!merged.includes(v)) merged.push(v)
      })
      return merged
    },
    /** 上线信息确认弹窗 - 程序号 = 程序版本 + 物料编码 */
    loadConfirmProgramNo() {
      const ver = this.loadConfirmDialog.detail.程序版本 || ''
      const code = this.loadConfirmDialog.detail.扫码物料编码 || ''
      if (!ver && !code) return ''
      return `${ver}${code}`
    }
  },
  created() {
    if (this.opNameType === '转台上线') {
      this.opName = 'OP_C1'
      this.opNameList = ['OP_C1L', 'OP_C1R']
    } else if (this.opNameType === '底架上线') {
      this.opName = 'OP_C2'
      this.opNameList = ['OP_C2L', 'OP_C2R']
    }
    this.selectOpNameFun(this.opNameList[0])
    this.initMqtt()
    this.readAllPlcStatus()
  },
  beforeDestroy() {
    this.stopAllPolling()
    this.mqttClient.end()
  },
  methods: {
    // ==================== 工位选择 ====================

    selectOpNameFun(opName) {
      axios.post(selectOpNameApi, { DeviceCode: opName }, { timeout: 5000 })
        .then(response => {
          if (response.data.code === 0) {
            this.clickOpName = opName
            this.$message.success('工位选择成功！')
          } else {
            this.$message.error(response.data.msg)
          }
        })
        .catch(error => {
          this.$message.error('选择工位失败' + error.message)
        })
    },

    // ==================== PLC 数据读取与轮询 ====================

    /** 读取单个PLC信号值 */
    async getPLCValue(opName, tagID) {
      try {
        const res = await axios.post(multiReadPlcApi, {
          tagTypeCodeIDs: [tagID],
          OPName: opName
        })
        if (res.data && res.data.code === 0 && Array.isArray(res.data.ReadRes_SignalValues) && res.data.ReadRes_SignalValues.length > 0) {
          return res.data.ReadRes_SignalValues[0].tagValue
        }
        return null
      } catch (error) {
        console.error('获取PLC值失败:', error)
        return null
      }
    },

    /** 写入单个PLC信号值 */
    async writePLCValue(opName, tagID, tagValue) {
      return axios.post(writePlcApi_BtnType, {
        tagTypeCodeID: tagID,
        OPName: opName,
        tagValue
      }, { timeout: 5000 })
    },

    /** 根据工位名获取对应的状态对象和托盘对象 */
    getWorkstationData(opName) {
      const isWs1 = opName === this.opNameList[0]
      return {
        status: isWs1 ? this.ws1Status : this.ws2Status,
        tray: isWs1 ? this.ws1Tray : this.ws2Tray,
        result: isWs1 ? this.ws1Result : this.ws2Result
      }
    },

    /** 解析PLC响应数据，统一更新对应工位的状态灯与托盘信息 */
    applyPlcData(opName, signalValues) {
      const valueMap = {}
      signalValues.forEach(item => {
        valueMap[item.tagTypeCodeID] = item.tagValue
      })
      const { status, tray, result } = this.getWorkstationData(opName)
      // 更新四个状态灯
      Object.entries(STATUS_TAG_MAP).forEach(([tagId, key]) => {
        if (valueMap[tagId] !== undefined) {
          status[key] = valueMap[tagId] === 'True'
        }
      })
      // 更新托盘信息
      if (valueMap['200380'] !== undefined) tray.托盘号 = valueMap['200380']
      if (valueMap['203240'] !== undefined) this.convertMaterialCode(opName, valueMap['203240'])
      if (valueMap['200440'] !== undefined) tray.托盘工件状态 = TRAY_STATUS_TEXT[valueMap['200440']] || ''
    },

    /** 将PLC读取到的物料号通过存储过程转换为物料编码 */
    convertMaterialCode(opName, materialNo) {
      if (!materialNo) return
      const param = [['物料号', materialNo]]
      const Data = this.CreateData('11', 'ZL_基础表_物料信息表_上线获取物料编码', param)
      this.ExecDatabase(Data).then(response => {
        const { tray } = this.getWorkstationData(opName)
        if (response.data && response.data.length > 0 && response.data[0]['物料编码']) {
          tray.托盘物料编码 = response.data[0]['物料编码']
        } else {
          tray.托盘物料编码 = materialNo // 转换失败时使用原始物料号
        }
      }).catch(err => {
        console.error('物料编码转换失败:', err)
        const { tray } = this.getWorkstationData(opName)
        tray.托盘物料编码 = materialNo
      })
    },

    /** 主动读取一次指定工位的全部PLC数据（状态灯 + 托盘信息） */
    pollPlcOnce(opName) {
      axios.post(multiReadPlcApi, {
        tagTypeCodeIDs: POLL_TAG_IDS,
        OPName: opName
      }).then(res => {
        if (res.data && res.data.code === 0 && Array.isArray(res.data.ReadRes_SignalValues)) {
          this.applyPlcData(opName, res.data.ReadRes_SignalValues)
        }
      }).catch(err => {
        console.error('读取PLC数据失败:', err)
      })
    },

    /** 启动轮询：每秒读取一次PLC数据，共10秒 */
    startPollingPlcStatus(opName) {
      // 如果该工位已有轮询，先停止
      this.stopPolling(opName)
      let count = 0
      // 立即读取第一次
      this.pollPlcOnce(opName)
      count++
      const timer = setInterval(() => {
        count++
        if (count > POLL_TOTAL_COUNT) {
          this.stopPolling(opName)
          return
        }
        this.pollPlcOnce(opName)
      }, POLL_INTERVAL)
      this.$set(this.pollTimers, opName, timer)
    },

    /** 停止指定工位的轮询 */
    stopPolling(opName) {
      if (this.pollTimers[opName]) {
        clearInterval(this.pollTimers[opName])
        this.$delete(this.pollTimers, opName)
      }
    },

    /** 停止所有工位的轮询 */
    stopAllPolling() {
      Object.keys(this.pollTimers).forEach(key => {
        clearInterval(this.pollTimers[key])
      })
      this.pollTimers = {}
    },

    /** 页面初始化时读取所有工位的PLC状态 */
    readAllPlcStatus() {
      this.opNameList.forEach(opName => {
        this.pollPlcOnce(opName)
      })
    },

    // ==================== MQTT ====================

    initMqtt() {
      const scope = this
      const connection = {
        host: mqttIp,
        port: 8083,
        endpoint: '/mqtt',
        clean: true,
        connectTimeout: 4000,
        reconnectPeriod: 4000,
        clientId: 'mqttjs_3be2c321' + new Date().getTime(),
        username: 'WEB1',
        password: ''
      }
      const subscription = {
        topic: mqttTopic,
        qos: 0
      }
      const { host, port, endpoint, ...options } = connection
      const connectUrl = `ws://${host}:${port}${endpoint}`
      try {
        this.mqttClient = mqtt.connect(connectUrl, options)
      } catch (error) {
        console.log('MQTT connect error', error)
      }

      this.mqttClient.on('connect', () => {
        this.$message.success('加载完成，连接MQTT服务器成功！')
        console.log('MQTT Connection succeeded!')
        const { topic, qos } = subscription
        // 按工位分别订阅主题（主题格式：原主题/工位号）
        this.opNameList.forEach(opName => {
          this.mqttClient.subscribe(`${topic}/${opName}`, { qos }, (error) => {
            if (error) console.log(`Subscribe ${topic}/${opName} error`, error)
          })
          this.mqttClient.subscribe(`UnloadingPartInfo/${opName}`, { qos: 0 }, (error) => {
            if (error) console.log(`Subscribe UnloadingPartInfo/${opName} error`, error)
          })
        })
        // 订阅完成后，发送初始化请求
        this.publishInitRequest()
      })

      this.mqttClient.on('error', error => {
        console.log('MQTT Connection failed', error)
        try {
          scope.$message.error('MQTT服务器连接失败')
          if (scope.IsAllowConnect) {
            scope.$message.warning('重新连接MQTT服务器')
            console.log('重新连接MQTT服务器')
            setTimeout(function () {
              scope.initMqtt()
            }, 5000)
          }
        } catch (exception) {
          const error = exception.toString()
          scope.$message.error('MQTT服务器出现错误,关闭连接' + error)
        }
      })

      // 接收消息
      this.mqttClient.on('message', (topic, message) => {
        try {
          scope.websocketonmessage(topic, { data: message.toString() })
        } catch (exception) {
          console.log(exception.toString())
        }
      })
    },

    /** 发送MQTT初始化请求，后端收到后通过已有主题推送当前状态数据 */
    publishInitRequest() {
      const message = `WebInitRequest|${this.opName}`
      this.mqttClient.publish(mqttInitRequestTopic, message, { qos: 0 }, (error) => {
        if (error) {
          console.error('发送初始化请求失败:', error)
        } else {
          console.log('已发送初始化请求:', message)
        }
      })
    },

    /** 处理MQTT消息（仅处理工件下线信息和扫码验证结果） */
    websocketonmessage(topic, msg) {
      console.log('websocketonmessage', topic, msg)

      // 处理工件下线信息 - JSON格式
      if (topic.startsWith('UnloadingPartInfo')) {
        try {
          const offlineData = JSON.parse(msg.data)
          console.log('收到工件下线信息JSON：', offlineData)
          // 根据工位号分配数据
          let targetOfflineData = null
          if (offlineData.OPCode === this.opNameList[0]) {
            targetOfflineData = this.ws1OfflineData
          } else if (offlineData.OPCode === this.opNameList[1]) {
            targetOfflineData = this.ws2OfflineData
          } else {
            return
          }
          targetOfflineData.托盘号 = offlineData.PalletNo ? offlineData.PalletNo.toString() : ''
          targetOfflineData.序列号 = offlineData.SerialNum || ''
          targetOfflineData.工单号 = offlineData.WorkOrderCode || ''
          targetOfflineData.物料编码 = offlineData.PartCode || ''
          console.log('工件下线信息已更新：', targetOfflineData)
        } catch (error) {
          console.error('解析工件下线信息失败：', error, msg.data)
        }
        return
      }

      // 处理管道分隔的消息（扫码验证结果等）
      const arrays_signal = msg.data.split('|')
      const interFaceType = arrays_signal[0]
      console.log('mqttaaa', topic, arrays_signal)

      switch (interFaceType) {
        case 'VerifyResult': {
          if (arrays_signal.length < 9) {
            this.$message.warning('收到扫码结果，但是数据异常，不予处理！')
            console.log(msg)
            return
          }
          const pos = arrays_signal[2]
          let targetResult = null
          if (pos === this.opNameList[0]) {
            targetResult = this.ws1Result
          } else if (pos === this.opNameList[1]) {
            targetResult = this.ws2Result
          } else {
            return
          }
          targetResult.扫码工位 = arrays_signal[1]
          targetResult.选择位置号 = arrays_signal[2]
          targetResult.验证结果 = arrays_signal[3]
          targetResult.错误消息 = arrays_signal[4]
          targetResult.扫码值 = arrays_signal[5]
          targetResult.扫码物料编码 = arrays_signal[6]
          targetResult.扫码序列号 = arrays_signal[7]
          targetResult.工单号 = arrays_signal[8]
          // targetResult.工单物料编号 = arrays_signal[9]
          // targetResult.工单序列号 = arrays_signal[10]
          if (targetResult.验证结果 == 1) {
            this.$message.success('收到校验结果，校验通过！' + targetResult.错误消息)
            if (pos === this.clickOpName) {
              const isFirst = pos === this.opNameList[0]
                ? !this.ws1FirstVerifyHandled
                : !this.ws2FirstVerifyHandled
              if (isFirst) {
                this.autoFillFirstVersion(pos)
                if (pos === this.opNameList[0]) this.ws1FirstVerifyHandled = true
                else this.ws2FirstVerifyHandled = true
              } else {
                this.openVersionDialog(pos)
              }
            }
            if (this.isAutoPartLoad && this.clickOpName === pos) {
              this.partLoadOpName(targetResult.扫码工位)
            }
          } else if (targetResult.验证结果 == 2) {
            this.$message.error('收到校验结果，校验失败！' + targetResult.错误消息)
          } else {
            this.$message.warning('校验未成功，服务未开启!' + targetResult.错误消息)
          }
          break
        }
      }
    },

    // ==================== 重置序列号 ====================

    async resetSerialNumber(opName) {
      const result = opName === this.opNameList[0] ? this.ws1Result : this.ws2Result
      // if (!result.扫码值) {
      //   this.$message.warning('请先扫码获取验证信息！')
      //   return
      // }

      const param = []
      param[0] = ['工位号', opName]
      param[1] = ['扫码值', result.扫码值 || '']
      param[2] = ['物料编号', result.扫码物料编码 || '']
      param[3] = ['工单号', result.工单号 || '']
      param[4] = ['序列号', result.扫码序列号 || '']
      const Data = this.CreateData('12', 'Event_序列号信息_重置', param)

      this.isResetSerialLoading = true
      console.log(param)
      this.ExecDatabase(Data).then(response => {
        if (response.data && response.data[0] && response.data[0].result === '1') {
          this.$message.success('重置序列号成功！')
        } else {
          this.$message.error('重置序列号失败！')
        }
      }).catch(error => {
        this.$message.error('重置序列号失败：' + error.message)
      }).finally(() => {
        this.isResetSerialLoading = false
      })
    },

    // ==================== 手动上线 ====================

    async partLoadOpName(opName) {
      const result = opName === this.opNameList[0] ? this.ws1Result : this.ws2Result
      if (result.验证结果 != 1) {
        let message = '请先进行扫码验证！'
        if (result.验证结果 == 2) message = '验证失败，无法请求上线！'
        else if (result.验证结果 == -1) message = '解析错误，无法请求上线！'
        this.$message.warning(message)
        return
      }

      const programVersion = result.程序版本 ? result.程序版本.toString() : ''
      if (!programVersion) {
        this.$message.warning('请先选择或输入程序版本！')
        return
      }

      this.loadConfirmDialog.opName = opName
      this.loadConfirmDialog.detail = {
        工位号: opName,
        扫码值: result.扫码值 || '',
        工单号: result.工单号 || '',
        扫码序列号: result.扫码序列号 || '',
        扫码物料编码: result.扫码物料编码 || '',
        程序版本: programVersion
      }
      this.loadConfirmDialog.loading = false
      this.loadConfirmDialog.visible = true
    },

    /** 二次确认弹窗"确定"：把弹窗里的程序版本写回扫码结果，再走 PLC 写值流程 */
    async confirmLoadDialog() {
      const opName = this.loadConfirmDialog.opName
      const version = this.loadConfirmDialog.detail.程序版本
        ? this.loadConfirmDialog.detail.程序版本.toString()
        : ''
      if (!version) {
        this.$message.warning('请选择或输入程序版本！')
        return
      }

      const result = opName === this.opNameList[0] ? this.ws1Result : this.ws2Result
      result.程序版本 = version
      this.setLastUsedVersion(opName, version)

      this.loadConfirmDialog.loading = true
      try {
        await this.doPartLoad(opName)
      } finally {
        this.loadConfirmDialog.loading = false
        this.loadConfirmDialog.visible = false
      }
    },

    /** 实际执行 PLC 写值上线流程；成功后写入手动上线记录 */
    async doPartLoad(opName) {
      const result = opName === this.opNameList[0] ? this.ws1Result : this.ws2Result
      const partLoadStatus = opName === this.opNameList[0] ? this.ws1PartLoadStatus : this.ws2PartLoadStatus
      const programVersion = result.程序版本 ? result.程序版本.toString() : ''
      const partCode = result.扫码物料编码 || ''

      const plcValue = await this.getPLCValue(opName, '205015')
      if (plcValue === 'True') {
        partLoadStatus.success = false
        partLoadStatus.message = '当前已请求上线状态！'
        partLoadStatus.show = true
        this.$message.warning('申请上线失败，当前已请求上线状态！')
        return
      }

      try {
        const r1 = await this.writePLCValue(opName, PROGRAM_VERSION_TAG_ID, programVersion)
        if (r1.data.code != 0) throw new Error(r1.data.msg || '程序版本写入失败')
        const r2 = await this.writePLCValue(opName, '205015', 'true')
        if (r2.data.code == 0) {
          partLoadStatus.success = true
          partLoadStatus.message = ''
          partLoadStatus.show = true
          this.$message.success('申请上线成功！')
          this.recordManualLoad(opName, partCode, programVersion)
        } else {
          partLoadStatus.success = false
          partLoadStatus.message = r2.data.msg || '申请失败'
          partLoadStatus.show = true
          this.$message.error(r2.data.msg)
        }
      } catch (error) {
        partLoadStatus.success = false
        partLoadStatus.message = error.message
        partLoadStatus.show = true
        this.$message.error('申请上线失败' + error.message)
      }
    },

    /** 写入手动上线 (物料编码,程序版本) 记录 */
    recordManualLoad(opName, partCode, version) {
      if (!partCode || !version) return
      const param = []
      param[0] = ['物料编码', partCode]
      param[1] = ['程序版本', version]
      param[2] = ['工位号', opName]
      param[3] = ['操作人', this.name || '']
      const Data = this.CreateData('12', 'ZL_物料程序版本_手动上线记录_Insert', param)
      this.ExecDatabase(Data).catch(err => console.error('记录手动上线失败:', err))
    },

    // ==================== 历史程序版本弹窗 ====================

    /** 查询某物料编码的历史程序版本（按最近使用时间倒序） */
    loadHistoryVersions(partCode) {
      if (!partCode) return Promise.resolve([])
      const param = [['物料编码', partCode]]
      const Data = this.CreateData('11', 'ZL_物料程序版本_手动上线记录_查询历史版本', param)
      return this.ExecDatabase(Data)
        .then(resp => Array.isArray(resp.data) ? resp.data.map(r => r.程序版本).filter(v => !!v) : [])
        .catch(err => {
          console.error('查询历史程序版本失败:', err)
          return []
        })
    },

    /** 打开"选择程序版本"弹窗：扫码验证通过且为当前工位时触发 */
    async openVersionDialog(opName) {
      const result = opName === this.opNameList[0] ? this.ws1Result : this.ws2Result
      const partCode = result.扫码物料编码 || ''
      const historyList = await this.loadHistoryVersions(partCode)
      const lastUsed = this.getLastUsedVersion(opName)
      this.versionDialog.opName = opName
      this.versionDialog.partCode = partCode
      this.versionDialog.historyList = historyList
      this.versionDialog.selectedVersion = (result.程序版本 || lastUsed || historyList[0] || '')
      this.versionDialog.visible = true
    },

    /** 首次扫码：从数据库取该物料最近一次的程序版本，直接回填，不弹窗 */
    async autoFillFirstVersion(opName) {
      const result = opName === this.opNameList[0] ? this.ws1Result : this.ws2Result
      const partCode = result.扫码物料编码 || ''
      if (!partCode) return
      const historyList = await this.loadHistoryVersions(partCode)
      if (historyList && historyList.length > 0) {
        result.程序版本 = historyList[0]
        this.setLastUsedVersion(opName, historyList[0])
      }
    },

    /** "选择程序版本"弹窗确定：把版本回填到对应工位，不自动上线 */
    confirmVersionDialog() {
      const version = this.versionDialog.selectedVersion ? this.versionDialog.selectedVersion.toString() : ''
      if (!version) {
        this.$message.warning('请选择或输入程序版本！')
        return
      }
      const opName = this.versionDialog.opName
      const result = opName === this.opNameList[0] ? this.ws1Result : this.ws2Result
      result.程序版本 = version
      this.setLastUsedVersion(opName, version)
      this.versionDialog.visible = false
    },

    /** 读取该工位上一次使用的程序版本（localStorage） */
    getLastUsedVersion(opName) {
      try {
        return localStorage.getItem(LAST_VERSION_STORAGE_PREFIX + opName) || ''
      } catch (e) {
        return ''
      }
    },

    /** 持久化保存该工位本次使用的程序版本（localStorage） */
    setLastUsedVersion(opName, version) {
      try {
        localStorage.setItem(LAST_VERSION_STORAGE_PREFIX + opName, version || '')
      } catch (e) { /* ignore */ }
    },

    // ==================== 读码 / 写码 / 清码 ====================

    /** 写码 - 发送写码请求并启动10秒轮询 */
    handleWriteCode(opName) {
      axios.post(writePlcApi_BtnType, {
        tagTypeCodeID: '205005',
        OPName: opName,
        tagValue: 'true'
      }).then(() => {
        this.$message.success('写码请求已发送')
        this.startPollingPlcStatus(opName)
      })
    },

    /** 读码 - 发送读码请求并启动10秒轮询 */
    handleReadCode(opName) {
      axios.post(writePlcApi_BtnType, {
        tagTypeCodeID: '205006',
        OPName: opName,
        tagValue: 'true'
      }).then(() => {
        this.$message.success('读码请求已发送')
        this.startPollingPlcStatus(opName)
      })
    },

    /** 清码 - 发送清码请求并启动10秒轮询 */
    handleClearCode(opName) {
      axios.post(writePlcApi_BtnType, {
        tagTypeCodeID: '205007',
        OPName: opName,
        tagValue: 'true'
      }).then(() => {
        this.$message.success('清码请求已发送')
        this.startPollingPlcStatus(opName)
      })
    },

    // ==================== 工件下线报工 ====================

    async handleOfflineReport(opName) {
      try {
        await this.$confirm('确定要执行工件下线完成报工操作吗？', '确认操作', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
        })
      } catch (error) {
        return
      }

      const offlineStatus = opName === this.opNameList[0] ? this.ws1OfflineStatus : this.ws2OfflineStatus

      axios.post(partUnloadedApi, {
        OPCode: opName
      }, { timeout: 5000 })
        .then(response => {
          if (response.data.code == 0) {
            offlineStatus.success = true
            offlineStatus.message = ''
            offlineStatus.show = true
            this.$message.success('工件下线完成报工成功！')
            setTimeout(() => {
              offlineStatus.show = false
            }, 3000)
          } else {
            offlineStatus.success = false
            offlineStatus.message = response.data.msg || '报工失败'
            offlineStatus.show = true
            this.$message.error('工件下线完成报工失败：' + response.data.msg)
          }
        })
        .catch(error => {
          offlineStatus.success = false
          offlineStatus.message = error.message
          offlineStatus.show = true
          this.$message.error('工件下线完成报工失败：' + error.message)
        })
    },
  }
}
</script>

<style lang="scss" scoped>
.app-container {
  background: #fff;
}

.workstation-panel {
  background: #fff;
  border: 1px solid #e0e0e0;
  border-radius: 8px;
  padding: 12px 10px 12px 10px;
  margin-bottom: 10px;
  min-height: 400px;
}

.workstation-title {
  font-size: 24px;
  font-weight: bold;
  color: #333;
  margin-bottom: 10px;
  text-align: center;
}

.status-bar {
  margin-bottom: 15px;
  font-size: 16px;
  margin: 0 auto 15px;
  text-align: center;

  &>span {
    margin-right: 30px;
    font-weight: 500;

    &:last-child {
      margin-right: 0;
    }
  }
}

.status-dot {
  display: inline-block;
  width: 30px;
  height: 20px;
  border-radius: 3px;
  margin-left: 6px;
  background: #e0e0e0;
  vertical-align: middle;
}

.status-dot.ok {
  background: #70b603;
}

.status-dot.err {
  background: #ff3b3b;
}

.section-title {
  font-size: 14px;
  color: #ff9d00;
  font-weight: bold;
  margin: 10px 0 6px 0;
}

.el-form-item {
  margin-bottom: 0px;
}

::v-deep .el-form-item__label {
  width: 25% !important;
}

.ShortLabel {
  ::v-deep .el-form-item__label {
    width: 15% !important;
  }

  ::v-deep .el-form-item__content {
    width: 85% !important;
  }
}

.SmallLabel {
  ::v-deep .el-form-item__label {
    width: 12% !important;
  }

  ::v-deep .el-form-item__content {
    width: 88% !important;
  }
}

::v-deep .el-form-item__content {
  width: 75% !important;
}

::v-deep .el-button {
  span {
    font-size: inherit !important;
  }
}

.material-version-row {
  display: flex;
  align-items: flex-end;
  gap: 8px;
  margin-top: 4px;

  .mv-version {
    flex: 0 0 90px;
  }

  .mv-material {
    flex: 2 1 0;
    min-width: 0;
  }

  .mv-result {
    flex: 1 1 0;
    min-width: 120px;
  }

  .top-label {
    font-size: 14px;
    color: #606266;
    font-weight: bold;
    line-height: 1.4;
    padding: 0 0 2px 2px;
  }

  .version-input {
    ::v-deep .el-input__inner {
      text-align: right;
      cursor: pointer;
      background-color: #fff;
    }
  }

  .result-display {
    height: 32px;
    display: flex;
    align-items: center;
    justify-content: center;

    .result-pass,
    .result-fail,
    .result-error {
      display: inline-block;
      width: 100%;
      font-weight: bold;
      font-size: 14px;
      padding: 4px 8px;
      border-radius: 4px;
      text-align: center;
    }

    .result-pass { background: #aaff33; color: #222; }
    .result-fail { background: #ff3b3b; color: #fff; }
    .result-error { background: #ffe066; color: #222; }
  }
}

.upline-form {
  ::v-deep .el-form-item__label {
    width: 70px !important;
    padding-right: 6px;
  }

  ::v-deep .el-form-item__content {
    width: calc(100% - 70px) !important;
  }
}
</style>

<style lang="scss">
.version-dialog {
  border-radius: 12px;
  overflow: hidden;
  box-shadow: 0 16px 40px rgba(0, 0, 0, 0.18);

  .el-dialog__header {
    padding: 18px 24px 14px;
    background: linear-gradient(135deg, #f5f9ff 0%, #ffffff 100%);
    border-bottom: 1px solid #ebeef5;
  }

  .el-dialog__headerbtn {
    top: 18px;
    right: 20px;
  }

  .el-dialog__body {
    padding: 18px 24px 4px;
  }

  .el-dialog__footer {
    padding: 12px 24px 18px;
  }

  .vd-header {
    .vd-title {
      font-size: 17px;
      font-weight: 600;
      color: #303133;
      line-height: 1.3;
    }
    .vd-subtitle {
      margin-top: 4px;
      font-size: 12px;
      color: #909399;
      line-height: 1.4;
      word-break: break-all;
    }
  }

  .vd-body {
    .vd-section-title {
      font-size: 13px;
      font-weight: 600;
      color: #606266;
      display: flex;
      align-items: center;
      justify-content: space-between;
      margin-bottom: 10px;

      .vd-count {
        font-size: 12px;
        font-weight: normal;
        color: #909399;
      }
    }

    .vd-section-title-spaced {
      margin-top: 18px;
    }

    .vd-tag-list {
      display: flex;
      flex-wrap: wrap;
      gap: 8px;
      max-height: 180px;
      overflow-y: auto;
      padding: 4px 4px 4px 0;

      &::-webkit-scrollbar {
        width: 6px;
      }
      &::-webkit-scrollbar-thumb {
        background: #dcdfe6;
        border-radius: 3px;
      }
    }

    .vd-tag {
      display: inline-flex;
      align-items: center;
      padding: 6px 14px;
      min-width: 56px;
      justify-content: center;
      border-radius: 16px;
      background: #f4f6fa;
      color: #606266;
      font-size: 13px;
      font-weight: 500;
      cursor: pointer;
      border: 1px solid transparent;
      transition: all 0.18s ease;
      user-select: none;

      &:hover {
        background: #e8f0ff;
        color: #409eff;
      }

      &.is-active {
        background: #409eff;
        color: #fff;
        border-color: #409eff;
        box-shadow: 0 4px 10px rgba(64, 158, 255, 0.28);
      }
    }

    .vd-empty {
      padding: 18px 12px;
      background: #fafbfc;
      border: 1px dashed #dcdfe6;
      border-radius: 8px;
      text-align: center;

      .vd-empty-text {
        font-size: 13px;
        color: #909399;
      }
      .vd-empty-hint {
        margin-top: 4px;
        font-size: 12px;
        color: #c0c4cc;
      }
    }
  }

  .vd-footer {
    text-align: right;
  }
}

.load-confirm-dialog {
  border-radius: 14px;
  overflow: hidden;
  box-shadow: 0 18px 44px rgba(0, 0, 0, 0.18);

  .el-dialog__header {
    padding: 18px 24px 14px;
    background: linear-gradient(135deg, #fff7eb 0%, #ffffff 100%);
    border-bottom: 1px solid #ebeef5;
  }

  .el-dialog__headerbtn {
    top: 18px;
    right: 20px;
  }

  .el-dialog__body {
    padding: 18px 22px 6px;
    background: #f7f9fc;
  }

  .el-dialog__footer {
    padding: 14px 22px 18px;
    background: #fff;
    border-top: 1px solid #ebeef5;
  }

  .lcd-header {
    .lcd-title {
      font-size: 18px;
      font-weight: 600;
      color: #303133;
      line-height: 1.3;
    }
    .lcd-subtitle {
      margin-top: 4px;
      font-size: 12px;
      color: #909399;
      line-height: 1.4;
    }
  }

  .lcd-body {
    display: flex;
    flex-direction: column;
    gap: 12px;
  }

  .lcd-card {
    background: #ffffff;
    border: 1px solid #ebeef5;
    border-radius: 10px;
    padding: 14px 16px;
    position: relative;

    &.lcd-card-primary {
      background: linear-gradient(180deg, #ffffff 0%, #fafcff 100%);
      border-color: #d9e7ff;
      padding-top: 22px;
    }
  }

  .lcd-card-tag {
    position: absolute;
    top: -10px;
    left: 14px;
    background: #ff9d00;
    color: #fff;
    font-size: 11px;
    font-weight: 600;
    padding: 3px 10px;
    border-radius: 10px;
    letter-spacing: 0.5px;
    box-shadow: 0 2px 6px rgba(255, 157, 0, 0.35);
  }

  .lcd-row-2col {
    display: grid;
    grid-template-columns: 1.4fr 1fr;
    gap: 14px;
    align-items: end;
  }

  .lcd-row-2col-key {
    grid-template-columns: 0.5fr 1.5fr;
    margin-bottom: 12px;
  }

  .lcd-field {
    .lcd-label {
      font-size: 12px;
      color: #909399;
      margin-bottom: 6px;
      font-weight: 500;
    }
    .lcd-value {
      font-size: 15px;
      color: #303133;
      font-weight: 500;
      word-break: break-all;
      line-height: 1.4;
      padding: 7px 12px;
      background: #f4f6fa;
      border-radius: 6px;
      min-height: 32px;
      display: flex;
      align-items: center;
    }
    .lcd-value-strong {
      font-weight: 600;
      color: #ff7a00;
      background: #fff5e6;
      border: 1px solid #ffe1b3;
    }
    .lcd-value-right {
      justify-content: flex-end;
      text-align: right;
    }
    .lcd-value-mono {
      font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
      font-size: 14px;
      letter-spacing: 0.3px;
    }
  }

  .lcd-field-program-no {
    .lcd-value {
      background: #f0f7ff;
      border: 1px dashed #b3d4ff;
      color: #1f6feb;
      font-weight: 600;
    }
  }

  .lcd-select {
    width: 100%;
    .el-input__inner {
      height: 32px;
      line-height: 32px;
    }
  }

  .lcd-info-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 10px 14px;
  }

  .lcd-info-item {
    display: flex;
    flex-direction: column;

    &.lcd-info-item-full {
      grid-column: 1 / -1;
    }
  }

  .lcd-info-label {
    font-size: 12px;
    color: #909399;
    margin-bottom: 4px;
  }

  .lcd-info-value {
    font-size: 13px;
    color: #303133;
    word-break: break-all;
    line-height: 1.5;
    font-weight: 500;
  }

  .lcd-info-value-mono {
    font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
    font-size: 12.5px;
    color: #606266;
    background: #f4f6fa;
    padding: 6px 10px;
    border-radius: 4px;
  }

  .lcd-footer {
    text-align: right;
    .el-button {
      min-width: 90px;
    }
    .el-button--primary {
      min-width: 130px;
      font-weight: 600;
    }
  }
}
</style>
