fix sku display

This commit is contained in:
Timi
2026-09-19 22:28:24 +08:00
parent 79d9efad73
commit 2aa29da3ff
15 changed files with 148 additions and 82 deletions
-4
View File
@@ -89,10 +89,6 @@ html.theme-dark body {
text-overflow: unset;
}
}
.t-cell__description {
margin-top: 0;
}
}
.t-dialog__footer {
+4
View File
@@ -9,6 +9,7 @@
>
<span class="picker-text" v-text="selectedProductName || props.placeholder" />
</t-form-item>
<product-sku-form-item :sku="selectedSku" :unit="selectedUnit" />
<bottom-popup
v-model="visible"
title="选择产品 SKU"
@@ -103,6 +104,7 @@
<script setup lang="ts">
import { BottomPopup } from "timi-tdesign-mobile";
import ProductAPI from "@/api/ProductAPI";
import ProductSkuFormItem from "@/components/ProductSkuFormItem.vue";
import type {
Product,
ProductSelection,
@@ -143,6 +145,8 @@ const selectedProduct = computed(() => {
return props.selectedProduct || productList.value.find(item => item.skuList?.some(sku => sku.id === props.modelValue?.skuId));
});
const selectedProductName = computed(() => selectedProduct.value?.name || "");
const selectedSku = computed(() => selectedProduct.value?.skuList?.find(sku => sku.id === props.modelValue?.skuId));
const selectedUnit = computed(() => selectedSku.value?.unitList?.find(unit => unit.id === props.modelValue?.skuUnitId));
const filteredProductList = computed(() => {
const text = keyword.value.toLocaleLowerCase();
return productList.value.filter(item => !text || `${item.name}${item.code}`.toLocaleLowerCase().includes(text));
+22
View File
@@ -0,0 +1,22 @@
<template>
<t-form-item v-if="skuText || unitText" label="产品 SKU" content-align="right">
<product-sku-tags :fallback="skuText" :unit="unitText" />
</t-form-item>
</template>
<script setup lang="ts">
import ProductSkuTags from "@/components/ProductSkuTags.vue";
import type { ProductSku, ProductSkuUnit } from "@/types/Product";
defineOptions({ name: "ProductSkuFormItem" });
interface Props {
sku?: ProductSku;
unit?: ProductSkuUnit;
}
const props = defineProps<Props>();
const skuText = computed(() => props.sku?.name?.trim() || props.sku?.specSummary?.trim() || "");
const unitText = computed(() => props.unit?.name?.trim() || "");
</script>
@@ -8,6 +8,7 @@
variant="light-outline"
:content="tag.content"
/>
<t-tag v-if="unitText" size="small" :content="unitText" />
</div>
</template>
@@ -15,7 +16,7 @@
import type { ProductSkuSpecSnapshot } from "@/types/Product";
import { getProductSkuSnapshotText } from "@/utils/product";
defineOptions({ name: "ProductSkuSnapshotTags" });
defineOptions({ name: "ProductSkuTags" });
interface Props {
productName?: string | null;
@@ -42,7 +42,7 @@
v-if="item.skuCodeSnapshot || item.skuSpecSnapshot?.length || item.skuNameSnapshot || item.specSummarySnapshot || item.unitSnapshot"
class="sku-tags"
>
<product-sku-snapshot-tags
<product-sku-tags
:product-name="item.productNameSnapshot"
:snapshot="item.skuSpecSnapshot"
:fallback="item.specSummarySnapshot || item.skuNameSnapshot"
@@ -128,7 +128,7 @@ import SalesOrderAPI from "@/api/SalesOrderAPI";
import FulfillmentAPI from "@/api/FulfillmentAPI";
import ProductAPI from "@/api/ProductAPI";
import MediaSelector from "@/components/mediaSelector/MediaSelector.vue";
import ProductSkuSnapshotTags from "@/components/ProductSkuSnapshotTags.vue";
import ProductSkuTags from "@/components/ProductSkuTags.vue";
import ProductStockPreview from "@/components/ProductStockPreview.vue";
import { useNavBarStore } from "@/store/navBarStore";
import type { Attachment } from "timi-web-api";
@@ -50,7 +50,7 @@
<template #description>
<div class="item-description">
<t-tag v-if="isGiftedFulfillmentItem(item)" size="small" theme="success" content="赠送" />
<product-sku-snapshot-tags
<product-sku-tags
:product-name="item.productNameSnapshot"
:snapshot="item.skuSpecSnapshot"
:fallback="item.specSummarySnapshot || item.skuNameSnapshot || '默认 SKU'"
@@ -152,7 +152,7 @@ import FulfillmentAPI from "@/api/FulfillmentAPI";
import ProductAPI from "@/api/ProductAPI";
import SalesOrderAPI from "@/api/SalesOrderAPI";
import MediaSelector from "@/components/mediaSelector/MediaSelector.vue";
import ProductSkuSnapshotTags from "@/components/ProductSkuSnapshotTags.vue";
import ProductSkuTags from "@/components/ProductSkuTags.vue";
import ProductStockPreview from "@/components/ProductStockPreview.vue";
import { useNavBarStore } from "@/store/navBarStore";
import type { OrderFulfillment, OrderFulfillmentItem } from "@/types/Fulfillment";
+19 -18
View File
@@ -30,7 +30,6 @@
<t-cell
class="item"
v-for="item in config.itemList || []"
:title="getItemTitle(item)"
:key="item.id || `${item.skuId}-${item.skuUnitId}`"
:note="`${item.defaultQuantity} ${item.unitSnapshot || ''}${item.remark ? `|${item.remark}` : ''}`"
>
@@ -48,12 +47,14 @@
v-text="(item.productNameSnapshot || '产').slice(0, 1)"
/>
</template>
<template #description>
<t-tag
:content="item.skuCodeSnapshot || item.sku?.code || 'SKU'"
theme="primary"
variant="light"
/>
<template #title>
<div class="title">
<span v-text="item.productNameSnapshot || '未命名产品'" />
<product-sku-tags
:product-name="item.productNameSnapshot"
:fallback="item.specSummarySnapshot || item.skuNameSnapshot || '默认 SKU'"
/>
</div>
</template>
</t-cell>
<div v-if="!config.itemList?.length" class="empty" v-text="'暂无产品权益'" />
@@ -82,6 +83,7 @@ import { DialogPlugin } from "tdesign-mobile-vue";
import { Time } from "timi-web";
import { useUserStore } from "timi-web-api";
import PackageAPI from "@/api/PackageAPI";
import ProductSkuTags from "@/components/ProductSkuTags.vue";
import { useCurrentStoreStore } from "@/store/currentStoreStore";
import { useNavBarStore } from "@/store/navBarStore";
import type { GaoPackageConfig, PackageConfigItem, PackageConfigStatus } from "@/types/Package";
@@ -89,7 +91,7 @@ import { getErrorMessage } from "@/utils/error";
import { formatPackageAmount, getPackageConfigStatusText } from "@/utils/package";
import { Message } from "@/utils/message";
import { gaoRoleCodes, permissionCodes } from "@/utils/permission";
import { getProductPhotoURL as getProductPhotoUrl, getProductSkuSnapshotText } from "@/utils/product";
import { getProductPhotoURL as getProductPhotoUrl } from "@/utils/product";
defineOptions({ name: "PackageConfigDetail" });
@@ -116,11 +118,6 @@ function getItemPhotoURL(item: PackageConfigItem): string {
return getProductPhotoUrl(item.sku || {});
}
function getItemTitle(item: PackageConfigItem): string {
const skuText = getProductSkuSnapshotText(item.productNameSnapshot, item.skuNameSnapshot, item.specSummarySnapshot);
return `${item.productNameSnapshot || "未命名产品"}${skuText ? ` · ${skuText}` : ""}`;
}
async function loadData(): Promise<void> {
const id = String(route.params.id || "");
if (!id) {
@@ -286,16 +283,20 @@ onUnmounted(() => navBarStore.clearRight());
.item {
padding: 10px;
.item-title {
gap: .5rem;
:deep(.t-cell__title) {
display: flex;
align-items: center;
justify-content: space-between;
}
.avatar {
width: 3.5rem;
height: 3.5rem;
width: 2.5rem;
height: 2.5rem;
}
.title {
gap: .5rem;
display: flex;
align-items: center;
}
}
}
-3
View File
@@ -52,9 +52,6 @@
@update:model-value="applyProduct(index, $event)"
@select="applyProductSelection(index, $event)"
/>
<t-form-item v-if="item.skuId" label="SKU 单位" content-align="right">
<span v-text="getUnit(item)?.name || '未选择单位'" />
</t-form-item>
<t-form-item label="默认配额" content-align="right" required-mark>
<t-stepper
v-model="item.defaultQuantity"
+20 -32
View File
@@ -5,6 +5,7 @@
v-model="selection"
:selected-product="form.product"
required-mark
@select="handleProductSelection"
/>
<t-form-item label="默认积分" required-mark>
<t-input
@@ -34,10 +35,14 @@
<t-textarea v-model.trim="form.remark" maxlength="500" autosize />
</t-form-item>
</t-form>
<div v-if="form.id" class="actions" v-entrance-transition="70">
<t-button class="bg-white" theme="danger" variant="outline" @click="disable">停用配置</t-button>
<t-button theme="danger" @click="remove">删除</t-button>
</div>
<t-button
v-if="form.id"
class="cell-button"
theme="danger"
v-entrance-transition="70"
content="删除"
@click="remove"
/>
</div>
<t-popup v-model="statusVisible" placement="bottom">
<t-picker
@@ -55,6 +60,7 @@ import PointAPI from "@/api/PointAPI";
import ProductSelector from "@/components/ProductSelector.vue";
import { useNavBarStore } from "@/store/navBarStore";
import type { PointRedeemSku, PointRedeemSkuStatus } from "@/types/Point";
import type { ProductSelection } from "@/types/Product";
import { getErrorMessage } from "@/utils/error";
import { Message } from "@/utils/message";
import { normalizePackageQuantity } from "@/utils/package";
@@ -71,6 +77,8 @@ const isSaving = ref(false);
const form = reactive({
id: "",
product: undefined as PointRedeemSku["product"],
sku: undefined as PointRedeemSku["sku"],
unit: undefined as PointRedeemSku["unit"],
pointText: "",
status: "DRAFT" as PointRedeemSkuStatus,
totalText: "",
@@ -97,6 +105,12 @@ function parseLimit(value: string): string | null | undefined {
return value ? normalizePackageQuantity(value) : null;
}
function handleProductSelection(value: ProductSelection): void {
form.product = value.product;
form.sku = value.sku;
form.unit = value.unit;
}
function confirmStatus(value: Array<string | number>): void {
form.status = String(value[0]) as PointRedeemSkuStatus;
statusVisible.value = false;
@@ -110,6 +124,8 @@ async function load(): Promise<void> {
const item = await PointAPI.redeemSkuDetail(id);
form.id = item.id || "";
form.product = item.product;
form.sku = item.sku;
form.unit = item.unit;
selection.value = item.skuId && item.skuUnitId ? { skuId: item.skuId, skuUnitId: item.skuUnitId } : undefined;
form.pointText = String(item.defaultPointCost || "");
form.status = item.status || "DRAFT";
@@ -155,22 +171,6 @@ async function save(): Promise<void> {
}
}
async function disable(): Promise<void> {
if (!form.id || isSaving.value) {
return;
}
isSaving.value = true;
try {
await PointAPI.redeemSkuDisable(form.id);
Message.success("兑换配置已停用");
await router.replace({ name: "PointRedeemSkuList" });
} catch (error) {
Message.error(getErrorMessage(error, "兑换配置停用失败"));
} finally {
isSaving.value = false;
}
}
async function remove(): Promise<void> {
if (!form.id || isSaving.value) {
return;
@@ -209,17 +209,5 @@ onUnmounted(() => navBarStore.clearRight());
.tips {
font-size: .875rem;
}
.actions {
--td-button-border-radius: 1rem;
gap: .75rem;
margin: 0 1rem;
display: flex;
.t-button {
flex: 1;
}
}
}
</style>
+18 -9
View File
@@ -16,8 +16,8 @@
v-for="(item, index) in result.list"
:key="item.id"
class="item"
:title="item.product?.name || item.sku?.name || '未命名产品'"
arrow
:description="item.unit?.name || '单位未知'"
v-entrance-transition="index * 30"
@click="toEdit(item)"
>
@@ -35,19 +35,23 @@
v-text="(item.product?.name || item.sku?.name || '产').slice(0, 1)"
/>
</template>
<template #title>
<div class="title">
<strong v-text="item.product?.name || item.sku?.name || '未命名产品'" />
<template #note>
<span class="orange" v-text="formatPoint(item.defaultPointCost)" />
</template>
<template #description>
<div class="description">
<t-tag
size="small"
:theme="item.status === 'ACTIVE' ? 'success' : 'default'"
:content="statusText(item.status)"
/>
<product-sku-tags
:product-name="item.product?.name"
:fallback="item.sku?.specSummary || item.sku?.name || '默认 SKU'"
:unit="item.unit?.name"
/>
</div>
</template>
<template #note>
<span class="orange" v-text="formatPoint(item.defaultPointCost)" />
</template>
</t-cell>
</t-cell-group>
<t-empty v-else :description="isLoading ? '正在加载兑换配置' : '暂无兑换配置'" />
@@ -63,6 +67,7 @@
<script setup lang="ts">
import { Toolkit } from "timi-web";
import PointAPI from "@/api/PointAPI";
import ProductSkuTags from "@/components/ProductSkuTags.vue";
import { useNavBarStore } from "@/store/navBarStore";
import type { PointRedeemSku, PointRedeemSkuStatus } from "@/types/Point";
import { Message } from "@/utils/message";
@@ -202,12 +207,16 @@ onUnmounted(() => {
.item {
padding: 10px;
border-radius: 1rem;
.avatar {
width: 3.5rem;
height: 3.5rem;
border-radius: .875rem;
}
.description {
gap: .5rem;
display: flex;
align-items: center;
}
}
+54 -3
View File
@@ -46,6 +46,10 @@
<t-form-item label="兑换商品" required-mark content-align="right" arrow @click="skuVisible = true">
<span v-text="skuName || '请选择兑换商品'" />
</t-form-item>
<product-sku-form-item
:sku="selectedSku?.sku"
:unit="selectedSku?.unit"
/>
<t-form-item label="兑换数量" content-align="right" required-mark>
<t-stepper
v-model="quantity"
@@ -97,10 +101,26 @@
v-for="item in skuList"
:key="item.id"
:title="item.product?.name || item.sku?.name || '未命名产品'"
:note="`${item.sku?.specSummary || item.sku?.code || '默认 SKU'} · ${item.unit?.name || ''} · ${formatPoint(item.defaultPointCost)} / 单位`"
:note="`${formatPoint(item.defaultPointCost)} / ${item.unit?.name || '单位未知'}`"
class="item"
arrow
@click="selectSku(item)"
/>
>
<template #left-icon>
<t-image
v-if="getProductPhoto(item)"
:src="getProductPhoto(item)"
:alt="item.product?.name || item.sku?.name || '产品图片'"
fit="cover"
class="avatar"
/>
<div
v-else
class="avatar fallback"
v-text="(item.product?.name || item.sku?.name || '产').slice(0, 1)"
/>
</template>
</t-cell>
<t-empty v-if="!skuList.length" description="暂无启用的兑换商品" />
</div>
</bottom-popup>
@@ -124,7 +144,7 @@ import { getErrorMessage } from "@/utils/error";
import { Message } from "@/utils/message";
import { formatWanInteger } from "@/utils/money";
import { normalizePackageQuantity } from "@/utils/package";
import { getProductQuantityStep } from "@/utils/product";
import { getProductPhotoURL, getProductQuantityStep } from "@/utils/product";
import { uploadPhotoList } from "@/utils/photo";
import { permissionCodes } from "@/utils/permission";
@@ -254,6 +274,11 @@ function selectSku(item: PointRedeemSku): void {
skuVisible.value = false;
}
function getProductPhoto(item: PointRedeemSku): string {
const product = item.product || item.sku;
return product ? getProductPhotoURL(product) : "";
}
function closeSkuPicker(): void {
skuVisible.value = false;
}
@@ -351,6 +376,32 @@ onUnmounted(() => navBarStore.clearRight());
max-height: 70vh;
overflow: auto;
padding: 1rem 0;
.item {
padding: .625rem 1rem;
border-radius: 1rem;
:deep(.t-cell__title) {
display: flex;
align-items: center;
}
.avatar {
width: 2.5rem;
height: 2.5rem;
border-radius: .5rem !important;
}
.fallback {
color: var(--td-brand-color);
display: flex;
font-size: 1rem;
background: var(--td-brand-color-1);
font-weight: 600;
align-items: center;
justify-content: center;
}
}
}
:global(html.theme-dark) {
@@ -5,7 +5,7 @@
<section class="snapshot-head" v-entrance-transition>
<div class="eyebrow">库存 SKU 快照</div>
<h2 v-text="record.productNameSnapshot || '未命名产品'" />
<product-sku-snapshot-tags
<product-sku-tags
class="sku"
:product-name="record.productNameSnapshot"
:snapshot="record.skuSpecSnapshot"
@@ -138,7 +138,7 @@ import { PassTimeLabel } from "timi-web";
import { useUserStore, type Attachment } from "timi-web-api";
import ProductAPI from "@/api/ProductAPI";
import MediaSelector from "@/components/mediaSelector/MediaSelector.vue";
import ProductSkuSnapshotTags from "@/components/ProductSkuSnapshotTags.vue";
import ProductSkuTags from "@/components/ProductSkuTags.vue";
import { useNavBarStore } from "@/store/navBarStore";
import type { PhotoItem } from "@/types/Photo";
import type { StockBizType, StockRecord } from "@/types/Product";
@@ -169,7 +169,7 @@ const editorVisible = ref(false);
const skuEditorZIndex = 12000;
const photoUrlList = computed(() => sku.value.photoItemList.map(item => item.url));
const skuSpecEntries = computed(() => getProductSkuSpecEntries(sku.value, props.specList));
const skuTitle = computed(() => props.specList.length ? `SKU 明细 ${props.skuIndex + 1}` : "默认 SKU");
const skuTitle = computed(() => sku.value.name?.trim() || sku.value.specSummary?.trim() || (props.specList.length ? `SKU 明细 ${props.skuIndex + 1}` : "默认 SKU"));
function openEditor(): void {
editorVisible.value = true;
@@ -62,9 +62,6 @@
required-mark
@update:model-value="applyProduct(index, $event)"
/>
<t-form-item v-if="item.skuId" label="SKU 单位" content-align="right">
<span v-text="getUnit(item)?.name || '未选择单位'" />
</t-form-item>
<t-form-item :label="canPriceOverride ? '成交单价' : '单价'">
<t-input
v-model.trim="item.unitPriceText"
+2 -2
View File
@@ -113,7 +113,7 @@
v-text="`¥${formatPackageAmount(item.lineAmount)}`"
/>
<t-tag v-else size="small" content="套餐权益" />
<product-sku-snapshot-tags
<product-sku-tags
:product-name="item.productNameSnapshot"
:snapshot="item.skuSpecSnapshot"
:fallback="item.specSummarySnapshot || item.skuNameSnapshot"
@@ -226,7 +226,7 @@ import FulfillmentAPI from "@/api/FulfillmentAPI";
import SalesOrderAPI from "@/api/SalesOrderAPI";
import CustomerProfileCard from "@/components/CustomerProfileCard.vue";
import MediaSelector from "@/components/mediaSelector/MediaSelector.vue";
import ProductSkuSnapshotTags from "@/components/ProductSkuSnapshotTags.vue";
import ProductSkuTags from "@/components/ProductSkuTags.vue";
import { useNavBarStore } from "@/store/navBarStore";
import type { GaoSalesOrderItem, GaoSalesPaymentStatus, SalesOrder } from "@/types/SalesOrder";
import type { PhotoItem } from "@/types/Photo";