feat(欢迎页): 添加统计数据显示功能
在欢迎页中新增了统计数据的显示功能,包括商店、商品、订单、柜子、格口等数据的动态展示,并引入了热门商品和今日最新订单的展示模块。通过调用 `getStats` API 获取数据,并在页面加载时进行渲染,提升了页面的信息丰富度和用户体验。
This commit is contained in:
parent
328158d829
commit
2c319bae8f
|
@ -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');
|
||||
};
|
|
@ -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>
|
Loading…
Reference in New Issue