更新 dashboard

This commit is contained in:
aaron 2025-03-10 09:37:16 +08:00
parent 08dc2d2db9
commit 8c26691f58
2 changed files with 232 additions and 281 deletions

31
src/api/dashboard.js Normal file
View File

@ -0,0 +1,31 @@
import request from '../utils/request';
/**
* 获取运营商汇总信息
* @returns {Promise} - 返回汇总数据
*/
export function getPartnerSummary() {
return request({
url: '/api/partner/summary',
method: 'get'
});
}
/**
* 获取运营商管理的小区列表
* @param {Object} params - 查询参数
* @param {number} params.page - 页码
* @param {number} params.pageSize - 每页条数
* @returns {Promise} - 返回小区列表数据
*/
export function getPartnerCommunityList(params) {
return request({
url: '/api/partner/community_list',
method: 'get',
params: {
skip: (params.page - 1) * params.pageSize,
limit: params.pageSize,
...params
}
});
}

View File

@ -7,148 +7,128 @@
<template #title>
<span><home-outlined /> 运营小区数</span>
</template>
<div class="card-content">
<h2>{{ dashboardData.communityCount }}</h2>
<p>本月新增 <a-tag color="success">+{{ dashboardData.newCommunityCount }}</a-tag></p>
<div class="card-content community-content">
<h2 class="community-data">{{ summaryData.community_count || 0 }}</h2>
</div>
</a-card>
</a-col>
<a-col :span="8" :xs="24" :sm="12" :md="8" :lg="8" :xl="8">
<a-card class="card-orders">
<template #title>
<span><shopping-outlined /> 总配送订单数</span>
<span><shopping-outlined /> 今日订单数</span>
</template>
<div class="card-content">
<h2>{{ dashboardData.totalOrders.toLocaleString() }}</h2>
<h2 class="orders-data">{{ summaryData.today_order_count || 0 }}</h2>
<p v-if="summaryData.yesterday_order_count > 0" class="comparison-rate">
同比昨日
<a-tag :color="getComparisonColor(summaryData.today_order_count, summaryData.yesterday_order_count)">
{{ getComparisonText(summaryData.today_order_count, summaryData.yesterday_order_count) }}
</a-tag>
</p>
<div class="comparison-data">
<div>
<span>今日</span>
<span class="highlight">{{ dashboardData.todayOrders }}</span>
</div>
<div>
<span>昨日</span>
<span>{{ dashboardData.yesterdayOrders }}</span>
<span>{{ summaryData.yesterday_order_count || 0 }}</span>
</div>
<div>
<span>总计</span>
<span>{{ summaryData.total_order_count || 0 }}</span>
</div>
</div>
</div>
</a-card>
</a-col>
<a-col :span="8" :xs="24" :sm="12" :md="8" :lg="8" :xl="8">
<a-card class="card-users">
<template #title>
<span><user-outlined /> 总用户数</span>
</template>
<div class="card-content">
<h2>{{ dashboardData.totalUsers.toLocaleString() }}</h2>
<div class="comparison-data">
<div>
<span>今日新增</span>
<span class="highlight">{{ dashboardData.todayNewUsers }}</span>
</div>
<div>
<span>昨日新增</span>
<span>{{ dashboardData.yesterdayNewUsers }}</span>
</div>
</div>
</div>
</a-card>
</a-col>
</a-row>
<a-row :gutter="16" style="margin-top: 16px">
<a-col :span="8" :xs="24" :sm="12" :md="8" :lg="8" :xl="8">
<a-card class="card-sales">
<template #title>
<span><dollar-outlined /> 累计销售金额</span>
<span><dollar-outlined /> 今日销售金额</span>
</template>
<div class="card-content">
<h2>¥ {{ dashboardData.totalSales.toLocaleString() }}</h2>
<h2 class="sales-data">¥ {{ formatAmount(summaryData.today_order_amount) }}</h2>
<p v-if="summaryData.yesterday_order_amount > 0" class="comparison-rate">
同比昨日
<a-tag :color="getComparisonColor(summaryData.today_order_amount, summaryData.yesterday_order_amount)">
{{ getComparisonText(summaryData.today_order_amount, summaryData.yesterday_order_amount) }}
</a-tag>
</p>
<div class="comparison-data">
<div>
<span>今日</span>
<span class="highlight">¥ {{ dashboardData.todaySales.toLocaleString() }}</span>
</div>
<div>
<span>昨日</span>
<span>¥ {{ dashboardData.yesterdaySales.toLocaleString() }}</span>
</div>
</div>
</div>
</a-card>
</a-col>
<a-col :span="8" :xs="24" :sm="12" :md="8" :lg="8" :xl="8">
<a-card class="card-profit">
<template #title>
<span><wallet-outlined /> 累计分润金额</span>
</template>
<div class="card-content">
<h2>¥ {{ dashboardData.totalProfit.toLocaleString() }}</h2>
<div class="comparison-data">
<div>
<span>今日</span>
<span class="highlight">¥ {{ dashboardData.todayProfit.toLocaleString() }}</span>
<span>¥ {{ formatAmount(summaryData.yesterday_order_amount) }}</span>
</div>
<div>
<span>昨日</span>
<span>¥ {{ dashboardData.yesterdayProfit.toLocaleString() }}</span>
<span>总计</span>
<span>¥ {{ formatAmount(summaryData.total_order_amount) }}</span>
</div>
</div>
</div>
</a-card>
</a-col>
<a-col :span="8" :xs="24" :sm="12" :md="8" :lg="8" :xl="8">
<a-card class="card-conversion">
<template #title>
<span><rise-outlined /> 订单转化率</span>
</template>
<div class="card-content">
<h2>{{ dashboardData.conversionRate }}%</h2>
<p>较上周 <a-tag :color="dashboardData.conversionRateChange > 0 ? 'success' : 'error'">{{ dashboardData.conversionRateChange > 0 ? '+' : '' }}{{ dashboardData.conversionRateChange }}%</a-tag></p>
</div>
</a-card>
</a-col>
</a-row>
<a-row :gutter="16" style="margin-top: 16px">
<a-col :span="24">
<a-card title="每日分润趋势" class="card-chart">
<div style="height: 350px; padding: 0 20px;">
<a-row style="margin-bottom: 20px;">
<a-col :span="24">
<div class="chart-legend">
<div class="chart-legend-item">
<div class="chart-legend-color" style="background-color: #1890ff;"></div>
<span>分润金额</span>
</div>
</div>
</a-col>
</a-row>
<div class="chart-container">
<div v-for="(item, index) in dashboardData.dailyProfitData" :key="index" class="chart-bar-item">
<div class="chart-bar-value">¥{{ item.value }}</div>
<div class="chart-bar">
<div class="chart-bar-inner" :style="{ height: (item.value / maxProfitValue * 100) + '%' }"></div>
</div>
<div class="chart-bar-label">{{ item.date }}</div>
</div>
</div>
</div>
<!-- 小区列表 -->
<a-card title="管理小区列表" style="margin-top: 16px">
<template #extra>
<a-button type="primary" @click="refreshData">
<reload-outlined style="color: #fff;" />
刷新
</a-button>
</template>
<a-table
:dataSource="communityList"
:loading="loading"
:pagination="{
total: total,
current: currentPage,
pageSize: pageSize,
showTotal: total => `${total} 条记录`,
showSizeChanger: true,
pageSizeOptions: ['10', '20', '50'],
onChange: handlePageChange,
onShowSizeChange: handlePageSizeChange
}"
rowKey="community_id"
>
<a-table-column title="小区ID" dataIndex="community_id" key="community_id" width="100px" />
<a-table-column title="小区名称" dataIndex="community_name" key="community_name" />
<a-table-column title="今日订单数" key="today_order_count">
<template #default="{ text, record }">
{{ record.today_order_count || 0 }}
</template>
</a-table-column>
<a-table-column title="昨日订单数" key="yesterday_order_count">
<template #default="{ text, record }">
{{ record.yesterday_order_count || 0 }}
</template>
</a-table-column>
<a-table-column title="今日收入" key="today_income">
<template #default="{ text, record }">
¥ {{ formatAmount(record.today_income) }}
</template>
</a-table-column>
<a-table-column title="昨日收入" key="yesterday_income">
<template #default="{ text, record }">
¥ {{ formatAmount(record.yesterday_income) }}
</template>
</a-table-column>
</a-table>
</a-card>
</a-col>
</a-row>
</div>
</template>
<script>
import { ref, reactive, onMounted, computed } from 'vue';
import { ref, reactive, onMounted } from 'vue';
import { message } from 'ant-design-vue';
import {
UserOutlined,
ShoppingOutlined,
DollarOutlined,
HomeOutlined,
WalletOutlined,
RiseOutlined
RiseOutlined,
ReloadOutlined
} from '@ant-design/icons-vue';
import { getPartnerSummary, getPartnerCommunityList } from '../api/dashboard';
export default {
name: 'Dashboard',
@ -158,66 +138,106 @@ export default {
DollarOutlined,
HomeOutlined,
WalletOutlined,
RiseOutlined
RiseOutlined,
ReloadOutlined
},
setup() {
//
const dashboardData = reactive({
//
communityCount: 68,
newCommunityCount: 5,
//
const summaryData = ref({});
//
const communityList = ref([]);
//
const loading = ref(false);
//
const currentPage = ref(1);
const pageSize = ref(10);
const total = ref(0);
//
totalOrders: 24689,
todayOrders: 386,
yesterdayOrders: 412,
//
const fetchSummaryData = async () => {
try {
const data = await getPartnerSummary();
summaryData.value = data || {};
} catch (error) {
console.error('获取汇总数据失败:', error);
message.error('获取汇总数据失败');
}
};
//
totalUsers: 8756,
todayNewUsers: 42,
yesterdayNewUsers: 38,
//
const fetchCommunityList = async () => {
loading.value = true;
try {
const params = {
page: currentPage.value,
pageSize: pageSize.value
};
const response = await getPartnerCommunityList(params);
communityList.value = response.items || [];
total.value = response.total || 0;
} catch (error) {
console.error('获取小区列表失败:', error);
message.error('获取小区列表失败');
} finally {
loading.value = false;
}
};
//
totalSales: 1268950,
todaySales: 28650,
yesterdaySales: 31280,
//
const handlePageChange = (page, size) => {
currentPage.value = page;
fetchCommunityList();
};
//
totalProfit: 253790,
todayProfit: 5730,
yesterdayProfit: 6256,
//
const handlePageSizeChange = (current, size) => {
currentPage.value = 1;
pageSize.value = size;
fetchCommunityList();
};
//
conversionRate: 32.6,
conversionRateChange: 2.8,
//
const refreshData = () => {
fetchSummaryData();
fetchCommunityList();
};
//
dailyProfitData: [
{ date: '6/1', value: 4280 },
{ date: '6/2', value: 5120 },
{ date: '6/3', value: 4830 },
{ date: '6/4', value: 5680 },
{ date: '6/5', value: 6120 },
{ date: '6/6', value: 5890 },
{ date: '6/7', value: 4950 },
{ date: '6/8', value: 5230 },
{ date: '6/9', value: 5780 },
{ date: '6/10', value: 6050 },
{ date: '6/11', value: 5840 },
{ date: '6/12', value: 5320 },
{ date: '6/13', value: 6256 },
{ date: '6/14', value: 5730 }
]
});
//
const getComparisonText = (today, yesterday) => {
if (!yesterday) return '0%';
const rate = ((today - yesterday) / yesterday * 100).toFixed(1);
return rate > 0 ? `+${rate}%` : `${rate}%`;
};
//
const maxProfitValue = computed(() => {
return Math.max(...dashboardData.dailyProfitData.map(item => item.value)) * 1.1;
//
const getComparisonColor = (today, yesterday) => {
if (!yesterday) return 'default';
return today >= yesterday ? 'success' : 'error';
};
// 2
const formatAmount = (amount) => {
if (amount === undefined || amount === null) return '0.00';
return Number(amount).toFixed(2);
};
onMounted(() => {
refreshData();
});
return {
dashboardData,
maxProfitValue
summaryData,
communityList,
loading,
currentPage,
pageSize,
total,
refreshData,
handlePageChange,
handlePageSizeChange,
getComparisonText,
getComparisonColor,
formatAmount
};
}
};
@ -230,6 +250,9 @@ export default {
.card-content {
text-align: center;
min-height: 100px;
display: flex;
flex-direction: column;
}
.card-content h2 {
@ -251,161 +274,58 @@ export default {
}
.comparison-data .highlight {
color: #1890ff;
font-weight: 500;
}
:deep(.ant-card) {
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
transition: all 0.3s;
margin-bottom: 16px;
.card-community, .card-orders, .card-sales {
height: 100%;
}
:deep(.ant-card:hover) {
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.1);
transform: translateY(-2px);
}
:deep(.ant-card-head) {
border-bottom: 1px solid #f0f0f0;
}
:deep(.ant-card-head-title) {
font-weight: 600;
}
/* 卡片颜色主题 */
.card-community {
border-top: 3px solid #722ed1;
}
.card-community h2, .card-community :deep(.anticon) {
color: #722ed1;
border-top: 3px solid #52c41a;
}
.card-orders {
border-top: 3px solid #1890ff;
}
.card-orders h2, .card-orders :deep(.anticon) {
color: #1890ff;
}
.card-users {
border-top: 3px solid #52c41a;
}
.card-users h2, .card-users :deep(.anticon) {
color: #52c41a;
}
.card-sales {
border-top: 3px solid #fa8c16;
}
.card-sales h2, .card-sales :deep(.anticon) {
color: #fa8c16;
.today-data {
font-size: 32px;
margin-bottom: 4px;
}
.card-profit {
border-top: 3px solid #eb2f96;
}
.card-profit h2, .card-profit :deep(.anticon) {
color: #eb2f96;
}
.card-conversion {
border-top: 3px solid #13c2c2;
}
.card-conversion h2, .card-conversion :deep(.anticon) {
color: #13c2c2;
}
.card-chart {
border-top: 3px solid #1890ff;
}
.card-chart :deep(.ant-card-head-title) {
.orders-data {
font-size: 32px;
color: #1890ff;
}
/* 图表样式 */
.chart-container {
display: flex;
justify-content: space-between;
align-items: flex-end;
height: 250px;
}
.chart-bar-item {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
}
.chart-bar-value {
font-size: 12px;
color: #666;
margin-bottom: 4px;
height: 20px;
}
.chart-bar {
width: 24px;
height: 200px;
background-color: #f5f5f5;
border-radius: 4px;
position: relative;
overflow: hidden;
}
.chart-bar-inner {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
background-color: #1890ff;
border-radius: 4px;
transition: height 0.3s ease;
}
.chart-bar-label {
margin-top: 8px;
font-size: 12px;
color: #666;
}
.chart-legend {
display: flex;
justify-content: flex-end;
}
.chart-legend-item {
display: flex;
align-items: center;
margin-left: 16px;
}
.chart-legend-color {
width: 12px;
height: 12px;
margin-right: 8px;
border-radius: 2px;
}
@media (max-width: 768px) {
.comparison-data {
flex-direction: column;
align-items: center;
}
.comparison-data div {
margin-bottom: 4px;
}
.chart-container {
overflow-x: auto;
padding-bottom: 10px;
.sales-data {
font-size: 32px;
color: #fa8c16;
margin-bottom: 4px;
}
.chart-bar-item {
min-width: 40px;
.comparison-rate {
margin-bottom: 12px;
font-size: 14px;
color: rgba(0, 0, 0, 0.65);
}
.community-content {
justify-content: center;
align-items: center;
flex-grow: 1;
}
.community-data {
font-size: 48px;
color: #52c41a;
margin: 0;
}
</style>