diff --git a/src/api/GaoProductSkuChartAPI.ts b/src/api/GaoProductSkuChartAPI.ts new file mode 100644 index 0000000..5ffe28e --- /dev/null +++ b/src/api/GaoProductSkuChartAPI.ts @@ -0,0 +1,58 @@ +import { axios } from "timi-web-api"; +import type { + GaoProductChartReq, + GaoProductSkuFulfillmentDailyStatView, + GaoProductSkuFulfillmentRankView, + GaoProductSkuSalesDailyStatView, + GaoProductSkuSalesRankView, + GaoProductSkuStockHealthView, + GaoProductSkuStockMovementDailyStatView, + GaoProductSkuUnitSalesRankView +} from "@/types/GaoChart"; +import { getStoreChartCache } from "@/utils/storeChartCache"; +import { withCurrentStoreId } from "@/utils/store"; + +const BASE_URI = "/gao/product/sku/chart"; + +async function salesDaily(req: GaoProductChartReq): Promise { + return await requestList("sales-daily", "/sales/daily", req); +} + +async function salesRank(req: GaoProductChartReq): Promise { + return await requestList("sales-rank", "/sales/rank", req); +} + +async function unitSalesRank(req: GaoProductChartReq): Promise { + return await requestList("sales-unit-rank", "/sales/unit/rank", req); +} + +async function fulfillmentDaily(req: GaoProductChartReq): Promise { + return await requestList("fulfillment-daily", "/fulfillment/daily", req); +} + +async function fulfillmentRank(req: GaoProductChartReq): Promise { + return await requestList("fulfillment-rank", "/fulfillment/rank", req); +} + +async function stockMovementDaily(req: GaoProductChartReq): Promise { + return await requestList("stock-movement-daily", "/stock/movement/daily", req); +} + +async function stockHealth(req: GaoProductChartReq): Promise { + return await requestList("stock-health", "/stock/health", req); +} + +async function requestList(name: string, path: string, req: GaoProductChartReq): Promise { + const request = withCurrentStoreId(req); + return await getStoreChartCache(`gao-product-sku:${name}:${JSON.stringify(request)}`, async () => await axios.post(`${BASE_URI}${path}`, request)); +} + +export default { + salesDaily, + salesRank, + unitSalesRank, + fulfillmentDaily, + fulfillmentRank, + stockMovementDaily, + stockHealth +}; diff --git a/src/api/GaoSalesOrderChartAPI.ts b/src/api/GaoSalesOrderChartAPI.ts index 24100ab..b9a71b4 100644 --- a/src/api/GaoSalesOrderChartAPI.ts +++ b/src/api/GaoSalesOrderChartAPI.ts @@ -2,6 +2,7 @@ import { axios } from "timi-web-api"; import type { GaoCustomerOrderProductRankView, GaoCustomerOrderRankView, + GaoCustomerOrderSkuRankView, GaoCustomerRepeatDailyStatView, GaoCustomerRepeatIntervalStatView, GaoSalesOrderCalendarStatView, @@ -47,6 +48,10 @@ async function customerProductRank(req: GaoSalesOrderChartReq): Promise { + return await requestList("customer-sku-rank", "/customer/sku/rank", req); +} + async function consumptionDaily(req: GaoSalesOrderChartReq): Promise { return await requestList("customer-consumption-daily", "/customer/consumption/daily", req); } @@ -76,6 +81,7 @@ export default { receivable, customerRank, customerProductRank, + customerSkuRank, consumptionDaily, calendar, repeatDaily, diff --git a/src/api/ProductAPI.ts b/src/api/ProductAPI.ts index 64521eb..a2bd317 100644 --- a/src/api/ProductAPI.ts +++ b/src/api/ProductAPI.ts @@ -5,7 +5,11 @@ import type { ProductCategoryPage, ProductPage, ProductPageResult, + ProductSku, + ProductSpec, + ProductSpecValue, ProductStock, + ProductStockProduct, ProductStockChangeRequest, ProductStockManualRevokeRequest, ProductStockPage, @@ -57,6 +61,34 @@ async function disable(id: string): Promise { await axios.post(`${PRODUCT_URI}/disable`, undefined, { params: { id } }); } +async function skuDetail(id: string): Promise { + return await axios.post(`${PRODUCT_URI}/sku/detail`, undefined, { params: { id } }); +} + +async function skuCreate(sku: ProductSku): Promise { + return await axios.post(`${PRODUCT_URI}/sku/create`, withCurrentStoreId(sku)); +} + +async function skuUpdate(sku: ProductSku): Promise { + await axios.post(`${PRODUCT_URI}/sku/update`, withCurrentStoreId(sku)); +} + +async function skuDisable(id: string): Promise { + await axios.post(`${PRODUCT_URI}/sku/disable`, undefined, { params: { id } }); +} + +async function specList(productId: string): Promise { + return await axios.post(`${PRODUCT_URI}/spec/list`, undefined, { params: { productId } }); +} + +async function specCreate(spec: ProductSpec): Promise { + return await axios.post(`${PRODUCT_URI}/spec/create`, spec); +} + +async function specValueCreate(value: ProductSpecValue): Promise { + return await axios.post(`${PRODUCT_URI}/spec/value/create`, value); +} + async function sellableList(page: ProductPage): Promise { return await axios.post(`${PRODUCT_URI}/sellable/list`, withCurrentStoreId(page)); } @@ -65,9 +97,13 @@ async function stockList(page: ProductStockPage): Promise { +async function stockProductList(page: ProductStockPage): Promise> { + return await axios.post(`${STOCK_URI}/product-list`, withCurrentStoreId(page)); +} + +async function stockDetail(skuId: string, storeId?: string): Promise { return await axios.post(`${STOCK_URI}/detail`, undefined, { - params: { productId, storeId: storeId || getCurrentStoreId() } + params: { skuId, storeId: storeId || getCurrentStoreId() } }); } @@ -114,8 +150,16 @@ export default { create, update, disable, + skuDetail, + skuCreate, + skuUpdate, + skuDisable, + specList, + specCreate, + specValueCreate, sellableList, stockList, + stockProductList, stockDetail, initialIn, manualIn, diff --git a/src/components/ProductSelector.vue b/src/components/ProductSelector.vue index 8ad29eb..a1fa6c9 100644 --- a/src/components/ProductSelector.vue +++ b/src/components/ProductSelector.vue @@ -11,47 +11,83 @@
- - - - - + + + + + +
@@ -77,6 +84,7 @@ import type { EventRecordCalendarListPayload } from "@/components/EventRecordCal import CustomerChartSummary from "@/pages/customer/component/CustomerChartSummary.vue"; import CustomerEventRecordCalendarSection from "@/pages/customer/component/CustomerEventRecordCalendarSection.vue"; import CustomerEventStructureChart from "@/pages/customer/component/CustomerEventStructureChart.vue"; +import CustomerOrderCharts from "@/pages/customer/component/CustomerOrderCharts.vue"; import CustomerRecordTrendChart from "@/pages/customer/component/CustomerRecordTrendChart.vue"; import { useSettingStore } from "@/store/settingStore"; import type { Customer } from "@/types/Customer"; diff --git a/src/pages/customer/component/CustomerConsumptionDailyChart.vue b/src/pages/customer/component/CustomerConsumptionDailyChart.vue new file mode 100644 index 0000000..2ddb73b --- /dev/null +++ b/src/pages/customer/component/CustomerConsumptionDailyChart.vue @@ -0,0 +1,77 @@ + + + diff --git a/src/pages/customer/component/CustomerOrderCalendarChart.vue b/src/pages/customer/component/CustomerOrderCalendarChart.vue new file mode 100644 index 0000000..5577fac --- /dev/null +++ b/src/pages/customer/component/CustomerOrderCalendarChart.vue @@ -0,0 +1,77 @@ + + + diff --git a/src/pages/customer/component/CustomerOrderCharts.vue b/src/pages/customer/component/CustomerOrderCharts.vue new file mode 100644 index 0000000..4db0af2 --- /dev/null +++ b/src/pages/customer/component/CustomerOrderCharts.vue @@ -0,0 +1,223 @@ + + + + + diff --git a/src/pages/customer/component/CustomerOrderProductRankChart.vue b/src/pages/customer/component/CustomerOrderProductRankChart.vue new file mode 100644 index 0000000..ee0f7d4 --- /dev/null +++ b/src/pages/customer/component/CustomerOrderProductRankChart.vue @@ -0,0 +1,81 @@ + + + diff --git a/src/pages/customer/component/CustomerOrderSkuRankChart.vue b/src/pages/customer/component/CustomerOrderSkuRankChart.vue new file mode 100644 index 0000000..1bdce8c --- /dev/null +++ b/src/pages/customer/component/CustomerOrderSkuRankChart.vue @@ -0,0 +1,81 @@ + + + diff --git a/src/pages/customer/component/CustomerRepeatDailyChart.vue b/src/pages/customer/component/CustomerRepeatDailyChart.vue new file mode 100644 index 0000000..9469500 --- /dev/null +++ b/src/pages/customer/component/CustomerRepeatDailyChart.vue @@ -0,0 +1,61 @@ + + + diff --git a/src/pages/customer/component/CustomerRepeatIntervalChart.vue b/src/pages/customer/component/CustomerRepeatIntervalChart.vue new file mode 100644 index 0000000..96be3ff --- /dev/null +++ b/src/pages/customer/component/CustomerRepeatIntervalChart.vue @@ -0,0 +1,43 @@ + + + diff --git a/src/pages/fulfillment/OrderFulfillmentCreate.vue b/src/pages/fulfillment/OrderFulfillmentCreate.vue index ba81150..9ebd8b8 100644 --- a/src/pages/fulfillment/OrderFulfillmentCreate.vue +++ b/src/pages/fulfillment/OrderFulfillmentCreate.vue @@ -24,7 +24,7 @@
- +
@@ -140,7 +140,12 @@ function getAvailableQuantity(item: GaoSalesOrderItem): string { } function getItemDescription(item: GaoSalesOrderItem): string { - return ` · 剩余 ${getAvailableQuantity(item)}`; + return ` · ${item.unitSnapshot || "业务单位"} · 剩余 ${getAvailableQuantity(item)}`; +} + +function getItemTitle(item: GaoSalesOrderItem): string { + const skuText = item.skuNameSnapshot || item.specSummarySnapshot; + return `${item.productNameSnapshot || "未命名产品"}${skuText ? ` · ${skuText}` : ""}`; } function fillAllQuantity(item: GaoSalesOrderItem): void { diff --git a/src/pages/fulfillment/OrderFulfillmentDetail.vue b/src/pages/fulfillment/OrderFulfillmentDetail.vue index 8f2f89b..b8eeb96 100644 --- a/src/pages/fulfillment/OrderFulfillmentDetail.vue +++ b/src/pages/fulfillment/OrderFulfillmentDetail.vue @@ -30,7 +30,7 @@ class="item" v-for="item in fulfillment.itemList || []" :key="item.id" - :title="item.productNameSnapshot" + :title="getItemTitle(item)" :description="`${item.quantity} ${item.unitSnapshot}`" :note="`待履约 ${item.reservedBefore || 0} → ${item.reservedAfter || 0}`" > @@ -174,6 +174,11 @@ const sourceItemType = computed(() => { const sourceOrderItemId = fulfillment.value?.itemList?.[0]?.sourceOrderItemId; return order.value?.itemList?.find(item => item.id === sourceOrderItemId)?.itemType; }); + +function getItemTitle(item: NonNullable[number]): string { + const skuText = item.skuNameSnapshot || item.specSummarySnapshot; + return `${item.productNameSnapshot || "未命名产品"}${skuText ? ` · ${skuText}` : ""}`; +} async function loadData(): Promise { const id = String(route.params.id || ""); if (!id) return; diff --git a/src/pages/fulfillment/components/OrderFulfillmentListCell.vue b/src/pages/fulfillment/components/OrderFulfillmentListCell.vue index d113d61..b890325 100644 --- a/src/pages/fulfillment/components/OrderFulfillmentListCell.vue +++ b/src/pages/fulfillment/components/OrderFulfillmentListCell.vue @@ -31,7 +31,7 @@
`共 ${itemList.value.length} 项 · 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} × ${quantity}${unit}`; + return `${productName}${skuText ? ` · ${skuText}` : ""} × ${quantity}${unit}`; } function getUserName(user: OrderFulfillment["operator"], userId: string | undefined, fallback: string): string { diff --git a/src/pages/package/PackageConfigDetail.vue b/src/pages/package/PackageConfigDetail.vue index 539f811..d722bd7 100644 --- a/src/pages/package/PackageConfigDetail.vue +++ b/src/pages/package/PackageConfigDetail.vue @@ -30,8 +30,8 @@