add product sku info

This commit is contained in:
Timi
2026-09-23 00:14:01 +08:00
parent dbf0b2e299
commit 23fc2c3577
3 changed files with 221 additions and 127 deletions
+6 -4
View File
@@ -55,8 +55,8 @@
</div>
</template>
</t-cell>
<t-cell v-if="0 < Number(stock.reservedQuantity || 0)" title="查看预占流水" arrow @click="openReservedRecordList" />
<t-cell title="查看该产品 SKU 流水" arrow @click="openRecordList" />
<t-cell title="查看库存流水" arrow @click="openRecordList" />
<t-cell v-if="0 < Number(stock.reservedQuantity || 0)" title="查看库存预占流水" arrow @click="openReservedRecordList" />
</t-cell-group>
<card-container
v-if="canOperate && sku?.status === 'ACTIVE' && sku.stockEnabled"
@@ -199,11 +199,12 @@ async function loadAll(): Promise<void> {
}
async function openRecordList(): Promise<void> {
if (product.value?.id) {
if (product.value?.id && sku.value?.id) {
await router.push({
name: "ProductStockRecordList",
query: {
productId: product.value.id,
skuId: sku.value.id,
transitionDepth: String(getCurrentRouteDepth() + 1)
}
});
@@ -211,11 +212,12 @@ async function openRecordList(): Promise<void> {
}
async function openReservedRecordList(): Promise<void> {
if (product.value?.id) {
if (product.value?.id && sku.value?.id) {
await router.push({
name: "ProductStockRecordList",
query: {
productId: product.value.id,
skuId: sku.value.id,
operationType: "ORDER_RESERVE",
activeReservationOnly: "true",
transitionDepth: String(getCurrentRouteDepth() + 1)
+95 -4
View File
@@ -20,6 +20,38 @@
@refresh="refresh"
@scrolltolower="loadMore"
>
<div class="list-wrap">
<t-cell-group v-if="filteredProduct && filteredSku" theme="card">
<t-cell class="sku">
<template #leftIcon>
<t-image
v-if="filteredSkuPhotoURL"
class="avatar"
:src="filteredSkuPhotoURL"
:alt="filteredProduct.name || '产品图片'"
fit="cover"
/>
<div
v-else
class="fallback"
v-text="(filteredProduct.name || filteredProduct.code || '产').slice(0, 1)"
/>
</template>
<template #title>
<div class="title">
<strong class="text-bold clip-text" v-text="filteredProduct.name || '未命名产品'" />
<span class="clip-text" v-text="filteredSku.specSummary || filteredSku.name || '默认 SKU'" />
</div>
</template>
<template #note>
<t-tag
variant="light-outline"
theme="primary"
:content="getProductTypeText(filteredProduct.productType)"
/>
</template>
</t-cell>
</t-cell-group>
<t-cell-group v-if="recordList.length" class="list empty-cell-title-margin" theme="card">
<t-cell
v-for="(record, index) in recordList"
@@ -84,6 +116,7 @@
</t-cell-group>
<div v-else class="center-tips" v-text="emptyText" />
<div v-if="recordList.length" class="center-tips" v-text="loadStateText" />
</div>
</t-pull-down-refresh>
</div>
</template>
@@ -94,9 +127,9 @@ import { PassTimeLabel } from "timi-web";
import { UserAPI } from "timi-web-api";
import ProductAPI from "@/api/ProductAPI";
import { useNavBarStore } from "@/store/navBarStore";
import type { StockOperationType, StockRecord, StockRecordDirection } from "@/types/Product";
import type { Product, ProductSku, StockOperationType, StockRecord, StockRecordDirection } from "@/types/Product";
import { getErrorMessage } from "@/utils/error";
import { getProductQuantityText, getStockOperationText } from "@/utils/product";
import { getProductPhotoURL, getProductQuantityText, getProductTypeText, getStockOperationText } from "@/utils/product";
import { Message } from "@/utils/message";
import { permissionCodes } from "@/utils/permission";
import { getCustomerPhotoURL } from "@/utils/customer";
@@ -118,6 +151,12 @@ const loading = ref(false);
const isRefreshing = ref(false);
const noMore = ref(false);
const productId = computed(() => getQueryText("productId"));
const skuId = computed(() => getQueryText("skuId"));
const filteredProduct = ref<Product>();
const filteredSku = ref<ProductSku>();
const filteredSkuUnitId = ref("");
const filteredSkuUnitList = computed(() => (filteredSku.value?.unitList || []).filter(unit => !!unit.id));
const filteredSkuPhotoURL = computed(() => filteredSku.value ? getProductPhotoURL(filteredSku.value) || (filteredProduct.value ? getProductPhotoURL(filteredProduct.value) : "") : "");
const activeReservationRequested = getQueryText("activeReservationOnly") === "true";
const operationType = ref<OperationFilter>(getQueryText("operationType") as OperationFilter || (activeReservationRequested ? "ORDER_RESERVE" : "ALL"));
const direction = ref<DirectionFilter>(getQueryText("direction") as DirectionFilter || "ALL");
@@ -132,7 +171,7 @@ const emptyText = computed(() => {
if (activeReservationOnly.value) {
return "暂无有效的预占流水";
}
return keyword.value.trim() || productId.value || operationType.value !== "ALL" || direction.value !== "ALL" ? "未找到匹配的库存流水" : "暂无库存流水";
return keyword.value.trim() || productId.value || skuId.value || operationType.value !== "ALL" || direction.value !== "ALL" ? "未找到匹配的库存流水" : "暂无库存流水";
});
const loadStateText = computed(() => loading.value ? "正在加载" : noMore.value ? "已经到底了" : "上拉加载更多");
const operationOptions = [
@@ -159,6 +198,10 @@ function getQueryText(key: string): string {
return Array.isArray(value) ? String(value[0] || "") : String(value || "");
}
function getDefaultFilteredSkuUnitId(): string {
return filteredSkuUnitList.value.find(unit => unit.isBase)?.id || filteredSkuUnitList.value[0]?.id || "";
}
function getOperationIcon(type?: StockOperationType): string {
return ({
INITIAL_IN: "import",
@@ -235,6 +278,7 @@ async function loadMore(force = false): Promise<void> {
try {
const result = await ProductAPI.recordList({
productId: productId.value || undefined,
skuId: skuId.value || undefined,
keyword: keyword.value.trim() || undefined,
operationType: operationType.value === "ALL" ? undefined : operationType.value,
activeReservationOnly: activeReservationOnly.value || undefined,
@@ -257,6 +301,23 @@ async function loadMore(force = false): Promise<void> {
}
}
async function loadSkuFilter(): Promise<void> {
if (!skuId.value) {
filteredProduct.value = undefined;
filteredSku.value = undefined;
filteredSkuUnitId.value = "";
return;
}
const sku = await ProductAPI.skuDetail(skuId.value);
const product = sku.productId ? await ProductAPI.detail(sku.productId) : undefined;
if (skuId.value !== getQueryText("skuId")) {
return;
}
filteredSku.value = sku;
filteredProduct.value = product;
filteredSkuUnitId.value = getDefaultFilteredSkuUnitId();
}
async function selectOperationType(value: DropdownValue): Promise<void> {
if (!Array.isArray(value)) {
operationType.value = String(value) as OperationFilter;
@@ -292,7 +353,7 @@ async function refresh(): Promise<void> {
onMounted(async () => {
navBarStore.clearRight();
try {
await reload();
await Promise.all([loadSkuFilter(), reload()]);
} catch (error) {
Message.error(getErrorMessage(error, "库存流水加载失败"));
}
@@ -372,6 +433,35 @@ onUnmounted(() => {
}
}
.list-wrap {
gap: 1rem;
display: flex;
flex-direction: column;
.sku {
--td-tag-square-border-radius: .5rem;
padding: 10px;
:deep(.t-cell__title) {
display: flex;
align-items: center;
}
.avatar {
width: 2rem;
height: 2rem;
border-radius: .5rem !important;
}
.title {
gap: .5rem;
display: flex;
min-width: 0;
align-items: center;
}
}
.list {
margin: 0;
@@ -454,6 +544,7 @@ onUnmounted(() => {
}
}
}
}
.positive {
color: var(--td-success-color);
+1
View File
@@ -192,6 +192,7 @@ export type StockRecord = {
export type StockRecordPage = {
storeId?: string;
productId?: string;
skuId?: string;
keyword?: string;
bizType?: StockBizType;
bizId?: string;