add ProductStockPackageSummary.vue

This commit is contained in:
Timi
2026-10-05 16:11:24 +08:00
parent f643656eb4
commit 58d99e52c5
3 changed files with 103 additions and 0 deletions
+2
View File
@@ -55,6 +55,7 @@
</div>
</template>
</t-cell>
<product-stock-package-summary :sku="sku" :stock="stock" />
<t-cell title="查看库存流水" arrow @click="openRecordList" />
<t-cell v-if="0 < Number(stock.reservedQuantity || 0)" title="查看库存预占流水" arrow @click="openReservedRecordList" />
</t-cell-group>
@@ -100,6 +101,7 @@ import { shallowRef } from "vue";
import { useUserStore } from "timi-web-api";
import ProductAPI from "@/api/ProductAPI";
import ProductProfile from "@/pages/product/component/ProductProfile.vue";
import ProductStockPackageSummary from "@/pages/product/component/ProductStockPackageSummary.vue";
import ProductStockOperationPopup from "@/pages/product/ProductStockOperationPopup.vue";
import { useNavBarStore } from "@/store/navBarStore";
import type { Product, ProductSku, ProductStock, ProductSkuUnit, StockOperationType } from "@/types/Product";
@@ -0,0 +1,78 @@
<template>
<template v-if="packageUnits.length">
<t-cell class="packages" title="整包装换算">
<template #note>
<div class="options" role="group" aria-label="选择整包装换算单位">
<t-button
v-for="unit in packageUnits"
:key="unit.id"
size="extra-small"
:theme="selectedUnit?.id === unit.id ? 'primary' : 'default'"
:variant="selectedUnit?.id === unit.id ? 'base' : 'outline'"
:aria-label="`按${unit.name || '未命名单位'}换算`"
:aria-pressed="selectedUnit?.id === unit.id"
@click="selectedUnitId = unit.id || ''"
>
<span v-text="`按${unit.name || '未命名单位'}`" />
</t-button>
</div>
</template>
</t-cell>
<t-cell title="库存折合" :note="summary.onHand" />
<t-cell title="可用折合" :note="summary.available" />
</template>
</template>
<script setup lang="ts">
import type { ProductSku, ProductStock } from "@/types/Product";
import { splitBaseQuantityIntoPackages } from "@/utils/package";
const props = defineProps<{ sku: ProductSku; stock: ProductStock }>();
// ---------- 整包装换算 ----------
const selectedUnitId = ref("");
const baseUnits = computed(() => (props.sku.unitList || []).filter(unit => unit.isBase));
const packageUnits = computed(() => (props.sku.unitList || [])
.filter(unit => unit.id && !unit.isBase && 1 < Number(unit.baseQuantityPerUnit))
.sort((left, right) => Number(right.baseQuantityPerUnit) - Number(left.baseQuantityPerUnit)));
const selectedUnit = computed(() => packageUnits.value.find(unit => unit.id === selectedUnitId.value) || packageUnits.value[0]);
const summary = computed(() => ({
onHand: getSummaryText(props.stock.onHandQuantity),
available: getSummaryText(props.stock.availableQuantity)
}));
/** 使用原始基础库存换算,保留基础单位余数,异常数据不按零库存展示。 */
function getSummaryText(quantity?: number | string): string {
const baseUnit = baseUnits.value[0];
const unit = selectedUnit.value;
if (quantity === undefined || baseUnits.value.length !== 1 || !baseUnit?.name
|| Number(baseUnit.baseQuantityPerUnit) !== 1 || !unit?.name) {
return "无法换算";
}
const result = splitBaseQuantityIntoPackages(quantity, unit.baseQuantityPerUnit);
if (!result) {
return "无法换算";
}
const parts: string[] = [];
if (result.whole !== "0") {
parts.push(`${result.whole} ${unit.name}`);
}
if (result.remainder !== "0" || !parts.length) {
parts.push(`${result.remainder} ${baseUnit.name}`);
}
return parts.join(" ");
}
</script>
<style scoped lang="less">
.packages {
.options {
--td-button-border-radius: .5rem;
gap: .5rem;
display: flex;
flex-wrap: wrap;
}
}
</style>
+23
View File
@@ -100,6 +100,29 @@ export function convertBaseQuantityToPackageQuantity(baseQuantity: number | stri
return formatDecimal(rounded, 3, 3);
}
/** 将非负基础数量精确拆分为整包装和基础单位余数,不对余数取整。 */
export function splitBaseQuantityIntoPackages(baseQuantity: number | string, baseQuantityPerUnit: number | string): { whole: string; remainder: string } | undefined {
const quantityParts = toDecimalParts(baseQuantity, 3);
const unitParts = toDecimalParts(baseQuantityPerUnit, 6);
if (!quantityParts || !unitParts) {
return undefined;
}
const scale = Math.max(quantityParts.scale, unitParts.scale);
const factor = 10n ** BigInt(scale);
const quantity = quantityParts.value * 10n ** BigInt(scale - quantityParts.scale);
const unit = unitParts.value * 10n ** BigInt(scale - unitParts.scale);
if (unit <= factor) {
return undefined;
}
const remainder = quantity % unit;
const integer = (remainder / factor).toString();
const fraction = (remainder % factor).toString().padStart(scale, "0").replace(/0+$/, "");
return {
whole: (quantity / unit).toString(),
remainder: fraction ? `${integer}.${fraction}` : integer
};
}
/** 精确相加两个最多三位小数的数量。 */
export function addPackageQuantity(left: number | string, right: number | string): string | undefined {
const leftParts = toQuantityParts(left);