|
|
@@ -11,6 +11,12 @@
|
|
|
宝时立库
|
|
|
<span class="title-meta">· 总库位 {{ locations.length }}</span>
|
|
|
<span class="title-meta">· 可用库位 {{ availableLocationsCount }}</span>
|
|
|
+ <span class="title-fill-rate">
|
|
|
+ <span class="title-meta">· 总库满度 {{ overallFillRate }}</span>
|
|
|
+ <span class="title-meta title-meta-a">A {{ categoryFillRateMap.A }}</span>
|
|
|
+ <span class="title-meta title-meta-b">B {{ categoryFillRateMap.B }}</span>
|
|
|
+ <span class="title-meta title-meta-c">C {{ categoryFillRateMap.C }}</span>
|
|
|
+ </span>
|
|
|
<span class="title-legend">
|
|
|
<button
|
|
|
type="button"
|
|
|
@@ -406,10 +412,43 @@ const categoryOptions = computed(() => {
|
|
|
return [...new Set(locations.value.map((loc) => loc.category).filter(Boolean))].sort()
|
|
|
})
|
|
|
|
|
|
+const hasContainer = (containerCode: string | null) => {
|
|
|
+ return Boolean(containerCode && containerCode.trim())
|
|
|
+}
|
|
|
+
|
|
|
+const formatFillRate = (total: number, occupied: number) => {
|
|
|
+ if (total <= 0) {
|
|
|
+ return '0.0%'
|
|
|
+ }
|
|
|
+ return `${((occupied / total) * 100).toFixed(1)}%`
|
|
|
+}
|
|
|
+
|
|
|
const availableLocationsCount = computed(() => {
|
|
|
return locations.value.filter((loc) => loc.locationAttribute === 'OK').length
|
|
|
})
|
|
|
|
|
|
+const overallFillRate = computed(() => {
|
|
|
+ const occupiedCount = locations.value.filter((loc) => hasContainer(loc.containerCode)).length
|
|
|
+ return formatFillRate(locations.value.length, occupiedCount)
|
|
|
+})
|
|
|
+
|
|
|
+const categoryFillRateMap = computed<Record<'A' | 'B' | 'C', string>>(() => {
|
|
|
+ const categories: Array<'A' | 'B' | 'C'> = ['A', 'B', 'C']
|
|
|
+ return categories.reduce(
|
|
|
+ (result, category) => {
|
|
|
+ const categoryLocations = locations.value.filter((loc) => loc.category === category)
|
|
|
+ const occupiedCount = categoryLocations.filter((loc) => hasContainer(loc.containerCode)).length
|
|
|
+ result[category] = formatFillRate(categoryLocations.length, occupiedCount)
|
|
|
+ return result
|
|
|
+ },
|
|
|
+ {
|
|
|
+ A: '0.0%',
|
|
|
+ B: '0.0%',
|
|
|
+ C: '0.0%'
|
|
|
+ }
|
|
|
+ )
|
|
|
+})
|
|
|
+
|
|
|
const locationAttributeOptions = computed<LocationAttributeCode[]>(() => {
|
|
|
return [
|
|
|
...new Set(locations.value.map((loc) => loc.locationAttribute).filter(Boolean))
|
|
|
@@ -692,6 +731,25 @@ onBeforeUnmount(() => {
|
|
|
margin-left: 2px;
|
|
|
}
|
|
|
|
|
|
+.title-fill-rate {
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 6px;
|
|
|
+ margin-left: 2px;
|
|
|
+}
|
|
|
+
|
|
|
+.title-meta-a {
|
|
|
+ color: rgba(201, 242, 215, 0.78);
|
|
|
+}
|
|
|
+
|
|
|
+.title-meta-b {
|
|
|
+ color: rgba(203, 220, 255, 0.8);
|
|
|
+}
|
|
|
+
|
|
|
+.title-meta-c {
|
|
|
+ color: rgba(240, 220, 156, 0.82);
|
|
|
+}
|
|
|
+
|
|
|
.legend-chip {
|
|
|
display: inline-flex;
|
|
|
align-items: center;
|