<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="50px" label-position="right" style="width: 100%;">
            <el-row :gutter="10">
              <el-col :span="14">
                <el-form-item label="扫码值" style="width: 100%;" class="SmallLabel">
                  <el-input v-model="ws1Result.扫码值" readonly size="small" style="width: 100%;" />
                </el-form-item>
              </el-col>
              <el-col :span="10">
                <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="14">
                <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="10">
                <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="14">
                <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-col :span="10">
                <el-form-item label="程序版本" style="width: 100%;">
                  <el-select v-model="ws1Result.程序版本" filterable allow-create default-first-option size="small" style="width: 100%;">
                    <el-option v-for="item in programVersionOptions" :key="item" :label="item" :value="item" />
                  </el-select>
                </el-form-item>
              </el-col>
            </el-row>
            <el-row :gutter="10">
              <el-col :span="14">
                <div style="height: 32px;"></div>
              </el-col>
              <el-col :span="10">
                <el-form-item label="验证结果" style="width: 100%;">
                  <div v-if="ws1Result.验证结果 == 1"
                    style="background:#aaff33;color:#222;font-weight:bold;font-size:14px;padding:2px 8px;border-radius:4px;width:100%;text-align:center;">
                    验证通过</div>
                  <div v-else-if="ws1Result.验证结果 == 2"
                    style="background:#ff3b3b;color:#fff;font-weight:bold;font-size:14px;padding:2px 8px;border-radius:4px;width:100%;text-align:center;">
                    验证失败</div>
                  <div v-else-if="ws1Result.验证结果 == -1"
                    style="background:#ffe066;color:#222;font-weight:bold;font-size:14px;padding:2px 8px;border-radius:4px;width:100%;text-align:center;">
                    解析错误</div>
                </el-form-item>
              </el-col>
            </el-row>
          </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="50px" label-position="right" style="width: 100%;">
            <el-row :gutter="10">
              <el-col :span="14">
                <el-form-item label="扫码值" style="width: 100%;" class="SmallLabel">
                  <el-input v-model="ws2Result.扫码值" readonly size="small" style="width: 100%;" />
                </el-form-item>
              </el-col>
              <el-col :span="10">
                <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="14">
                <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="10">
                <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="14">
                <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-col :span="10">
                <el-form-item label="程序版本" style="width: 100%;">
                  <el-select v-model="ws2Result.程序版本" filterable allow-create default-first-option size="small" style="width: 100%;">
                    <el-option v-for="item in programVersionOptions" :key="item" :label="item" :value="item" />
                  </el-select>
                </el-form-item>
              </el-col>
            </el-row>
            <el-row :gutter="10">
              <el-col :span="14">
                <div style="height: 32px;"></div>
              </el-col>
              <el-col :span="10">
                <el-form-item label="验证结果" style="width: 100%;">
                  <div v-if="ws2Result.验证结果 == 1"
                    style="background:#aaff33;color:#222;font-weight:bold;font-size:14px;padding:2px 8px;border-radius:4px;width:100%;text-align:center;">
                    验证通过</div>
                  <div v-else-if="ws2Result.验证结果 == 2"
                    style="background:#ff3b3b;color:#fff;font-weight:bold;font-size:14px;padding:2px 8px;border-radius:4px;width:100%;text-align:center;">
                    验证失败</div>
                  <div v-else-if="ws2Result.验证结果 == -1"
                    style="background:#ffe066;color:#222;font-weight:bold;font-size:14px;padding:2px 8px;border-radius:4px;width:100%;text-align:center;">
                    解析错误</div>
                </el-form-item>
              </el-col>
            </el-row>
          </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>
  </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'

/** 程序版本下拉选项 */
const PROGRAM_VERSION_OPTIONS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('')

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

/** 状态灯信号 → 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: { 扫码值: '', 工单号: '', 扫码物料编码: '', 扫码序列号: '', 错误消息: '', 验证结果: '', 程序版本: '' },
      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: {},
    }
  },
  computed: {
    ...mapGetters(['id', 'token'])
  },
  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']] || ''
      if (valueMap[PROGRAM_VERSION_TAG_ID] !== undefined) result.程序版本 = valueMap[PROGRAM_VERSION_TAG_ID] || ''
    },

    /** 将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 < 11) {
            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 (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
      }

      try {
        await this.$confirm('确定要执行手动请求上线操作吗？', '确认操作', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
        })
      } catch (error) {
        return
      }

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

      this.writePLCValue(opName, PROGRAM_VERSION_TAG_ID, programVersion)
        .then(response => {
          if (response.data.code != 0) {
            return Promise.reject(new Error(response.data.msg || '程序版本写入失败'))
          }
          return this.writePLCValue(opName, '205015', 'true')
        })
        .then(response => {
          if (response.data.code == 0) {
            partLoadStatus.success = true
            partLoadStatus.message = ''
            partLoadStatus.show = true
            this.$message.success('申请上线成功！')
          } else {
            partLoadStatus.success = false
            partLoadStatus.message = response.data.msg || '申请失败'
            partLoadStatus.show = true
            this.$message.error(response.data.msg)
          }
        })
        .catch(error => {
          partLoadStatus.success = false
          partLoadStatus.message = error.message
          partLoadStatus.show = true
          this.$message.error('申请上线失败' + error.message)
        })
    },

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

    /** 写码 - 发送写码请求并启动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;
  }
}
</style>
