<template>
|
<view class="process-route-items">
|
<PageHeader title="路线项目"
|
@back="goBack" />
|
<!-- 路线基础信息卡片 -->
|
<view class="route-info-card">
|
<view class="info-row">
|
<text class="label">工艺路线编号</text>
|
<text class="value">{{ routeInfo.processRouteCode || '-' }}</text>
|
</view>
|
<view class="info-row">
|
<text class="label">产品名称</text>
|
<text class="value">{{ routeInfo.productName || '-' }}</text>
|
</view>
|
<view class="info-row">
|
<text class="label">规格名称</text>
|
<text class="value">{{ routeInfo.model || '-' }}</text>
|
</view>
|
<view class="info-row">
|
<text class="label">BOM编号</text>
|
<text class="value">{{ routeInfo.bomNo || '-' }}</text>
|
</view>
|
</view>
|
<!-- 选项卡切换 -->
|
<view class="tabs-box">
|
<up-tabs :list="tabsList"
|
@click="handleTabClick"
|
:current="currentTab"></up-tabs>
|
</view>
|
<!-- 工序项目列表 -->
|
<scroll-view scroll-y
|
class="content-scroll"
|
v-if="currentTab === 0">
|
<view v-if="itemsList.length > 0">
|
<view v-for="(item, index) in itemsList"
|
:key="index"
|
class="process-card">
|
<view class="card-header">
|
<view class="index-badge">{{ index + 1 }}</view>
|
<text class="process-name">{{ item.technologyOperationName || item.operationName || '-' }}</text>
|
</view>
|
<view class="card-content">
|
<view class="detail-row">
|
<text class="detail-label">关联产品</text>
|
<text class="detail-value">{{ item.productName || '-' }}</text>
|
</view>
|
<view class="detail-row">
|
<text class="detail-label">规格型号</text>
|
<text class="detail-value">{{ item.model || '-' }}</text>
|
</view>
|
<view class="detail-row">
|
<text class="detail-label">单位</text>
|
<text class="detail-value">{{ item.unit || '-' }}</text>
|
</view>
|
<view class="tag-row">
|
<up-tag v-if="item.isQuality"
|
text="质检"
|
type="primary"
|
size="mini"
|
plain />
|
<up-tag v-if="item.isProduction"
|
text="生产"
|
type="success"
|
size="mini"
|
plain />
|
<up-tag v-if="item.type==0"
|
text="计时"
|
type="info"
|
size="mini"
|
plain />
|
<up-tag v-else
|
text="计件"
|
type="warning"
|
size="mini"
|
plain />
|
</view>
|
</view>
|
<view class="card-footer"
|
@click="showParams(item)">
|
<text class="action-text">查看参数列表</text>
|
<up-icon name="arrow-right"
|
size="14"
|
color="#3c9cff"></up-icon>
|
</view>
|
</view>
|
</view>
|
<view v-else
|
class="no-data">
|
<up-empty mode="data"
|
text="暂无路线项目"></up-empty>
|
</view>
|
</scroll-view>
|
<!-- BOM 结构展示 -->
|
<scroll-view scroll-y
|
class="content-scroll"
|
v-if="currentTab === 1">
|
<view v-if="bomList.length > 0"
|
class="bom-tree">
|
<view v-for="(node, nIndex) in flatBomList"
|
:key="nIndex"
|
class="bom-node"
|
:style="{ paddingLeft: (node.level * 40) + 'rpx' }">
|
<view class="bom-node-inner">
|
<view class="bom-line"
|
v-if="node.level > 0"></view>
|
<view class="bom-content">
|
<view class="bom-header">
|
<text class="bom-product">{{ node.productName }}</text>
|
<text class="bom-model"
|
v-if="node.model">({{ node.model }})</text>
|
</view>
|
<view class="bom-details">
|
<text class="bom-info">工序: {{ node.operationName || '-' }}</text>
|
<text class="bom-info">所需: {{ node.unitQuantity || 0 }} {{ node.unit || '' }}</text>
|
</view>
|
</view>
|
</view>
|
</view>
|
</view>
|
<view v-else
|
class="no-data">
|
<up-empty mode="data"
|
text="暂无 BOM 结构"></up-empty>
|
</view>
|
</scroll-view>
|
<!-- 参数列表弹窗 -->
|
<up-popup :show="showPopup"
|
mode="bottom"
|
@close="showPopup = false"
|
round="10">
|
<view class="popup-content">
|
<view class="popup-header">
|
<text class="title">参数列表 - {{ currentItem.technologyOperationName || currentItem.operationName }}</text>
|
<up-icon name="close"
|
size="20"
|
@click="showPopup = false"></up-icon>
|
</view>
|
<scroll-view scroll-y
|
class="param-list">
|
<view v-if="paramList.length > 0">
|
<view v-for="(param, pIndex) in paramList"
|
:key="pIndex"
|
class="param-item">
|
<view class="param-row">
|
<text class="param-label">参数名称:</text>
|
<text class="param-value">{{ param.paramName || '-' }}</text>
|
</view>
|
<view class="param-row">
|
<text class="param-label">标准值:</text>
|
<text class="param-value">{{ param.standardValue || '-' }}</text>
|
</view>
|
<view class="param-row">
|
<text class="param-label">单位:</text>
|
<text class="param-value">{{ param.unit || '-' }}</text>
|
</view>
|
</view>
|
</view>
|
<view v-else
|
class="no-record">
|
<text>暂无参数记录</text>
|
</view>
|
</scroll-view>
|
</view>
|
</up-popup>
|
</view>
|
</template>
|
|
<script setup>
|
import { ref, reactive, computed } from "vue";
|
import { onLoad } from "@dcloudio/uni-app";
|
import {
|
findProcessRouteItemList,
|
getProcessParamList,
|
queryBomList,
|
} from "@/api/productionManagement/processRoute.js";
|
import {
|
queryOrderBomList,
|
findProcessParamListOrder,
|
} from "@/api/productionManagement/productionOrder.js";
|
import PageHeader from "@/components/PageHeader.vue";
|
|
const routeInfo = ref({});
|
const itemsList = ref([]);
|
const bomList = ref([]);
|
const loading = ref(false);
|
const pageType = ref("route"); // route | order
|
|
// 选项卡
|
const tabsList = reactive([{ name: "路线项目" }, { name: "BOM结构" }]);
|
const currentTab = ref(0);
|
|
// 弹窗相关
|
const showPopup = ref(false);
|
const currentItem = ref({});
|
const paramList = ref([]);
|
const paramLoading = ref(false);
|
|
const goBack = () => {
|
uni.navigateBack();
|
};
|
|
const handleTabClick = item => {
|
currentTab.value = item.index;
|
if (item.index === 1 && bomList.value.length === 0) {
|
fetchBom();
|
}
|
};
|
|
// 扁平化 BOM 树用于展示
|
const flatBomList = computed(() => {
|
const result = [];
|
const flatten = (nodes, level = 0) => {
|
nodes.forEach(node => {
|
result.push({ ...node, level });
|
if (node.children && node.children.length > 0) {
|
flatten(node.children, level + 1);
|
}
|
});
|
};
|
flatten(bomList.value);
|
return result;
|
});
|
|
onLoad(options => {
|
if (options.id) {
|
pageType.value = options.type || "route";
|
routeInfo.value = {
|
id: options.id,
|
processRouteCode: options.processRouteCode || "",
|
productName: decodeURIComponent(options.productName || ""),
|
model: decodeURIComponent(options.model || ""),
|
bomNo: options.bomNo || "",
|
bomId: options.bomId || "",
|
description: decodeURIComponent(options.description || ""),
|
orderId: options.orderId || "",
|
};
|
fetchItems(options.id);
|
}
|
});
|
|
const fetchItems = id => {
|
loading.value = true;
|
findProcessRouteItemList({ routeId: id, orderId: routeInfo.value.orderId })
|
.then(res => {
|
itemsList.value = res.data || [];
|
loading.value = false;
|
})
|
.catch(() => {
|
loading.value = false;
|
uni.showToast({
|
title: "获取项目失败",
|
icon: "error",
|
});
|
});
|
};
|
|
const fetchBom = () => {
|
console.log(routeInfo.value.bomId, "routeInfo.value.bomId");
|
|
if (!routeInfo.value.bomId) return;
|
loading.value = true;
|
const api = pageType.value === "order" ? queryOrderBomList : queryBomList;
|
api(routeInfo.value.bomId)
|
.then(res => {
|
bomList.value = res.data || [];
|
loading.value = false;
|
})
|
.catch(() => {
|
loading.value = false;
|
uni.showToast({
|
title: "获取 BOM 失败",
|
icon: "error",
|
});
|
});
|
};
|
|
const showParams = item => {
|
currentItem.value = item;
|
showPopup.value = true;
|
paramLoading.value = true;
|
paramList.value = [];
|
|
const api =
|
pageType.value === "order"
|
? findProcessParamListOrder
|
: getProcessParamList;
|
const params =
|
pageType.value === "order"
|
? {
|
productionOrderRoutingOperationId: item.id,
|
productionOrderId: routeInfo.value.orderId,
|
}
|
: { technologyRoutingOperationId: item.id };
|
|
api(params)
|
.then(res => {
|
paramList.value = res.data || [];
|
paramLoading.value = false;
|
})
|
.catch(() => {
|
paramLoading.value = false;
|
uni.showToast({
|
title: "获取参数失败",
|
icon: "error",
|
});
|
});
|
};
|
</script>
|
|
<style scoped lang="scss">
|
.process-route-items {
|
min-height: 100vh;
|
background: #f8f9fa;
|
display: flex;
|
flex-direction: column;
|
}
|
|
.route-info-card {
|
background: #fff;
|
margin: 20rpx;
|
padding: 24rpx;
|
border-radius: 16rpx;
|
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
|
|
.info-row {
|
display: flex;
|
justify-content: space-between;
|
margin-bottom: 12rpx;
|
&:last-child {
|
margin-bottom: 0;
|
}
|
|
.label {
|
font-size: 26rpx;
|
color: #999;
|
}
|
.value {
|
font-size: 26rpx;
|
color: #333;
|
font-weight: bold;
|
}
|
}
|
}
|
|
.tabs-box {
|
background: #fff;
|
margin-bottom: 10rpx;
|
}
|
|
.content-scroll {
|
flex: 1;
|
height: 0;
|
padding: 0 20rpx;
|
}
|
|
.process-card {
|
background: #fff;
|
margin-bottom: 24rpx;
|
border-radius: 16rpx;
|
overflow: hidden;
|
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.05);
|
|
.card-header {
|
display: flex;
|
align-items: center;
|
padding: 20rpx 24rpx;
|
background: #fcfcfc;
|
border-bottom: 1rpx solid #f5f5f5;
|
|
.index-badge {
|
width: 40rpx;
|
height: 40rpx;
|
background: #3c9cff;
|
color: #fff;
|
border-radius: 20rpx;
|
display: flex;
|
justify-content: center;
|
align-items: center;
|
font-size: 24rpx;
|
margin-right: 20rpx;
|
}
|
|
.process-name {
|
font-size: 28rpx;
|
font-weight: bold;
|
color: #333;
|
}
|
}
|
|
.card-content {
|
padding: 24rpx;
|
|
.detail-row {
|
display: flex;
|
justify-content: space-between;
|
margin-bottom: 12rpx;
|
|
.detail-label {
|
font-size: 24rpx;
|
color: #999;
|
}
|
.detail-value {
|
font-size: 24rpx;
|
color: #666;
|
}
|
}
|
|
.tag-row {
|
display: flex;
|
gap: 16rpx;
|
margin-top: 10rpx;
|
}
|
}
|
|
.card-footer {
|
padding: 16rpx 24rpx;
|
border-top: 1rpx dashed #eee;
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
|
.action-text {
|
font-size: 24rpx;
|
color: #3c9cff;
|
}
|
}
|
}
|
|
/* BOM 树样式 */
|
.bom-tree {
|
padding: 20rpx 0;
|
}
|
|
.bom-node {
|
position: relative;
|
margin-bottom: 20rpx;
|
}
|
|
.bom-node-inner {
|
background: #fff;
|
padding: 20rpx;
|
border-radius: 12rpx;
|
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.03);
|
display: flex;
|
align-items: center;
|
}
|
|
.bom-line {
|
position: absolute;
|
left: -20rpx;
|
top: 50%;
|
width: 20rpx;
|
height: 2rpx;
|
background: #ddd;
|
}
|
|
.bom-content {
|
flex: 1;
|
}
|
|
.bom-header {
|
display: flex;
|
align-items: center;
|
margin-bottom: 8rpx;
|
|
.bom-product {
|
font-size: 28rpx;
|
font-weight: bold;
|
color: #333;
|
}
|
|
.bom-model {
|
font-size: 24rpx;
|
color: #999;
|
margin-left: 10rpx;
|
}
|
}
|
|
.bom-details {
|
display: flex;
|
justify-content: space-between;
|
|
.bom-info {
|
font-size: 24rpx;
|
color: #666;
|
}
|
}
|
|
.no-data {
|
padding-top: 100rpx;
|
}
|
|
/* 弹窗样式 */
|
.popup-content {
|
background: #fff;
|
padding: 30rpx;
|
max-height: 70vh;
|
display: flex;
|
flex-direction: column;
|
}
|
|
.popup-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
padding-bottom: 30rpx;
|
border-bottom: 1rpx solid #eee;
|
|
.title {
|
font-size: 30rpx;
|
font-weight: bold;
|
color: #333;
|
}
|
}
|
|
.param-list {
|
flex: 1;
|
height: 0;
|
padding-top: 20rpx;
|
}
|
|
.param-item {
|
padding: 20rpx;
|
background: #f9f9f9;
|
border-radius: 12rpx;
|
margin-bottom: 16rpx;
|
|
.param-row {
|
display: flex;
|
margin-bottom: 8rpx;
|
&:last-child {
|
margin-bottom: 0;
|
}
|
|
.param-label {
|
font-size: 24rpx;
|
color: #999;
|
width: 140rpx;
|
}
|
.param-value {
|
font-size: 24rpx;
|
color: #333;
|
flex: 1;
|
}
|
}
|
}
|
|
.no-record {
|
padding: 100rpx 0;
|
text-align: center;
|
color: #999;
|
font-size: 26rpx;
|
}
|
</style>
|