From 03a05815d7ea5612206b6cfe5d6537b4d4cedc0a Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期二, 15 九月 2026 11:41:32 +0800
Subject: [PATCH] 天津潜宇塑胶app 1.添加采购需求、库存盘点、计件单价配置、产量统计、材料消耗核算、成本核算分析页面
---
src/pages/productionManagement/productionTraceability/index.vue | 1970 ++++++++++++++++++++++++++++++++---------------------------
1 files changed, 1,056 insertions(+), 914 deletions(-)
diff --git a/src/pages/productionManagement/productionTraceability/index.vue b/src/pages/productionManagement/productionTraceability/index.vue
index d187543..7a55b5f 100644
--- a/src/pages/productionManagement/productionTraceability/index.vue
+++ b/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')">姝e悜杩芥函</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>
+ <!-- ================= 姝e悜杩芥函 ================= -->
+ <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("");
+ // 褰撳墠鏂瑰悜锛歠orward 姝e悜 / 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();
+ };
+
+ // 璺宠浆姝e悜杩芥函骞跺洖濉垚鍝佹壒鍙�
+ 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>
--
Gitblit v1.9.3