| | |
| | | <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"> |
| | |
| | | |
| | | .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> |