feat(欢迎页): 添加统计数据显示功能

在欢迎页中新增了统计数据的显示功能,包括商店、商品、订单、柜子、格口等数据的动态展示,并引入了热门商品和今日最新订单的展示模块。通过调用 `getStats` API 获取数据,并在页面加载时进行渲染,提升了页面的信息丰富度和用户体验。
This commit is contained in:
dzq 2025-05-20 11:00:22 +08:00
parent 328158d829
commit 2c319bae8f
2 changed files with 263 additions and 13 deletions

73
src/api/shop/stats.ts Normal file
View File

@ -0,0 +1,73 @@
import { http } from '@/utils/http';
/** 热门商品DTO */
export interface TopGoodsDTO {
/** 商品ID */
goodsId: number;
/** 商品名称 */
goodsName: string;
/** 封面图片 */
coverImg: string;
/** 出现次数 */
occurrenceCount: number;
}
/** 今日最新订单商品DTO */
export interface TodayLatestOrderGoodsDTO {
/** 订单商品唯一ID */
orderGoodsId: number;
/** 关联订单ID */
orderId: number;
/** 关联商品ID */
goodsId: number;
/** 关联格口ID */
cellId: number;
/** 购买数量 */
quantity: number;
/** 购买时单价 */
price: number;
/** 商品总金额 */
totalAmount: number;
/** 商品名称 */
goodsName: string;
/** 封面图URL */
coverImg: string;
/** 商品状态1正常 2已退货 3已换货 4已完成 5审核中 6退货未通过 */
status: number;
/** 买家姓名 */
buyerName: string;
createTime: string;
createTimeStr: string;
}
/** 统计数据DTO */
export interface StatsDTO {
/** 商店数量 */
shopCount: number;
/** 商品数量 */
goodsCount: number;
/** 订单数量 */
orderCount: number;
/** 总柜子数量 */
cabinetCount: number;
/** 总格口数量 */
cellCount: number;
/** 已关联格口数量 */
linkedCellCount: number;
/** 未管理格口数量 */
unmanagedCellCount: number;
/** 网关数量 */
gatewayCount: number;
/** 热门商品列表 */
topGoods: TopGoodsDTO[];
/** 今日最新订单商品列表 */
todayLatestOrderGoods: TodayLatestOrderGoodsDTO[];
}
/**
*
* @returns
*/
export const getStats = () => {
return http.request<ResponseData<StatsDTO>>('get', '/shop/shops/Stats');
};

View File

