add product sku
This commit is contained in:
@@ -27,43 +27,50 @@
|
||||
</div>
|
||||
<customer-profile-card v-entrance-transition class="customer" :customer="selectedCustomer" />
|
||||
<div class="charts">
|
||||
<customer-chart-summary
|
||||
v-entrance-transition="70"
|
||||
v-if="selectedCustomer"
|
||||
:customer="selectedCustomer"
|
||||
:month-cursor="monthCursor"
|
||||
:selected-event-id-list="selectedEventIdList"
|
||||
/>
|
||||
<customer-event-record-calendar-section
|
||||
v-entrance-transition="140"
|
||||
v-if="selectedCustomer"
|
||||
v-model="monthCursor"
|
||||
v-model:selected-event-id-list="selectedEventIdList"
|
||||
:customer-id="selectedCustomer.id || ''"
|
||||
:customer-name="selectedCustomer.name"
|
||||
:store-id="selectedCustomer.storeId"
|
||||
@list="toEventRecordList"
|
||||
/>
|
||||
<customer-record-trend-chart
|
||||
v-entrance-transition="210"
|
||||
v-if="selectedCustomer"
|
||||
:customer-id="selectedCustomer.id || ''"
|
||||
:date-text-list="chartDateTextList"
|
||||
:selected-event-id-list="selectedEventIdList"
|
||||
/>
|
||||
<customer-event-structure-chart
|
||||
v-entrance-transition="280"
|
||||
v-if="selectedCustomer"
|
||||
:customer-id="selectedCustomer.id || ''"
|
||||
:date-text-list="monthDateTextList"
|
||||
:selected-event-id-list="selectedEventIdList"
|
||||
/>
|
||||
<customer-selector
|
||||
v-entrance-transition="350"
|
||||
v-model="selectedCustomerId"
|
||||
v-model:visible="selectorVisible"
|
||||
@change="handleCustomerSelected"
|
||||
/>
|
||||
<customer-order-charts
|
||||
v-entrance-transition="35"
|
||||
v-if="selectedCustomer"
|
||||
:customer-id="selectedCustomer.id || ''"
|
||||
:date-text-list="chartDateTextList"
|
||||
:store-id="selectedCustomer.storeId"
|
||||
/>
|
||||
<customer-chart-summary
|
||||
v-entrance-transition="70"
|
||||
v-if="selectedCustomer"
|
||||
:customer="selectedCustomer"
|
||||
:month-cursor="monthCursor"
|
||||
:selected-event-id-list="selectedEventIdList"
|
||||
/>
|
||||
<customer-event-record-calendar-section
|
||||
v-entrance-transition="140"
|
||||
v-if="selectedCustomer"
|
||||
v-model="monthCursor"
|
||||
v-model:selected-event-id-list="selectedEventIdList"
|
||||
:customer-id="selectedCustomer.id || ''"
|
||||
:customer-name="selectedCustomer.name"
|
||||
:store-id="selectedCustomer.storeId"
|
||||
@list="toEventRecordList"
|
||||
/>
|
||||
<customer-record-trend-chart
|
||||
v-entrance-transition="210"
|
||||
v-if="selectedCustomer"
|
||||
:customer-id="selectedCustomer.id || ''"
|
||||
:date-text-list="chartDateTextList"
|
||||
:selected-event-id-list="selectedEventIdList"
|
||||
/>
|
||||
<customer-event-structure-chart
|
||||
v-entrance-transition="280"
|
||||
v-if="selectedCustomer"
|
||||
:customer-id="selectedCustomer.id || ''"
|
||||
:date-text-list="monthDateTextList"
|
||||
:selected-event-id-list="selectedEventIdList"
|
||||
/>
|
||||
<customer-selector
|
||||
v-entrance-transition="350"
|
||||
v-model="selectedCustomerId"
|
||||
v-model:visible="selectorVisible"
|
||||
@change="handleCustomerSelected"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -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";
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
<template>
|
||||
<gao-chart-card
|
||||
title="有效消费趋势"
|
||||
caption="金额已换算为元"
|
||||
:date-text-list="dateTextList"
|
||||
:option="buildOption"
|
||||
:loading="loading"
|
||||
:has-data="list.length > 0"
|
||||
height="18rem"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { EChartsOption } from "echarts";
|
||||
import GaoChartCard from "@/pages/product/component/GaoChartCard.vue";
|
||||
import type { GaoSalesOrderConsumptionDailyStatView } from "@/types/GaoChart";
|
||||
import { buildAxisChartOption, getDailyItem, getNumber } from "@/pages/product/component/chartUtils";
|
||||
|
||||
defineOptions({
|
||||
name: "CustomerConsumptionDailyChart"
|
||||
});
|
||||
|
||||
const props = defineProps<{
|
||||
dateTextList: string[];
|
||||
list: GaoSalesOrderConsumptionDailyStatView[];
|
||||
loading: boolean;
|
||||
}>();
|
||||
|
||||
function buildOption(visibleDateTextList: string[]): EChartsOption {
|
||||
const labelList = visibleDateTextList.map(item => item.slice(5));
|
||||
return {
|
||||
...buildAxisChartOption(labelList),
|
||||
xAxis: {
|
||||
type: "category",
|
||||
data: labelList,
|
||||
axisTick: { show: false },
|
||||
axisLabel: { color: "#52607a", interval: "auto", hideOverlap: true }
|
||||
},
|
||||
yAxis: [
|
||||
{
|
||||
type: "value",
|
||||
name: "记录",
|
||||
axisLabel: { color: "#52607a" },
|
||||
splitLine: { lineStyle: { color: "rgba(148, 163, 184, .2)" } }
|
||||
},
|
||||
{
|
||||
type: "value",
|
||||
name: "元",
|
||||
axisLabel: { color: "#52607a" },
|
||||
splitLine: { show: false }
|
||||
}
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: "消费记录",
|
||||
type: "bar",
|
||||
yAxisIndex: 0,
|
||||
data: visibleDateTextList.map(dateText => getNumber(getDailyItem(props.list, dateText)?.recordCount))
|
||||
},
|
||||
{
|
||||
name: "当日消费",
|
||||
type: "line",
|
||||
yAxisIndex: 1,
|
||||
smooth: true,
|
||||
data: visibleDateTextList.map(dateText => getNumber(getDailyItem(props.list, dateText)?.amount) / 100)
|
||||
},
|
||||
{
|
||||
name: "累计消费",
|
||||
type: "line",
|
||||
yAxisIndex: 1,
|
||||
smooth: true,
|
||||
data: visibleDateTextList.map(dateText => getNumber(getDailyItem(props.list, dateText)?.cumulativeAmount) / 100)
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,77 @@
|
||||
<template>
|
||||
<gao-chart-card
|
||||
title="订单日历"
|
||||
caption="按下单日期统计"
|
||||
:date-text-list="dateTextList"
|
||||
:option="buildOption"
|
||||
:loading="loading"
|
||||
:has-data="list.length > 0"
|
||||
height="18rem"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { EChartsOption } from "echarts";
|
||||
import GaoChartCard from "@/pages/product/component/GaoChartCard.vue";
|
||||
import type { GaoSalesOrderCalendarStatView } from "@/types/GaoChart";
|
||||
import { buildAxisChartOption, getDailyItem, getNumber } from "@/pages/product/component/chartUtils";
|
||||
|
||||
defineOptions({
|
||||
name: "CustomerOrderCalendarChart"
|
||||
});
|
||||
|
||||
const props = defineProps<{
|
||||
dateTextList: string[];
|
||||
list: GaoSalesOrderCalendarStatView[];
|
||||
loading: boolean;
|
||||
}>();
|
||||
|
||||
function buildOption(visibleDateTextList: string[]): EChartsOption {
|
||||
const labelList = visibleDateTextList.map(item => item.slice(5));
|
||||
return {
|
||||
...buildAxisChartOption(labelList),
|
||||
xAxis: {
|
||||
type: "category",
|
||||
data: labelList,
|
||||
axisTick: { show: false },
|
||||
axisLabel: { color: "#52607a", interval: "auto", hideOverlap: true }
|
||||
},
|
||||
yAxis: [
|
||||
{
|
||||
type: "value",
|
||||
name: "订单",
|
||||
axisLabel: { color: "#52607a" },
|
||||
splitLine: { lineStyle: { color: "rgba(148, 163, 184, .2)" } }
|
||||
},
|
||||
{
|
||||
type: "value",
|
||||
name: "元",
|
||||
axisLabel: { color: "#52607a" },
|
||||
splitLine: { show: false }
|
||||
}
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: "订单数",
|
||||
type: "bar",
|
||||
yAxisIndex: 0,
|
||||
data: visibleDateTextList.map(dateText => getNumber(getDailyItem(props.list, dateText)?.orderCount))
|
||||
},
|
||||
{
|
||||
name: "成交金额",
|
||||
type: "line",
|
||||
yAxisIndex: 1,
|
||||
smooth: true,
|
||||
data: visibleDateTextList.map(dateText => getNumber(getDailyItem(props.list, dateText)?.saleAmount) / 100)
|
||||
},
|
||||
{
|
||||
name: "净收款",
|
||||
type: "line",
|
||||
yAxisIndex: 1,
|
||||
smooth: true,
|
||||
data: visibleDateTextList.map(dateText => getNumber(getDailyItem(props.list, dateText)?.netPaidAmount) / 100)
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,223 @@
|
||||
<template>
|
||||
<section class="order-charts">
|
||||
<div class="section-heading">
|
||||
<div>
|
||||
<div class="eyebrow" v-text="'订单经营分析'" />
|
||||
<h2 v-text="'客户订单看板'" />
|
||||
</div>
|
||||
<span v-text="'金额单位:元,数量单位:基础单位'" />
|
||||
</div>
|
||||
<order-daily-chart
|
||||
:date-text-list="dateTextList"
|
||||
:list="dailyList"
|
||||
:loading="isLoading"
|
||||
/>
|
||||
<div class="two-columns">
|
||||
<customer-order-product-rank-chart
|
||||
:list="productRankList"
|
||||
:loading="isLoading"
|
||||
/>
|
||||
<customer-order-sku-rank-chart
|
||||
:list="skuRankList"
|
||||
:loading="isLoading"
|
||||
/>
|
||||
</div>
|
||||
<customer-consumption-daily-chart
|
||||
:date-text-list="dateTextList"
|
||||
:list="consumptionDailyList"
|
||||
:loading="isLoading"
|
||||
/>
|
||||
<customer-order-calendar-chart
|
||||
:date-text-list="dateTextList"
|
||||
:list="calendarList"
|
||||
:loading="isLoading"
|
||||
/>
|
||||
<div class="two-columns">
|
||||
<customer-repeat-daily-chart
|
||||
:date-text-list="dateTextList"
|
||||
:list="repeatDailyList"
|
||||
:loading="isLoading"
|
||||
/>
|
||||
<customer-repeat-interval-chart
|
||||
:list="repeatIntervalList"
|
||||
:loading="isLoading"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import GaoSalesOrderChartAPI from "@/api/GaoSalesOrderChartAPI";
|
||||
import CustomerConsumptionDailyChart from "@/pages/customer/component/CustomerConsumptionDailyChart.vue";
|
||||
import CustomerOrderCalendarChart from "@/pages/customer/component/CustomerOrderCalendarChart.vue";
|
||||
import CustomerOrderProductRankChart from "@/pages/customer/component/CustomerOrderProductRankChart.vue";
|
||||
import CustomerOrderSkuRankChart from "@/pages/customer/component/CustomerOrderSkuRankChart.vue";
|
||||
import CustomerRepeatDailyChart from "@/pages/customer/component/CustomerRepeatDailyChart.vue";
|
||||
import CustomerRepeatIntervalChart from "@/pages/customer/component/CustomerRepeatIntervalChart.vue";
|
||||
import OrderDailyChart from "@/pages/product/component/OrderDailyChart.vue";
|
||||
import type {
|
||||
GaoCustomerOrderProductRankView,
|
||||
GaoCustomerOrderSkuRankView,
|
||||
GaoCustomerRepeatDailyStatView,
|
||||
GaoCustomerRepeatIntervalStatView,
|
||||
GaoSalesOrderCalendarStatView,
|
||||
GaoSalesOrderConsumptionDailyStatView,
|
||||
GaoSalesOrderDailyStatView
|
||||
} from "@/types/GaoChart";
|
||||
import { getErrorMessage } from "@/utils/error";
|
||||
import { Message } from "@/utils/message";
|
||||
|
||||
defineOptions({
|
||||
name: "CustomerOrderCharts"
|
||||
});
|
||||
|
||||
const props = defineProps<{
|
||||
customerId: string;
|
||||
dateTextList: string[];
|
||||
storeId?: string;
|
||||
}>();
|
||||
|
||||
const dailyList = ref<GaoSalesOrderDailyStatView[]>([]);
|
||||
const productRankList = ref<GaoCustomerOrderProductRankView[]>([]);
|
||||
const skuRankList = ref<GaoCustomerOrderSkuRankView[]>([]);
|
||||
const consumptionDailyList = ref<GaoSalesOrderConsumptionDailyStatView[]>([]);
|
||||
const calendarList = ref<GaoSalesOrderCalendarStatView[]>([]);
|
||||
const repeatDailyList = ref<GaoCustomerRepeatDailyStatView[]>([]);
|
||||
const repeatIntervalList = ref<GaoCustomerRepeatIntervalStatView[]>([]);
|
||||
const isLoading = ref(false);
|
||||
let requestId = 0;
|
||||
|
||||
watch([
|
||||
() => props.customerId,
|
||||
() => props.storeId,
|
||||
() => props.dateTextList
|
||||
], async () => {
|
||||
await loadCharts();
|
||||
}, {
|
||||
deep: true,
|
||||
immediate: true
|
||||
});
|
||||
|
||||
async function loadCharts(): Promise<void> {
|
||||
if (!props.customerId || !props.dateTextList.length) {
|
||||
clearData();
|
||||
return;
|
||||
}
|
||||
|
||||
const currentRequestId = ++requestId;
|
||||
isLoading.value = true;
|
||||
try {
|
||||
const request = {
|
||||
storeId: props.storeId,
|
||||
customerId: props.customerId,
|
||||
beginAt: getDateStart(props.dateTextList[0]),
|
||||
endAt: getDateEnd(props.dateTextList[props.dateTextList.length - 1]),
|
||||
topN: 10
|
||||
};
|
||||
const [daily, productRank, skuRank, consumptionDaily, calendar, repeatDaily, repeatInterval] = await Promise.all([
|
||||
GaoSalesOrderChartAPI.daily(request),
|
||||
GaoSalesOrderChartAPI.customerProductRank(request),
|
||||
GaoSalesOrderChartAPI.customerSkuRank(request),
|
||||
GaoSalesOrderChartAPI.consumptionDaily(request),
|
||||
GaoSalesOrderChartAPI.calendar(request),
|
||||
GaoSalesOrderChartAPI.repeatDaily(request),
|
||||
GaoSalesOrderChartAPI.repeatInterval(request)
|
||||
]);
|
||||
if (currentRequestId !== requestId) {
|
||||
return;
|
||||
}
|
||||
dailyList.value = daily || [];
|
||||
productRankList.value = productRank || [];
|
||||
skuRankList.value = skuRank || [];
|
||||
consumptionDailyList.value = consumptionDaily || [];
|
||||
calendarList.value = calendar || [];
|
||||
repeatDailyList.value = repeatDaily || [];
|
||||
repeatIntervalList.value = repeatInterval || [];
|
||||
} catch (error) {
|
||||
if (currentRequestId === requestId) {
|
||||
Message.error(getErrorMessage(error, "客户订单图表加载失败"));
|
||||
}
|
||||
} finally {
|
||||
if (currentRequestId === requestId) {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function clearData(): void {
|
||||
isLoading.value = false;
|
||||
dailyList.value = [];
|
||||
productRankList.value = [];
|
||||
skuRankList.value = [];
|
||||
consumptionDailyList.value = [];
|
||||
calendarList.value = [];
|
||||
repeatDailyList.value = [];
|
||||
repeatIntervalList.value = [];
|
||||
}
|
||||
|
||||
function getDateStart(dateText: string): number {
|
||||
const [year, month, day] = dateText.split("-").map(Number);
|
||||
return new Date(year, month - 1, day, 0, 0, 0, 0).getTime();
|
||||
}
|
||||
|
||||
function getDateEnd(dateText: string): number {
|
||||
const [year, month, day] = dateText.split("-").map(Number);
|
||||
return new Date(year, month - 1, day, 23, 59, 59, 999).getTime();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.order-charts {
|
||||
gap: 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.section-heading {
|
||||
gap: .5rem;
|
||||
padding: 0 .25rem;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
color: var(--td-brand-color);
|
||||
font-size: .6875rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: .12em;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: #0f172a;
|
||||
margin: .25rem 0 0;
|
||||
font-size: 1.125rem;
|
||||
}
|
||||
|
||||
.section-heading > span {
|
||||
color: #94a3b8;
|
||||
font-size: .6875rem;
|
||||
}
|
||||
|
||||
.two-columns {
|
||||
gap: 1rem;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
:global(html.theme-dark) {
|
||||
.order-charts {
|
||||
h2 {
|
||||
color: #e2e8f0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 42rem) {
|
||||
.order-charts {
|
||||
.two-columns {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<gao-chart-card
|
||||
title="客户产品购买排行"
|
||||
caption="基础单位购买及履约进度,前 10"
|
||||
:option="chartOption"
|
||||
:loading="loading"
|
||||
:has-data="list.length > 0"
|
||||
height="20rem"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { EChartsOption } from "echarts";
|
||||
import GaoChartCard from "@/pages/product/component/GaoChartCard.vue";
|
||||
import type { GaoCustomerOrderProductRankView } from "@/types/GaoChart";
|
||||
import { buildHorizontalBarOption, getNumber } from "@/pages/product/component/chartUtils";
|
||||
|
||||
defineOptions({
|
||||
name: "CustomerOrderProductRankChart"
|
||||
});
|
||||
|
||||
const props = defineProps<{
|
||||
list: GaoCustomerOrderProductRankView[];
|
||||
loading: boolean;
|
||||
}>();
|
||||
|
||||
const chartOption = computed<EChartsOption>(() => {
|
||||
const list = props.list.slice(0, 10);
|
||||
return {
|
||||
...buildHorizontalBarOption(list.map(getProductName)),
|
||||
legend: {
|
||||
top: 0,
|
||||
icon: "roundRect",
|
||||
textStyle: { color: "#52607a" }
|
||||
},
|
||||
grid: {
|
||||
top: 32,
|
||||
left: 8,
|
||||
right: 18,
|
||||
bottom: 12,
|
||||
containLabel: true
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: "直接购买",
|
||||
type: "bar",
|
||||
stack: "quantity",
|
||||
data: list.map(item => getNumber(item.directBaseQuantity))
|
||||
},
|
||||
{
|
||||
name: "套餐权益",
|
||||
type: "bar",
|
||||
stack: "quantity",
|
||||
data: list.map(item => getNumber(item.packageBaseQuantity))
|
||||
},
|
||||
{
|
||||
name: "待履约",
|
||||
type: "bar",
|
||||
stack: "fulfillment",
|
||||
data: list.map(item => getNumber(item.reservedBaseQuantity))
|
||||
},
|
||||
{
|
||||
name: "已履约",
|
||||
type: "bar",
|
||||
stack: "fulfillment",
|
||||
data: list.map(item => getNumber(item.fulfilledBaseQuantity))
|
||||
},
|
||||
{
|
||||
name: "可履约",
|
||||
type: "bar",
|
||||
stack: "fulfillment",
|
||||
data: list.map(item => getNumber(item.availableBaseQuantity))
|
||||
}
|
||||
]
|
||||
};
|
||||
});
|
||||
|
||||
function getProductName(item: GaoCustomerOrderProductRankView): string {
|
||||
return item.productName || "未命名产品";
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<gao-chart-card
|
||||
title="客户 SKU 购买排行"
|
||||
caption="基础单位购买及履约进度,前 10"
|
||||
:option="chartOption"
|
||||
:loading="loading"
|
||||
:has-data="list.length > 0"
|
||||
height="20rem"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { EChartsOption } from "echarts";
|
||||
import GaoChartCard from "@/pages/product/component/GaoChartCard.vue";
|
||||
import type { GaoCustomerOrderSkuRankView } from "@/types/GaoChart";
|
||||
import { buildHorizontalBarOption, getNumber } from "@/pages/product/component/chartUtils";
|
||||
|
||||
defineOptions({
|
||||
name: "CustomerOrderSkuRankChart"
|
||||
});
|
||||
|
||||
const props = defineProps<{
|
||||
list: GaoCustomerOrderSkuRankView[];
|
||||
loading: boolean;
|
||||
}>();
|
||||
|
||||
const chartOption = computed<EChartsOption>(() => {
|
||||
const list = props.list.slice(0, 10);
|
||||
return {
|
||||
...buildHorizontalBarOption(list.map(getSkuName)),
|
||||
legend: {
|
||||
top: 0,
|
||||
icon: "roundRect",
|
||||
textStyle: { color: "#52607a" }
|
||||
},
|
||||
grid: {
|
||||
top: 32,
|
||||
left: 8,
|
||||
right: 18,
|
||||
bottom: 12,
|
||||
containLabel: true
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: "直接购买",
|
||||
type: "bar",
|
||||
stack: "quantity",
|
||||
data: list.map(item => getNumber(item.directBaseQuantity))
|
||||
},
|
||||
{
|
||||
name: "套餐权益",
|
||||
type: "bar",
|
||||
stack: "quantity",
|
||||
data: list.map(item => getNumber(item.packageBaseQuantity))
|
||||
},
|
||||
{
|
||||
name: "待履约",
|
||||
type: "bar",
|
||||
stack: "fulfillment",
|
||||
data: list.map(item => getNumber(item.reservedBaseQuantity))
|
||||
},
|
||||
{
|
||||
name: "已履约",
|
||||
type: "bar",
|
||||
stack: "fulfillment",
|
||||
data: list.map(item => getNumber(item.fulfilledBaseQuantity))
|
||||
},
|
||||
{
|
||||
name: "可履约",
|
||||
type: "bar",
|
||||
stack: "fulfillment",
|
||||
data: list.map(item => getNumber(item.availableBaseQuantity))
|
||||
}
|
||||
]
|
||||
};
|
||||
});
|
||||
|
||||
function getSkuName(item: GaoCustomerOrderSkuRankView): string {
|
||||
return [item.skuName || item.skuCode || "未命名规格", item.specSummary].filter(Boolean).join(" · ");
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<gao-chart-card
|
||||
title="复购趋势"
|
||||
caption="缺失日期按 0 展示"
|
||||
:date-text-list="dateTextList"
|
||||
:option="buildOption"
|
||||
:loading="loading"
|
||||
:has-data="list.length > 0"
|
||||
height="18rem"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { EChartsOption } from "echarts";
|
||||
import GaoChartCard from "@/pages/product/component/GaoChartCard.vue";
|
||||
import type { GaoCustomerRepeatDailyStatView } from "@/types/GaoChart";
|
||||
import { buildAxisChartOption, getDailyItem, getNumber } from "@/pages/product/component/chartUtils";
|
||||
|
||||
defineOptions({
|
||||
name: "CustomerRepeatDailyChart"
|
||||
});
|
||||
|
||||
const props = defineProps<{
|
||||
dateTextList: string[];
|
||||
list: GaoCustomerRepeatDailyStatView[];
|
||||
loading: boolean;
|
||||
}>();
|
||||
|
||||
function buildOption(visibleDateTextList: string[]): EChartsOption {
|
||||
const labelList = visibleDateTextList.map(item => item.slice(5));
|
||||
return {
|
||||
...buildAxisChartOption(labelList),
|
||||
xAxis: {
|
||||
type: "category",
|
||||
data: labelList,
|
||||
axisTick: { show: false },
|
||||
axisLabel: { color: "#52607a", interval: "auto", hideOverlap: true }
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: "客户数",
|
||||
type: "line",
|
||||
smooth: true,
|
||||
data: visibleDateTextList.map(dateText => getNumber(getDailyItem(props.list, dateText)?.customerCount))
|
||||
},
|
||||
{
|
||||
name: "复购客户",
|
||||
type: "line",
|
||||
smooth: true,
|
||||
data: visibleDateTextList.map(dateText => getNumber(getDailyItem(props.list, dateText)?.repeatCustomerCount))
|
||||
},
|
||||
{
|
||||
name: "复购订单",
|
||||
type: "line",
|
||||
smooth: true,
|
||||
data: visibleDateTextList.map(dateText => getNumber(getDailyItem(props.list, dateText)?.repeatOrderCount))
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,43 @@
|
||||
<template>
|
||||
<gao-chart-card
|
||||
title="复购间隔"
|
||||
caption="按间隔天数统计订单数"
|
||||
:option="chartOption"
|
||||
:loading="loading"
|
||||
:has-data="list.length > 0"
|
||||
height="18rem"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { EChartsOption } from "echarts";
|
||||
import GaoChartCard from "@/pages/product/component/GaoChartCard.vue";
|
||||
import type { GaoCustomerRepeatIntervalStatView } from "@/types/GaoChart";
|
||||
import { buildHorizontalBarOption, getNumber } from "@/pages/product/component/chartUtils";
|
||||
|
||||
defineOptions({
|
||||
name: "CustomerRepeatIntervalChart"
|
||||
});
|
||||
|
||||
const props = defineProps<{
|
||||
list: GaoCustomerRepeatIntervalStatView[];
|
||||
loading: boolean;
|
||||
}>();
|
||||
|
||||
const chartOption = computed<EChartsOption>(() => {
|
||||
const list = [...props.list].sort((left, right) => getNumber(left.intervalDays) - getNumber(right.intervalDays));
|
||||
return {
|
||||
...buildHorizontalBarOption(list.map(getIntervalText)),
|
||||
legend: { show: false },
|
||||
series: [{
|
||||
name: "复购订单",
|
||||
type: "bar",
|
||||
data: list.map(item => getNumber(item.orderCount))
|
||||
}]
|
||||
};
|
||||
});
|
||||
|
||||
function getIntervalText(item: GaoCustomerRepeatIntervalStatView): string {
|
||||
return `${getNumber(item.intervalDays)} 天`;
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user