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">
|
<script setup lang="ts">
|
||||||
import { Clock, House, Lightning, Connection } from '@element-plus/icons-vue';
|
import { Clock, House, Lightning, Connection } from '@element-plus/icons-vue';
|
||||||
|
import { getStats, TodayLatestOrderGoodsDTO, TopGoodsDTO } from '@/api/shop/stats';
|
||||||
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: "Welcome"
|
name: "Welcome"
|
||||||
|
@ -13,19 +15,46 @@ const todoItems = [
|
||||||
{ name: '水表离线', icon: Connection, count: 4 }
|
{ name: '水表离线', icon: Connection, count: 4 }
|
||||||
];
|
];
|
||||||
|
|
||||||
const shopData = [
|
const shopData = ref([
|
||||||
{ name: '商店数量', value: 12 },
|
{ name: '商店', value: 0 },
|
||||||
{ name: '商品数量', value: 356 },
|
{ name: '商品', value: 0 },
|
||||||
{ name: '订单数量', value: 89 }
|
{ name: '订单', value: 0 }
|
||||||
];
|
]);
|
||||||
|
|
||||||
const deviceData = [
|
const deviceData = ref([
|
||||||
{ name: '总柜子数量', value: 15 },
|
{ name: '总柜子', value: 0 },
|
||||||
{ name: '总格口数量', value: 240 },
|
{ name: '总格口', value: 0 },
|
||||||
{ name: '已关联数量', value: 180 },
|
{ name: '已关联', value: 0 },
|
||||||
{ name: '未关联数量', value: 60 },
|
{ name: '未关联', value: 0 },
|
||||||
{ name: '网关数量', value: 8 }
|
{ 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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -93,6 +122,62 @@ const deviceData = [
|
||||||
</div>
|
</div>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -106,7 +191,7 @@ const deviceData = [
|
||||||
.section-title {
|
.section-title {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 15px;
|
margin-bottom: 5px;
|
||||||
|
|
||||||
.title-bar {
|
.title-bar {
|
||||||
width: 3px;
|
width: 3px;
|
||||||
|
@ -152,6 +237,7 @@ const deviceData = [
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.todo-count {
|
.todo-count {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
@ -164,8 +250,99 @@ const deviceData = [
|
||||||
color: var(--el-text-color-secondary);
|
color: var(--el-text-color-secondary);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.data-section {
|
.data-section {
|
||||||
margin-bottom: 0;
|
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>
|
</style>
|
Loading…
Reference in New Issue