src/pages/productionManagement/productionTraceability/index.vue
@@ -2,510 +2,887 @@
  <view class="production-traceability">
    <PageHeader title="生产追溯"
                @back="goBack" />
    <!-- 方向页签 -->
    <view class="trace-tabs">
      <view class="tab-item"
            :class="{ active: direction === 'forward' }"
            @click="switchDirection('forward')">正向追溯</view>
      <view class="tab-item"
            :class="{ active: direction === 'reverse' }"
            @click="switchDirection('reverse')">反向追溯</view>
    </view>
    <!-- 搜索区域 -->
    <view class="search-section">
      <view class="search-bar"
            @click="openNpsNoSelector">
      <view class="search-bar">
        <view class="search-input">
          <text v-if="!selectedNpsNo"
                class="placeholder">请选择生产订单号</text>
          <text v-else
                class="value">{{ selectedNpsNoLabel }}</text>
        </view>
        <view class="search-button">
          <up-icon name="arrow-down"
                   size="20"
          <up-icon name="search"
                   size="18"
                   color="#999"></up-icon>
          <up-input class="search-text"
                    v-model="batchNo"
                    :placeholder="placeholder"
                    border="none"
                    clearable
                    @input="onBatchInput"
                    @focus="onBatchFocus"
                    @clear="onBatchClear" />
        </view>
      </view>
      <!-- 批号候选下拉 -->
      <view class="option-panel"
            v-if="showOptions">
        <view class="option-item"
              v-for="(opt, index) in batchOptions"
              :key="index"
              @click="onSelectOption(opt)">
          <view class="option-top">
            <text class="option-no">{{ opt.batchNo || '-' }}</text>
            <text class="option-qty">{{ num(opt.qty) }}{{ opt.unit || '' }}</text>
          </view>
          <view class="option-bottom">
            <text class="option-name">{{ text(opt.productName) }} / {{ text(opt.productModel) }}</text>
            <text class="option-time">{{ opt.inTime || '-' }}</text>
          </view>
        </view>
        <view class="option-empty"
              v-if="!optionsLoading && batchOptions.length === 0">无匹配批号,可直接输入后查询</view>
      </view>
      <view class="search-tip">{{ direction === 'forward' ? '输入成品批号,追溯生产全链路' : '输入原料批号,定位受影响的成品范围' }}</view>
      <view class="btn-row">
        <up-button type="primary"
                   size="small"
                   text="查询"
                   @click="handleQuery"></up-button>
      </view>
    </view>
    <!-- 内容区域 -->
    <view class="content-container"
          v-if="rowData.productionOrderDto">
      <!-- 基础信息 -->
      <view class="info-card">
        <view class="card-title">基础信息</view>
        <view class="base-info">
          <view class="info-row">
            <text class="label">生产订单号:</text>
            <text class="value">{{ rowData.productionOrderDto?.npsNo || '-' }}</text>
          </view>
          <view class="info-row">
            <text class="label">产品名称:</text>
            <text class="value">{{ rowData.productionOrderDto?.productName || '-' }}</text>
          </view>
          <view class="info-row">
            <text class="label">规格型号:</text>
            <text class="value">{{ rowData.productionOrderDto?.model || '-' }}</text>
          </view>
          <view class="info-row">
            <text class="label">计划数量:</text>
            <text class="value">{{ rowData.productionOrderDto?.quantity || 0 }} {{ rowData.productionOrderDto?.unit }}</text>
          </view>
          <view class="info-row">
            <text class="label">当前状态:</text>
            <view class="value">
              <up-tag :text="getStatusText(rowData.productionOrderDto?.status)"
                      :type="getStatusType(rowData.productionOrderDto?.status)"
    <!-- 加载中 -->
    <view v-if="loading"
          class="loading-box">
      <up-loading-icon text="加载中..."></up-loading-icon>
    </view>
    <template v-else>
      <!-- 未查询提示 -->
      <view v-if="!queried"
            class="no-data">
        <up-empty mode="search"
                  :text="direction === 'forward' ? '请输入成品批号查询追溯链路' : '请输入原料批号查询影响范围'"></up-empty>
      </view>
      <!-- ================= 正向追溯 ================= -->
      <view v-else-if="direction === 'forward'"
            class="result-container">
        <view v-if="forwardEmpty"
              class="no-data">
          <up-empty mode="data"
                    text="未查询到该批号的追溯记录"></up-empty>
        </view>
        <template v-else>
          <view class="result-title">生产订单链路({{ forwardData.chains.length }})</view>
          <view v-for="(chain, ci) in forwardData.chains"
                :key="ci"
                class="ledger-item">
            <view class="item-header">
              <view class="item-left">
                <view class="document-icon">
                  <up-icon name="file-text"
                           size="16"
                           color="#ffffff"></up-icon>
                </view>
                <text class="item-id">{{ text(chain.npsNo) }}</text>
              </view>
              <up-tag :text="orderStatusText(chain.orderStatus)"
                      :type="orderStatusType(chain.orderStatus)"
                      size="mini" />
            </view>
          </view>
          <view class="info-row">
            <text class="label">客户名称:</text>
            <text class="value">{{ rowData.productionOrderDto?.customerName || '-' }}</text>
          </view>
          <view class="info-row">
            <text class="label">开始日期:</text>
            <text class="value">{{ formatDate(rowData.productionOrderDto?.startTime) }}</text>
          </view>
          <view class="info-row">
            <text class="label">完成进度:</text>
            <view class="value progress-box">
              <up-line-progress :percentage="formatProgress(rowData.productionOrderDto?.completionStatus)"
                                :activeColor="progressColor(formatProgress(rowData.productionOrderDto?.completionStatus))"
                                :showText="true" />
            <up-divider></up-divider>
            <view class="item-details">
              <view class="detail-row">
                <text class="detail-label">成品</text>
                <text class="detail-value font-bold">{{ text(chain.finishedProduct) }}</text>
              </view>
              <view class="detail-row">
                <text class="detail-label">单位</text>
                <text class="detail-value">{{ text(chain.unit) }}</text>
              </view>
              <view class="detail-row">
                <text class="detail-label">计划数量</text>
                <text class="detail-value">{{ num(chain.planQty) }}</text>
              </view>
              <view class="detail-row">
                <text class="detail-label">完工数量</text>
                <text class="detail-value highlight">{{ num(chain.completeQty) }}</text>
              </view>
              <view class="detail-row">
                <text class="detail-label">订单创建时间</text>
                <text class="detail-value">{{ text(chain.orderCreateTime) }}</text>
              </view>
            </view>
            <!-- 命中的入库批次 -->
            <view class="chip-wrap"
                  v-if="chain.batches && chain.batches.length">
              <text class="chip"
                    v-for="(b, bi) in chain.batches"
                    :key="bi">{{ text(b.batchNo) }} · {{ recordTypeText(b.recordType) }} · {{ num(b.qty) }}{{ chain.unit || '' }}</text>
            </view>
            <!-- 报工工序 -->
            <view class="collapse-block">
              <view class="collapse-head"
                    @click="toggleSection(ci, 'reports')">
                <text class="collapse-title">报工工序</text>
                <text class="collapse-count">{{ (chain.reports || []).length }}</text>
                <up-icon :name="isSectionOpen(ci, 'reports') ? 'arrow-up' : 'arrow-down'"
                         size="14"
                         color="#999"></up-icon>
              </view>
              <view class="collapse-body"
                    v-show="isSectionOpen(ci, 'reports')">
                <view class="sub-item"
                      v-for="(r, ri) in chain.reports"
                      :key="ri">
                  <view class="detail-row">
                    <text class="detail-label">工单号</text>
                    <text class="detail-value font-bold">{{ text(r.workOrderNo) }}</text>
                  </view>
                  <view class="detail-row">
                    <text class="detail-label">工序</text>
                    <text class="detail-value">{{ text(r.operationName) }}</text>
                  </view>
                  <view class="detail-row">
                    <text class="detail-label">报工人</text>
                    <text class="detail-value">{{ text(r.userName) }}</text>
                  </view>
                  <view class="detail-row">
                    <text class="detail-label">设备</text>
                    <text class="detail-value">{{ text(r.deviceName) }}</text>
                  </view>
                  <view class="detail-row">
                    <text class="detail-label">报工单号</text>
                    <text class="detail-value">{{ text(r.productNo) }}</text>
                  </view>
                  <view class="detail-row">
                    <text class="detail-label">产出数量</text>
                    <text class="detail-value">{{ num(r.outputQty) }}</text>
                  </view>
                  <view class="detail-row">
                    <text class="detail-label">报废数量</text>
                    <text class="detail-value danger">{{ num(r.scrapQty) }}</text>
                  </view>
                  <view class="detail-row">
                    <text class="detail-label">报工时间</text>
                    <text class="detail-value">{{ text(r.reportTime) }}</text>
                  </view>
                </view>
                <view class="sub-empty"
                      v-if="!(chain.reports || []).length">暂无报工记录</view>
              </view>
            </view>
            <!-- 投入物料 -->
            <view class="collapse-block">
              <view class="collapse-head"
                    @click="toggleSection(ci, 'inputs')">
                <text class="collapse-title">投入物料</text>
                <text class="collapse-count">{{ (chain.inputs || []).length }}</text>
                <up-icon :name="isSectionOpen(ci, 'inputs') ? 'arrow-up' : 'arrow-down'"
                         size="14"
                         color="#999"></up-icon>
              </view>
              <view class="collapse-body"
                    v-show="isSectionOpen(ci, 'inputs')">
                <view class="sub-item"
                      v-for="(m, mi) in chain.inputs"
                      :key="mi">
                  <view class="detail-row">
                    <text class="detail-label">物料</text>
                    <text class="detail-value font-bold">{{ text(m.materialName) }}</text>
                  </view>
                  <view class="detail-row">
                    <text class="detail-label">规格型号</text>
                    <text class="detail-value">{{ text(m.materialModel) }}</text>
                  </view>
                  <view class="detail-row">
                    <text class="detail-label">投入数量</text>
                    <text class="detail-value">{{ num(m.inputQty) }} {{ text(m.unit) }}</text>
                  </view>
                  <view class="detail-row">
                    <text class="detail-label">工序</text>
                    <text class="detail-value">{{ text(m.operationName) }}</text>
                  </view>
                </view>
                <view class="sub-empty"
                      v-if="!(chain.inputs || []).length">暂无投入物料</view>
              </view>
            </view>
            <!-- 领料批次 -->
            <view class="collapse-block">
              <view class="collapse-head"
                    @click="toggleSection(ci, 'picks')">
                <text class="collapse-title">领料批次</text>
                <text class="collapse-count">{{ (chain.picks || []).length }}</text>
                <up-icon :name="isSectionOpen(ci, 'picks') ? 'arrow-up' : 'arrow-down'"
                         size="14"
                         color="#999"></up-icon>
              </view>
              <view class="collapse-body"
                    v-show="isSectionOpen(ci, 'picks')">
                <view class="sub-item"
                      v-for="(p, pi) in chain.picks"
                      :key="pi">
                  <view class="detail-row">
                    <text class="detail-label">原料批号</text>
                    <text class="detail-value link"
                          @click="jumpToReverse(p.batchNo)">{{ text(p.batchNo) }}</text>
                  </view>
                  <view class="detail-row">
                    <text class="detail-label">物料</text>
                    <text class="detail-value">{{ text(p.materialName) }}</text>
                  </view>
                  <view class="detail-row">
                    <text class="detail-label">规格型号</text>
                    <text class="detail-value">{{ text(p.materialModel) }}</text>
                  </view>
                  <view class="detail-row">
                    <text class="detail-label">领用数量</text>
                    <text class="detail-value">{{ num(p.quantity) }} {{ text(p.unit) }}</text>
                  </view>
                  <view class="detail-row">
                    <text class="detail-label">工序</text>
                    <text class="detail-value">{{ text(p.operationName) }}</text>
                  </view>
                  <view class="detail-row">
                    <text class="detail-label">领料时间</text>
                    <text class="detail-value">{{ text(p.pickTime) }}</text>
                  </view>
                  <view class="detail-row">
                    <text class="detail-label">供应商</text>
                    <text class="detail-value">{{ text(p.supplierName) }}</text>
                  </view>
                  <view class="detail-row">
                    <text class="detail-label">采购合同号</text>
                    <text class="detail-value">{{ text(p.contractNo) }}</text>
                  </view>
                  <view class="detail-row">
                    <text class="detail-label">原料检验结论</text>
                    <text class="detail-value">{{ text(p.checkResult) }}</text>
                  </view>
                </view>
                <view class="sub-empty"
                      v-if="!(chain.picks || []).length">暂无领料记录</view>
              </view>
            </view>
            <!-- 检验单 -->
            <view class="collapse-block">
              <view class="collapse-head"
                    @click="toggleSection(ci, 'inspects')">
                <text class="collapse-title">检验单</text>
                <text class="collapse-count">{{ (chain.inspects || []).length }}</text>
                <up-icon :name="isSectionOpen(ci, 'inspects') ? 'arrow-up' : 'arrow-down'"
                         size="14"
                         color="#999"></up-icon>
              </view>
              <view class="collapse-body"
                    v-show="isSectionOpen(ci, 'inspects')">
                <view class="sub-item"
                      v-for="(ins, ii) in chain.inspects"
                      :key="ii">
                  <view class="detail-row">
                    <text class="detail-label">检验类型</text>
                    <text class="detail-value font-bold">{{ text(ins.inspectTypeName) }}</text>
                  </view>
                  <view class="detail-row">
                    <text class="detail-label">检验员</text>
                    <text class="detail-value">{{ text(ins.checkName) }}</text>
                  </view>
                  <view class="detail-row">
                    <text class="detail-label">数量</text>
                    <text class="detail-value">{{ num(ins.quantity) }}</text>
                  </view>
                  <view class="detail-row">
                    <text class="detail-label">合格数量</text>
                    <text class="detail-value">{{ num(ins.qualifiedQuantity) }}</text>
                  </view>
                  <view class="detail-row">
                    <text class="detail-label">不合格数量</text>
                    <text class="detail-value danger">{{ num(ins.unqualifiedQuantity) }}</text>
                  </view>
                  <view class="detail-row">
                    <text class="detail-label">检验结论</text>
                    <text class="detail-value">{{ text(ins.checkResult) }}</text>
                  </view>
                  <view class="detail-row">
                    <text class="detail-label">检验时间</text>
                    <text class="detail-value">{{ text(ins.checkTime) }}</text>
                  </view>
                  <!-- 不合格处理 -->
                  <view class="unqualified-box"
                        v-if="(ins.unqualifiedList || []).length">
                    <view class="unqualified-title">不合格处理</view>
                    <view class="inner-item"
                          v-for="(u, ui) in ins.unqualifiedList"
                          :key="ui">
                      <view class="detail-row">
                        <text class="detail-label">缺陷现象</text>
                        <text class="detail-value danger">{{ text(u.defectivePhenomena) }}</text>
                      </view>
                      <view class="detail-row">
                        <text class="detail-label">数量</text>
                        <text class="detail-value">{{ num(u.quantity) }}</text>
                      </view>
                      <view class="detail-row">
                        <text class="detail-label">处理结果</text>
                        <text class="detail-value">{{ text(u.dealResult) }}</text>
                      </view>
                      <view class="detail-row">
                        <text class="detail-label">处理人</text>
                        <text class="detail-value">{{ text(u.dealName) }}</text>
                      </view>
                      <view class="detail-row">
                        <text class="detail-label">处理时间</text>
                        <text class="detail-value">{{ text(u.dealTime) }}</text>
                      </view>
                    </view>
                  </view>
                </view>
                <view class="sub-empty"
                      v-if="!(chain.inspects || []).length">暂无检验记录</view>
              </view>
            </view>
          </view>
        </view>
          <!-- 出库流向 -->
          <view class="result-title">出库流向({{ forwardData.outbounds.length }})</view>
          <view v-for="(o, oi) in forwardData.outbounds"
                :key="oi"
                class="ledger-item">
            <view class="item-header">
              <view class="item-left">
                <view class="document-icon out">
                  <up-icon name="arrow-upward"
                           size="16"
                           color="#ffffff"></up-icon>
                </view>
                <text class="item-id">{{ text(o.batchNo) }}</text>
              </view>
              <text class="type-tag">{{ recordTypeText(o.recordType) }}</text>
            </view>
            <up-divider></up-divider>
            <view class="item-details">
              <view class="detail-row">
                <text class="detail-label">产品名称</text>
                <text class="detail-value font-bold">{{ text(o.productName) }}</text>
              </view>
              <view class="detail-row">
                <text class="detail-label">规格型号</text>
                <text class="detail-value">{{ text(o.productModel) }}</text>
              </view>
              <view class="detail-row">
                <text class="detail-label">出库数量</text>
                <text class="detail-value highlight">{{ num(o.qty) }} {{ text(o.unit) }}</text>
              </view>
              <view class="detail-row">
                <text class="detail-label">出库时间</text>
                <text class="detail-value">{{ text(o.outTime) }}</text>
              </view>
              <view class="detail-row">
                <text class="detail-label">备注</text>
                <text class="detail-value">{{ text(o.remark) }}</text>
              </view>
            </view>
          </view>
          <view class="sub-empty card"
                v-if="!forwardData.outbounds.length">暂无出库流向记录</view>
        </template>
      </view>
      <!-- 工单信息 -->
      <view class="work-order-section"
            v-if="rowData.productionRecords && rowData.productionRecords.length > 0">
        <view class="section-title">工单信息</view>
        <view v-for="(item, index) in rowData.productionRecords"
              :key="index"
              class="work-order-card">
          <view class="card-header">
            <text class="work-order-no">{{ item.workOrder.workOrderNo }}</text>
            <text class="progress-tag"
                  :style="{ color: progressColor(item.workOrder.completionStatus) }">{{ item.workOrder.completionStatus || 0 }}%</text>
          </view>
          <view class="card-content">
            <view class="content-row">
              <text class="label">产品/规格:</text>
              <text class="value">{{ item.workOrder.productName }} / {{ item.workOrder.model }}</text>
            </view>
            <view class="content-row">
              <text class="label">当前工序:</text>
              <text class="value">{{ item.workOrder.operationName || '-' }}</text>
            </view>
            <view class="content-row">
              <text class="label">需求/完成:</text>
              <text class="value">{{ item.workOrder.planQuantity }} / {{ item.workOrder.completeQuantity }}</text>
            </view>
            <view class="content-row">
              <text class="label">报废数量:</text>
              <text class="value error-text">{{ item.workOrder.scrapQty || 0 }}</text>
            </view>
          </view>
          <view class="card-footer">
            <up-button type="primary"
                       size="small"
                       plain
                       text="报工记录"
                       @click="handleShowReports(item)"></up-button>
            <up-button type="success"
                       size="small"
                       plain
                       text="质检信息"
                       @click="handleShowQuality(item)"></up-button>
          </view>
        </view>
      </view>
      <!-- ================= 反向追溯 ================= -->
      <view v-else
            class="no-data-minor">
        <up-empty mode="data"
                  text="暂无工单信息"
                  icon-size="40"></up-empty>
      </view>
    </view>
    <view v-else
          class="no-data">
      <up-empty mode="search"
                text="请选择生产订单号查看追溯信息"></up-empty>
    </view>
    <!-- 生产订单号选择弹窗 -->
    <up-popup :show="showNpsNoSelector"
              mode="bottom"
              @close="showNpsNoSelector = false"
              round="10">
      <view class="selector-popup">
        <view class="popup-header">
          <text class="popup-title">选择生产订单号</text>
          <up-icon name="close"
                   size="20"
                   @click="showNpsNoSelector = false"></up-icon>
            class="result-container">
        <view v-if="reverseEmpty"
              class="no-data">
          <up-empty mode="data"
                    text="未查询到该批号的追溯记录"></up-empty>
        </view>
        <view class="search-box">
          <up-search placeholder="输入关键字搜索"
                     v-model="npsNoQuery"
                     :show-action="false"
                     @change="handleNpsNoSearch"
                     @search="handleNpsNoSearch"
                     :loading="npsNoLoading"></up-search>
        </view>
        <scroll-view scroll-y
                     class="options-list">
          <view v-for="item in npsNoOptions"
                :key="item.id"
                class="option-item"
                @click="onSelectNpsNo(item)">
            <view class="option-main">
              <text class="nps-no">{{ item.npsNo }}</text>
              <text class="product-info">{{ item.productName }} / {{ item.model }}</text>
            </view>
            <up-icon v-if="selectedNpsNo === item.id"
                     name="checkbox-mark"
                     color="#3c9cff"
                     size="20"></up-icon>
          </view>
          <view v-if="npsNoOptions.length === 0"
                class="no-options">
            <text>{{ npsNoLoading ? '加载中...' : '暂无选项' }}</text>
          </view>
        </scroll-view>
      </view>
    </up-popup>
    <!-- 报工详情弹窗 -->
    <up-popup :show="reportPopupVisible"
              mode="bottom"
              @close="reportPopupVisible = false"
              round="10">
      <view class="popup-content">
        <view class="popup-header">
          <text class="popup-title">生产报工详情</text>
          <up-icon name="close"
                   size="20"
                   @click="reportPopupVisible = false"></up-icon>
        </view>
        <scroll-view scroll-y
                     class="popup-scroll">
          <view class="detail-info">
            <view class="info-row"><text class="label">工单号:</text><text class="value">{{ detailData.workOrder.workOrderNo }}</text></view>
            <view class="info-row"><text class="label">计划/完成:</text><text class="value">{{ detailData.workOrder.planQuantity }} / {{ detailData.workOrder.completeQuantity }}</text></view>
            <view class="info-row"><text class="label">实际时间:</text><text class="value">{{ formatDate(detailData.workOrder.actualStartTime) }} 至 {{ formatDate(detailData.workOrder.actualEndTime) }}</text></view>
          </view>
          <view class="list-title">报工明细</view>
          <view v-for="(report, idx) in detailData.reports"
                :key="idx"
                class="detail-item">
            <view class="item-main">
              <view class="item-row"><text class="label">报工单号:</text><text class="value">{{ report.productNo }}</text></view>
              <view class="item-row"><text class="label">产出数量:</text><text class="value">{{ report.quantity || 0 }}</text></view>
              <view class="item-row"><text class="label">报废数量:</text><text class="value error-text">{{ report.scrapQty || 0 }}</text></view>
              <view class="item-row"><text class="label">工时(h):</text><text class="value">{{ report.workHour || 0 }}</text></view>
              <view class="item-row"><text class="label">创建人:</text><text class="value">{{ report.userName }}</text></view>
              <view class="item-row"><text class="label">创建时间:</text><text class="value">{{ formatDate(report.createTime, '{y}-{m}-{d} {h}:{i}') }}</text></view>
            </view>
            <view class="item-actions">
              <text class="action-link"
                    @click="showParams(report.productionOperationParamList)">参数详情</text>
              <text class="action-link green"
                    @click="handleShowInput(report.id)">投入详情</text>
            </view>
          </view>
          <view v-if="!detailData.reports || detailData.reports.length === 0"
                class="no-data-minor">暂无报工明细</view>
        </scroll-view>
      </view>
    </up-popup>
    <!-- 投入详情弹窗 -->
    <up-popup :show="inputPopupVisible"
              mode="bottom"
              @close="inputPopupVisible = false"
              round="10">
      <view class="popup-content">
        <view class="popup-header">
          <text class="popup-title">投入信息详情</text>
          <up-icon name="close"
                   size="20"
                   @click="inputPopupVisible = false"></up-icon>
        </view>
        <scroll-view scroll-y
                     class="popup-scroll">
          <view class="input-list-popup">
            <view v-for="(item, idx) in inputListData"
                  :key="idx"
                  class="quality-record">
              <view class="record-title">投入记录 {{ idx + 1 }}</view>
              <view class="info-grid">
                <view class="info-item"><text class="label">报工单号</text><text class="value">{{ item.productNo || '-' }}</text></view>
                <view class="info-item"><text class="label">投入数量</text><text class="value">{{ item.quantity || 0 }} {{ item.unit || '' }}</text></view>
                <view class="info-item full-width"><text class="label">投入产品名称</text><text class="value">{{ item.productName || '-' }}</text></view>
                <view class="info-item full-width"><text class="label">投入产品型号</text><text class="value">{{ item.model || '-' }}</text></view>
        <template v-else>
          <!-- 原料来源 -->
          <view class="result-title">原料来源</view>
          <view class="ledger-item"
                v-if="reverseData.material">
            <view class="item-header">
              <view class="item-left">
                <view class="document-icon material">
                  <up-icon name="cube"
                           size="16"
                           color="#ffffff"></up-icon>
                </view>
                <text class="item-id">{{ text(reverseData.material.materialName) }}</text>
              </view>
              <text class="type-tag">{{ recordTypeText(reverseData.material.recordType) }}</text>
            </view>
            <view v-if="!inputListData || inputListData.length === 0"
                  class="no-data-minor">{{ inputLoading ? '加载中...' : '暂无投入记录' }}</view>
          </view>
        </scroll-view>
      </view>
    </up-popup>
    <!-- 质检详情弹窗 -->
    <up-popup :show="qualityPopupVisible"
              mode="bottom"
              @close="qualityPopupVisible = false"
              round="10">
      <view class="popup-content">
        <view class="popup-header">
          <text class="popup-title">质检详情</text>
          <up-icon name="close"
                   size="20"
                   @click="qualityPopupVisible = false"></up-icon>
        </view>
        <scroll-view scroll-y
                     class="popup-scroll">
          <view v-for="(record, idx) in qualityRecords"
                :key="idx"
                class="quality-record">
            <view class="record-title">检测记录 {{ idx + 1 }}</view>
            <view class="info-grid">
              <view class="info-item"><text class="label">检测日期</text><text class="value">{{ formatDate(record.createTime) }}</text></view>
              <view class="info-item"><text class="label">检测结果</text><up-tag style="width:100rpx"
                        :text="record.checkResult || '待检测'"
                        :type="record.checkResult === '合格' ? 'success' : 'error'"
                        size="mini" /></view>
              <view class="info-item"><text class="label">检验员</text><text class="value">{{ record.userName }}</text></view>
              <view class="info-item"><text class="label">数量</text><text class="value">{{ record.quantity }} {{ record.unit }}</text></view>
              <view class="info-item"><text class="label">报工单号</text><text class="value">{{ record.reportNo || '-' }}</text></view>
              <view class="info-item"><text class="label">产品名称</text><text class="value">{{ record.productName || '-' }}</text></view>
              <view class="info-item"><text class="label">规格型号</text><text class="value">{{ record.model || '-' }}</text></view>
              <view class="info-item"><text class="label">检测单位</text><text class="value">{{ record.checkCompany || '-' }}</text></view>
            </view>
            <view class="params-table">
              <view class="table-header">
                <text class="col">指标</text>
                <text class="col">单位</text>
                <text class="col">标准值</text>
                <text class="col">内控值</text>
                <text class="col">实际值</text>
            <up-divider></up-divider>
            <view class="item-details">
              <view class="detail-row">
                <text class="detail-label">原料批号</text>
                <text class="detail-value font-bold">{{ text(reverseData.material.batchNo) }}</text>
              </view>
              <view v-for="(param, pIdx) in record.inspectParamList"
                    :key="pIdx"
                    class="table-row">
                <text class="col">{{ param.parameterItem }}</text>
                <text class="col">{{ param.unit || '-' }}</text>
                <text class="col">{{ param.standardValue }}</text>
                <text class="col">{{ param.controlValue || '-' }}</text>
                <text class="col"
                      :class="{ 'error-text': param.testValue != param.standardValue }">{{ param.testValue }}</text>
              <view class="detail-row">
                <text class="detail-label">规格型号</text>
                <text class="detail-value">{{ text(reverseData.material.materialModel) }}</text>
              </view>
              <view class="detail-row">
                <text class="detail-label">单位</text>
                <text class="detail-value">{{ text(reverseData.material.unit) }}</text>
              </view>
              <view class="detail-row">
                <text class="detail-label">入库数量</text>
                <text class="detail-value">{{ num(reverseData.material.qty) }}</text>
              </view>
              <view class="detail-row">
                <text class="detail-label">入库时间</text>
                <text class="detail-value">{{ text(reverseData.material.inTime) }}</text>
              </view>
              <view class="detail-row">
                <text class="detail-label">检验结论</text>
                <text class="detail-value">{{ text(reverseData.material.checkResult) }}</text>
              </view>
              <view class="detail-row">
                <text class="detail-label">检验数量</text>
                <text class="detail-value">{{ num(reverseData.material.inspectQty) }}</text>
              </view>
              <view class="detail-row">
                <text class="detail-label">合格数量</text>
                <text class="detail-value">{{ num(reverseData.material.qualifiedQty) }}</text>
              </view>
              <view class="detail-row">
                <text class="detail-label">不合格数量</text>
                <text class="detail-value danger">{{ num(reverseData.material.unqualifiedQty) }}</text>
              </view>
              <view class="detail-row">
                <text class="detail-label">检验时间</text>
                <text class="detail-value">{{ text(reverseData.material.checkTime) }}</text>
              </view>
              <view class="detail-row">
                <text class="detail-label">供应商</text>
                <text class="detail-value">{{ text(reverseData.material.supplierName) }}</text>
              </view>
              <view class="detail-row">
                <text class="detail-label">采购合同号</text>
                <text class="detail-value">{{ text(reverseData.material.contractNo) }}</text>
              </view>
              <view class="detail-row">
                <text class="detail-label">采购日期</text>
                <text class="detail-value">{{ text(reverseData.material.entryDate) }}</text>
              </view>
            </view>
          </view>
          <view v-if="!qualityRecords || qualityRecords.length === 0"
                class="no-data-minor">暂无质检记录</view>
        </scroll-view>
          <view class="ledger-item"
                v-else>
            <view class="sub-empty">该批号无入库记录</view>
          </view>
          <!-- 受影响的生产订单 -->
          <view class="result-title">受影响生产订单({{ reverseData.orders.length }})</view>
          <view v-for="(order, oi) in reverseData.orders"
                :key="oi"
                class="ledger-item">
            <view class="item-header">
              <view class="item-left">
                <view class="document-icon">
                  <up-icon name="file-text"
                           size="16"
                           color="#ffffff"></up-icon>
                </view>
                <text class="item-id">{{ text(order.npsNo) }}</text>
              </view>
              <up-tag :text="orderStatusText(order.orderStatus)"
                      :type="orderStatusType(order.orderStatus)"
                      size="mini" />
            </view>
            <up-divider></up-divider>
            <view class="item-details">
              <view class="detail-row">
                <text class="detail-label">成品</text>
                <text class="detail-value font-bold">{{ text(order.finishedProduct) }}</text>
              </view>
              <view class="detail-row">
                <text class="detail-label">计划数量</text>
                <text class="detail-value">{{ num(order.planQty) }} {{ text(order.unit) }}</text>
              </view>
              <view class="detail-row">
                <text class="detail-label">完工数量</text>
                <text class="detail-value">{{ num(order.completeQty) }}</text>
              </view>
              <view class="detail-row">
                <text class="detail-label">订单创建时间</text>
                <text class="detail-value">{{ text(order.orderCreateTime) }}</text>
              </view>
              <view class="detail-row">
                <text class="detail-label">本订单领用数量</text>
                <text class="detail-value highlight">{{ num(order.pickedQty) }}</text>
              </view>
            </view>
            <!-- 领用明细 -->
            <view class="collapse-block">
              <view class="collapse-head"
                    @click="toggleSection(oi, 'rpicks')">
                <text class="collapse-title">领用明细</text>
                <text class="collapse-count">{{ (order.picks || []).length }}</text>
                <up-icon :name="isSectionOpen(oi, 'rpicks') ? 'arrow-up' : 'arrow-down'"
                         size="14"
                         color="#999"></up-icon>
              </view>
              <view class="collapse-body"
                    v-show="isSectionOpen(oi, 'rpicks')">
                <view class="sub-item"
                      v-for="(p, pi) in order.picks"
                      :key="pi">
                  <view class="detail-row">
                    <text class="detail-label">批号</text>
                    <text class="detail-value font-bold">{{ text(p.batchNo) }}</text>
                  </view>
                  <view class="detail-row">
                    <text class="detail-label">物料</text>
                    <text class="detail-value">{{ text(p.materialName) }}</text>
                  </view>
                  <view class="detail-row">
                    <text class="detail-label">规格型号</text>
                    <text class="detail-value">{{ text(p.materialModel) }}</text>
                  </view>
                  <view class="detail-row">
                    <text class="detail-label">领用数量</text>
                    <text class="detail-value">{{ num(p.quantity) }} {{ text(p.unit) }}</text>
                  </view>
                  <view class="detail-row">
                    <text class="detail-label">工序</text>
                    <text class="detail-value">{{ text(p.operationName) }}</text>
                  </view>
                  <view class="detail-row">
                    <text class="detail-label">领料时间</text>
                    <text class="detail-value">{{ text(p.pickTime) }}</text>
                  </view>
                  <view class="detail-row">
                    <text class="detail-label">供应商</text>
                    <text class="detail-value">{{ text(p.supplierName) }}</text>
                  </view>
                  <view class="detail-row">
                    <text class="detail-label">采购合同号</text>
                    <text class="detail-value">{{ text(p.contractNo) }}</text>
                  </view>
                  <view class="detail-row">
                    <text class="detail-label">检验结论</text>
                    <text class="detail-value">{{ text(p.checkResult) }}</text>
                  </view>
                </view>
                <view class="sub-empty"
                      v-if="!(order.picks || []).length">暂无领用记录</view>
              </view>
            </view>
            <!-- 报工工序 -->
            <view class="collapse-block">
              <view class="collapse-head"
                    @click="toggleSection(oi, 'rreports')">
                <text class="collapse-title">报工工序</text>
                <text class="collapse-count">{{ (order.reports || []).length }}</text>
                <up-icon :name="isSectionOpen(oi, 'rreports') ? 'arrow-up' : 'arrow-down'"
                         size="14"
                         color="#999"></up-icon>
              </view>
              <view class="collapse-body"
                    v-show="isSectionOpen(oi, 'rreports')">
                <view class="sub-item"
                      v-for="(r, ri) in order.reports"
                      :key="ri">
                  <view class="detail-row">
                    <text class="detail-label">工单号</text>
                    <text class="detail-value font-bold">{{ text(r.workOrderNo) }}</text>
                  </view>
                  <view class="detail-row">
                    <text class="detail-label">工序</text>
                    <text class="detail-value">{{ text(r.operationName) }}</text>
                  </view>
                  <view class="detail-row">
                    <text class="detail-label">报工人</text>
                    <text class="detail-value">{{ text(r.userName) }}</text>
                  </view>
                  <view class="detail-row">
                    <text class="detail-label">产出数量</text>
                    <text class="detail-value">{{ num(r.outputQty) }}</text>
                  </view>
                  <view class="detail-row">
                    <text class="detail-label">报废数量</text>
                    <text class="detail-value danger">{{ num(r.scrapQty) }}</text>
                  </view>
                  <view class="detail-row">
                    <text class="detail-label">报工时间</text>
                    <text class="detail-value">{{ text(r.reportTime) }}</text>
                  </view>
                </view>
                <view class="sub-empty"
                      v-if="!(order.reports || []).length">暂无报工记录</view>
              </view>
            </view>
            <!-- 产出成品批号 -->
            <view class="collapse-block">
              <view class="collapse-head"
                    @click="toggleSection(oi, 'batches')">
                <text class="collapse-title">产出成品批号</text>
                <text class="collapse-count">{{ (order.finishedBatches || []).length }}</text>
                <up-icon :name="isSectionOpen(oi, 'batches') ? 'arrow-up' : 'arrow-down'"
                         size="14"
                         color="#999"></up-icon>
              </view>
              <view class="collapse-body"
                    v-show="isSectionOpen(oi, 'batches')">
                <view class="chip-wrap">
                  <text class="chip link-chip"
                        v-for="(b, bi) in order.finishedBatches"
                        :key="bi"
                        @click="jumpToForward(b.batchNo)">{{ text(b.batchNo) }} · {{ num(b.qty) }}{{ order.unit || '' }}</text>
                </view>
                <view class="sub-empty"
                      v-if="!(order.finishedBatches || []).length">暂无成品批次</view>
              </view>
            </view>
          </view>
          <view class="sub-empty card"
                v-if="!reverseData.orders.length">该批号暂无生产订单领用记录</view>
        </template>
      </view>
    </up-popup>
    <!-- 参数详情弹窗 -->
    <up-modal :show="paramModalVisible"
              title="参数详情"
              @confirm="paramModalVisible = false">
      <view class="modal-content">
        <view v-for="(param, idx) in currentParams"
              :key="idx"
              class="param-row">
          <text class="label">{{ param.paramName }}:</text>
          <text class="value">{{ param.inputValue }} {{ param.unit && param.unit !== '/' ? param.unit : '' }}</text>
        </view>
        <view v-if="!currentParams || currentParams.length === 0"
              class="no-data-minor">暂无参数数据</view>
      </view>
    </up-modal>
    </template>
  </view>
