文件

687 行
24 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>配送统计</title>
<link rel="stylesheet" type="text/css" href="__PUBLIC__/css/menu.css?v=1.0.5"/>
<link rel="stylesheet" type="text/css" href="__PUBLIC__/css/minimal-admin.css?v=1.0.0"/>
<link rel="stylesheet" type="text/css" href="__PUBLIC__/Retail/css/myscroll.css?v=1.0.2"/>
<link rel="stylesheet" type="text/css" href="__PUBLIC__/layui-v2.4.3/layui/css/layui.css"/>
<link rel="stylesheet" type="text/css" href="__PUBLIC__/Retail/css/home.css"/>
<link rel="stylesheet" type="text/css" href="__PUBLIC__/vue/element-ui.css?v=2.13.2">
<link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<script type="text/javascript" charset="utf-8" src="__PUBLIC__/vue/js/vue2.5.16.js"></script>
<script type="text/javascript" charset="utf-8" src="__PUBLIC__/vue/element-ui.js?v=2.13.2"></script>
<script type="text/javascript" charset="utf-8" src="__PUBLIC__/js/common.js?v=1.0.2"></script>
<script type="text/javascript" charset="utf-8" src="__PUBLIC__/jquery/js/jquery-3.2.1.min.js"></script>
<script type="text/javascript" charset="utf-8" src="__PUBLIC__/layui-v2.4.3/layui/layui.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/echarts/5.4.3/echarts.min.js"></script>
</head>
<body>
<div id="RiderStatsManagement" v-cloak>
<div class="goodsBox">
<div class="goods_title">
<div class="goods_title_inner">
<i class="fas fa-chart-bar" style="margin-right: 8px; color: #409EFF;"></i>
配送统计
</div>
</div>
</div>
<!-- 时间筛选器 -->
<div style="padding: 20px; background: white; margin: 20px; border-radius: 8px; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);">
<el-row :gutter="20" style="align-items: center;">
<el-col :span="6">
<el-select v-model="timeRange" placeholder="选择时间范围" @change="handleTimeRangeChange">
<el-option label="今天" value="today"></el-option>
<el-option label="昨天" value="yesterday"></el-option>
<el-option label="最近7天" value="week"></el-option>
<el-option label="最近30天" value="month"></el-option>
<el-option label="自定义" value="custom"></el-option>
</el-select>
</el-col>
<el-col :span="8" v-if="timeRange === 'custom'">
<el-date-picker
v-model="customDateRange"
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
@change="loadStatistics">
</el-date-picker>
</el-col>
<el-col :span="4">
<el-button type="primary" icon="el-icon-refresh" @click="loadStatistics">刷新数据</el-button>
</el-col>
</el-row>
</div>
<!-- 核心指标卡片 -->
<div class="stats-overview" style="padding: 20px; background: #f5f7fa;">
<el-row :gutter="20">
<el-col :span="6">
<div class="metric-card">
<div class="metric-icon" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);">
<i class="fas fa-list-alt"></i>
</div>
<div class="metric-content">
<div class="metric-number">{{ overview.totalOrders }}</div>
<div class="metric-label">总订单数</div>
<div class="metric-change" :class="overview.orderChange >= 0 ? 'positive' : 'negative'">
<i :class="overview.orderChange >= 0 ? 'fas fa-arrow-up' : 'fas fa-arrow-down'"></i>
{{ Math.abs(overview.orderChange) }}%
</div>
</div>
</div>
</el-col>
<el-col :span="6">
<div class="metric-card">
<div class="metric-icon" style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);">
<i class="fas fa-dollar-sign"></i>
</div>
<div class="metric-content">
<div class="metric-number">¥{{ overview.totalRevenue }}</div>
<div class="metric-label">总收入</div>
<div class="metric-change" :class="overview.revenueChange >= 0 ? 'positive' : 'negative'">
<i :class="overview.revenueChange >= 0 ? 'fas fa-arrow-up' : 'fas fa-arrow-down'"></i>
{{ Math.abs(overview.revenueChange) }}%
</div>
</div>
</div>
</el-col>
<el-col :span="6">
<div class="metric-card">
<div class="metric-icon" style="background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);">
<i class="fas fa-clock"></i>
</div>
<div class="metric-content">
<div class="metric-number">{{ overview.avgDeliveryTime }}</div>
<div class="metric-label">平均配送时间(分钟)</div>
<div class="metric-change" :class="overview.timeChange <= 0 ? 'positive' : 'negative'">
<i :class="overview.timeChange <= 0 ? 'fas fa-arrow-down' : 'fas fa-arrow-up'"></i>
{{ Math.abs(overview.timeChange) }}%
</div>
</div>
</div>
</el-col>
<el-col :span="6">
<div class="metric-card">
<div class="metric-icon" style="background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);">
<i class="fas fa-percentage"></i>
</div>
<div class="metric-content">
<div class="metric-number">{{ overview.completionRate }}%</div>
<div class="metric-label">完成率</div>
<div class="metric-change" :class="overview.completionChange >= 0 ? 'positive' : 'negative'">
<i :class="overview.completionChange >= 0 ? 'fas fa-arrow-up' : 'fas fa-arrow-down'"></i>
{{ Math.abs(overview.completionChange) }}%
</div>
</div>
</div>
</el-col>
</el-row>
</div>
<!-- 图表区域 -->
<div style="padding: 20px;">
<el-row :gutter="20">
<!-- 订单趋势图 -->
<el-col :span="12">
<div class="chart-container">
<div class="chart-header">
<h3><i class="fas fa-chart-line"></i> 订单趋势</h3>
</div>
<div id="orderTrendChart" style="height: 300px;"></div>
</div>
</el-col>
<!-- 收入趋势图 -->
<el-col :span="12">
<div class="chart-container">
<div class="chart-header">
<h3><i class="fas fa-chart-area"></i> 收入趋势</h3>
</div>
<div id="revenueTrendChart" style="height: 300px;"></div>
</div>
</el-col>
</el-row>
<el-row :gutter="20" style="margin-top: 20px;">
<!-- 订单状态分布 -->
<el-col :span="8">
<div class="chart-container">
<div class="chart-header">
<h3><i class="fas fa-chart-pie"></i> 订单状态分布</h3>
</div>
<div id="orderStatusChart" style="height: 300px;"></div>
</div>
</el-col>
<!-- 骑手排行榜 -->
<el-col :span="8">
<div class="chart-container">
<div class="chart-header">
<h3><i class="fas fa-trophy"></i> 骑手排行榜</h3>
</div>
<div id="riderRankChart" style="height: 300px;"></div>
</div>
</el-col>
<!-- 配送时段分析 -->
<el-col :span="8">
<div class="chart-container">
<div class="chart-header">
<h3><i class="fas fa-clock"></i> 配送时段分析</h3>
</div>
<div id="timeSlotChart" style="height: 300px;"></div>
</div>
</el-col>
</el-row>
</div>
<!-- 详细数据表格 -->
<div style="padding: 20px; background: white; margin: 20px; border-radius: 8px; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);">
<div style="margin-bottom: 20px;">
<h3><i class="fas fa-table"></i> 骑手详细统计</h3>
</div>
<el-table
:data="riderStats"
v-loading="loading"
element-loading-text="加载中..."
style="width: 100%"
:header-cell-style="{background:'#f5f7fa',color:'#606266'}">
<el-table-column label="排名" width="80" align="center">
<template slot-scope="scope">
<el-tag v-if="scope.$index < 3" :type="['', 'warning', 'info'][scope.$index]" size="small">
{{ scope.$index + 1 }}
</el-tag>
<span v-else>{{ scope.$index + 1 }}</span>
</template>
</el-table-column>
<el-table-column label="骑手" width="150">
<template slot-scope="scope">
<div style="display: flex; align-items: center;">
<el-avatar :size="40" :src="scope.row.avatar" icon="el-icon-user-solid"></el-avatar>
<div style="margin-left: 10px;">
<div style="font-weight: bold;">{{ scope.row.name }}</div>
<div style="font-size: 12px; color: #666;">{{ scope.row.phone }}</div>
</div>
</div>
</template>
</el-table-column>
<el-table-column prop="totalOrders" label="完成订单" width="100" align="center" sortable></el-table-column>
<el-table-column label="总收入" width="120" align="center" sortable>
<template slot-scope="scope">
<span style="color: #67C23A; font-weight: bold;">¥{{ scope.row.totalRevenue }}</span>
</template>
</el-table-column>
<el-table-column prop="avgDeliveryTime" label="平均配送时间" width="120" align="center" sortable>
<template slot-scope="scope">
{{ scope.row.avgDeliveryTime }}分钟
</template>
</el-table-column>
<el-table-column label="完成率" width="100" align="center" sortable>
<template slot-scope="scope">
<el-progress :percentage="scope.row.completionRate" :color="getProgressColor(scope.row.completionRate)"></el-progress>
</template>
</el-table-column>
<el-table-column label="评分" width="120" align="center" sortable>
<template slot-scope="scope">
<el-rate v-model="scope.row.rating" disabled show-score text-color="#ff9900"></el-rate>
</template>
</el-table-column>
<el-table-column prop="totalDistance" label="总里程(km)" width="120" align="center" sortable></el-table-column>
<el-table-column label="在线时长" width="120" align="center">
<template slot-scope="scope">
{{ scope.row.onlineHours }}小时
</template>
</el-table-column>
<el-table-column label="操作" width="120" align="center">
<template slot-scope="scope">
<el-button size="mini" type="primary" @click="viewRiderDetail(scope.row)">详情</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>
<style>
/* 指标卡片样式 */
.metric-card {
background: white;
border-radius: 12px;
padding: 20px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
display: flex;
align-items: center;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.metric-card:hover {
transform: translateY(-5px);
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.15);
}
.metric-icon {
width: 60px;
height: 60px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 15px;
}
.metric-icon i {
font-size: 24px;
color: white;
}
.metric-content {
flex: 1;
}
.metric-number {
font-size: 28px;
font-weight: bold;
color: #2c3e50;
line-height: 1;
}
.metric-label {
font-size: 14px;
color: #7f8c8d;
margin: 5px 0;
}
.metric-change {
font-size: 12px;
font-weight: bold;
}
.metric-change.positive {
color: #67C23A;
}
.metric-change.negative {
color: #F56C6C;
}
/* 图表容器样式 */
.chart-container {
background: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
margin-bottom: 20px;
}
.chart-header {
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 1px solid #eee;
}
.chart-header h3 {
margin: 0;
color: #2c3e50;
font-size: 16px;
}
.chart-header i {
margin-right: 8px;
color: #409EFF;
}
/* 响应式设计 */
@media (max-width: 768px) {
.stats-overview .el-col {
margin-bottom: 15px;
}
.chart-container {
margin-bottom: 15px;
}
}
</style>
<script>
new Vue({
el: '#RiderStatsManagement',
data() {
return {
loading: false,
timeRange: 'today',
customDateRange: [],
// 概览数据
overview: {
totalOrders: 1250,
orderChange: 12.5,
totalRevenue: 15680.50,
revenueChange: 8.3,
avgDeliveryTime: 28,
timeChange: -5.2,
completionRate: 95.8,
completionChange: 2.1
},
// 骑手统计数据
riderStats: [
{
id: 1,
name: '张三',
phone: '13800138001',
avatar: '',
totalOrders: 156,
totalRevenue: 2340.50,
avgDeliveryTime: 25,
completionRate: 98.5,
rating: 4.8,
totalDistance: 1250.5,
onlineHours: 180
},
{
id: 2,
name: '李四',
phone: '13800138002',
avatar: '',
totalOrders: 142,
totalRevenue: 2130.00,
avgDeliveryTime: 27,
completionRate: 96.2,
rating: 4.6,
totalDistance: 1180.2,
onlineHours: 165
},
{
id: 3,
name: '王五',
phone: '13800138003',
avatar: '',
totalOrders: 128,
totalRevenue: 1920.00,
avgDeliveryTime: 30,
completionRate: 94.1,
rating: 4.4,
totalDistance: 1050.8,
onlineHours: 150
}
],
// 图表实例
charts: {
orderTrend: null,
revenueTrend: null,
orderStatus: null,
riderRank: null,
timeSlot: null
}
}
},
methods: {
// 时间范围变化
handleTimeRangeChange() {
if (this.timeRange !== 'custom') {
this.loadStatistics();
}
},
// 加载统计数据
loadStatistics() {
this.loading = true;
// 这里应该调用API获取数据
setTimeout(() => {
this.loading = false;
this.initCharts();
}, 1000);
},
// 初始化图表
initCharts() {
this.$nextTick(() => {
this.initOrderTrendChart();
this.initRevenueTrendChart();
this.initOrderStatusChart();
this.initRiderRankChart();
this.initTimeSlotChart();
});
},
// 订单趋势图
initOrderTrendChart() {
if (this.charts.orderTrend) {
this.charts.orderTrend.dispose();
}
this.charts.orderTrend = echarts.init(document.getElementById('orderTrendChart'));
const option = {
tooltip: {
trigger: 'axis'
},
xAxis: {
type: 'category',
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
},
yAxis: {
type: 'value'
},
series: [{
data: [120, 132, 101, 134, 90, 230, 210],
type: 'line',
smooth: true,
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(64, 158, 255, 0.3)' },
{ offset: 1, color: 'rgba(64, 158, 255, 0.1)' }
])
},
lineStyle: {
color: '#409EFF'
}
}]
};
this.charts.orderTrend.setOption(option);
},
// 收入趋势图
initRevenueTrendChart() {
if (this.charts.revenueTrend) {
this.charts.revenueTrend.dispose();
}
this.charts.revenueTrend = echarts.init(document.getElementById('revenueTrendChart'));
const option = {
tooltip: {
trigger: 'axis',
formatter: '{b}: ¥{c}'
},
xAxis: {
type: 'category',
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
},
yAxis: {
type: 'value'
},
series: [{
data: [1800, 1950, 1520, 2010, 1350, 3450, 3150],
type: 'bar',
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#67C23A' },
{ offset: 1, color: '#85CE61' }
])
}
}]
};
this.charts.revenueTrend.setOption(option);
},
// 订单状态分布图
initOrderStatusChart() {
if (this.charts.orderStatus) {
this.charts.orderStatus.dispose();
}
this.charts.orderStatus = echarts.init(document.getElementById('orderStatusChart'));
const option = {
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)'
},
series: [{
name: '订单状态',
type: 'pie',
radius: '70%',
data: [
{ value: 1182, name: '已完成', itemStyle: { color: '#67C23A' } },
{ value: 23, name: '配送中', itemStyle: { color: '#409EFF' } },
{ value: 45, name: '待分配', itemStyle: { color: '#E6A23C' } },
{ value: 12, name: '已取消', itemStyle: { color: '#F56C6C' } }
],
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}]
};
this.charts.orderStatus.setOption(option);
},
// 骑手排行榜
initRiderRankChart() {
if (this.charts.riderRank) {
this.charts.riderRank.dispose();
}
this.charts.riderRank = echarts.init(document.getElementById('riderRankChart'));
const option = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'value'
},
yAxis: {
type: 'category',
data: ['王五', '李四', '张三']
},
series: [{
type: 'bar',
data: [128, 142, 156],
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{ offset: 0, color: '#FF6B6B' },
{ offset: 1, color: '#FFE66D' }
])
}
}]
};
this.charts.riderRank.setOption(option);
},
// 配送时段分析
initTimeSlotChart() {
if (this.charts.timeSlot) {
this.charts.timeSlot.dispose();
}
this.charts.timeSlot = echarts.init(document.getElementById('timeSlotChart'));
const option = {
tooltip: {
trigger: 'axis'
},
xAxis: {
type: 'category',
data: ['8-10', '10-12', '12-14', '14-16', '16-18', '18-20', '20-22']
},
yAxis: {
type: 'value'
},
series: [{
data: [45, 78, 156, 89, 134, 189, 98],
type: 'line',
smooth: true,
lineStyle: {
color: '#9C27B0'
},
itemStyle: {
color: '#9C27B0'
}
}]
};
this.charts.timeSlot.setOption(option);
},
// 获取进度条颜色
getProgressColor(percentage) {
if (percentage >= 95) return '#67C23A';
if (percentage >= 90) return '#E6A23C';
return '#F56C6C';
},
// 查看骑手详情
viewRiderDetail(rider) {
this.$message.info(`查看骑手 ${rider.name} 的详细信息`);
}
},
mounted() {
this.loadStatistics();
// 监听窗口大小变化,重新调整图表
window.addEventListener('resize', () => {
Object.values(this.charts).forEach(chart => {
if (chart) {
chart.resize();
}
});
});
},
beforeDestroy() {
// 销毁图表实例
Object.values(this.charts).forEach(chart => {
if (chart) {
chart.dispose();
}
});
}
});
</script>
</body>
</html>