add product/order chart
This commit is contained in:
@@ -0,0 +1,337 @@
|
||||
<template>
|
||||
<div v-can="permissionCodes.stat.read" class="gao-chart-page order-data-charts">
|
||||
<section class="hero">
|
||||
<div class="eyebrow" v-text="'订单经营分析'" />
|
||||
<h1 v-text="'客户经营看板'" />
|
||||
<p v-text="`${currentStoreStore.currentStoreName} · 订单、收款与客户贡献 · 近 1 个月`" />
|
||||
</section>
|
||||
<div v-if="!storeId" class="no-store">
|
||||
<t-empty description="暂无可统计的门店" />
|
||||
</div>
|
||||
<template v-else>
|
||||
<section class="summary">
|
||||
<div v-for="item in summaryList" :key="item.label" class="summary-item">
|
||||
<span v-text="item.label" />
|
||||
<strong v-text="item.value" />
|
||||
</div>
|
||||
</section>
|
||||
<div class="chart-list">
|
||||
<order-daily-chart
|
||||
v-entrance-transition
|
||||
:date-text-list="dateTextList"
|
||||
:list="dailyList"
|
||||
:loading="isLoading"
|
||||
/>
|
||||
<div class="two-columns">
|
||||
<order-type-chart
|
||||
v-entrance-transition="70"
|
||||
:list="typeList"
|
||||
:loading="isLoading"
|
||||
/>
|
||||
<order-payment-chart
|
||||
v-entrance-transition="140"
|
||||
:list="paymentList"
|
||||
:loading="isLoading"
|
||||
/>
|
||||
</div>
|
||||
<div class="two-columns">
|
||||
<order-status-chart
|
||||
v-entrance-transition="210"
|
||||
:list="statusList"
|
||||
:loading="isLoading"
|
||||
/>
|
||||
<customer-order-rank-chart
|
||||
v-entrance-transition="280"
|
||||
:list="customerRankList"
|
||||
:loading="isLoading"
|
||||
/>
|
||||
</div>
|
||||
<order-receivable-card
|
||||
v-entrance-transition="350"
|
||||
:receivable="receivable"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import GaoSalesOrderChartAPI from "@/api/GaoSalesOrderChartAPI";
|
||||
import CustomerOrderRankChart from "@/pages/product/component/CustomerOrderRankChart.vue";
|
||||
import OrderDailyChart from "@/pages/product/component/OrderDailyChart.vue";
|
||||
import OrderPaymentChart from "@/pages/product/component/OrderPaymentChart.vue";
|
||||
import OrderReceivableCard from "@/pages/product/component/OrderReceivableCard.vue";
|
||||
import OrderStatusChart from "@/pages/product/component/OrderStatusChart.vue";
|
||||
import OrderTypeChart from "@/pages/product/component/OrderTypeChart.vue";
|
||||
import type {
|
||||
GaoCustomerOrderRankView,
|
||||
GaoSalesOrderDailyStatView,
|
||||
GaoSalesOrderPaymentStatView,
|
||||
GaoSalesOrderReceivableStatView,
|
||||
GaoSalesOrderStatusStatView,
|
||||
GaoSalesOrderTypeStatView
|
||||
} from "@/types/GaoChart";
|
||||
import { useCurrentStoreStore } from "@/store/currentStoreStore";
|
||||
import { useNavBarStore } from "@/store/navBarStore";
|
||||
import { getCurrentStoreId } from "@/utils/store";
|
||||
import { clearStoreChartCache } from "@/utils/storeChartCache";
|
||||
import { getErrorMessage } from "@/utils/error";
|
||||
import { Message } from "@/utils/message";
|
||||
import { permissionCodes } from "@/utils/permission";
|
||||
import { buildDateTextList, formatMoney, formatQuantity, toUnixRange } from "@/pages/product/component/chartUtils";
|
||||
|
||||
interface SummaryItem {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
defineOptions({
|
||||
name: "OrderDataCharts"
|
||||
});
|
||||
|
||||
const currentStoreStore = useCurrentStoreStore();
|
||||
const navBarStore = useNavBarStore();
|
||||
const dateRange = buildDefaultDateRange();
|
||||
const dateTextList = buildDateTextList(dateRange[0], dateRange[1]);
|
||||
const dailyList = ref<GaoSalesOrderDailyStatView[]>([]);
|
||||
const typeList = ref<GaoSalesOrderTypeStatView[]>([]);
|
||||
const paymentList = ref<GaoSalesOrderPaymentStatView[]>([]);
|
||||
const statusList = ref<GaoSalesOrderStatusStatView[]>([]);
|
||||
const customerRankList = ref<GaoCustomerOrderRankView[]>([]);
|
||||
const receivable = ref<GaoSalesOrderReceivableStatView>({});
|
||||
const isLoading = ref(false);
|
||||
let requestId = 0;
|
||||
|
||||
const storeId = computed(() => currentStoreStore.currentStoreId || getCurrentStoreId());
|
||||
const summaryList = computed<SummaryItem[]>(() => [
|
||||
{
|
||||
label: "订单数",
|
||||
value: `${formatQuantity(receivable.value.orderCount)} 单`
|
||||
},
|
||||
{
|
||||
label: "成交金额",
|
||||
value: formatMoney(receivable.value.saleAmount)
|
||||
},
|
||||
{
|
||||
label: "有效收款",
|
||||
value: formatMoney(receivable.value.paidAmount)
|
||||
},
|
||||
{
|
||||
label: "未收金额",
|
||||
value: formatMoney(receivable.value.unpaidAmount)
|
||||
},
|
||||
{
|
||||
label: "净收款",
|
||||
value: formatMoney(receivable.value.netPaidAmount)
|
||||
}
|
||||
]);
|
||||
|
||||
watch(storeId, async () => {
|
||||
await loadCharts();
|
||||
}, { immediate: true });
|
||||
|
||||
onMounted(async () => {
|
||||
navBarStore.setRightIcon("refresh", reload);
|
||||
if (!currentStoreStore.currentStoreId) {
|
||||
await currentStoreStore.loadLoginStore();
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
navBarStore.clearRight();
|
||||
});
|
||||
|
||||
async function reload(): Promise<void> {
|
||||
clearStoreChartCache();
|
||||
await loadCharts();
|
||||
}
|
||||
|
||||
async function loadCharts(): Promise<void> {
|
||||
if (!storeId.value) {
|
||||
clearData();
|
||||
return;
|
||||
}
|
||||
|
||||
const currentRequestId = ++requestId;
|
||||
isLoading.value = true;
|
||||
try {
|
||||
const range = toUnixRange(dateRange);
|
||||
const request = { storeId: storeId.value, ...range, topN: 10 };
|
||||
const [daily, type, payment, status, receivableData, customerRank] = await Promise.all([
|
||||
GaoSalesOrderChartAPI.daily(request),
|
||||
GaoSalesOrderChartAPI.type(request),
|
||||
GaoSalesOrderChartAPI.payment(request),
|
||||
GaoSalesOrderChartAPI.status(request),
|
||||
GaoSalesOrderChartAPI.receivable(request),
|
||||
GaoSalesOrderChartAPI.customerRank(request)
|
||||
]);
|
||||
if (currentRequestId !== requestId) {
|
||||
return;
|
||||
}
|
||||
dailyList.value = daily || [];
|
||||
typeList.value = type || [];
|
||||
paymentList.value = payment || [];
|
||||
statusList.value = status || [];
|
||||
receivable.value = receivableData || {};
|
||||
customerRankList.value = customerRank || [];
|
||||
} catch (error) {
|
||||
if (currentRequestId === requestId) {
|
||||
Message.error(getErrorMessage(error, "订单图表加载失败"));
|
||||
}
|
||||
} finally {
|
||||
if (currentRequestId === requestId) {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function clearData(): void {
|
||||
isLoading.value = false;
|
||||
dailyList.value = [];
|
||||
typeList.value = [];
|
||||
paymentList.value = [];
|
||||
statusList.value = [];
|
||||
receivable.value = {};
|
||||
customerRankList.value = [];
|
||||
}
|
||||
|
||||
function buildDefaultDateRange(): [string, string] {
|
||||
const end = getDateEnd(new Date());
|
||||
const today = getDateStart(end);
|
||||
const beginMonth = new Date(today.getFullYear(), today.getMonth() - 1, 1, 0, 0, 0, 0);
|
||||
const lastDayOfBeginMonth = new Date(beginMonth.getFullYear(), beginMonth.getMonth() + 1, 0).getDate();
|
||||
const begin = new Date(beginMonth);
|
||||
begin.setDate(Math.min(today.getDate(), lastDayOfBeginMonth));
|
||||
return [formatDateTime(begin), formatDateTime(end)];
|
||||
}
|
||||
|
||||
function getDateStart(date: Date): Date {
|
||||
return new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
function getDateEnd(date: Date): Date {
|
||||
return new Date(date.getFullYear(), date.getMonth(), date.getDate(), 23, 59, 59, 999);
|
||||
}
|
||||
|
||||
function formatDateTime(date: Date): string {
|
||||
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
||||
}
|
||||
|
||||
function pad(value: number): string {
|
||||
return String(value).padStart(2, "0");
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.gao-chart-page {
|
||||
gap: 1rem;
|
||||
padding: calc(var(--app-nav-height) + 1rem) 1rem 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.hero {
|
||||
padding: .25rem .25rem 0;
|
||||
|
||||
.eyebrow {
|
||||
color: var(--td-brand-color);
|
||||
font-size: .6875rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: .12em;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #0f172a;
|
||||
margin: .25rem 0 0;
|
||||
font-size: 1.5rem;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
p {
|
||||
color: #7b889d;
|
||||
margin: .375rem 0 0;
|
||||
font-size: .8125rem;
|
||||
}
|
||||
}
|
||||
|
||||
.summary {
|
||||
gap: .625rem;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.summary-item {
|
||||
padding: .75rem .625rem;
|
||||
background: rgba(255, 255, 255, .92);
|
||||
box-shadow: var(--td-shadow-1);
|
||||
border-radius: 1rem;
|
||||
|
||||
span {
|
||||
color: #52607a;
|
||||
display: block;
|
||||
font-size: .75rem;
|
||||
}
|
||||
|
||||
strong {
|
||||
color: #0f172a;
|
||||
margin-top: .25rem;
|
||||
display: block;
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
.chart-list {
|
||||
gap: 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.two-columns {
|
||||
gap: 1rem;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.no-store {
|
||||
padding: 1.5rem 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
:global(html.theme-dark) {
|
||||
.gao-chart-page {
|
||||
.hero {
|
||||
h1 {
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
p {
|
||||
color: #94a3b8;
|
||||
}
|
||||
}
|
||||
|
||||
.summary-item {
|
||||
background: rgba(15, 23, 42, .88);
|
||||
box-shadow: 0 .75rem 2rem rgba(2, 6, 23, .32);
|
||||
|
||||
span {
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
strong {
|
||||
color: #e2e8f0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 42rem) {
|
||||
.gao-chart-page {
|
||||
.two-columns {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user