liu
6 天以前 70b7935fc261cd5e8eaaf1cfbc8078b04db576d2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
<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>