<script lang="ts" setup>
|
import type { FormType } from '../data';
|
import type { MesProRouteProcessApi } from '#/api/mes/pro/route/process';
|
import type { MesProRouteProductApi } from '#/api/mes/pro/route/product';
|
import type { MesProRouteProductBomApi } from '#/api/mes/pro/route/productbom';
|
|
import { computed, ref, watch } from 'vue';
|
|
import { Alert, Button, Empty, Tag } from 'ant-design-vue';
|
|
import { getRouteProductBomList } from '#/api/mes/pro/route/productbom';
|
|
import ProductBomList from './product-bom-list.vue';
|
|
const props = defineProps<{
|
formType: FormType;
|
processes: MesProRouteProcessApi.RouteProcess[];
|
products: MesProRouteProductApi.RouteProduct[];
|
routeId: number;
|
}>();
|
|
const emit = defineEmits<{
|
change: [list: MesProRouteProductBomApi.RouteProductBom[]];
|
}>();
|
|
const activeProductId = ref<number>();
|
const allBomList = ref<MesProRouteProductBomApi.RouteProductBom[]>([]);
|
|
const activeProduct = computed(() =>
|
props.products.find((item) => item.itemId === activeProductId.value),
|
);
|
|
function materialCount(productId?: number) {
|
if (!productId) return 0;
|
return allBomList.value.filter((item) => item.productId === productId).length;
|
}
|
|
async function loadAll() {
|
allBomList.value = (await getRouteProductBomList({ routeId: props.routeId })) || [];
|
emit('change', allBomList.value);
|
}
|
|
watch(
|
() => props.products.map((item) => item.itemId).filter(Boolean),
|
(productIds) => {
|
if (!productIds.length) {
|
activeProductId.value = undefined;
|
return;
|
}
|
if (!activeProductId.value || !productIds.includes(activeProductId.value)) {
|
activeProductId.value = productIds[0];
|
}
|
},
|
{ immediate: true },
|
);
|
|
watch(
|
() => props.routeId,
|
(value) => value && loadAll(),
|
{ immediate: true },
|
);
|
</script>
|
|
<template>
|
<Alert
|
class="mb-4"
|
message="按产出产品配置每道工序需要投入的物料"
|
description="先选择产出产品,再切换到具体工序添加投料物料和用料比例。开启“报工自动扣料”的工序,会在报工时按这里的配置自动扣减库存。"
|
show-icon
|
type="info"
|
/>
|
|
<Empty
|
v-if="!products.length || !processes.length"
|
:description="!products.length ? '请先完成产出产品配置' : '请先完成工序配置'"
|
/>
|
|
<div v-else class="material-layout">
|
<aside class="product-selector" aria-label="选择要配置投料的产出产品">
|
<div class="selector-heading">
|
<strong>产出产品</strong>
|
<span>{{ products.length }} 个</span>
|
</div>
|
<Button
|
v-for="product in products"
|
:key="product.itemId"
|
:class="['product-button', { active: product.itemId === activeProductId }]"
|
type="text"
|
@click="activeProductId = product.itemId"
|
>
|
<span class="product-copy">
|
<strong>{{ product.itemName }}</strong>
|
<small>{{ product.itemCode }}</small>
|
</span>
|
<Tag :color="materialCount(product.itemId) ? 'success' : 'warning'">
|
{{ materialCount(product.itemId) ? `${materialCount(product.itemId)} 条` : '未配置' }}
|
</Tag>
|
</Button>
|
</aside>
|
|
<section v-if="activeProduct" class="material-detail">
|
<header class="material-detail-header">
|
<div>
|
<h3>{{ activeProduct.itemName }} · 工序投料</h3>
|
<p>依次切换工序,维护该产品在该工序实际消耗的物料。</p>
|
</div>
|
<Tag color="blue">{{ processes.length }} 道工序</Tag>
|
</header>
|
<ProductBomList
|
:form-type="formType"
|
:product-id="activeProduct.itemId!"
|
:product-name="activeProduct.itemName"
|
:route-id="routeId"
|
@change="loadAll"
|
/>
|
</section>
|
</div>
|
</template>
|
|
<style scoped>
|
.material-layout { display: grid; grid-template-columns: minmax(210px, 260px) minmax(0, 1fr); gap: 18px; }
|
.product-selector { overflow: hidden; align-self: start; border: 1px solid rgb(226 232 240); border-radius: 12px; background: rgb(248 250 252); }
|
.selector-heading { display: flex; align-items: center; justify-content: space-between; padding: 14px 16px; border-bottom: 1px solid rgb(226 232 240); color: rgb(15 23 42); }
|
.selector-heading span { color: rgb(100 116 139); font-size: 12px; }
|
.product-button { display: flex; width: 100%; height: auto; min-height: 62px; align-items: center; justify-content: space-between; gap: 10px; padding: 10px 12px; border-radius: 0; text-align: left; }
|
.product-button + .product-button { border-top: 1px solid rgb(226 232 240); }
|
.product-button.active { background: rgb(219 234 254); color: rgb(29 78 216); }
|
.product-copy { display: grid; min-width: 0; gap: 2px; }
|
.product-copy strong, .product-copy small { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
.product-copy small { color: rgb(100 116 139); }
|
.material-detail { min-width: 0; padding: 18px; border: 1px solid rgb(226 232 240); border-radius: 12px; }
|
.material-detail-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
|
.material-detail-header h3 { margin: 0; color: rgb(15 23 42); font-size: 16px; font-weight: 650; }
|
.material-detail-header p { margin: 4px 0 0; color: rgb(100 116 139); font-size: 13px; }
|
:global(.dark) .product-selector, :global(.dark) .material-detail { border-color: rgb(51 65 85); background: rgb(15 23 42); }
|
:global(.dark) .selector-heading, :global(.dark) .product-button + .product-button { border-color: rgb(51 65 85); color: rgb(241 245 249); }
|
:global(.dark) .product-button.active { background: rgb(30 64 175 / 35%); color: rgb(147 197 253); }
|
:global(.dark) .material-detail-header h3 { color: rgb(241 245 249); }
|
@media (max-width: 900px) {
|
.material-layout { grid-template-columns: 1fr; }
|
.product-selector { display: flex; overflow-x: auto; }
|
.selector-heading { display: none; }
|
.product-button { min-width: 210px; border-top: 0 !important; border-right: 1px solid rgb(226 232 240); }
|
}
|
</style>
|