<template>
|
<view class="revenue-page">
|
<view class="filter-panel">
|
<view class="quick-tabs">
|
<view v-for="item in quickTabs" :key="item.value" class="quick-tab"
|
:class="{ active: currentRange === item.value }" @click="currentRange = item.value">
|
{{ item.label }}
|
</view>
|
</view>
|
|
<view v-if="currentRange === 'custom'" class="date-bar">
|
<view class="date-item" @click="showStartDatePicker = true">
|
<text :class="startDate ? 'date-text' : 'date-placeholder'">{{ startDate || '开始时间' }}</text>
|
</view>
|
<text class="date-separator">-</text>
|
<view class="date-item" @click="showEndDatePicker = true">
|
<text :class="endDate ? 'date-text' : 'date-placeholder'">{{ endDate || '结束时间' }}</text>
|
</view>
|
</view>
|
|
<!-- 开始日期选择器 -->
|
<u-datetime-picker :show="showStartDatePicker" mode="date" v-model="startPickerValue" @confirm="onStartDateConfirm"
|
@cancel="showStartDatePicker = false" placeholder="选择开始日期"></u-datetime-picker>
|
|
<!-- 结束日期选择器 -->
|
<u-datetime-picker :show="showEndDatePicker" mode="date" v-model="endPickerValue" @confirm="onEndDateConfirm"
|
@cancel="showEndDatePicker = false" placeholder="选择结束日期"></u-datetime-picker>
|
</view>
|
|
<view class="section-card metrics-card">
|
<view class="section-title-wrap">
|
<view class="title-bar"></view>
|
<text class="section-title">核心业绩指标</text>
|
</view>
|
|
<view class="metrics-grid">
|
<view v-for="item in metrics" :key="item.label" class="metric-item">
|
<view class="metric-icon-wrap">
|
<image class="metric-icon" :src="item.icon" mode="aspectFit"></image>
|
</view>
|
<view class="metric-copy">
|
<view class="metric-value">{{ item.value }}</view>
|
<view class="metric-label">{{ item.label }}</view>
|
</view>
|
</view>
|
</view>
|
</view>
|
|
<view class="section-card chart-card">
|
<view class="section-title-wrap">
|
<view class="title-bar"></view>
|
<text class="section-title">近七日订单趋势</text>
|
</view>
|
|
<view class="chart-content">
|
<view class="chart-area">
|
<qiun-data-charts type="line" :opts="opts" :chartData="chartData" />
|
</view>
|
</view>
|
</view>
|
</view>
|
</template>
|
|
<script>
|
export default {
|
data() {
|
return {
|
currentRange: 'today',
|
startDate: '',
|
endDate: '',
|
startPickerValue: Number(new Date()),
|
endPickerValue: Number(new Date()),
|
showStartDatePicker: false,
|
showEndDatePicker: false,
|
minEndDate: '',
|
quickTabs: [{
|
label: '今日',
|
value: 'today'
|
},
|
{
|
label: '近7日',
|
value: '7days'
|
},
|
{
|
label: '近30日',
|
value: '30days'
|
},
|
{
|
label: '近半年',
|
value: 'halfYear'
|
},
|
{
|
label: '自定义',
|
value: 'custom'
|
}
|
],
|
metrics: [{
|
label: '总订单(个)',
|
value: '0',
|
icon: '/static/image/yingshou_ic_zongdingdan@2x.png'
|
},
|
{
|
label: '总完成订单(个)',
|
value: '0',
|
icon: '/static/image/yingshou_ic_zongwancheng@2x.png'
|
},
|
{
|
label: '总营收(元)',
|
value: '0.00',
|
icon: '/static/image/yingshou_ic_zongyingshou@2x.png'
|
},
|
{
|
label: '司机分成(元)',
|
value: '0.00',
|
icon: '/static/image/yingshou_ic_siji@2x.png'
|
},
|
{
|
label: '退款订单(个)',
|
value: '0',
|
icon: '/static/image/yingshou_ic_tuikuan@2x.png'
|
},
|
{
|
label: '责任扣款(元)',
|
value: '0.00',
|
icon: '/static/image/yingshou_ic_koukuan@2x.png'
|
}
|
],
|
luggageDistribution: [],
|
|
chartData: {},
|
opts: {
|
color: ["#106EFA"],
|
padding: [15, 10, 0, 15],
|
dataLabel: false,
|
dataLine: false,
|
enableScroll: false,
|
legend: {},
|
xAxis: {
|
disableGrid: true
|
},
|
yAxis: {
|
gridType: "dash",
|
dashLength: 2
|
},
|
extra: {
|
line: {
|
type: "straight",
|
width: 2,
|
activeType: "hollow"
|
}
|
}
|
}
|
}
|
},
|
onLoad() {
|
this.initDateRange();
|
this.getDriverKpiData();
|
this.getShopLuggageTypeData();
|
},
|
watch: {
|
currentRange(newVal) {
|
if (newVal !== 'custom') {
|
this.initDateRange();
|
this.getDriverKpiData();
|
}
|
},
|
startDate(newVal) {
|
if (newVal) {
|
// 结束日期最小为开始日期
|
this.minEndDate = new Date(newVal).getTime();
|
} else {
|
this.minEndDate = '';
|
}
|
}
|
},
|
methods: {
|
formatDate(value) {
|
const date = new Date(value)
|
const year = date.getFullYear()
|
const month = String(date.getMonth() + 1).padStart(2, '0')
|
const day = String(date.getDate()).padStart(2, '0')
|
return `${year}-${month}-${day}`
|
},
|
validateCustomDateRange() {
|
if (!this.startDate || !this.endDate) {
|
uni.showToast({
|
title: '请选择日期范围',
|
icon: 'none'
|
})
|
return false
|
}
|
|
const start = new Date(this.startDate + ' 00:00:00').getTime()
|
const end = new Date(this.endDate + ' 00:00:00').getTime()
|
|
if (start > end) {
|
uni.showToast({
|
title: '开始日期不能大于截止日期',
|
icon: 'none'
|
})
|
return false
|
}
|
|
const oneYear = 365 * 24 * 60 * 60 * 1000
|
if (end - start > oneYear) {
|
uni.showToast({
|
title: '日期跨度不能大于一年',
|
icon: 'none'
|
})
|
return false
|
}
|
|
return true
|
},
|
initDateRange() {
|
const today = new Date();
|
const todayStr = today.toISOString().split('T')[0];
|
|
switch (this.currentRange) {
|
case 'today':
|
this.startDate = todayStr;
|
this.endDate = todayStr;
|
break;
|
case '7days':
|
const sevenDaysAgo = new Date(today.setDate(today.getDate() - 6));
|
this.startDate = sevenDaysAgo.toISOString().split('T')[0];
|
this.endDate = new Date().toISOString().split('T')[0];
|
break;
|
case '30days':
|
const thirtyDaysAgo = new Date(today.setDate(today.getDate() - 29));
|
this.startDate = thirtyDaysAgo.toISOString().split('T')[0];
|
this.endDate = new Date().toISOString().split('T')[0];
|
break;
|
case 'halfYear':
|
const halfYearAgo = new Date(today.setMonth(today.getMonth() - 6));
|
this.startDate = halfYearAgo.toISOString().split('T')[0];
|
this.endDate = new Date().toISOString().split('T')[0];
|
break;
|
default:
|
break;
|
}
|
},
|
async getDriverKpiData() {
|
if (this.currentRange === 'custom' && !this.validateCustomDateRange()) {
|
return;
|
}
|
|
// 显示加载状态
|
uni.showLoading({
|
title: '加载中...',
|
mask: true
|
});
|
|
try {
|
const kpiRes = await this.$u.api.driverKpi({
|
startDate: this.startDate,
|
endDate: this.endDate
|
})
|
|
// 处理KPI数据
|
if (kpiRes.code === 200) {
|
this.processDriverKpiData(kpiRes.data);
|
}
|
|
} finally {
|
uni.hideLoading();
|
}
|
},
|
async getShopLuggageTypeData() {
|
try {
|
const luggageRes = await this.$u.api.shopLuggageType({})
|
if (luggageRes.code === 200) {
|
this.processLuggageTypeData(luggageRes.data)
|
}
|
} catch (error) {
|
console.log('shopLuggageType error', error)
|
}
|
},
|
processDriverKpiData(data) {
|
// 处理接口返回的数据,更新页面显示
|
console.log('KPI数据:', data);
|
|
// 将接口返回的分转换为元,并保留两位小数
|
const formatAmount = (cents) => {
|
if (typeof cents !== 'number') return '0.00';
|
return (cents / 100).toFixed(2);
|
};
|
|
// 更新指标数据
|
this.metrics = [{
|
label: '总订单(个)',
|
value: data.totalOrderCount,
|
icon: '/static/image/yingshou_ic_zongdingdan@2x.png'
|
},
|
{
|
label: '总完成订单(个)',
|
value: data.finishedOrderCount,
|
icon: '/static/image/yingshou_ic_zongwancheng@2x.png'
|
},
|
{
|
label: '总营收(元)',
|
value: formatAmount(data.totalRevenue),
|
icon: '/static/image/yingshou_ic_zongyingshou@2x.png'
|
},
|
{
|
label: '司机分成(元)',
|
value: formatAmount(data.driverFeeTotal),
|
icon: '/static/image/yingshou_ic_siji@2x.png'
|
},
|
{
|
label: '退款订单(个)',
|
value: data.refundOrderCount,
|
icon: '/static/image/yingshou_ic_tuikuan@2x.png'
|
},
|
{
|
label: '责任扣款(元)',
|
value: formatAmount(data.deductTotal),
|
icon: '/static/image/yingshou_ic_koukuan@2x.png'
|
}
|
];
|
|
|
},
|
processLuggageTypeData(data) {
|
setTimeout(() => {
|
let res = {
|
categories: data.map(item => item.date),
|
series: [
|
{
|
name: "近七日",
|
data: data.map(item => item.orderCount)
|
}
|
]
|
};
|
this.chartData = JSON.parse(JSON.stringify(res));
|
}, 500);
|
},
|
initDateRange() {
|
// 移除开始日期的时间限制,允许选择任意日期
|
|
// 根据不同的快速选择设置日期范围
|
const now = new Date();
|
const year = now.getFullYear();
|
const month = String(now.getMonth() + 1).padStart(2, '0');
|
const day = String(now.getDate()).padStart(2, '0');
|
|
switch (this.currentRange) {
|
case 'today':
|
this.startDate = `${year}-${month}-${day}`;
|
this.endDate = `${year}-${month}-${day}`;
|
break;
|
case '7days':
|
const sevenDaysAgo = new Date(now)
|
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 6)
|
this.startDate =
|
`${sevenDaysAgo.getFullYear()}-${String(sevenDaysAgo.getMonth() + 1).padStart(2, '0')}-${String(sevenDaysAgo.getDate()).padStart(2, '0')}`;
|
this.endDate = `${year}-${month}-${day}`;
|
break;
|
case '30days':
|
const monthAgo = new Date(now)
|
monthAgo.setDate(monthAgo.getDate() - 29)
|
this.startDate =
|
`${monthAgo.getFullYear()}-${String(monthAgo.getMonth() + 1).padStart(2, '0')}-${String(monthAgo.getDate()).padStart(2, '0')}`;
|
this.endDate = `${year}-${month}-${day}`;
|
break;
|
case 'halfYear':
|
const halfYearAgo = new Date(now)
|
halfYearAgo.setMonth(halfYearAgo.getMonth() - 6)
|
this.startDate =
|
`${halfYearAgo.getFullYear()}-${String(halfYearAgo.getMonth() + 1).padStart(2, '0')}-${String(halfYearAgo.getDate()).padStart(2, '0')}`;
|
this.endDate = `${year}-${month}-${day}`;
|
break;
|
default:
|
this.startDate = ''
|
this.endDate = ''
|
break;
|
}
|
this.startPickerValue = this.startDate ? new Date(this.startDate).getTime() : Number(new Date())
|
this.endPickerValue = this.endDate ? new Date(this.endDate).getTime() : Number(new Date())
|
},
|
onStartDateConfirm(e) {
|
this.startDate = this.formatDate(e.value);
|
this.startPickerValue = new Date(this.startDate).getTime();
|
this.showStartDatePicker = false;
|
// 更新结束日期的最小值
|
this.minEndDate = this.startDate ? new Date(this.startDate).getTime() : Date.now();
|
if (this.endDate && new Date(this.startDate + ' 00:00:00').getTime() > new Date(this.endDate + ' 00:00:00').getTime()) {
|
this.startDate = ''
|
this.startPickerValue = Number(new Date())
|
uni.showToast({
|
title: '开始日期不能大于截止日期',
|
icon: 'none'
|
})
|
return
|
}
|
// 自动获取数据
|
if (this.endDate && this.validateCustomDateRange()) {
|
this.getDriverKpiData();
|
}
|
},
|
onEndDateConfirm(e) {
|
this.endDate = this.formatDate(e.value);
|
this.endPickerValue = new Date(this.endDate).getTime();
|
this.showEndDatePicker = false;
|
if (this.startDate && new Date(this.startDate + ' 00:00:00').getTime() > new Date(this.endDate + ' 00:00:00').getTime()) {
|
this.endDate = ''
|
this.endPickerValue = Number(new Date())
|
uni.showToast({
|
title: '截止日期不能小于开始日期',
|
icon: 'none'
|
})
|
return
|
}
|
// 自动获取数据
|
if (this.startDate && this.validateCustomDateRange()) {
|
this.getDriverKpiData();
|
}
|
},
|
confirmDateRange() {
|
if (!this.validateCustomDateRange()) {
|
return
|
}
|
|
this.showDatePicker = false;
|
this.getDriverKpiData();
|
}
|
}
|
};
|
</script>
|
|
<style lang="scss" scoped>
|
.revenue-page {
|
min-height: 100vh;
|
padding: 24rpx;
|
background: #f5f7fb;
|
box-sizing: border-box;
|
}
|
|
.filter-panel,
|
.section-card {
|
background: #ffffff;
|
border-radius: 24rpx;
|
box-shadow: 0 12rpx 30rpx rgba(22, 47, 94, 0.04);
|
}
|
|
.filter-panel {
|
padding: 20rpx;
|
}
|
|
.quick-tabs {
|
display: flex;
|
gap: 16rpx;
|
}
|
|
.quick-tab {
|
flex: 1;
|
height: 60rpx;
|
line-height: 60rpx;
|
border-radius: 999rpx;
|
background: #f6f7f9;
|
font-size: 28rpx;
|
font-weight: 500;
|
text-align: center;
|
color: #50555f;
|
}
|
|
/* 日期选择器样式 */
|
.date-picker-wrap {
|
padding: 24rpx;
|
background: #ffffff;
|
border-radius: 24rpx 24rpx 0 0;
|
}
|
|
.date-picker-title {
|
font-size: 32rpx;
|
font-weight: 500;
|
color: #333333;
|
text-align: center;
|
margin-bottom: 32rpx;
|
}
|
|
.date-picker-content {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
margin-bottom: 32rpx;
|
gap: 24rpx;
|
}
|
|
.date-picker-separator {
|
font-size: 28rpx;
|
color: #999999;
|
padding: 0 16rpx;
|
}
|
|
.date-picker-footer {
|
display: flex;
|
gap: 16rpx;
|
padding-top: 24rpx;
|
border-top: 1rpx solid #eeeeee;
|
}
|
|
.date-picker-btn {
|
flex: 1;
|
height: 72rpx;
|
line-height: 72rpx;
|
text-align: center;
|
border-radius: 36rpx;
|
font-size: 28rpx;
|
transition: all 0.3s;
|
}
|
|
.cancel-btn {
|
background: #f5f7fb;
|
color: #666666;
|
}
|
|
.confirm-btn {
|
background: linear-gradient(135deg, #ff8b14 0%, #ff4d0a 100%);
|
color: #ffffff;
|
}
|
|
.date-text {
|
font-size: 28rpx;
|
color: #333333;
|
}
|
|
.quick-tab.active {
|
background: #106EFA;
|
color: #ffffff;
|
}
|
|
.date-bar {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
height: 66rpx;
|
padding: 0 48rpx;
|
margin-top: 18rpx;
|
border-radius: 999rpx;
|
background: #f6f7f9;
|
}
|
|
.date-placeholder,
|
.date-separator {
|
font-size: 28rpx;
|
color: #b1b5be;
|
}
|
|
.metrics-card,
|
.chart-card {
|
margin-top: 22rpx;
|
padding: 28rpx 24rpx 30rpx;
|
}
|
|
.section-title-wrap {
|
display: flex;
|
align-items: center;
|
gap: 14rpx;
|
}
|
|
.title-bar {
|
width: 6rpx;
|
height: 34rpx;
|
border-radius: 999rpx;
|
background: #0d5db8;
|
}
|
|
.section-title {
|
font-size: 38rpx;
|
font-weight: 700;
|
color: #1f2430;
|
}
|
|
.metrics-grid {
|
display: grid;
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
gap: 26rpx 18rpx;
|
margin-top: 30rpx;
|
}
|
|
.metric-item {
|
display: flex;
|
align-items: center;
|
gap: 18rpx;
|
}
|
|
.metric-icon-wrap {
|
flex-shrink: 0;
|
width: 68rpx;
|
height: 68rpx;
|
// border-radius: 14rpx;
|
// background: #ffffff;
|
// box-shadow: 0 6rpx 18rpx rgba(20, 42, 74, 0.08);
|
box-sizing: border-box;
|
}
|
|
.metric-icon,
|
.placeholder-box {
|
width: 100%;
|
height: 100%;
|
border-radius: 8rpx;
|
}
|
|
.placeholder-box {
|
background: linear-gradient(135deg, #48576a 0%, #758398 100%);
|
}
|
|
.metric-copy {
|
min-width: 0;
|
}
|
|
.metric-value {
|
font-size: 44rpx;
|
font-weight: 700;
|
line-height: 1.1;
|
color: #106EFA;
|
}
|
|
.metric-label {
|
margin-top: 8rpx;
|
font-size: 28rpx;
|
line-height: 40rpx;
|
color: #979da8;
|
}
|
|
.chart-content {
|
display: flex;
|
align-items: center;
|
gap: 26rpx;
|
margin-top: 26rpx;
|
}
|
|
.chart-area {
|
position: relative;
|
flex-shrink: 0;
|
width: 100%;
|
height: 350rpx;
|
}
|
|
.echart-host {
|
width: 100%;
|
height: 100%;
|
}
|
</style>
|