update sku tags
This commit is contained in:
@@ -7,7 +7,27 @@
|
|||||||
arrow
|
arrow
|
||||||
@click="open"
|
@click="open"
|
||||||
>
|
>
|
||||||
<span class="picker-text" v-text="selectedText || props.placeholder" />
|
<span class="picker-text" v-text="selectedProductName || props.placeholder" />
|
||||||
|
<template #help>
|
||||||
|
<div v-if="selectedSku" class="selected-tags">
|
||||||
|
<t-tag
|
||||||
|
v-for="entry in selectedSkuSpecEntries"
|
||||||
|
:key="`${entry.specName}-${entry.valueName}`"
|
||||||
|
size="small"
|
||||||
|
theme="primary"
|
||||||
|
variant="light-outline"
|
||||||
|
:content="entry.valueName"
|
||||||
|
/>
|
||||||
|
<t-tag
|
||||||
|
v-if="!selectedSkuSpecEntries.length"
|
||||||
|
size="small"
|
||||||
|
theme="primary"
|
||||||
|
variant="light-outline"
|
||||||
|
:content="selectedSku.specSummary || selectedSku.name || '默认 SKU'"
|
||||||
|
/>
|
||||||
|
<t-tag v-if="selectedUnit?.name" size="small" :content="selectedUnit.name" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
<bottom-popup
|
<bottom-popup
|
||||||
v-model="visible"
|
v-model="visible"
|
||||||
@@ -106,7 +126,7 @@ import ProductAPI from "@/api/ProductAPI";
|
|||||||
import type { Product, ProductSku, ProductSkuUnit, ProductSpec, ProductSpecValue } from "@/types/Product";
|
import type { Product, ProductSku, ProductSkuUnit, ProductSpec, ProductSpecValue } from "@/types/Product";
|
||||||
import { getErrorMessage } from "@/utils/error";
|
import { getErrorMessage } from "@/utils/error";
|
||||||
import { Message } from "@/utils/message";
|
import { Message } from "@/utils/message";
|
||||||
import { formatProductAmount, getProductPhotoURL, getProductSkuText } from "@/utils/product";
|
import { formatProductAmount, getProductPhotoURL, getProductSkuSpecEntries } from "@/utils/product";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
modelValue: { skuId: string; skuUnitId: string } | undefined;
|
modelValue: { skuId: string; skuUnitId: string } | undefined;
|
||||||
@@ -126,15 +146,16 @@ const activeProduct = ref<Product>();
|
|||||||
const expandedProductKeyList = ref<Array<string | number>>([]);
|
const expandedProductKeyList = ref<Array<string | number>>([]);
|
||||||
const selectedSpecValueMap = reactive<Record<string, string>>({});
|
const selectedSpecValueMap = reactive<Record<string, string>>({});
|
||||||
|
|
||||||
const selectedText = computed(() => {
|
const selectedProduct = computed(() => {
|
||||||
if (!props.modelValue?.skuId) {
|
if (!props.modelValue?.skuId) {
|
||||||
return "";
|
return undefined;
|
||||||
}
|
}
|
||||||
const product = props.selectedProduct || productList.value.find(item => item.skuList?.some(sku => sku.id === props.modelValue?.skuId));
|
return props.selectedProduct || productList.value.find(item => item.skuList?.some(sku => sku.id === props.modelValue?.skuId));
|
||||||
const sku = product?.skuList?.find(item => item.id === props.modelValue?.skuId);
|
|
||||||
const unit = sku?.unitList?.find(item => item.id === props.modelValue?.skuUnitId);
|
|
||||||
return `${getProductSkuText(product?.name, sku)}${unit?.name ? ` · ${unit.name}` : ""}`;
|
|
||||||
});
|
});
|
||||||
|
const selectedSku = computed(() => selectedProduct.value?.skuList?.find(item => item.id === props.modelValue?.skuId));
|
||||||
|
const selectedUnit = computed(() => selectedSku.value?.unitList?.find(item => item.id === props.modelValue?.skuUnitId));
|
||||||
|
const selectedProductName = computed(() => selectedProduct.value?.name || "");
|
||||||
|
const selectedSkuSpecEntries = computed(() => getProductSkuSpecEntries(selectedSku.value || { specValueIdList: [] }, selectedProduct.value?.specList || []));
|
||||||
const filteredProductList = computed(() => {
|
const filteredProductList = computed(() => {
|
||||||
const text = keyword.value.toLocaleLowerCase();
|
const text = keyword.value.toLocaleLowerCase();
|
||||||
return productList.value.filter(item => !text || `${item.name}${item.code}`.toLocaleLowerCase().includes(text));
|
return productList.value.filter(item => !text || `${item.name}${item.code}`.toLocaleLowerCase().includes(text));
|
||||||
@@ -231,6 +252,13 @@ function selectUnit(sku: ProductSku, skuUnitId: string): void {
|
|||||||
.picker-text {
|
.picker-text {
|
||||||
color: var(--td-text-color-primary);
|
color: var(--td-text-color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.selected-tags {
|
||||||
|
gap: .375rem;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.popup {
|
.popup {
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="tagList.length || unitText" class="tags">
|
||||||
|
<t-tag
|
||||||
|
v-for="tag in tagList"
|
||||||
|
:key="tag.key"
|
||||||
|
size="small"
|
||||||
|
theme="primary"
|
||||||
|
variant="light-outline"
|
||||||
|
:content="tag.content"
|
||||||
|
/>
|
||||||
|
<t-tag v-if="unitText" size="small" :content="unitText" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { ProductSkuSpecSnapshot } from "@/types/Product";
|
||||||
|
|
||||||
|
defineOptions({ name: "ProductSkuSnapshotTags" });
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
snapshot?: ProductSkuSpecSnapshot[];
|
||||||
|
fallback?: string;
|
||||||
|
unit?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>();
|
||||||
|
|
||||||
|
const tagList = computed(() => {
|
||||||
|
const result = (props.snapshot || []).flatMap((item, index) => {
|
||||||
|
const content = item.specValueName?.trim() || item.specName?.trim();
|
||||||
|
return content ? [{ key: `${item.specId || "spec"}-${item.specValueId || index}`, content }] : [];
|
||||||
|
});
|
||||||
|
if (result.length || !props.fallback?.trim()) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return [{ key: "fallback", content: props.fallback.trim() }];
|
||||||
|
});
|
||||||
|
const unitText = computed(() => props.unit?.trim() || "");
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.tags {
|
||||||
|
gap: .375rem;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
import AppNavBar from "@/components/navbar/index.vue";
|
import AppNavBar from "@/components/navbar/index.vue";
|
||||||
import PageTransition from "@/components/PageTransition.vue";
|
import PageTransition from "@/components/PageTransition.vue";
|
||||||
import { useTabBarStore } from "@/store/tabBarStore";
|
import { useTabBarStore } from "@/store/tabBarStore";
|
||||||
import { gaoRoleCodes, permissionCodes } from "@/utils/permission";
|
import { permissionCodes } from "@/utils/permission";
|
||||||
import { useUserStore } from "timi-web-api";
|
import { useUserStore } from "timi-web-api";
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
@@ -71,7 +71,22 @@ const tabList: TabItem[] = [
|
|||||||
text: "客户",
|
text: "客户",
|
||||||
permission: [permissionCodes.customer.create, permissionCodes.eventRecord.create]
|
permission: [permissionCodes.customer.create, permissionCodes.eventRecord.create]
|
||||||
},
|
},
|
||||||
{ value: "/product", icon: "layers", text: "产品", role: gaoRoleCodes.storeManager },
|
{
|
||||||
|
value: "/product",
|
||||||
|
icon: "layers",
|
||||||
|
text: "产品",
|
||||||
|
permission: [
|
||||||
|
permissionCodes.productCategory.read,
|
||||||
|
permissionCodes.product.read,
|
||||||
|
permissionCodes.productStock.read,
|
||||||
|
permissionCodes.salesOrder.read,
|
||||||
|
permissionCodes.fundRecord.read,
|
||||||
|
permissionCodes.orderFulfillment.read,
|
||||||
|
permissionCodes.packageConfig.read,
|
||||||
|
permissionCodes.stat.read
|
||||||
|
],
|
||||||
|
permissionMode: "or"
|
||||||
|
},
|
||||||
{ value: "/store", icon: "shop-3", text: "门店", permission: permissionCodes.stat.read },
|
{ value: "/store", icon: "shop-3", text: "门店", permission: permissionCodes.stat.read },
|
||||||
{ value: "/management", icon: "setting-1", text: "管理" }
|
{ value: "/management", icon: "setting-1", text: "管理" }
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -24,8 +24,27 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="main">
|
<div class="main">
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<strong v-text="getItemTitle(item)" />
|
<div class="heading">
|
||||||
<span v-text="getItemDescription(item)" />
|
<strong v-text="item.productNameSnapshot || '未命名产品'" />
|
||||||
|
<span v-text="getItemDescription(item)" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="item.skuCodeSnapshot || item.skuSpecSnapshot?.length || item.skuNameSnapshot || item.specSummarySnapshot || item.unitSnapshot"
|
||||||
|
class="sku-tags"
|
||||||
|
>
|
||||||
|
<t-tag
|
||||||
|
v-if="item.skuCodeSnapshot"
|
||||||
|
size="small"
|
||||||
|
theme="primary"
|
||||||
|
variant="light"
|
||||||
|
:content="item.skuCodeSnapshot"
|
||||||
|
/>
|
||||||
|
<product-sku-snapshot-tags
|
||||||
|
:snapshot="item.skuSpecSnapshot"
|
||||||
|
:fallback="item.skuNameSnapshot || item.specSummarySnapshot"
|
||||||
|
:unit="item.unitSnapshot"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="input">
|
<div class="input">
|
||||||
<t-input
|
<t-input
|
||||||
@@ -87,6 +106,7 @@
|
|||||||
import SalesOrderAPI from "@/api/SalesOrderAPI";
|
import SalesOrderAPI from "@/api/SalesOrderAPI";
|
||||||
import FulfillmentAPI from "@/api/FulfillmentAPI";
|
import FulfillmentAPI from "@/api/FulfillmentAPI";
|
||||||
import PhotoScroller from "@/components/PhotoScroller.vue";
|
import PhotoScroller from "@/components/PhotoScroller.vue";
|
||||||
|
import ProductSkuSnapshotTags from "@/components/ProductSkuSnapshotTags.vue";
|
||||||
import { useNavBarStore } from "@/store/navBarStore";
|
import { useNavBarStore } from "@/store/navBarStore";
|
||||||
import { useUserStore } from "timi-web-api";
|
import { useUserStore } from "timi-web-api";
|
||||||
import type { GaoOrderFulfillmentItemRequest } from "@/types/Fulfillment";
|
import type { GaoOrderFulfillmentItemRequest } from "@/types/Fulfillment";
|
||||||
@@ -140,12 +160,7 @@ function getAvailableQuantity(item: GaoSalesOrderItem): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getItemDescription(item: GaoSalesOrderItem): string {
|
function getItemDescription(item: GaoSalesOrderItem): string {
|
||||||
return ` · ${item.unitSnapshot || "业务单位"} · 剩余 ${getAvailableQuantity(item)}`;
|
return `剩余 ${getAvailableQuantity(item)}`;
|
||||||
}
|
|
||||||
|
|
||||||
function getItemTitle(item: GaoSalesOrderItem): string {
|
|
||||||
const skuText = item.skuNameSnapshot || item.specSummarySnapshot;
|
|
||||||
return `${item.productNameSnapshot || "未命名产品"}${skuText ? ` · ${skuText}` : ""}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function fillAllQuantity(item: GaoSalesOrderItem): void {
|
function fillAllQuantity(item: GaoSalesOrderItem): void {
|
||||||
@@ -281,7 +296,37 @@ onUnmounted(() => navBarStore.clearRight());
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
|
gap: .375rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.heading {
|
||||||
|
gap: .5rem;
|
||||||
|
display: flex;
|
||||||
|
min-width: 0;
|
||||||
|
align-items: baseline;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
strong {
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
color: var(--td-text-color-secondary);
|
||||||
|
font-size: .75rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sku-tags {
|
||||||
|
gap: .375rem;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.input {
|
.input {
|
||||||
|
|||||||
@@ -30,10 +30,19 @@
|
|||||||
class="item"
|
class="item"
|
||||||
v-for="item in fulfillment.itemList || []"
|
v-for="item in fulfillment.itemList || []"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
:title="getItemTitle(item)"
|
:title="item.productNameSnapshot || '未命名产品'"
|
||||||
:description="`${item.quantity} ${item.unitSnapshot}`"
|
|
||||||
:note="`待履约 ${item.reservedBefore || 0} → ${item.reservedAfter || 0}`"
|
:note="`待履约 ${item.reservedBefore || 0} → ${item.reservedAfter || 0}`"
|
||||||
>
|
>
|
||||||
|
<template #description>
|
||||||
|
<div class="item-description">
|
||||||
|
<product-sku-snapshot-tags
|
||||||
|
:snapshot="item.skuSpecSnapshot"
|
||||||
|
:fallback="item.skuNameSnapshot || item.specSummarySnapshot || '默认 SKU'"
|
||||||
|
:unit="item.unitSnapshot"
|
||||||
|
/>
|
||||||
|
<span v-text="`履约数量 ${item.quantity || 0}`" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
<template #leftIcon>
|
<template #leftIcon>
|
||||||
<t-image
|
<t-image
|
||||||
v-if="getProductPhotoURL(item)"
|
v-if="getProductPhotoURL(item)"
|
||||||
@@ -121,6 +130,7 @@ import { useUserStore } from "timi-web-api";
|
|||||||
import FulfillmentAPI from "@/api/FulfillmentAPI";
|
import FulfillmentAPI from "@/api/FulfillmentAPI";
|
||||||
import SalesOrderAPI from "@/api/SalesOrderAPI";
|
import SalesOrderAPI from "@/api/SalesOrderAPI";
|
||||||
import PhotoScroller from "@/components/PhotoScroller.vue";
|
import PhotoScroller from "@/components/PhotoScroller.vue";
|
||||||
|
import ProductSkuSnapshotTags from "@/components/ProductSkuSnapshotTags.vue";
|
||||||
import { useNavBarStore } from "@/store/navBarStore";
|
import { useNavBarStore } from "@/store/navBarStore";
|
||||||
import type { OrderFulfillment } from "@/types/Fulfillment";
|
import type { OrderFulfillment } from "@/types/Fulfillment";
|
||||||
import type { GaoSalesOrderItemType, SalesOrder } from "@/types/SalesOrder";
|
import type { GaoSalesOrderItemType, SalesOrder } from "@/types/SalesOrder";
|
||||||
@@ -175,10 +185,6 @@ const sourceItemType = computed<GaoSalesOrderItemType | undefined>(() => {
|
|||||||
return order.value?.itemList?.find(item => item.id === sourceOrderItemId)?.itemType;
|
return order.value?.itemList?.find(item => item.id === sourceOrderItemId)?.itemType;
|
||||||
});
|
});
|
||||||
|
|
||||||
function getItemTitle(item: NonNullable<OrderFulfillment["itemList"]>[number]): string {
|
|
||||||
const skuText = item.skuNameSnapshot || item.specSummarySnapshot;
|
|
||||||
return `${item.productNameSnapshot || "未命名产品"}${skuText ? ` · ${skuText}` : ""}`;
|
|
||||||
}
|
|
||||||
async function loadData(): Promise<void> {
|
async function loadData(): Promise<void> {
|
||||||
const id = String(route.params.id || "");
|
const id = String(route.params.id || "");
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
@@ -318,6 +324,13 @@ onUnmounted(() => navBarStore.clearRight());
|
|||||||
.item {
|
.item {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
|
||||||
|
.item-description {
|
||||||
|
gap: .375rem;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
.avatar {
|
.avatar {
|
||||||
width: 3.5rem;
|
width: 3.5rem;
|
||||||
height: 3.5rem;
|
height: 3.5rem;
|
||||||
|
|||||||
@@ -29,13 +29,21 @@
|
|||||||
<t-divider />
|
<t-divider />
|
||||||
<div class="product">
|
<div class="product">
|
||||||
<div class="items">
|
<div class="items">
|
||||||
<t-tag
|
<div
|
||||||
v-for="(item, index) in itemList"
|
v-for="(item, index) in itemList"
|
||||||
:key="item.id || `${item.skuId}-${item.skuUnitId}-${index}`"
|
:key="item.id || `${item.skuId}-${item.skuUnitId}-${index}`"
|
||||||
:content="getProductText(item)"
|
class="item"
|
||||||
theme="primary"
|
>
|
||||||
variant="light"
|
<div class="item-title">
|
||||||
/>
|
<strong v-text="item.productNameSnapshot || '未命名产品'" />
|
||||||
|
<span v-text="`× ${getPackageQuantityText(item.quantity)}`" />
|
||||||
|
</div>
|
||||||
|
<product-sku-snapshot-tags
|
||||||
|
:snapshot="item.skuSpecSnapshot"
|
||||||
|
:fallback="item.skuNameSnapshot || item.specSummarySnapshot || '默认 SKU'"
|
||||||
|
:unit="item.unitSnapshot"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<t-tag v-if="!itemList.length" content="暂无产品" />
|
<t-tag v-if="!itemList.length" content="暂无产品" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -64,7 +72,8 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { PassTimeLabel } from "timi-web";
|
import { PassTimeLabel } from "timi-web";
|
||||||
import type { OrderFulfillment, OrderFulfillmentItem } from "@/types/Fulfillment";
|
import ProductSkuSnapshotTags from "@/components/ProductSkuSnapshotTags.vue";
|
||||||
|
import type { OrderFulfillment } from "@/types/Fulfillment";
|
||||||
import { getCustomerPhotoURL } from "@/utils/customer";
|
import { getCustomerPhotoURL } from "@/utils/customer";
|
||||||
import { getPackageQuantityText } from "@/utils/package";
|
import { getPackageQuantityText } from "@/utils/package";
|
||||||
import { getFulfillmentStatusText, getStatusTheme } from "@/utils/salesOrder";
|
import { getFulfillmentStatusText, getStatusTheme } from "@/utils/salesOrder";
|
||||||
@@ -99,14 +108,6 @@ const totalProductQuantity = computed(() => {
|
|||||||
});
|
});
|
||||||
const productSummaryText = computed(() => `共 ${itemList.value.length} 项 · 总数量 ${totalProductQuantity.value}`);
|
const productSummaryText = computed(() => `共 ${itemList.value.length} 项 · 总数量 ${totalProductQuantity.value}`);
|
||||||
|
|
||||||
function getProductText(item: OrderFulfillmentItem): string {
|
|
||||||
const productName = item.productNameSnapshot || "未命名产品";
|
|
||||||
const skuText = item.skuNameSnapshot || item.specSummarySnapshot;
|
|
||||||
const quantity = getPackageQuantityText(item.quantity);
|
|
||||||
const unit = item.unitSnapshot ? ` ${item.unitSnapshot}` : "";
|
|
||||||
return `${productName}${skuText ? ` · ${skuText}` : ""} × ${quantity}${unit}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getUserName(user: OrderFulfillment["operator"], userId: string | undefined, fallback: string): string {
|
function getUserName(user: OrderFulfillment["operator"], userId: string | undefined, fallback: string): string {
|
||||||
return user?.nick || user?.name || userId || fallback;
|
return user?.nick || user?.name || userId || fallback;
|
||||||
}
|
}
|
||||||
@@ -178,6 +179,34 @@ function getUserName(user: OrderFulfillment["operator"], userId: string | undefi
|
|||||||
scrollbar-width: none;
|
scrollbar-width: none;
|
||||||
-webkit-overflow-scrolling: touch;
|
-webkit-overflow-scrolling: touch;
|
||||||
|
|
||||||
|
.item {
|
||||||
|
gap: .375rem;
|
||||||
|
display: flex;
|
||||||
|
min-width: 9rem;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
flex-direction: column;
|
||||||
|
white-space: normal;
|
||||||
|
|
||||||
|
.item-title {
|
||||||
|
gap: .375rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
|
||||||
|
strong {
|
||||||
|
max-width: 9rem;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
color: var(--td-text-color-secondary);
|
||||||
|
font-size: .75rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
:deep(.t-tag) {
|
:deep(.t-tag) {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,12 @@
|
|||||||
<section class="snapshot-head" v-entrance-transition>
|
<section class="snapshot-head" v-entrance-transition>
|
||||||
<div class="eyebrow">库存 SKU 快照</div>
|
<div class="eyebrow">库存 SKU 快照</div>
|
||||||
<h2 v-text="record.productNameSnapshot || '未命名产品'" />
|
<h2 v-text="record.productNameSnapshot || '未命名产品'" />
|
||||||
<div class="sku" v-text="record.skuNameSnapshot || record.specSummarySnapshot || '默认 SKU'" />
|
<product-sku-snapshot-tags
|
||||||
|
class="sku"
|
||||||
|
:snapshot="record.skuSpecSnapshot"
|
||||||
|
:fallback="record.skuNameSnapshot || record.specSummarySnapshot || '默认 SKU'"
|
||||||
|
:unit="record.unitSnapshot"
|
||||||
|
/>
|
||||||
<div class="meta">
|
<div class="meta">
|
||||||
<span v-text="record.skuCodeSnapshot || '无 SKU 编码'" />
|
<span v-text="record.skuCodeSnapshot || '无 SKU 编码'" />
|
||||||
<t-tag size="small" variant="light-outline" :theme="getOperationTheme(record.onHandDelta)" :content="getStockOperationText(record.operationType)" />
|
<t-tag size="small" variant="light-outline" :theme="getOperationTheme(record.onHandDelta)" :content="getStockOperationText(record.operationType)" />
|
||||||
@@ -132,6 +137,7 @@ import { PassTimeLabel } from "timi-web";
|
|||||||
import { useUserStore, type Attachment } from "timi-web-api";
|
import { useUserStore, type Attachment } from "timi-web-api";
|
||||||
import ProductAPI from "@/api/ProductAPI";
|
import ProductAPI from "@/api/ProductAPI";
|
||||||
import PhotoScroller from "@/components/PhotoScroller.vue";
|
import PhotoScroller from "@/components/PhotoScroller.vue";
|
||||||
|
import ProductSkuSnapshotTags from "@/components/ProductSkuSnapshotTags.vue";
|
||||||
import { useNavBarStore } from "@/store/navBarStore";
|
import { useNavBarStore } from "@/store/navBarStore";
|
||||||
import type { PhotoItem } from "@/types/Photo";
|
import type { PhotoItem } from "@/types/Photo";
|
||||||
import type { StockBizType, StockRecord } from "@/types/Product";
|
import type { StockBizType, StockRecord } from "@/types/Product";
|
||||||
|
|||||||
@@ -54,7 +54,12 @@
|
|||||||
</template>
|
</template>
|
||||||
<template #description>
|
<template #description>
|
||||||
<div class="description">
|
<div class="description">
|
||||||
<span v-text="getRecordTitle(record)" />
|
<strong v-text="record.productNameSnapshot || '未命名产品'" />
|
||||||
|
<product-sku-snapshot-tags
|
||||||
|
:snapshot="record.skuSpecSnapshot"
|
||||||
|
:fallback="record.skuNameSnapshot || record.specSummarySnapshot || '默认 SKU'"
|
||||||
|
:unit="record.unitSnapshot"
|
||||||
|
/>
|
||||||
<pass-time-label :timestamp="record.occurredAt" underline />
|
<pass-time-label :timestamp="record.occurredAt" underline />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -70,6 +75,7 @@
|
|||||||
import { type DropdownValue } from "tdesign-mobile-vue";
|
import { type DropdownValue } from "tdesign-mobile-vue";
|
||||||
import { PassTimeLabel } from "timi-web";
|
import { PassTimeLabel } from "timi-web";
|
||||||
import ProductAPI from "@/api/ProductAPI";
|
import ProductAPI from "@/api/ProductAPI";
|
||||||
|
import ProductSkuSnapshotTags from "@/components/ProductSkuSnapshotTags.vue";
|
||||||
import { useNavBarStore } from "@/store/navBarStore";
|
import { useNavBarStore } from "@/store/navBarStore";
|
||||||
import type { StockOperationType, StockRecord, StockRecordDirection } from "@/types/Product";
|
import type { StockOperationType, StockRecord, StockRecordDirection } from "@/types/Product";
|
||||||
import { getErrorMessage } from "@/utils/error";
|
import { getErrorMessage } from "@/utils/error";
|
||||||
@@ -157,11 +163,6 @@ function getStockQuantityText(record: StockRecord): string {
|
|||||||
return `${getProductQuantityText(record.quantity)}${unitText}`;
|
return `${getProductQuantityText(record.quantity)}${unitText}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRecordTitle(record: StockRecord): string {
|
|
||||||
const skuText = record.skuNameSnapshot || record.specSummarySnapshot;
|
|
||||||
return `${record.productNameSnapshot || "未命名产品"}${skuText ? ` · ${skuText}` : ""}${record.unitSnapshot ? ` / ${record.unitSnapshot}` : ""}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDeltaText(value?: number | string): string {
|
function getDeltaText(value?: number | string): string {
|
||||||
const amount = Number(value || 0);
|
const amount = Number(value || 0);
|
||||||
const text = String(value || "0").replace(/^-/, "");
|
const text = String(value || "0").replace(/^-/, "");
|
||||||
|
|||||||
@@ -96,7 +96,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="main">
|
<div class="main">
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<span class="name text-bold" v-text="getItemTitle(item)" />
|
<span class="name text-bold" v-text="item.productNameSnapshot || '未命名产品'" />
|
||||||
<t-tag
|
<t-tag
|
||||||
:content="getItemFulfillmentStatus(item).text"
|
:content="getItemFulfillmentStatus(item).text"
|
||||||
:theme="getItemFulfillmentStatus(item).theme"
|
:theme="getItemFulfillmentStatus(item).theme"
|
||||||
@@ -111,13 +111,17 @@
|
|||||||
/>
|
/>
|
||||||
<t-tag v-else size="small" content="套餐权益" />
|
<t-tag v-else size="small" content="套餐权益" />
|
||||||
<t-tag
|
<t-tag
|
||||||
v-if="item.skuCodeSnapshot || item.skuNameSnapshot || item.specSummarySnapshot"
|
v-if="item.skuCodeSnapshot"
|
||||||
size="small"
|
size="small"
|
||||||
theme="primary"
|
theme="primary"
|
||||||
variant="light"
|
variant="light"
|
||||||
:content="item.skuCodeSnapshot || item.skuNameSnapshot || item.specSummarySnapshot"
|
:content="item.skuCodeSnapshot"
|
||||||
|
/>
|
||||||
|
<product-sku-snapshot-tags
|
||||||
|
:snapshot="item.skuSpecSnapshot"
|
||||||
|
:fallback="item.skuNameSnapshot || item.specSummarySnapshot"
|
||||||
|
:unit="item.unitSnapshot"
|
||||||
/>
|
/>
|
||||||
<t-tag size="small" theme="primary" variant="light" :content="item.unitSnapshot" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -216,6 +220,7 @@ import FulfillmentAPI from "@/api/FulfillmentAPI";
|
|||||||
import SalesOrderAPI from "@/api/SalesOrderAPI";
|
import SalesOrderAPI from "@/api/SalesOrderAPI";
|
||||||
import CustomerProfileCard from "@/components/CustomerProfileCard.vue";
|
import CustomerProfileCard from "@/components/CustomerProfileCard.vue";
|
||||||
import PhotoScroller from "@/components/PhotoScroller.vue";
|
import PhotoScroller from "@/components/PhotoScroller.vue";
|
||||||
|
import ProductSkuSnapshotTags from "@/components/ProductSkuSnapshotTags.vue";
|
||||||
import { useNavBarStore } from "@/store/navBarStore";
|
import { useNavBarStore } from "@/store/navBarStore";
|
||||||
import type { GaoSalesOrderItem, GaoSalesPaymentStatus, SalesOrder } from "@/types/SalesOrder";
|
import type { GaoSalesOrderItem, GaoSalesPaymentStatus, SalesOrder } from "@/types/SalesOrder";
|
||||||
import type { PhotoItem } from "@/types/Photo";
|
import type { PhotoItem } from "@/types/Photo";
|
||||||
@@ -257,11 +262,6 @@ const canCancel = computed(() => !!order.value && order.value.status === "ACTIVE
|
|||||||
const canFulfillByPayment = computed(() => order.value?.paymentStatus === "PAID" || (order.value?.paymentStatus === "PARTIALLY_PAID" && canOperateStock.value));
|
const canFulfillByPayment = computed(() => order.value?.paymentStatus === "PAID" || (order.value?.paymentStatus === "PARTIALLY_PAID" && canOperateStock.value));
|
||||||
const photoURLList = computed(() => photoItems.value.map(item => item.url));
|
const photoURLList = computed(() => photoItems.value.map(item => item.url));
|
||||||
|
|
||||||
function getItemTitle(item: GaoSalesOrderItem): string {
|
|
||||||
const skuText = item.skuNameSnapshot || item.specSummarySnapshot;
|
|
||||||
return `${item.productNameSnapshot || "未命名产品"}${skuText ? ` · ${skuText}` : ""}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function toPhotoItems(attachmentList?: readonly Partial<Attachment>[]): PhotoItem[] {
|
function toPhotoItems(attachmentList?: readonly Partial<Attachment>[]): PhotoItem[] {
|
||||||
return (attachmentList || [])
|
return (attachmentList || [])
|
||||||
.filter(item => !!item.id)
|
.filter(item => !!item.id)
|
||||||
@@ -594,6 +594,8 @@ onUnmounted(() => navBarStore.clearRight());
|
|||||||
.description {
|
.description {
|
||||||
gap: .5rem;
|
gap: .5rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { Attachment, Model, PageResult, User } from "timi-web-api";
|
import type { Attachment, Model, PageResult, User } from "timi-web-api";
|
||||||
import type { Customer } from "@/types/Customer";
|
import type { Customer } from "@/types/Customer";
|
||||||
|
import type { ProductSkuSpecSnapshot, StockRecord } from "@/types/Product";
|
||||||
|
|
||||||
export type GaoFulfillmentStatus = "WAITING_STOCK" | "PENDING" | "VERIFIED" | "REJECTED" | "CANCELLED" | "REVOKED";
|
export type GaoFulfillmentStatus = "WAITING_STOCK" | "PENDING" | "VERIFIED" | "REJECTED" | "CANCELLED" | "REVOKED";
|
||||||
|
|
||||||
@@ -20,6 +21,7 @@ export type OrderFulfillmentItem = {
|
|||||||
skuCodeSnapshot?: string;
|
skuCodeSnapshot?: string;
|
||||||
skuNameSnapshot?: string;
|
skuNameSnapshot?: string;
|
||||||
specSummarySnapshot?: string;
|
specSummarySnapshot?: string;
|
||||||
|
skuSpecSnapshot?: ProductSkuSpecSnapshot[];
|
||||||
unitSnapshot?: string;
|
unitSnapshot?: string;
|
||||||
baseQuantityPerUnitSnapshot?: number | string;
|
baseQuantityPerUnitSnapshot?: number | string;
|
||||||
} & Model;
|
} & Model;
|
||||||
@@ -42,7 +44,7 @@ export type OrderFulfillment = {
|
|||||||
verifier?: Partial<User>;
|
verifier?: Partial<User>;
|
||||||
sourceFulfillment?: OrderFulfillment;
|
sourceFulfillment?: OrderFulfillment;
|
||||||
itemList?: OrderFulfillmentItem[];
|
itemList?: OrderFulfillmentItem[];
|
||||||
stockRecordList?: Record<string, unknown>[];
|
stockRecordList?: StockRecord[];
|
||||||
attachmentList?: Partial<Attachment>[];
|
attachmentList?: Partial<Attachment>[];
|
||||||
} & Model;
|
} & Model;
|
||||||
|
|
||||||
|
|||||||
@@ -69,6 +69,13 @@ export type ProductSku = {
|
|||||||
attachmentList?: Attachment[];
|
attachmentList?: Attachment[];
|
||||||
} & Model;
|
} & Model;
|
||||||
|
|
||||||
|
export type ProductSkuSpecSnapshot = {
|
||||||
|
specId?: string;
|
||||||
|
specName?: string;
|
||||||
|
specValueId?: string;
|
||||||
|
specValueName?: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type Product = {
|
export type Product = {
|
||||||
storeId?: string;
|
storeId?: string;
|
||||||
categoryId?: string;
|
categoryId?: string;
|
||||||
@@ -143,6 +150,7 @@ export type StockRecord = {
|
|||||||
skuCodeSnapshot?: string;
|
skuCodeSnapshot?: string;
|
||||||
skuNameSnapshot?: string;
|
skuNameSnapshot?: string;
|
||||||
specSummarySnapshot?: string;
|
specSummarySnapshot?: string;
|
||||||
|
skuSpecSnapshot?: ProductSkuSpecSnapshot[];
|
||||||
unitSnapshot?: string;
|
unitSnapshot?: string;
|
||||||
baseQuantityPerUnitSnapshot?: number | string;
|
baseQuantityPerUnitSnapshot?: number | string;
|
||||||
quantity?: number | string;
|
quantity?: number | string;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { Attachment, Model, PageResult, User } from "timi-web-api";
|
import type { Attachment, Model, PageResult, User } from "timi-web-api";
|
||||||
import type { Customer } from "@/types/Customer";
|
import type { Customer } from "@/types/Customer";
|
||||||
import type { EventRecord } from "@/types/EventRecord";
|
import type { EventRecord } from "@/types/EventRecord";
|
||||||
|
import type { ProductSkuSpecSnapshot } from "@/types/Product";
|
||||||
|
|
||||||
export type GaoSalesOrderType = "PACKAGE" | "DIRECT_PRODUCT";
|
export type GaoSalesOrderType = "PACKAGE" | "DIRECT_PRODUCT";
|
||||||
export type GaoSalesOrderStatus = "ACTIVE" | "FULFILLED" | "CANCELLED";
|
export type GaoSalesOrderStatus = "ACTIVE" | "FULFILLED" | "CANCELLED";
|
||||||
@@ -22,6 +23,7 @@ export type GaoSalesOrderItem = {
|
|||||||
skuCodeSnapshot?: string;
|
skuCodeSnapshot?: string;
|
||||||
skuNameSnapshot?: string;
|
skuNameSnapshot?: string;
|
||||||
specSummarySnapshot?: string;
|
specSummarySnapshot?: string;
|
||||||
|
skuSpecSnapshot?: ProductSkuSpecSnapshot[];
|
||||||
unitSnapshot?: string;
|
unitSnapshot?: string;
|
||||||
baseQuantityPerUnitSnapshot?: number | string;
|
baseQuantityPerUnitSnapshot?: number | string;
|
||||||
quantity?: number | string;
|
quantity?: number | string;
|
||||||
|
|||||||
Reference in New Issue
Block a user