|
@@ -1,29 +1,30 @@
|
|
|
<template>
|
|
<template>
|
|
|
<ele-page>
|
|
<ele-page>
|
|
|
- <ele-card :body-style="{ padding: '20px' }">
|
|
|
|
|
- <!-- Filters -->
|
|
|
|
|
|
|
+ <ele-card v-loading="loading" :body-style="{ padding: '20px' }">
|
|
|
<div class="filter-container">
|
|
<div class="filter-container">
|
|
|
<div class="date-ranges">
|
|
<div class="date-ranges">
|
|
|
- <el-button-group>
|
|
|
|
|
- <el-button type="primary">实时</el-button>
|
|
|
|
|
- <el-button>昨日</el-button>
|
|
|
|
|
- <el-button>7日</el-button>
|
|
|
|
|
- <el-button>15日</el-button>
|
|
|
|
|
- <el-button>30日</el-button>
|
|
|
|
|
- </el-button-group>
|
|
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ v-for="item in dateRanges"
|
|
|
|
|
+ :key="item.value"
|
|
|
|
|
+ :type="activeRange === item.value ? 'primary' : ''"
|
|
|
|
|
+ @click="handleRangeChange(item.value)"
|
|
|
|
|
+ >
|
|
|
|
|
+ {{ item.label }}
|
|
|
|
|
+ </el-button>
|
|
|
</div>
|
|
</div>
|
|
|
<div class="date-picker-wrapper">
|
|
<div class="date-picker-wrapper">
|
|
|
<el-date-picker
|
|
<el-date-picker
|
|
|
v-model="dateRange"
|
|
v-model="dateRange"
|
|
|
type="daterange"
|
|
type="daterange"
|
|
|
- range-separator="到"
|
|
|
|
|
|
|
+ range-separator="-"
|
|
|
start-placeholder="开始时间"
|
|
start-placeholder="开始时间"
|
|
|
end-placeholder="结束时间"
|
|
end-placeholder="结束时间"
|
|
|
- size="default"
|
|
|
|
|
|
|
+ value-format="YYYY-MM-DD"
|
|
|
|
|
+ @change="handleDateChange"
|
|
|
/>
|
|
/>
|
|
|
</div>
|
|
</div>
|
|
|
- <el-button type="primary" class="search-btn">搜索</el-button>
|
|
|
|
|
- <el-button>重置</el-button>
|
|
|
|
|
|
|
+ <el-button type="primary" @click="handleSearch">搜索</el-button>
|
|
|
|
|
+ <el-button @click="handleReset">重置</el-button>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<!-- Stats Cards -->
|
|
<!-- Stats Cards -->
|
|
@@ -42,133 +43,201 @@
|
|
|
/>
|
|
/>
|
|
|
|
|
|
|
|
<!-- Refund Stats -->
|
|
<!-- Refund Stats -->
|
|
|
- <refund-stats :refund-data="refundData" />
|
|
|
|
|
|
|
+ <refund-stats />
|
|
|
</ele-card>
|
|
</ele-card>
|
|
|
</ele-page>
|
|
</ele-page>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script setup>
|
|
<script setup>
|
|
|
- import { ref, reactive } from 'vue';
|
|
|
|
|
|
|
+ import { computed, onMounted, ref } from 'vue';
|
|
|
|
|
+ import dayjs from 'dayjs';
|
|
|
|
|
+ import { EleMessage } from 'ele-admin-plus/es';
|
|
|
import SalesStatCards from './components/SalesStatCards.vue';
|
|
import SalesStatCards from './components/SalesStatCards.vue';
|
|
|
import OrderTrendChart from './components/OrderTrendChart.vue';
|
|
import OrderTrendChart from './components/OrderTrendChart.vue';
|
|
|
import DistributionCharts from './components/DistributionCharts.vue';
|
|
import DistributionCharts from './components/DistributionCharts.vue';
|
|
|
import RefundStats from './components/RefundStats.vue';
|
|
import RefundStats from './components/RefundStats.vue';
|
|
|
|
|
+ import {
|
|
|
|
|
+ getOrderDashboardKpiStat,
|
|
|
|
|
+ getOrderDashboardPieChart,
|
|
|
|
|
+ getOrderDashboardRealtimeCount,
|
|
|
|
|
+ getOrderDashboardTrend
|
|
|
|
|
+ } from '@/api/statsAnalysis/sell';
|
|
|
|
|
|
|
|
|
|
+ const activeRange = ref('realtime');
|
|
|
const dateRange = ref([]);
|
|
const dateRange = ref([]);
|
|
|
-
|
|
|
|
|
- // Mock Data
|
|
|
|
|
- const statusData = [
|
|
|
|
|
- { label: '待付款', value: 11 },
|
|
|
|
|
- { label: '待发货', value: 11 },
|
|
|
|
|
- { label: '待售后', value: 11 },
|
|
|
|
|
- { label: '待处理投诉', value: 11 }
|
|
|
|
|
|
|
+ const loading = ref(false);
|
|
|
|
|
+ const realtimeCount = ref({});
|
|
|
|
|
+ const kpiStat = ref({});
|
|
|
|
|
+ const trendList = ref([]);
|
|
|
|
|
+ const pieStat = ref({});
|
|
|
|
|
+ const dateRanges = [
|
|
|
|
|
+ { label: "实时", value: "realtime" },
|
|
|
|
|
+ { label: "昨日", value: "yesterday" },
|
|
|
|
|
+ { label: "7日", value: "7days" },
|
|
|
|
|
+ { label: "30日", value: "30days" }
|
|
|
];
|
|
];
|
|
|
|
|
|
|
|
- const salesData = [
|
|
|
|
|
- { label: '支付金额', value: '55366', yesterdayValue: '53669' },
|
|
|
|
|
- { label: '访客数', value: '55366', yesterdayValue: '53669' },
|
|
|
|
|
- { label: '支付订单数', value: '55366', yesterdayValue: '53669' },
|
|
|
|
|
- { label: '支付转化率', value: '55366', yesterdayValue: '53669' },
|
|
|
|
|
- { label: '商品浏览量', value: '55366', yesterdayValue: '53669' },
|
|
|
|
|
- { label: '客单价', value: '55366', yesterdayValue: '53669' },
|
|
|
|
|
- { label: '支付买家数', value: '55366', yesterdayValue: '53669' },
|
|
|
|
|
- { label: '加购商品数', value: '55366', yesterdayValue: '53669' },
|
|
|
|
|
- { label: '收藏商品数', value: '55366', yesterdayValue: '53669' },
|
|
|
|
|
- { label: '在途订单数', value: '55366', yesterdayValue: '53669' },
|
|
|
|
|
- { label: '在途金额', value: '55366', yesterdayValue: '53669' },
|
|
|
|
|
- { label: '老客复购率', value: '55366', yesterdayValue: '53669' },
|
|
|
|
|
- { label: '老客复购金额', value: '55366', yesterdayValue: '53669' },
|
|
|
|
|
- { label: '老客复购人数', value: '55366', yesterdayValue: '53669' },
|
|
|
|
|
- { label: '净支付金额', value: '55366', yesterdayValue: '53669' }
|
|
|
|
|
- ];
|
|
|
|
|
|
|
+ function buildDates(value) {
|
|
|
|
|
+ const today = dayjs();
|
|
|
|
|
+ if (value === 'yesterday') {
|
|
|
|
|
+ const day = today.subtract(1, 'day').format('YYYY-MM-DD');
|
|
|
|
|
+ return [day, day];
|
|
|
|
|
+ }
|
|
|
|
|
+ const offset = value === '7days' ? 6 : value === '30days' ? 29 : 0;
|
|
|
|
|
+ return [
|
|
|
|
|
+ today.subtract(offset, 'day').format('YYYY-MM-DD'),
|
|
|
|
|
+ today.format('YYYY-MM-DD')
|
|
|
|
|
+ ];
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- const trendChartData = reactive({
|
|
|
|
|
- dates: [
|
|
|
|
|
- '15日',
|
|
|
|
|
- '16日',
|
|
|
|
|
- '17日',
|
|
|
|
|
- '18日',
|
|
|
|
|
- '19日',
|
|
|
|
|
- '20日',
|
|
|
|
|
- '21日',
|
|
|
|
|
- '22日',
|
|
|
|
|
- '23日',
|
|
|
|
|
- '24日',
|
|
|
|
|
- '25日',
|
|
|
|
|
- '26日',
|
|
|
|
|
- '27日'
|
|
|
|
|
- ],
|
|
|
|
|
- orderCount: [
|
|
|
|
|
- 100, 120, 200, 100, 130, 150, 180, 220, 150, 170, 110, 160, 210
|
|
|
|
|
- ],
|
|
|
|
|
- paymentAmount: [
|
|
|
|
|
- 150, 100, 230, 110, 100, 170, 210, 110, 108, 150, 190, 180, 170
|
|
|
|
|
- ],
|
|
|
|
|
- conversionRate: [
|
|
|
|
|
- 150, 130, 180, 150, 210, 210, 210, 220, 180, 130, 160, 180, 170
|
|
|
|
|
- ],
|
|
|
|
|
- refundAmount: [
|
|
|
|
|
- 160, 220, 230, 160, 210, 150, 160, 150, 170, 200, 170, 220, 100
|
|
|
|
|
- ]
|
|
|
|
|
|
|
+ function buildParams() {
|
|
|
|
|
+ const now = dayjs();
|
|
|
|
|
+ if (activeRange.value === 'realtime') {
|
|
|
|
|
+ return {
|
|
|
|
|
+ startTime: now.startOf('day').format('YYYY-MM-DD HH:mm:ss'),
|
|
|
|
|
+ endTime: now.format('YYYY-MM-DD HH:mm:ss')
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+ const [start, end] = dateRange.value || [];
|
|
|
|
|
+ const startDay = start ? dayjs(start) : now;
|
|
|
|
|
+ const endDay = end ? dayjs(end) : now;
|
|
|
|
|
+ return {
|
|
|
|
|
+ startTime: startDay.startOf('day').format('YYYY-MM-DD HH:mm:ss'),
|
|
|
|
|
+ endTime: endDay.endOf('day').format('YYYY-MM-DD HH:mm:ss')
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function showValue(value) {
|
|
|
|
|
+ if (value == null || value === '') {
|
|
|
|
|
+ return 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ return value;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function toNumber(value) {
|
|
|
|
|
+ if (value == null || value === '') {
|
|
|
|
|
+ return 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ const num = Number(String(value).replace('%', ''));
|
|
|
|
|
+ return Number.isFinite(num) ? num : 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const statusData = computed(() => [
|
|
|
|
|
+ { label: '待付款', value: showValue(realtimeCount.value.pendingPayCount) },
|
|
|
|
|
+ { label: '待发货', value: showValue(realtimeCount.value.pendingShipCount) },
|
|
|
|
|
+ { label: '待售后', value: showValue(realtimeCount.value.pendingAfterSalesCount) },
|
|
|
|
|
+ { label: '待处理投诉', value: showValue(realtimeCount.value.pendingComplaintsCount) }
|
|
|
|
|
+ ]);
|
|
|
|
|
+
|
|
|
|
|
+ const salesData = computed(() => {
|
|
|
|
|
+ const data = kpiStat.value;
|
|
|
|
|
+ return [
|
|
|
|
|
+ { label: '支付金额', value: showValue(data.payAmount), yesterdayValue: showValue(data.yesterdayPayAmount) },
|
|
|
|
|
+ { label: '访客数', value: showValue(data.visitorNum), yesterdayValue: showValue(data.yesterdayVisitorNum) },
|
|
|
|
|
+ { label: '支付订单数', value: showValue(data.payOrderNum), yesterdayValue: showValue(data.yesterdayPayOrderNum) },
|
|
|
|
|
+ { label: '支付转化率', value: showValue(data.payConversionRate), yesterdayValue: showValue(data.yesterdayPayConversionRate) },
|
|
|
|
|
+ { label: '商品浏览量', value: showValue(data.productViewNum), yesterdayValue: showValue(data.yesterdayProductViewNum) },
|
|
|
|
|
+ { label: '客单价', value: showValue(data.avgOrderValue), yesterdayValue: showValue(data.yesterdayAvgOrderValue) },
|
|
|
|
|
+ { label: '支付买家数', value: showValue(data.payBuyerNum), yesterdayValue: showValue(data.yesterdayPayBuyerNum) },
|
|
|
|
|
+ { label: '加购商品数', value: showValue(data.cartAddNum), yesterdayValue: showValue(data.yesterdayCartAddNum) },
|
|
|
|
|
+ { label: '收藏商品数', value: showValue(data.collectProductNum), yesterdayValue: showValue(data.yesterdayCollectProductNum) },
|
|
|
|
|
+ { label: '在途订单数', value: showValue(data.inTransitOrderNum), showCompare: false },
|
|
|
|
|
+ { label: '在途金额', value: showValue(data.inTransitAmount), showCompare: false },
|
|
|
|
|
+ { label: '老客复购率', value: showValue(data.oldCustomerRepurchaseRate), yesterdayValue: showValue(data.yesterdayOldCustomerRepurchaseRate) },
|
|
|
|
|
+ { label: '老客复购金额', value: showValue(data.oldCustomerPurchaseAmount), yesterdayValue: showValue(data.yesterdayOldCustomerPurchaseAmount) },
|
|
|
|
|
+ { label: '老客复购人数', value: showValue(data.oldCustomerNum), yesterdayValue: showValue(data.yesterdayOldCustomerNum) },
|
|
|
|
|
+ { label: '净支付金额', value: showValue(data.netPayAmount), yesterdayValue: showValue(data.yesterdayNetPayAmount) }
|
|
|
|
|
+ ];
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
- const paymentMethodData = [
|
|
|
|
|
- { value: 100, name: '微信支付', itemStyle: { color: '#5b8ff9' } },
|
|
|
|
|
- { value: 150, name: '支付宝支付', itemStyle: { color: '#5ebcb9' } },
|
|
|
|
|
- { value: 224, name: '余额支付', itemStyle: { color: '#4caf50' } }
|
|
|
|
|
- ];
|
|
|
|
|
|
|
+ const trendChartData = computed(() => ({
|
|
|
|
|
+ dates: trendList.value.map((item) => item.timeLabel),
|
|
|
|
|
+ orderCount: trendList.value.map((item) => toNumber(item.payOrderNum)),
|
|
|
|
|
+ paymentAmount: trendList.value.map((item) => toNumber(item.payAmount)),
|
|
|
|
|
+ conversionRate: trendList.value.map((item) => toNumber(item.payConversionRate)),
|
|
|
|
|
+ refundAmount: trendList.value.map((item) => toNumber(item.refundAmount))
|
|
|
|
|
+ }));
|
|
|
|
|
|
|
|
- const orderStatusData = [
|
|
|
|
|
- { value: 122, name: '待发货', itemStyle: { color: '#ff9800' } },
|
|
|
|
|
- { value: 150, name: '已退款', itemStyle: { color: '#5ebcb9' } },
|
|
|
|
|
- { value: 170, name: '待收货', itemStyle: { color: '#4caf50' } },
|
|
|
|
|
- { value: 174, name: '已取消', itemStyle: { color: '#f44336' } },
|
|
|
|
|
- { value: 175, name: '已完成', itemStyle: { color: '#e91e63' } },
|
|
|
|
|
- { value: 100, name: '已发货', itemStyle: { color: '#5b8ff9' } }
|
|
|
|
|
- ];
|
|
|
|
|
|
|
+ const paymentMethodData = computed(() => [
|
|
|
|
|
+ { value: toNumber(pieStat.value.payTypeBalanceNum), name: '余额支付', itemStyle: { color: '#4caf50' } },
|
|
|
|
|
+ { value: toNumber(pieStat.value.payTypeAlipayNum), name: '支付宝支付', itemStyle: { color: '#5ebcb9' } },
|
|
|
|
|
+ { value: toNumber(pieStat.value.payTypeWechatNum), name: '微信支付', itemStyle: { color: '#5b8ff9' } }
|
|
|
|
|
+ ]);
|
|
|
|
|
+
|
|
|
|
|
+ const orderStatusData = computed(() => [
|
|
|
|
|
+ { value: toNumber(pieStat.value.statusPendingPayNum), name: '待付款', itemStyle: { color: '#5ebcb9' } },
|
|
|
|
|
+ { value: toNumber(pieStat.value.statusPendingShipNum), name: '待发货', itemStyle: { color: '#ff9800' } },
|
|
|
|
|
+ { value: toNumber(pieStat.value.statusPendingReceiveNum), name: '待收货', itemStyle: { color: '#4caf50' } },
|
|
|
|
|
+ { value: toNumber(pieStat.value.statusReceivedNum), name: '已收货', itemStyle: { color: '#5b8ff9' } },
|
|
|
|
|
+ { value: toNumber(pieStat.value.statusCompletedNum), name: '已完成', itemStyle: { color: '#e91e63' } },
|
|
|
|
|
+ { value: toNumber(pieStat.value.statusCancelledNum), name: '已取消', itemStyle: { color: '#f44336' } }
|
|
|
|
|
+ ]);
|
|
|
|
|
+
|
|
|
|
|
+ async function loadDashboard() {
|
|
|
|
|
+ loading.value = true;
|
|
|
|
|
+ const params = buildParams();
|
|
|
|
|
+ await Promise.all([
|
|
|
|
|
+ getOrderDashboardRealtimeCount()
|
|
|
|
|
+ .then((data) => {
|
|
|
|
|
+ realtimeCount.value = data;
|
|
|
|
|
+ })
|
|
|
|
|
+ .catch((error) => {
|
|
|
|
|
+ realtimeCount.value = {};
|
|
|
|
|
+ EleMessage.error(error.message || '获取实时计数失败');
|
|
|
|
|
+ }),
|
|
|
|
|
+ getOrderDashboardKpiStat(params)
|
|
|
|
|
+ .then((data) => {
|
|
|
|
|
+ kpiStat.value = data;
|
|
|
|
|
+ })
|
|
|
|
|
+ .catch((error) => {
|
|
|
|
|
+ kpiStat.value = {};
|
|
|
|
|
+ EleMessage.error(error.message || '获取KPI数据失败');
|
|
|
|
|
+ }),
|
|
|
|
|
+ getOrderDashboardTrend(params)
|
|
|
|
|
+ .then((data) => {
|
|
|
|
|
+ trendList.value = Array.isArray(data) ? data : [];
|
|
|
|
|
+ })
|
|
|
|
|
+ .catch((error) => {
|
|
|
|
|
+ trendList.value = [];
|
|
|
|
|
+ EleMessage.error(error.message || '获取曲线图数据失败');
|
|
|
|
|
+ }),
|
|
|
|
|
+ getOrderDashboardPieChart(params)
|
|
|
|
|
+ .then((data) => {
|
|
|
|
|
+ pieStat.value = data;
|
|
|
|
|
+ })
|
|
|
|
|
+ .catch((error) => {
|
|
|
|
|
+ pieStat.value = {};
|
|
|
|
|
+ EleMessage.error(error.message || '获取饼图数据失败');
|
|
|
|
|
+ })
|
|
|
|
|
+ ]);
|
|
|
|
|
+ loading.value = false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function handleRangeChange(value) {
|
|
|
|
|
+ activeRange.value = value;
|
|
|
|
|
+ dateRange.value = buildDates(value);
|
|
|
|
|
+ loadDashboard();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function handleDateChange() {
|
|
|
|
|
+ activeRange.value = '';
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- const refundData = [
|
|
|
|
|
- {
|
|
|
|
|
- title: '全部',
|
|
|
|
|
- refundUsers: 2720,
|
|
|
|
|
- subOrders: 3089,
|
|
|
|
|
- refundRate: '10.63%',
|
|
|
|
|
- refundAmount: 36738.53,
|
|
|
|
|
- amountRate: '11.03%'
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- title: '退货退款',
|
|
|
|
|
- refundUsers: 339,
|
|
|
|
|
- subOrders: 354,
|
|
|
|
|
- refundRate: '1.22%',
|
|
|
|
|
- refundAmount: 36738.53,
|
|
|
|
|
- amountRate: '11.03%'
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- title: '已收货退款',
|
|
|
|
|
- refundUsers: 28,
|
|
|
|
|
- subOrders: 3089,
|
|
|
|
|
- refundRate: '10.63%',
|
|
|
|
|
- refundAmount: 36738.53,
|
|
|
|
|
- amountRate: '11.03%'
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- title: '未收货退款',
|
|
|
|
|
- refundUsers: 2720,
|
|
|
|
|
- subOrders: 3089,
|
|
|
|
|
- refundRate: '10.63%',
|
|
|
|
|
- refundAmount: 36738.53,
|
|
|
|
|
- amountRate: '11.03%'
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- title: '未发货退款',
|
|
|
|
|
- refundUsers: 2720,
|
|
|
|
|
- subOrders: 3089,
|
|
|
|
|
- refundRate: '10.63%',
|
|
|
|
|
- refundAmount: 36738.53,
|
|
|
|
|
- amountRate: '11.03%'
|
|
|
|
|
|
|
+ function handleSearch() {
|
|
|
|
|
+ if (!dateRange.value?.length) {
|
|
|
|
|
+ handleRangeChange('realtime');
|
|
|
|
|
+ return;
|
|
|
}
|
|
}
|
|
|
- ];
|
|
|
|
|
|
|
+ loadDashboard();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function handleReset() {
|
|
|
|
|
+ handleRangeChange('realtime');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ onMounted(() => {
|
|
|
|
|
+ handleRangeChange('realtime');
|
|
|
|
|
+ });
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
|
<style scoped lang="scss">
|
|
<style scoped lang="scss">
|