fix product sku style

This commit is contained in:
Timi
2026-09-07 14:31:57 +08:00
parent 4aef5bddc7
commit fe371d8b6d
4 changed files with 136 additions and 98 deletions
+126 -92
View File
@@ -11,81 +11,88 @@
</t-form-item>
<bottom-popup
v-model="visible"
:title="activeProduct ? '选择 SKU 单位' : '选择产品'"
title="选择产品 SKU"
height="80vh"
right-button-text=""
@left-button-click="close"
>
<div class="popup">
<div v-if="activeProduct" class="selection-header">
<t-button variant="text" size="small" @click="backToProducts">返回产品</t-button>
<strong v-text="activeProduct.name || '未命名产品'" />
<span v-text="activeProduct.code || ''" />
</div>
<div v-if="activeProduct" class="selection">
<div v-if="specList.length" class="specs">
<div v-for="spec in specList" :key="spec.id || spec.name" class="spec">
<strong v-text="spec.name" />
<div class="values">
<t-button
v-for="value in enabledValues(spec)"
:key="value.id"
size="extra-small"
:theme="selectedSpecValueMap[spec.id || ''] === value.id ? 'primary' : 'default'"
:variant="selectedSpecValueMap[spec.id || ''] === value.id ? 'base' : 'outline'"
:content="value.name"
@click="selectSpecValue(spec.id || '', value.id || '')"
/>
</div>
</div>
</div>
<div v-if="matchedSku" class="sku-card">
<div class="sku-title">
<strong v-text="matchedSku.name || matchedSku.specSummary || '默认 SKU'" />
<span v-if="matchedSku.code" v-text="matchedSku.code" />
</div>
<div class="units">
<t-button
v-for="unit in saleableUnits(matchedSku)"
:key="unit.id"
size="small"
:theme="props.modelValue?.skuId === matchedSku.id && props.modelValue?.skuUnitId === unit.id ? 'primary' : 'default'"
:variant="props.modelValue?.skuId === matchedSku.id && props.modelValue?.skuUnitId === unit.id ? 'base' : 'outline'"
@click="selectUnit(matchedSku, unit.id || '')"
>
<span v-text="unit.name" />
<span v-if="unit.salePrice !== null && unit.salePrice !== undefined" class="price" v-text="`¥${formatProductAmount(unit.salePrice)}`" />
</t-button>
</div>
</div>
<div v-else class="empty" v-text="specList.length ? '请选择每个规格项的规格值' : '暂无可售 SKU'" />
</div>
<div v-else class="products">
<div class="products">
<div class="search-wrap">
<t-input v-model="keyword" class="search" borderless placeholder="搜索产品名称或编码" />
</div>
<div class="items">
<t-cell-group v-if="visibleProductList.length" theme="card">
<t-cell
v-for="product in visibleProductList"
:key="product.id"
class="item"
:title="product.name"
:description="`${product.code || '-'} · ${product.skuList?.length || 0} 个 SKU`"
@click="selectProduct(product)"
<t-collapse
v-if="visibleProductList.length"
v-model="expandedProductKeyList"
class="collapse"
theme="card"
:expand-mutex="true"
@change="handleProductChange"
>
<t-collapse-panel
v-for="(product, productIndex) in visibleProductList"
:key="getProductKey(product, productIndex)"
:value="getProductKey(product, productIndex)"
class="panel"
placement="bottom"
>
<template #leftIcon>
<t-image
v-if="getProductPhotoURL(product)"
:src="getProductPhotoURL(product)"
:alt="product.name || '产品图片'"
fit="cover"
class="avatar"
/>
<div v-else class="avatar fallback" v-text="(product.name || product.code || '产').slice(0, 1)" />
<template #header>
<div class="product-header">
<t-image
v-if="getProductPhotoURL(product)"
:src="getProductPhotoURL(product)"
:alt="product.name || '产品图片'"
fit="cover"
class="avatar"
/>
<div v-else class="avatar fallback" v-text="(product.name || product.code || '产').slice(0, 1)" />
<div class="product-info">
<strong v-text="product.name || '未命名产品'" />
<span v-text="`${product.code || '-'} · ${product.skuList?.length || 0} 个 SKU`" />
</div>
</div>
</template>
</t-cell>
</t-cell-group>
<div v-if="isExpandedProduct(product, productIndex)" class="selection">
<div v-if="specList.length" class="specs">
<div v-for="spec in specList" :key="spec.id || spec.name" class="spec">
<strong v-text="spec.name" />
<div class="values">
<t-button
v-for="value in enabledValues(spec)"
:key="value.id"
size="extra-small"
:theme="selectedSpecValueMap[spec.id || ''] === value.id ? 'primary' : 'default'"
:variant="selectedSpecValueMap[spec.id || ''] === value.id ? 'base' : 'outline'"
:content="value.name"
@click="selectSpecValue(spec.id || '', value.id || '')"
/>
</div>
</div>
</div>
<div v-if="matchedSku" class="sku-card">
<div class="sku-title">
<strong v-text="matchedSku.name || matchedSku.specSummary || '默认 SKU'" />
<span v-if="matchedSku.code" v-text="matchedSku.code" />
</div>
<div class="units">
<t-button
v-for="unit in saleableUnits(matchedSku)"
:key="unit.id"
size="small"
:theme="props.modelValue?.skuId === matchedSku.id && props.modelValue?.skuUnitId === unit.id ? 'primary' : 'default'"
:variant="props.modelValue?.skuId === matchedSku.id && props.modelValue?.skuUnitId === unit.id ? 'base' : 'outline'"
@click="selectUnit(matchedSku, unit.id || '')"
>
<span v-text="unit.name" />
<span v-if="unit.salePrice !== null && unit.salePrice !== undefined" class="price" v-text="`¥${formatProductAmount(unit.salePrice)}`" />
</t-button>
</div>
</div>
<div v-else class="empty" v-text="specList.length ? '请选择每个规格项的规格值' : '暂无可售 SKU'" />
</div>
</t-collapse-panel>
</t-collapse>
<div v-else class="empty" v-text="loading ? '正在加载可售产品' : '暂无可售产品'" />
</div>
</div>
@@ -116,6 +123,7 @@ const loading = ref(false);
const keyword = ref("");
const productList = ref<Product[]>([]);
const activeProduct = ref<Product>();
const expandedProductKeyList = ref<Array<string | number>>([]);
const selectedSpecValueMap = reactive<Record<string, string>>({});
const selectedText = computed(() => {
@@ -144,6 +152,7 @@ const matchedSku = computed(() => {
async function open(): Promise<void> {
visible.value = true;
activeProduct.value = undefined;
expandedProductKeyList.value = [];
if (productList.value.length) {
return;
}
@@ -160,10 +169,7 @@ async function open(): Promise<void> {
function close(): void {
visible.value = false;
activeProduct.value = undefined;
}
function backToProducts(): void {
activeProduct.value = undefined;
expandedProductKeyList.value = [];
}
function enabledValues(spec: ProductSpec): ProductSpecValue[] {
@@ -174,6 +180,24 @@ function saleableUnits(sku: ProductSku): ProductSkuUnit[] {
return (sku.unitList || []).filter(unit => unit.allowSale !== false && unit.id);
}
function getProductKey(product: Product, index: number): string {
return product.id || product.code || `product-${index}`;
}
function isExpandedProduct(product: Product, index: number): boolean {
return expandedProductKeyList.value.includes(getProductKey(product, index));
}
function handleProductChange(value: Array<string | number>): void {
const productIndex = visibleProductList.value.findIndex((product, index) => getProductKey(product, index) === value[0]);
const product = visibleProductList.value[productIndex];
if (product) {
selectProduct(product);
return;
}
activeProduct.value = undefined;
}
function selectProduct(product: Product): void {
activeProduct.value = product;
Object.keys(selectedSpecValueMap).forEach(key => delete selectedSpecValueMap[key]);
@@ -217,29 +241,7 @@ function selectUnit(sku: ProductSku, skuUnitId: string): void {
display: flex;
flex-direction: column;
.selection-header {
gap: .5rem;
display: flex;
padding: 0 1rem .75rem;
align-items: center;
border-bottom: 1px solid var(--td-component-border);
strong {
flex: 1;
min-width: 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
span {
color: var(--td-text-color-secondary);
font-size: .75rem;
}
}
.selection {
padding: 1rem;
overflow-y: auto;
.specs {
@@ -318,12 +320,44 @@ function selectUnit(sku: ProductSku, skuUnitId: string): void {
overflow-y: auto;
padding-bottom: env(safe-area-inset-bottom, 0);
.item {
padding: 10px;
.collapse {
margin-bottom: 1rem;
}
.product-header {
gap: .75rem;
display: flex;
min-width: 0;
align-items: center;
.avatar {
width: 3.5rem;
height: 3.5rem;
flex: 0 0 auto;
}
.product-info {
gap: .25rem;
display: flex;
min-width: 0;
flex: 1;
flex-direction: column;
strong,
span {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
span {
color: var(--td-text-color-secondary);
font-size: .75rem;
}
}
&:active {
opacity: .7;
}
}
}
-3
View File
@@ -37,9 +37,6 @@
<t-tag size="small" variant="outline" :content="item.productCode || '无产品编码'" />
</div>
</template>
<template #note>
<span v-text="`${item.skuCount || 0} 个 SKU 明细`" />
</template>
<template #description>
<div class="stock">
<span class="on-hand">
@@ -43,7 +43,14 @@
</t-form-item>
</t-form>
</div>
<t-button class="add-button" variant="dashed" size="small" content="添加销售单位" @click="addUnit" />
<t-button
class="add-button"
theme="light"
variant="dashed"
size="small"
content="添加销售单位"
@click="addUnit"
/>
</div>
</div>
</bottom-popup>
@@ -182,7 +189,7 @@ function setBaseUnit(index: number): void {
}
.add-button {
align-self: flex-start;
--td-button-border-radius: 1rem;
}
}
@@ -55,7 +55,7 @@
class="add"
variant="dashed"
size="small"
theme="primary"
theme="light"
content="添加 SKU 单位"
@click="addUnit"
/>