update customer chart

This commit is contained in:
Timi
2026-09-11 17:33:28 +08:00
parent 537eb5ac7a
commit 62add33884
25 changed files with 150 additions and 135 deletions
+2 -1
View File
@@ -21,12 +21,13 @@
"@zxing/library": "^0.21.3",
"browser-image-compression": "^2.0.2",
"echarts": "^6.0.0",
"echarts-wordcloud": "^2.1.0",
"less": "^4.3.0",
"pinia": "^3.0.2",
"tdesign-mobile-vue": "^1.16.0",
"timi-tdesign-mobile": "link:E:/WebProject/timi-tdesign-mobile",
"timi-web-api": "link:E:/WebProject/timi-web-api",
"timi-web": "link:E:/WebProject/timi-web",
"timi-web-api": "link:E:/WebProject/timi-web-api",
"ts-node": "^10.9.2",
"vue": "^3.5.16",
"vue-draggable-next": "^2.3.0",
+12
View File
@@ -25,6 +25,9 @@ importers:
echarts:
specifier: ^6.0.0
version: 6.1.0
echarts-wordcloud:
specifier: ^2.1.0
version: 2.1.0(echarts@6.1.0)
less:
specifier: ^4.3.0
version: 4.8.0
@@ -971,6 +974,11 @@ packages:
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
engines: {node: '>= 0.4'}
echarts-wordcloud@2.1.0:
resolution: {integrity: sha512-Kt1JmbcROgb+3IMI48KZECK2AP5lG6bSsOEs+AsuwaWJxQom31RTNd6NFYI01E/YaI1PFZeueaupjlmzSQasjQ==}
peerDependencies:
echarts: ^5.0.1
echarts@6.1.0:
resolution: {integrity: sha512-q0yaFPggC9FUdsWH4blavRWFmxdrIodbkoKNAjJudAI6CA9gNPxHtV2RcZNEepZVlk4yvBYkOkbk6HIVpIyHZA==}
@@ -2807,6 +2815,10 @@ snapshots:
es-errors: 1.3.0
gopd: 1.2.0
echarts-wordcloud@2.1.0(echarts@6.1.0):
dependencies:
echarts: 6.1.0
echarts@6.1.0:
dependencies:
tslib: 2.3.0
+13 -2
View File
@@ -1,9 +1,14 @@
import { axios } from "timi-web-api";
import type { CustomerChartReq } from "@/types/Customer";
import type { CustomerChartReq, CustomerDiseaseWordView } from "@/types/Customer";
import type { StoreEventRecordCalendarView, StoreEventRecordDailyStatView } from "@/types/Store";
import { getStoreChartCache } from "@/utils/storeChartCache";
import { withCurrentStoreId } from "@/utils/store";
const BASE_URI = "/gao/customer/chart";
type CustomerDiseaseWordReq = {
storeId?: string;
topN: number;
};
async function eventRecordDailyStat(req: CustomerChartReq): Promise<StoreEventRecordDailyStatView[]> {
return await axios.post(`${BASE_URI}/event/record/daily`, withCurrentStoreId(req));
@@ -13,7 +18,13 @@ async function eventRecordCalendar(req: CustomerChartReq): Promise<StoreEventRec
return await axios.post(`${BASE_URI}/event/record/calendar`, withCurrentStoreId(req));
}
async function diseaseWord(topN = 60): Promise<CustomerDiseaseWordView[]> {
const request = withCurrentStoreId<CustomerDiseaseWordReq>({ topN });
return await getStoreChartCache(`customer-disease-word:${JSON.stringify(request)}`, async () => await axios.post(`${BASE_URI}/customer/disease/word`, request));
}
export default {
eventRecordDailyStat,
eventRecordCalendar
eventRecordCalendar,
diseaseWord
};
@@ -1,16 +1,13 @@
<template>
<section class="chart-section">
<div class="title-row">
<section class="date-range-chart-card">
<div class="title">
<h2 v-text="title" />
<span v-if="caption" class="caption" v-text="caption" />
</div>
<div ref="chartElement" class="chart" :style="{ height }" />
<div v-if="loading" class="state">
<div v-if="loading" class="center-tips">
<t-loading size="1.5rem" text="加载中" />
</div>
<div v-else-if="!hasData" class="state">
<t-empty description="暂无数据" />
</div>
<chart-range-slider
v-if="chartShowSlider"
:model-value="sliderValue"
@@ -41,7 +38,6 @@ const props = withDefaults(defineProps<{
caption: "",
dateTextList: () => [],
height: "16rem",
hasData: true,
loading: false
});
@@ -61,19 +57,18 @@ const { chartElement } = useStoreChart(option);
</script>
<style scoped lang="less">
.chart-section {
.date-range-chart-card {
padding: 1rem;
position: relative;
background: rgba(255, 255, 255, .92);
box-shadow: var(--td-shadow-1);
border-radius: 1.25rem;
.title-row {
.title {
gap: .5rem;
display: flex;
align-items: baseline;
justify-content: space-between;
}
h2 {
color: #0f172a;
@@ -86,18 +81,11 @@ const { chartElement } = useStoreChart(option);
color: #94a3b8;
font-size: .6875rem;
}
}
.chart {
width: 100%;
}
.state {
inset: 3.5rem 0 0 0;
display: flex;
position: absolute;
align-items: center;
justify-content: center;
}
}
:global(html.theme-dark) {
+27 -7
View File
@@ -2,10 +2,10 @@
<div v-can="permissionCodes.stat.read" class="customer-data-charts-page">
<div class="header bg-white">
<t-input
v-model.trim="customerCode"
v-model="customerCode"
class="customer-search"
type="search"
borderless
clearable
placeholder="输入客户编码或姓名"
@clear="applyCustomerSearch"
@enter="applyCustomerSearch"
@@ -23,23 +23,30 @@
</div>
<customer-profile-card v-entrance-transition class="customer" :customer="selectedCustomer" />
<div class="charts">
<t-segmented
v-if="selectedCustomer"
v-model="chartMode"
class="chart-mode"
block
:options="chartModeOptions"
v-entrance-transition="20"
/>
<customer-order-charts
v-entrance-transition="35"
v-if="selectedCustomer"
v-if="selectedCustomer && chartMode === 'order'"
:customer-id="selectedCustomer.id || ''"
:date-text-list="chartDateTextList"
:store-id="selectedCustomer.storeId"
/>
<template v-else-if="selectedCustomer">
<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 || ''"
@@ -49,18 +56,17 @@
/>
<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"
/>
</template>
<customer-selector
v-entrance-transition="350"
v-model="selectedCustomerId"
@@ -123,6 +129,16 @@ const selectorVisible = ref(false);
const selectedCustomer = ref<Customer>();
const selectedEventIdList = ref<string[]>([]);
// ---------- 图表切换 ----------
type ChartMode = "order" | "customer";
const chartMode = ref<ChartMode>("customer");
const chartModeOptions = [
{ value: "customer" as ChartMode, label: "客户分析" },
{ value: "order" as ChartMode, label: "订单分析" }
];
async function loadCustomer(code: string): Promise<void> {
const keyword = code.trim();
if (!keyword) {
@@ -284,6 +300,10 @@ function getDateEnd(date: Date): Date {
padding: 0 1rem;
display: flex;
flex-direction: column;
.chart-mode {
width: 100%;
}
}
}
@@ -1,18 +1,17 @@
<template>
<gao-chart-card
<date-range-chart-card
title="有效消费趋势"
caption="金额已换算为元"
:date-text-list="dateTextList"
:option="buildOption"
:loading="loading"
:has-data="list.length > 0"
height="18rem"
height="14rem"
/>
</template>
<script setup lang="ts">
import type { EChartsOption } from "echarts";
import GaoChartCard from "@/pages/product/component/GaoChartCard.vue";
import DateRangeChartCard from "@/components/DateRangeChartCard.vue";
import type { GaoSalesOrderConsumptionDailyStatView } from "@/types/GaoChart";
import { buildAxisChartOption, formatChartYuan, getDailyItem, getNumber } from "@/pages/product/component/chartUtils";
@@ -1,18 +1,17 @@
<template>
<gao-chart-card
<date-range-chart-card
title="订单日历"
caption="按下单日期统计"
:date-text-list="dateTextList"
:option="buildOption"
:loading="loading"
:has-data="list.length > 0"
height="18rem"
height="14rem"
/>
</template>
<script setup lang="ts">
import type { EChartsOption } from "echarts";
import GaoChartCard from "@/pages/product/component/GaoChartCard.vue";
import DateRangeChartCard from "@/components/DateRangeChartCard.vue";
import type { GaoSalesOrderCalendarStatView } from "@/types/GaoChart";
import { buildAxisChartOption, formatChartYuan, getDailyItem, getNumber } from "@/pages/product/component/chartUtils";
@@ -1,12 +1,5 @@
<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"
@@ -1,17 +1,16 @@
<template>
<gao-chart-card
title="客户产品购买排行"
<date-range-chart-card
title="产品购买排行"
caption="基础单位购买及履约进度,前 10"
:option="chartOption"
:loading="loading"
:has-data="list.length > 0"
height="20rem"
height="14rem"
/>
</template>
<script setup lang="ts">
import type { EChartsOption } from "echarts";
import GaoChartCard from "@/pages/product/component/GaoChartCard.vue";
import DateRangeChartCard from "@/components/DateRangeChartCard.vue";
import type { GaoCustomerOrderProductRankView } from "@/types/GaoChart";
import { buildHorizontalBarOption, getNumber } from "@/pages/product/component/chartUtils";
@@ -1,17 +1,16 @@
<template>
<gao-chart-card
title="客户 SKU 购买排行"
<date-range-chart-card
title="SKU 购买排行"
caption="基础单位购买及履约进度,前 10"
:option="chartOption"
:loading="loading"
:has-data="list.length > 0"
height="20rem"
height="14rem"
/>
</template>
<script setup lang="ts">
import type { EChartsOption } from "echarts";
import GaoChartCard from "@/pages/product/component/GaoChartCard.vue";
import DateRangeChartCard from "@/components/DateRangeChartCard.vue";
import type { GaoCustomerOrderSkuRankView } from "@/types/GaoChart";
import { buildHorizontalBarOption, getNumber } from "@/pages/product/component/chartUtils";
@@ -1,18 +1,17 @@
<template>
<gao-chart-card
<date-range-chart-card
title="复购趋势"
caption="缺失日期按 0 展示"
:date-text-list="dateTextList"
:option="buildOption"
:loading="loading"
:has-data="list.length > 0"
height="18rem"
height="14rem"
/>
</template>
<script setup lang="ts">
import type { EChartsOption } from "echarts";
import GaoChartCard from "@/pages/product/component/GaoChartCard.vue";
import DateRangeChartCard from "@/components/DateRangeChartCard.vue";
import type { GaoCustomerRepeatDailyStatView } from "@/types/GaoChart";
import { buildAxisChartOption, getDailyItem, getNumber } from "@/pages/product/component/chartUtils";
@@ -1,17 +1,16 @@
<template>
<gao-chart-card
<date-range-chart-card
title="复购间隔"
caption="按间隔天数统计订单数"
:option="chartOption"
:loading="loading"
:has-data="list.length > 0"
height="18rem"
height="14rem"
/>
</template>
<script setup lang="ts">
import type { EChartsOption } from "echarts";
import GaoChartCard from "@/pages/product/component/GaoChartCard.vue";
import DateRangeChartCard from "@/components/DateRangeChartCard.vue";
import type { GaoCustomerRepeatIntervalStatView } from "@/types/GaoChart";
import { buildHorizontalBarOption, getNumber } from "@/pages/product/component/chartUtils";
@@ -1,17 +1,16 @@
<template>
<gao-chart-card
<date-range-chart-card
title="客户净收款排行"
caption="排除已取消订单,前 10"
:option="chartOption"
:loading="loading"
:has-data="list.length > 0"
height="17rem"
/>
</template>
<script setup lang="ts">
import type { EChartsOption } from "echarts";
import GaoChartCard from "@/pages/product/component/GaoChartCard.vue";
import DateRangeChartCard from "@/components/DateRangeChartCard.vue";
import type { GaoCustomerOrderRankView } from "@/types/GaoChart";
import { buildHorizontalBarOption, formatChartYuan, getNumber } from "@/pages/product/component/chartUtils";
@@ -1,18 +1,17 @@
<template>
<gao-chart-card
<date-range-chart-card
title="订单每日趋势"
caption="金额已换算为元"
:date-text-list="dateTextList"
:option="buildOption"
:loading="loading"
:has-data="list.length > 0"
height="18rem"
height="12rem"
/>
</template>
<script setup lang="ts">
import type { EChartsOption } from "echarts";
import GaoChartCard from "@/pages/product/component/GaoChartCard.vue";
import DateRangeChartCard from "@/components/DateRangeChartCard.vue";
import type { GaoSalesOrderDailyStatView } from "@/types/GaoChart";
import { buildAxisChartOption, formatChartYuan, formatQuantity, getDailyItem, getNumber } from "@/pages/product/component/chartUtils";
@@ -80,5 +79,4 @@ function buildOption(visibleDateTextList: string[]): EChartsOption {
]
};
}
</script>
@@ -1,17 +1,16 @@
<template>
<gao-chart-card
<date-range-chart-card
title="付款状态分布"
caption="当前订单快照"
:option="chartOption"
:loading="loading"
:has-data="list.length > 0"
height="17rem"
/>
</template>
<script setup lang="ts">
import type { EChartsOption } from "echarts";
import GaoChartCard from "@/pages/product/component/GaoChartCard.vue";
import DateRangeChartCard from "@/components/DateRangeChartCard.vue";
import type { GaoSalesOrderPaymentStatView } from "@/types/GaoChart";
import { buildHorizontalBarOption, getNumber } from "@/pages/product/component/chartUtils";
@@ -1,17 +1,16 @@
<template>
<gao-chart-card
<date-range-chart-card
title="订单状态分布"
caption="当前订单快照"
:option="chartOption"
:loading="loading"
:has-data="list.length > 0"
height="17rem"
/>
</template>
<script setup lang="ts">
import type { EChartsOption } from "echarts";
import GaoChartCard from "@/pages/product/component/GaoChartCard.vue";
import DateRangeChartCard from "@/components/DateRangeChartCard.vue";
import type { GaoSalesOrderStatusStatView } from "@/types/GaoChart";
import { buildHorizontalBarOption, getNumber } from "@/pages/product/component/chartUtils";
@@ -1,17 +1,16 @@
<template>
<gao-chart-card
<date-range-chart-card
title="订单类型分布"
caption="按订单数量"
:option="chartOption"
:loading="loading"
:has-data="list.length > 0"
height="17rem"
/>
</template>
<script setup lang="ts">
import type { EChartsOption } from "echarts";
import GaoChartCard from "@/pages/product/component/GaoChartCard.vue";
import DateRangeChartCard from "@/components/DateRangeChartCard.vue";
import type { GaoSalesOrderTypeStatView } from "@/types/GaoChart";
import { buildPieChartOption, getNumber } from "@/pages/product/component/chartUtils";
@@ -1,17 +1,16 @@
<template>
<gao-chart-card
<date-range-chart-card
title="履约进度排行"
caption="基础单位购买数量的当前履约快照"
: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 DateRangeChartCard from "@/components/DateRangeChartCard.vue";
import type { GaoProductFulfillmentRankView } from "@/types/GaoChart";
import { buildHorizontalBarOption, getNumber } from "@/pages/product/component/chartUtils";
@@ -1,18 +1,17 @@
<template>
<gao-chart-card
<date-range-chart-card
title="每日履约趋势"
caption="基础单位,冲正数量允许为负值"
:date-text-list="dateTextList"
:option="buildOption"
:loading="loading"
:has-data="list.length > 0"
height="17rem"
/>
</template>
<script setup lang="ts">
import type { EChartsOption } from "echarts";
import GaoChartCard from "@/pages/product/component/GaoChartCard.vue";
import DateRangeChartCard from "@/components/DateRangeChartCard.vue";
import type { GaoProductFulfillmentDailyStatView } from "@/types/GaoChart";
import { buildAxisChartOption, getDailyItem, getNumber } from "@/pages/product/component/chartUtils";
@@ -1,17 +1,16 @@
<template>
<gao-chart-card
<date-range-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 DateRangeChartCard from "@/components/DateRangeChartCard.vue";
import type { GaoProductSalesRankView } from "@/types/GaoChart";
import { buildHorizontalBarOption, getNumber } from "@/pages/product/component/chartUtils";
@@ -1,18 +1,17 @@
<template>
<gao-chart-card
<date-range-chart-card
title="产品销售趋势"
caption="基础单位,直接产品与套餐权益分开展示"
:date-text-list="dateTextList"
:option="buildOption"
:loading="loading"
:has-data="list.length > 0"
height="17rem"
/>
</template>
<script setup lang="ts">
import type { EChartsOption } from "echarts";
import GaoChartCard from "@/pages/product/component/GaoChartCard.vue";
import DateRangeChartCard from "@/components/DateRangeChartCard.vue";
import type { GaoProductSalesDailyStatView } from "@/types/GaoChart";
import { buildAxisChartOption, getDailyItem, getNumber } from "@/pages/product/component/chartUtils";
@@ -1,18 +1,17 @@
<template>
<gao-chart-card
<date-range-chart-card
title="库存变动"
caption="预占与释放不计入实际出入库"
:date-text-list="dateTextList"
:option="buildOption"
:loading="loading"
:has-data="list.length > 0"
height="17rem"
/>
</template>
<script setup lang="ts">
import type { EChartsOption } from "echarts";
import GaoChartCard from "@/pages/product/component/GaoChartCard.vue";
import DateRangeChartCard from "@/components/DateRangeChartCard.vue";
import type { GaoProductStockMovementDailyStatView } from "@/types/GaoChart";
import { buildAxisChartOption, getDailyItem, getNumber } from "@/pages/product/component/chartUtils";
+4 -4
View File
@@ -7,10 +7,10 @@ export function buildAxisChartOption(labelList: string[]): EChartsOption {
return {
color: chartColorList,
grid: {
top: 48,
left: 12,
right: 12,
bottom: 12,
top: 58,
left: 0,
right: 0,
bottom: 0,
containLabel: true
},
legend: {
+4 -2
View File
@@ -5,8 +5,9 @@
<store-event-record-trend-chart v-entrance-transition="140" :date-text-list="dateTextList" />
<store-event-record-number-value-chart v-entrance-transition="210" :date-text-list="dateTextList" />
<store-event-structure-chart v-entrance-transition="280" :date-text-list="dateTextList" />
<store-customer-growth-chart v-entrance-transition="350" :date-text-list="dateTextList" />
<store-customer-gender-chart v-entrance-transition="420" />
<store-customer-disease-word-cloud-chart v-entrance-transition="350" />
<store-customer-growth-chart v-entrance-transition="420" :date-text-list="dateTextList" />
<store-customer-gender-chart v-entrance-transition="490" />
</div>
</template>
@@ -15,6 +16,7 @@ import { Time } from "timi-web";
import StoreChartSummary from "@/pages/store/component/StoreChartSummary.vue";
import StoreCustomerGenderChart from "@/pages/store/component/StoreCustomerGenderChart.vue";
import StoreCustomerGrowthChart from "@/pages/store/component/StoreCustomerGrowthChart.vue";
import StoreCustomerDiseaseWordCloudChart from "@/pages/store/component/StoreCustomerDiseaseWordCloudChart.vue";
import StoreEventRecordCalendarSection from "@/pages/store/component/StoreEventRecordCalendarSection.vue";
import StoreEventRecordTrendChart from "@/pages/store/component/StoreEventRecordTrendChart.vue";
import StoreEventRecordNumberValueChart from "@/pages/store/component/StoreEventRecordNumberValueChart.vue";
+5
View File
@@ -64,6 +64,11 @@ export type CustomerGenderStatView = {
count?: number;
};
export type CustomerDiseaseWordView = {
word?: string;
value?: number;
};
export type CustomerChartReq = {
storeId?: string;
customerId: string;