@ -1,5 +1,7 @@
<script setup lang="ts">
import { Clock, House, Lightning, Connection } from '@element-plus/icons-vue';
import { getStats, TodayLatestOrderGoodsDTO, TopGoodsDTO } from '@/api/shop/stats';
import { onMounted, ref } from 'vue';
defineOptions({
name: "Welcome"
@ -13,19 +15,46 @@ const todoItems = [
{ name: '水表离线', icon: Connection, count: 4 }
];
const shopData = [
{ name: '商店数量', value: 12 },
{ name: '商品数量', value: 356 },
{ name: '订单数量', value: 89 }
];
const shopData = ref([
{ name: '商店', value: 0 },
{ name: '商品', value: 0 },
{ name: '订单', value: 0 }
]);
const deviceData = [
{ name: '总柜子数量', value: 15 },
{ name: '总格口数量', value: 240 },
{ name: '已关联数量', value: 180 },
{ name: '未关联数量', value: 60 },
{ name: '网关数量', value: 8 }
];
const deviceData = ref([
{ name: '总柜子', value: 0 },
{ name: '总格口', value: 0 },
{ name: '已关联', value: 0 },
{ name: '未关联', value: 0 },
{ name: '网关', value: 0 }
]);
const topGoods = ref<TopGoodsDTO[]>([]);
const todayLatestOrderGoods = ref<TodayLatestOrderGoodsDTO[]>([]);
const maxOccurrenceCount = ref(0);
onMounted(async () => {
try {
const { data } = await getStats();
shopData.value = [
{ name: '商店', value: data.shopCount },
{ name: '商品', value: data.goodsCount },
{ name: '订单', value: data.orderCount }
];
deviceData.value = [
{ name: '总柜子', value: data.cabinetCount },
{ name: '总格口', value: data.cellCount },
{ name: '已关联', value: data.linkedCellCount },
{ name: '未关联', value: data.unmanagedCellCount },
{ name: '网关', value: data.gatewayCount }
];
topGoods.value = data.topGoods;
todayLatestOrderGoods.value = data.todayLatestOrderGoods;
maxOccurrenceCount.value = Math.max(...data.topGoods.map(item => item.occurrenceCount), 1);
} catch (error) {
console.error('获取统计数据失败:', error);
}
});
</script>
<template>
@ -93,6 +122,62 @@ const deviceData = [
</div>
</el-col>
</el-row>
<!-- 商品信息 -->
<el-row :gutter="12">
<!-- 热门商品 -->
<el-col :span="12">
<div class="section-container goods-container">
<div class="section-title">
<div class="title-bar"></div>
<div class="title-text">借还排行</div>
</div>
<div class="goods-list">
<el-card shadow="never" class="goods-item" v-for="item in topGoods" :key="item.goodsId"
:body-style="{ padding: '0' }">
<div class="goods-item-content">
<div class="goods-left">
<el-image :src="item.coverImg" fit="contain" class="goods-image" />
<div class="goods-name">{{ item.goodsName }}</div>
</div>
<div class="goods-right">
<el-progress :percentage="(item.occurrenceCount / maxOccurrenceCount) * 100" :show-text="false"
:stroke-width="12" class="goods-progress" />
<div class="goods-count">{{ item.occurrenceCount }}</div>
</div>
</div>
</el-card>
</div>
</div>
</el-col>
<!-- 今日最新订单 -->
<el-col :span="12">
<div class="section-container order-container">
<div class="section-title">
<div class="title-bar"></div>
<div class="title-text">今日数据</div>
</div>
<div class="goods-list">
<el-card shadow="never" class="goods-item" v-for="item in todayLatestOrderGoods" :key="item.orderGoodsId"
:body-style="{ padding: '0' }">
<div class="goods-item-content">
<div class="goods-left">
<el-image :src="item.coverImg" fit="contain" class="goods-image" />
<div class="goods-name">{{ item.goodsName }}</div>
</div>
<div class="goods-right">
<div class="goods-count">数量{{ item.quantity }}</div>
<div class="goods-count">{{ item.totalAmount }}</div>
<div class="goods-count buyer-name">{{ item.buyerName }}</div>
<div class="goods-count">{{ item.createTimeStr }}</div>
</div>
</div>
</el-card>
</div>
</div>
</el-col>
</el-row>
</div>
</template>
@ -106,7 +191,7 @@ const deviceData = [
.section-title {
display: flex;
align-items: center;
margin-bottom: 15px;
margin-bottom: 5px;
.title-bar {
width: 3px;
@ -152,6 +237,7 @@ const deviceData = [
}
}
}
.todo-count {
font-size: 24px;
font-weight: bold;
@ -164,8 +250,99 @@ const deviceData = [
color: var(--el-text-color-secondary);
text-align: center;
}
.data-section {
margin-bottom: 0;
}
.goods-card,
.order-card {
height: 100%;
border: none;
.goods-image,
.order-image {
width: 100%;
height: 140px;
border-radius: 4px 4px 0 0;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.goods-content,
.order-content {
padding: 10px;
.goods-name,
.order-name {
font-size: 14px;
font-weight: bold;
margin-bottom: 5px;
text-align: center;
}
.goods-count,
.order-info {
font-size: 12px;
color: var(--el-text-color-secondary);
text-align: center;
}
}
}
.goods-list {
.goods-item {
border: none;
.goods-item-content {
display: flex;
align-items: center;
justify-content: space-between;
padding: 3px;
.goods-left {
display: flex;
align-items: center;
flex: 1;
.goods-image {
width: 100px;
height: 80px;
border-radius: 4px;
margin-right: 12px;
/* box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1); */
}
.goods-name {
font-size: 14px;
font-weight: bold;
}
}
.goods-right {
display: flex;
align-items: center;
width: 450px;
.goods-progress {
flex: 1;
margin-right: 12px;
}
.goods-count {
width: 60px;
text-align: right;
font-size: 14px;
color: var(--el-text-color-primary);
}
.buyer-name {
padding-left: 10px;
flex-grow: 1;
text-align: left;
}
}
}
}
}
}
</style>