</template>
<script setup>
  import { ref, reactive, computed } from "vue";
  import { onLoad } from "@dcloudio/uni-app";
  import {
    getOrderDetail,
    productOrderListPage,
  } from "@/api/productionManagement/productionOrder";
  import { productionProductInputListPage } from "@/api/productionManagement/productionProductMain";
  import PageHeader from "@/components/PageHeader.vue";
  import { parseTime } from "@/utils/ruoyi";
  import {
    traceForward,
    traceReverse,
    traceBatchOptions,
  } from "@/api/qualityManagement/productionTrace.js";
  // 选择器相关
  const showNpsNoSelector = ref(false);
  const npsNoQuery = ref("");
  const npsNoOptions = ref([]);
  const npsNoLoading = ref(false);
  const selectedNpsNo = ref(null);
  const selectedNpsNoLabel = ref("");
  // 当前方向:forward 正向 / reverse 反向
  const direction = ref("forward");
  const batchNo = ref("");
  const loading = ref(false);
  // 是否已执行过查询
  const queried = ref(false);
  const rowData = reactive({
    productionOrderDto: null,
    productionRecords: [],
  });
  // 批号候选下拉
  const batchOptions = ref([]);
  const showOptions = ref(false);
  const optionsLoading = ref(false);
  let searchTimer = null;
  // 报工详情
  const reportPopupVisible = ref(false);
  const detailData = ref({ workOrder: {}, reports: [] });
  const forwardData = reactive({ batchNo: "", chains: [], outbounds: [] });
  const reverseData = reactive({ batchNo: "", material: null, orders: [] });
  // 折叠面板展开状态
  const openMap = reactive({});
  // 投入详情
  const inputPopupVisible = ref(false);
  const inputListData = ref([]);
  const inputLoading = ref(false);
  const placeholder = computed(() =>
    direction.value === "forward" ? "请输入或选择成品批号" : "请输入或选择原料批号"
  );
  // 质检详情
  const qualityPopupVisible = ref(false);
  const qualityRecords = ref([]);
  const forwardEmpty = computed(
    () =>
      forwardData.chains.length === 0 && forwardData.outbounds.length === 0
  );
  const reverseEmpty = computed(
    () => !reverseData.material && reverseData.orders.length === 0
  );
  // 参数详情
  const paramModalVisible = ref(false);
  const currentParams = ref([]);
  // 生产订单状态字典(与生产订单列表页保持一致)
  const ORDER_STATUS_TEXT = {
    1: "待开始",
    2: "进行中",
    3: "已完成",
    4: "已取消",
    5: "已结束",
  };
  const ORDER_STATUS_TYPE = {
    1: "primary",
    2: "warning",
    3: "success",
    4: "info",
    5: "error",
  };
  // 入库/出库类型字典
  const RECORD_TYPE_TEXT = {
    0: "自定义入库",
    1: "自定义出库",
    2: "生产报工入库",
    3: "生产报工出库",
    6: "质检合格入库",
    7: "采购入库",
    8: "销售出库",
    10: "采购入库",
    13: "销售发货出库",
  };
  const num = val => {
    const n = Number(val);
    return Number.isFinite(n) ? n : 0;
  };
  const text = val =>
    val === null || val === undefined || val === "" ? "-" : val;
  const orderStatusText = status =>
    ORDER_STATUS_TEXT[status] ?? text(status);
  const orderStatusType = status => ORDER_STATUS_TYPE[status] || "info";
  const recordTypeText = type => {
    if (type === null || type === undefined || type === "") return "-";
    return RECORD_TYPE_TEXT[String(type)] || `类型${type}`;
  };
  const goBack = () => {
    uni.navigateBack();
  };
  const openNpsNoSelector = () => {
    showNpsNoSelector.value = true;
    if (npsNoOptions.value.length === 0) {
      handleNpsNoSearch();
    }
  // 折叠面板
  const isSectionOpen = (key, name) => !!openMap[key + "-" + name];
  const toggleSection = (key, name) => {
    const k = key + "-" + name;
    openMap[k] = !openMap[k];
  };
  const handleNpsNoSearch = async () => {
    npsNoLoading.value = true;
    try {
      const res = await productOrderListPage({
        npsNo: npsNoQuery.value || "",
        pageNum: 1,
        pageSize: 50,
  // 加载批号候选
  const loadBatchOptions = () => {
    optionsLoading.value = true;
    traceBatchOptions({
      type: direction.value === "forward" ? "finished" : "material",
      keyword: batchNo.value || "",
      limit: 50,
    })
      .then(res => {
        batchOptions.value = res.data || [];
      })
      .catch(() => {
        batchOptions.value = [];
      })
      .finally(() => {
        optionsLoading.value = false;
      });
      npsNoOptions.value = res.data?.records || res.rows || [];
    } catch (error) {
      console.error(error);
    } finally {
      npsNoLoading.value = false;
    }
  };
  const onSelectNpsNo = async item => {
    selectedNpsNo.value = item.id;
    selectedNpsNoLabel.value = item.npsNo;
    showNpsNoSelector.value = false;
    uni.showLoading({ title: "加载中..." });
    try {
      const res = await getOrderDetail(item.npsNo);
      if (res.code === 200 && res.data) {
        const { productionOrder, workOrderList } = res.data;
        rowData.productionOrderDto = productionOrder || item;
        rowData.productionRecords = workOrderList || [];
      } else {
        rowData.productionOrderDto = item;
        rowData.productionRecords = [];
      }
    } catch (error) {
      console.error(error);
      rowData.productionOrderDto = item;
      rowData.productionRecords = [];
      uni.showToast({ title: "获取详情失败", icon: "none" });
    } finally {
      uni.hideLoading();
    }
  const onBatchInput = () => {
    showOptions.value = true;
    if (searchTimer) clearTimeout(searchTimer);
    searchTimer = setTimeout(() => {
      loadBatchOptions();
    }, 300);
  };
  onLoad(async options => {
    if (options.npsNo) {
      uni.showLoading({ title: "加载中..." });
      try {
        const res = await productOrderListPage({
          npsNo: options.npsNo,
          pageNum: 1,
          pageSize: 10,
  const onBatchFocus = () => {
    showOptions.value = true;
    loadBatchOptions();
  };
  const onBatchClear = () => {
    batchNo.value = "";
    batchOptions.value = [];
    showOptions.value = false;
  };
  const onSelectOption = opt => {
    batchNo.value = opt.batchNo || "";
    showOptions.value = false;
    handleQuery();
  };
  // 切换方向
  const switchDirection = dir => {
    if (direction.value === dir) return;
    direction.value = dir;
    batchNo.value = "";
    batchOptions.value = [];
    showOptions.value = false;
    queried.value = false;
    forwardData.chains = [];
    forwardData.outbounds = [];
    reverseData.material = null;
    reverseData.orders = [];
  };
  // 查询追溯数据
  const handleQuery = () => {
    const no = (batchNo.value || "").trim();
    if (!no) {
      uni.showToast({ title: "请输入批号", icon: "none" });
      return;
    }
    showOptions.value = false;
    loading.value = true;
    if (direction.value === "forward") {
      traceForward({ batchNo: no })
        .then(res => {
          const data = res.data || {};
          forwardData.batchNo = data.batchNo || no;
          forwardData.chains = data.chains || [];
          forwardData.outbounds = data.outbounds || [];
          queried.value = true;
        })
        .catch(() => {
          // 错误文案由 request 统一提示
        })
        .finally(() => {
          loading.value = false;
        });
        const records = res.data?.records || res.rows || [];
        if (records.length > 0) {
          onSelectNpsNo(records[0]);
        } else {
          uni.showToast({ title: "未找到相关订单", icon: "none" });
        }
      } catch (error) {
        console.error(error);
      } finally {
        uni.hideLoading();
    } else {
      traceReverse({ batchNo: no })
        .then(res => {
          const data = res.data || {};
          reverseData.batchNo = data.batchNo || no;
          reverseData.material = data.material || null;
          reverseData.orders = data.orders || [];
          queried.value = true;
        })
        .catch(() => {
          // 错误文案由 request 统一提示
        })
        .finally(() => {
          loading.value = false;
        });
    }
  };
  // 跳转反向追溯并回填原料批号
  const jumpToReverse = no => {
    if (!no) return;
    direction.value = "reverse";
    batchNo.value = no;
    batchOptions.value = [];
    showOptions.value = false;
    queried.value = false;
    forwardData.chains = [];
    forwardData.outbounds = [];
    reverseData.material = null;
    reverseData.orders = [];
    uni.pageScrollTo({ scrollTop: 0, duration: 0 });
    handleQuery();
  };
  // 跳转正向追溯并回填成品批号
  const jumpToForward = no => {
    if (!no) return;
    direction.value = "forward";
    batchNo.value = no;
    batchOptions.value = [];
    showOptions.value = false;
    queried.value = false;
    forwardData.chains = [];
    forwardData.outbounds = [];
    reverseData.material = null;
    reverseData.orders = [];
    uni.pageScrollTo({ scrollTop: 0, duration: 0 });
    handleQuery();
  };
  onLoad(options => {
    // 支持从其它页面带批号进入
    if (options && options.batchNo) {
      try {
        batchNo.value = decodeURIComponent(options.batchNo);
      } catch (e) {
        batchNo.value = options.batchNo;
      }
      direction.value = options.type === "material" ? "reverse" : "forward";
      handleQuery();
    }
  });
  const getStatusText = status => {
    const statusMap = { 1: "待开始", 2: "进行中", 3: "已完成", 5: "已结束" };
    return statusMap[status] || "已取消";
  };
  const getStatusType = status => {
    const typeMap = { 1: "primary", 2: "warning", 3: "success", 5: "error" };
    return typeMap[status] || "info";
  };
  const formatDate = (date, pattern = "{y}-{m}-{d}") => {
    return parseTime(date, pattern) || "-";
  };
  const formatProgress = val => {
    const p = parseFloat(val || 0);
    return p >= 100 ? 100 : p;
  };
  const progressColor = percentage => {
    if (percentage < 30) return "#f56c6c";
    if (percentage < 70) return "#e6a23c";
    return "#67c23a";
  };
  const handleShowReports = row => {
    detailData.value = {
      workOrder: row.workOrder || {},
      reports: (row.reportList || []).map(r => ({
        ...r.reportMain,
        ...(r.reportOutputList ? r.reportOutputList[0] : {}),
        id: r.reportMain.id,
        productionOperationParamList: r.reportParamList || [],
      })),
    };
    reportPopupVisible.value = true;
  };
  const handleShowInput = async reportId => {
    inputPopupVisible.value = true;
    inputLoading.value = true;
    inputListData.value = [];
    try {
      const res = await productionProductInputListPage({
        productMainId: reportId,
        pageNum: 1,
        pageSize: 100,
      });
      inputListData.value = res.data?.records || res.rows || [];
    } catch (error) {
      console.error(error);
      uni.showToast({ title: "获取投入信息失败", icon: "none" });
    } finally {
      inputLoading.value = false;
    }
  };
  const handleShowQuality = row => {
    const inspects = row.inspectList || [];
    qualityRecords.value = inspects.map(i => ({
      ...i.inspect,
      reportNo: i.reportNo,
      productName: row.workOrder?.productName || "-",
      model: row.workOrder?.model || "-",
      userName: i.reportMain?.userName || "-",
      inspectParamList: i.inspectParamList || [],
    }));
    qualityPopupVisible.value = true;
  };
  const showParams = params => {
    currentParams.value = params || [];
    paramModalVisible.value = true;
  };
</script>
<style scoped lang="scss">
@@ -513,520 +890,285 @@
  .production-traceability {
    min-height: 100vh;
    background-color: #f5f7fa;
    background: #f8f9fa;
    padding-bottom: 40rpx;
  }
  // 方向页签
  .trace-tabs {
    display: flex;
    background: #ffffff;
    .tab-item {
      flex: 1;
      text-align: center;
      padding: 24rpx 0;
      font-size: 28rpx;
      color: #666;
      position: relative;
      &.active {
        color: #2979ff;
        font-weight: 600;
        &::after {
          content: "";
          position: absolute;
          left: 50%;
          bottom: 0;
          transform: translateX(-50%);
          width: 80rpx;
          height: 6rpx;
          background: #2979ff;
          border-radius: 4rpx;
        }
      }
    }
  }
  .search-section {
    background-color: #fff;
    padding: 20rpx 24rpx;
    margin-bottom: 20rpx;
  }
  .search-bar {
    display: flex;
    align-items: center;
    background-color: #f2f2f2;
    border-radius: 8rpx;
    padding: 0 20rpx;
    height: 80rpx;
    position: relative;
    border-bottom: 1rpx solid #f0f0f0;
    .search-input {
      flex: 1;
      display: flex;
      align-items: center;
      .placeholder {
        font-size: 28rpx;
        color: #999;
      }
      .value {
        font-size: 28rpx;
        color: #333;
        font-weight: 500;
      }
      height: 76rpx;
    }
    .search-button {
      padding: 0 10rpx;
    }
  }
  .selector-popup {
    background: #fff;
    padding: 30rpx;
    max-height: 70vh;
    display: flex;
    flex-direction: column;
    .popup-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 24rpx;
      .popup-title {
        font-size: 32rpx;
        font-weight: bold;
      }
    .search-text {
      font-size: 28rpx;
    }
    .search-box {
      margin-bottom: 20rpx;
    }
    .options-list {
      flex: 1;
      overflow: hidden;
    }
    .option-item {
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding: 24rpx 0;
      border-bottom: 1rpx solid #f0f0f0;
      .option-main {
        flex: 1;
        .nps-no {
          font-size: 28rpx;
          font-weight: bold;
          color: #333;
          display: block;
          margin-bottom: 4rpx;
        }
        .product-info {
          font-size: 24rpx;
          color: #999;
        }
      }
    }
    .no-options {
      text-align: center;
      padding: 40rpx;
    .search-tip {
      font-size: 22rpx;
      color: #999;
      font-size: 26rpx;
      margin-top: 12rpx;
    }
  }
  .content-container {
    padding: 0 24rpx 40rpx;
  }
  .info-card {
    background: #fff;
    border-radius: 16rpx;
    padding: 24rpx;
    margin-bottom: 24rpx;
    box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
    .card-title {
      font-size: 32rpx;
      font-weight: bold;
      color: #333;
      margin-bottom: 24rpx;
      padding-left: 16rpx;
      border-left: 8rpx solid #3c9cff;
    }
  }
  .info-grid {
    display: flex;
    flex-wrap: wrap;
    .info-item {
      width: 50%;
      margin-bottom: 20rpx;
    .btn-row {
      display: flex;
      flex-direction: column;
      &.full-width {
        width: 100%;
      }
      .label {
        font-size: 24rpx;
        color: #999;
        margin-bottom: 8rpx;
      }
      .value {
        font-size: 28rpx;
        color: #333;
        word-break: break-all;
      }
    }
  }
  .progress-container {
    display: flex;
    align-items: center;
    gap: 20rpx;
    up-line-progress {
      flex: 1;
    }
    .progress-text {
      font-size: 24rpx;
      color: #666;
      min-width: 60rpx;
    }
  }
  .section-title {
    font-size: 32rpx;
    font-weight: bold;
    color: #333;
    margin: 32rpx 0 20rpx;
  }
  .work-order-card {
    background: #fff;
    border-radius: 16rpx;
    padding: 24rpx;
    margin-bottom: 20rpx;
    box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
    .card-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 20rpx;
      padding-bottom: 16rpx;
      border-bottom: 1rpx solid #f0f0f0;
      .work-order-no {
        font-size: 28rpx;
        font-weight: bold;
        color: #3c9cff;
      }
      .progress-tag {
        font-size: 28rpx;
        font-weight: bold;
      }
    }
    .card-content {
      .content-row {
        margin-bottom: 12rpx;
        font-size: 26rpx;
        .label {
          color: #999;
        }
        .value {
          color: #333;
        }
      }
    }
    .card-footer {
      display: flex;
      justify-content: flex-end;
      gap: 20rpx;
      margin-top: 20rpx;
    }
  }
  .base-info {
  // 批号候选下拉
  .option-panel {
    margin-top: 12rpx;
    background: #fff;
    padding: 24rpx;
    border-radius: 16rpx;
    margin-bottom: 30rpx;
    border: 1rpx solid #ebeef5;
    border-radius: 12rpx;
    max-height: 520rpx;
    overflow-y: auto;
    .info-row {
      margin-bottom: 16rpx;
      font-size: 28rpx;
      display: flex;
      align-items: center;
    .option-item {
      padding: 18rpx 20rpx;
      border-bottom: 1rpx solid #f5f5f5;
      &:last-child {
        margin-bottom: 0;
        border-bottom: none;
      }
      .label {
        color: #999;
        min-width: 180rpx;
      &:active {
        background: #f5f7fa;
      }
      .value {
        color: #333;
        flex: 1;
        font-weight: 500;
        &.progress-box {
      .option-top {
        display: flex;
        justify-content: space-between;
        align-items: center;
        .option-no {
          font-size: 28rpx;
          color: #333;
          font-weight: 600;
          flex: 1;
          margin-right: 16rpx;
        }
        .option-qty {
          font-size: 24rpx;
          color: #2979ff;
        }
      }
      .option-bottom {
        display: flex;
        justify-content: space-between;
        align-items: center;
        margin-top: 8rpx;
        .option-name {
          font-size: 24rpx;
          color: #666;
          flex: 1;
          margin-right: 16rpx;
        }
        .option-time {
          font-size: 22rpx;
          color: #999;
        }
      }
    }
    .option-empty {
      padding: 30rpx 20rpx;
      text-align: center;
      font-size: 24rpx;
      color: #999;
    }
  }
  .info-grid {
  .loading-box {
    display: flex;
    justify-content: center;
    padding-top: 120rpx;
  }
  .result-container {
    padding: 20rpx;
  }
  .result-title {
    font-size: 28rpx;
    font-weight: 600;
    color: #333;
    margin: 10rpx 0 16rpx;
    &::before {
      content: "";
      display: inline-block;
      width: 6rpx;
      height: 26rpx;
      background: #2979ff;
      border-radius: 4rpx;
      margin-right: 12rpx;
      vertical-align: middle;
    }
  }
  .type-tag {
    font-size: 22rpx;
    color: #2979ff;
    background: #ecf5ff;
    border-radius: 6rpx;
    padding: 4rpx 12rpx;
  }
  .document-icon {
    &.out {
      background: #ff9900;
    }
    &.material {
      background: #52c41a;
    }
  }
  .font-bold {
    font-weight: 500;
  }
  .detail-value.link,
  .chip.link-chip {
    color: #2979ff;
    text-decoration: underline;
  }
  // 批次标签
  .chip-wrap {
    display: flex;
    flex-wrap: wrap;
    padding: 10rpx 0;
    gap: 12rpx;
    padding-bottom: 12rpx;
    .info-item {
      width: 50%;
      margin-bottom: 20rpx;
    .chip {
      font-size: 22rpx;
      color: #333;
      background: #f5f7fa;
      border-radius: 8rpx;
      padding: 8rpx 16rpx;
    }
  }
  // 折叠面板
  .collapse-block {
    border-top: 1rpx solid #f0f0f0;
    .collapse-head {
      display: flex;
      flex-direction: column;
      align-items: center;
      padding: 24rpx 0;
      &.full-width {
        width: 100%;
      }
      .label {
        font-size: 24rpx;
        color: #999;
        margin-bottom: 4rpx;
      }
      .value {
      .collapse-title {
        font-size: 28rpx;
        color: #333;
        font-weight: 500;
      }
    }
  }
  .popup-content {
    background: #fff;
    padding: 30rpx;
    max-height: 80vh;
    display: flex;
    flex-direction: column;
    border-radius: 20rpx 20rpx 0 0;
    .popup-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 30rpx;
      .popup-title {
        font-size: 34rpx;
        font-weight: bold;
        color: #333;
      .collapse-count {
        font-size: 22rpx;
        color: #2979ff;
        background: #ecf5ff;
        border-radius: 20rpx;
        padding: 2rpx 14rpx;
        margin: 0 12rpx 0 auto;
      }
    }
    .popup-scroll {
      flex: 1;
      overflow: hidden;
    .collapse-body {
      padding-bottom: 16rpx;
    }
  }
  .detail-info {
  .sub-item {
    background: #f8f9fa;
    padding: 24rpx;
    border-radius: 16rpx;
    margin-bottom: 30rpx;
    flex-direction: column;
    border-radius: 10rpx;
    padding: 16rpx;
    margin-bottom: 12rpx;
    .info-row {
    &:last-child {
      margin-bottom: 0;
    }
  }
  .inner-item {
    padding: 12rpx;
    margin-top: 10rpx;
    background: #fff5f5;
    border-radius: 8rpx;
    &:first-child {
      margin-top: 0;
    }
  }
  .unqualified-box {
    margin-top: 16rpx;
    padding-top: 16rpx;
    border-top: 1rpx dashed #e4e7ed;
    .unqualified-title {
      font-size: 24rpx;
      color: #ee0a24;
      font-weight: 500;
      margin-bottom: 12rpx;
      font-size: 28rpx;
      display: flex;
      &:last-child {
        margin-bottom: 0;
      }
      .label {
        color: #999;
        min-width: 140rpx;
      }
      .value {
        color: #333;
        flex: 1;
        font-weight: 500;
      }
    }
  }
  .list-title {
    font-size: 30rpx;
    font-weight: bold;
    margin-bottom: 20rpx;
    color: #333;
    display: flex;
    align-items: center;
    &::before {
      content: "";
      width: 6rpx;
      height: 28rpx;
      background: #3c9cff;
      margin-right: 12rpx;
      border-radius: 4rpx;
    }
  }
  .detail-item {
    background: #fff;
    border: 1rpx solid #f0f0f0;
    border-radius: 12rpx;
    padding: 24rpx;
    margin-bottom: 20rpx;
    display: flex;
    justify-content: space-between;
    align-items: center;
    .item-main {
      flex: 1;
      .item-row {
        font-size: 26rpx;
        margin-bottom: 8rpx;
        display: flex;
        &:last-child {
          margin-bottom: 0;
        }
        .label {
          color: #999;
          min-width: 130rpx;
        }
        .value {
          color: #333;
          flex: 1;
        }
      }
    }
    .item-actions {
      display: flex;
      flex-direction: column;
      gap: 16rpx;
      padding-left: 20rpx;
      border-left: 1rpx solid #f0f0f0;
      .action-link {
        font-size: 26rpx;
        color: #3c9cff;
        white-space: nowrap;
        &.green {
          color: #52c41a;
        }
      }
    }
  }
  .quality-record {
    background: #fff;
    border: 1rpx solid #f0f0f0;
    border-radius: 16rpx;
    padding: 24rpx;
    margin-bottom: 30rpx;
    .record-title {
      font-size: 30rpx;
      font-weight: bold;
      color: #3c9cff;
      margin-bottom: 24rpx;
      display: flex;
      justify-content: space-between;
    }
  }
  .params-table {
    margin-top: 24rpx;
    border: 1rpx solid #f0f0f0;
    border-radius: 12rpx;
    overflow: hidden;
    .table-header {
      display: flex;
      background: #f8f9fa;
      padding: 20rpx 16rpx;
      font-size: 26rpx;
      font-weight: bold;
      color: #666;
    }
    .table-row {
      display: flex;
      padding: 20rpx 16rpx;
      font-size: 26rpx;
      border-top: 1rpx solid #f0f0f0;
      color: #333;
      &:nth-child(even) {
        background: #fafafa;
      }
    }
    .col {
      flex: 1;
      text-align: center;
      word-break: break-all;
    }
  }
  .modal-content {
    padding: 30rpx;
    .param-row {
      margin-bottom: 20rpx;
      font-size: 28rpx;
      display: flex;
      &:last-child {
        margin-bottom: 0;
      }
      .label {
        color: #666;
        min-width: 160rpx;
      }
      .value {
        color: #333;
        font-weight: 500;
        flex: 1;
      }
    }
  }
  .input-list-popup {
    .input-item {
      background: #fff;
      border: 1rpx solid #f0f0f0;
      border-radius: 12rpx;
      padding: 20rpx;
      margin-bottom: 20rpx;
      .input-row {
        display: flex;
        font-size: 26rpx;
        margin-bottom: 8rpx;
        &:last-child {
          margin-bottom: 0;
        }
        .label {
          color: #999;
          min-width: 160rpx;
        }
        .value {
          color: #333;
          flex: 1;
        }
      }
    }
  }
  .error-text {
    color: #f56c6c;
    font-weight: bold;
  }
  .no-data-minor {
    text-align: center;
    padding: 60rpx 40rpx;
  .sub-empty {
    font-size: 24rpx;
    color: #999;
    font-size: 28rpx;
    text-align: center;
    padding: 24rpx 0;
    &.card {
      background: #fff;
      border-radius: 12rpx;
      margin-bottom: 16rpx;
    }
  }
  .no-data {
    padding-top: 100rpx;
  }
</style>