lxp 2 hete
szülő
commit
68d175f5c6
6 módosított fájl, 711 hozzáadás és 1 törlés
  1. 178 0
      my/jgOrder/DateSelector.vue
  2. 101 0
      my/jgOrder/DetailItem.vue
  3. 247 0
      my/jgOrder/revenue.vue
  4. 6 0
      pages.json
  5. 1 1
      pages/riderMy/index.vue
  6. 178 0
      utils/dateUtil.js

+ 178 - 0
my/jgOrder/DateSelector.vue

@@ -0,0 +1,178 @@
+<template>
+	<view class="date-selector">
+		<view style="padding: 0 26%;">
+			<u-tabs
+				:list="list"
+				:is-scroll="false"
+				:current="currentTab"
+				@change="tabChange"
+				activeColor="#00C18A"
+				inactiveColor="#999999"
+				bgColor="transparent"
+			></u-tabs>
+		</view>
+		<view class="date-control-box">
+			<view class="contol-btn">
+				<view class="left-btn" @click="onClickLeft" :class="startDisabled ? 'disable' : ''">
+					<view class="left-bracket bracket"></view>
+				</view>
+				<view class="right-btn" @click="onClickRight" :class="endDisabled ? 'disable' : ''">
+					<view class="right-bracket bracket"></view>
+				</view>
+			</view>
+			<view>{{ currentSelect.start}} - {{ currentSelect.end }}</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import { 
+		isSameDay,
+		getCurrentWeekDates, getLastWeekDates,
+		getMonthlyDates, getLastMonthDates ,
+		getNextWeekDates, getNextMonthDates
+	} from '@/utils/dateUtil.js'
+	
+	export default {
+		name: 'DateSelector',
+		data() {
+			return {
+				list: [
+					{ name: '周统计' },
+					{ name: '月统计' },
+				],
+				currentTab: 0,
+				currentSelect: {},
+				startDisabled: false,
+				endDisabled: false,
+			}
+		},
+		methods: {
+			initDate(param) {
+				if(this.currentTab === 0) {
+					this.currentSelect = getCurrentWeekDates(param)
+				} else {
+					this.currentSelect = getMonthlyDates(param)
+				}
+				this.fetchData()
+			},
+			tabChange(index) {
+				this.currentTab = index
+				this.initDate(new Date())
+			},
+			onClickLeft() {
+				const start = this.currentSelect.start
+				const startDate = new Date(start)
+				if(start) {
+					if(this.currentTab === 0) {
+						// 周统计
+						this.currentSelect = getLastWeekDates(startDate)
+					} else {
+						// 月统计
+						this.currentSelect = getLastMonthDates(startDate)
+					}
+					this.fetchData()
+				}
+			},
+			onClickRight() {
+				const end = this.currentSelect.end
+				const endDate = new Date(end)
+				const isSame = isSameDay(new Date(), endDate)
+				if(isSame) {
+					return
+				}
+				if(end) {
+					if(this.currentTab === 0) {
+						// 周统计
+						this.currentSelect = getNextWeekDates(endDate)
+					} else {
+						// 月统计
+						this.currentSelect = getNextMonthDates(endDate)
+					}
+					this.fetchData()
+				}
+			},
+			fetchData() {
+				this.$emit('dateChange', this.currentSelect)
+			},
+		},
+	}
+</script>
+
+<style lang="scss">
+	.date-selector {
+		
+		.date-control-box {
+			height: 80rpx;
+			display: flex;
+			align-items: center;
+			justify-content: center;
+			font-size: 28rpx;
+			line-height: 42rpx;
+			font-weight: 700;
+			color: #00C18A;
+			position: relative;
+			
+			.contol-btn {
+				position: absolute;
+				left: 0;
+				top: 0;
+				width: 100%;
+				display: flex;
+				justify-content: space-between;
+				
+				.left-btn, .right-btn {
+					height: 80rpx;
+					width: 80rpx;
+					border-radius: 40rpx;
+					color: #00C18A;
+					background-color: #E8FBF6;
+					// background-color: red;
+					display: flex;
+					justify-content: center;
+					align-items: center;
+					
+					&:active {
+						opacity: .5;
+					}
+				}
+				
+				.bracket {
+					position: relative;
+					width: 40rpx;
+					height: 40rpx;
+					display: flex;
+					align-items: center;
+					justify-content: center;
+				}
+				
+				.left-bracket {
+					margin-right: 8rpx;
+				}
+				
+				.right-bracket {
+					margin-left: 8rpx;
+				}
+			 
+				.left-bracket::before,
+				.right-bracket::after {
+					content: '';
+					position: absolute;
+					width: 0;
+					height: 0;
+					border-style: solid;
+				}
+			 
+				.left-bracket::before {
+					border-width: 20rpx 20rpx 20rpx 0;
+					border-color: transparent #00C18A transparent transparent;
+				}
+			 
+				.right-bracket::after {
+					border-width: 20rpx 0 20rpx 20rpx;
+					border-color: transparent transparent transparent #00C18A;
+				}
+			}
+		}
+	}
+</style>

+ 101 - 0
my/jgOrder/DetailItem.vue

@@ -0,0 +1,101 @@
+<template>
+	<view class="detail-item">
+		<view class="item-left">
+			<view class="item-left-top">
+				<text>{{ monthDay }}</text>
+				<text>{{ weekday }}</text>
+			</view>
+			<view class="item-left-bottom">
+				<text>{{ detailData.orderCount }} 单</text>
+				<text> · </text>
+				<text>{{ detailData.online }} 小时</text>
+			</view>
+		</view>
+		<view class="item-right">
+			<view class="item-right-top">¥{{ detailData.total }}</view>
+			<view class="item-right-bottom" :style="{ color: detailData.sign === '-' ? '#F56C6C' : '#00C18A' }">{{ detailData.sign }}{{ detailData.ratio }}%</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import { formatToMonthDay, getWeekday } from '@/utils/dateUtil.js'
+	export default {
+		name: 'DetailItem',
+		props: {
+			detailData: {
+				type: Object,
+				default: () => {
+					return {
+						date: '2025-05-23',
+						orderCount: 87,
+						online: 8.5,
+						total: 428.50,
+						sign: '-',
+						ratio: 12.5
+					}
+				}
+			},
+		},
+		data() {
+			return {}
+		},
+		computed: {
+			monthDay() {
+				const date = new Date(this.detailData.date)
+				return formatToMonthDay(date)
+			},
+			weekday() {
+				const date = new Date(this.detailData.date)
+				return getWeekday(date)
+			}
+		},
+		methods: {},
+	}
+</script>
+
+<style lang="scss">
+	.detail-item {
+		padding: 12rpx 0;
+		border-bottom: 1px solid #E4E7ED;
+		display: flex;
+		justify-content: space-between;
+		
+		.item-left {
+			display: flex;
+			flex-direction: column;
+			align-items: flex-start;
+			
+			.item-left-top {
+				font-size: 28rpx;
+				line-height: 42rpx;
+				color: #333333;
+				font-weight: 500;
+			}
+			
+			.item-left-bottom {
+				font-size: 24rpx;
+				line-height: 32rpx;
+				color: #999999;
+			}
+		}
+		
+		.item-right {
+			display: flex;
+			flex-direction: column;
+			align-items: flex-end;
+			
+			.item-right-top {
+				font-weight: 700;
+				color: #333333;
+				font-size: 28rpx;
+				line-height: 42rpx;
+			}
+			.item-right-bottom {
+				font-weight: 500;
+				font-size: 24rpx;
+				line-height: 32rpx;
+			}
+		}
+	}
+</style>

+ 247 - 0
my/jgOrder/revenue.vue

@@ -0,0 +1,247 @@
+<template>
+	<view class="revenue-statistics">
+		<view><date-selector ref="dateSelector" @dateChange="handleDateChange"></date-selector></view>
+		
+		<u-card padding="32" full :showHead="false" borderRadius="30">
+			<view slot="body" >
+				<view style="font-size: 28rpx;line-height: 40rpx;color: #333333;">总收入</view>
+				<view style="display: flex;color: #00C18A;justify-content: space-between;align-items: flex-end;margin-top: 8rpx;">
+					<view style="font-size: 60rpx;line-height: 72rpx;font-weight: 700;text-indent: -10rpx;">¥{{ formatNumberWithCommas(top1Data.total) }}</view>
+					<view style="font-size: 28rpx;line-height: 40rpx;font-weight: 500;">{{ top1Data.ratio }}%</view>
+				</view>
+				<view style="margin-top: 24rpx;color: #333333;font-size: 28rpx;display: flex;justify-content: space-between;">
+					<view>
+						<view>订单数量</view>
+						<view style="margin-top: 8rpx;">{{ top1Data.orderCount }}单</view>
+					</view>
+					<view style="text-align: right;">
+						<view>在线时长</view>
+						<view style="margin-top: 8rpx;font-weight: 600;">{{ top1Data.online }}小时</view>
+					</view>
+				</view>
+			</view>
+		</u-card>
+		
+		<u-card padding="32" full :showHead="false" borderRadius="30">
+			<view slot="body" >
+				<view class="chart">
+					<uni-ec-canvas id="revenue" canvas-id="revenue" :ec="ec" ref="canvas"></uni-ec-canvas>
+				</view>
+				<view class="chart-bottom">
+					<view class="chart-bottom-item" v-for="(value, key) in revenueData" :key="key">
+						<view style="display: flex;align-items: center;">
+							<view :style="{ backgroundColor: colors[key] }" style="height: 24rpx;width: 24rpx;border-radius: 50%;"></view>
+							<view style="margin-left: 16rpx;font-size: 28rpx;line-height: 40rpx;color: #333333;">{{ value.title }}</view>
+						</view>
+						<view style="display: flex;">
+							<view style="font-weight: 700;font-size: 28rpx;line-height: 40rpx;">¥{{ formatNumberWithCommas(value.total) }}</view>
+							<view style="margin-left: 8rpx;font-size: 24rpx;line-height: 40rpx;color: #666666;">{{ value.ratio }}%</view>
+						</view>
+					</view>
+				</view>
+			</view>
+		</u-card>
+		
+		<u-card padding="32" full borderRadius="30" :bodyStyle="{ padding: '0 32rpx' }">
+			<view slot="head" style="display: flex;justify-content: space-between;align-items: center;position: relative;">
+				<view style="font-size: 28rpx;line-height: 48rpx;color: #333333;">每日明细</view>
+				<view>
+					<btn-group v-model="currentBtn" :defaultVal="1" :options="selectOption"></btn-group>
+				</view>
+			</view>
+			<view slot="body" >
+				<detail-item v-for="(item, index) in detailDataList" :key="index"></detail-item>
+				<u-empty v-if="!(detailDataList.length) && loadmore =='nomore'" mode="list" height="280" marginTop="50"></u-empty>
+				<u-loadmore :status="loadmore" v-if="detailDataList.length" line icon-color="#00C18A" marginTop="20"/>
+			</view>
+		</u-card>
+	</view>
+</template>
+<!-- 收入统计 -->
+<script>
+	import DateSelector from './DateSelector.vue'
+	import BtnGroup from './BtnGroup.vue'
+	import uniEcCanvas from '@/components/uni-ec-canvas/uni-ec-canvas'
+	import * as echarts from '@/components/uni-ec-canvas/echarts.js'
+	import DetailItem from './DetailItem.vue'
+	
+	export default {
+		name: 'Revenue',
+		components: { DateSelector, BtnGroup, uniEcCanvas, DetailItem },
+		data() {
+			return {
+				top1Data: {
+					total: 2856.50,
+					ratio: 12.5,
+					orderCount: 87,
+					online: 32.5,
+				},
+				colors: ['#00C18A', '#F56C6C', '#E6A23C'],
+				ec: {
+					option: {},
+				},
+				revenueData: {
+					0: { title: '基础车费', total: 2156.50, ratio: 75.5 },
+					1: { title: '高峰奖励', total: 420.00, ratio: 14.7 },
+					2: { title: '活动奖励', total: 280.00, ratio: 9.8 }
+				},
+				currentBtn: null,
+				selectOption: [
+					{ label: '全 部', value: 1 },
+					{ label: '已完成', value: 2 },
+					{ label: '已取消', value: 3 },
+				],
+				current: 1, //当前页数
+				pageSize: 10, //页数大小
+				totalPage: 3, //总页数
+				loadmore: 'loadmore',
+				detailDataList: [1,2],
+			}
+		},
+		onLoad() {
+			this.$nextTick(() => {
+				this.$refs.dateSelector.initDate(new Date())
+			})
+		},
+		watch: {
+			currentBtn: {
+				handler(newVal) {
+					this.fetchDetailData()
+				},
+			},
+		},
+		onReachBottom() {
+			// 如果当前页数大于等于总页数,状态修改为没有更多了,不再继续往下执行代码
+			if(this.current >= this.totalPage) {
+				this.loadmore = 'nomore'
+				return
+			}
+			this.loadmore = 'loading'//状态改为加载中
+			this.current = ++ this.current//页面新增一页
+			this.fetchDetailData()//修改页数后,重新获取数据
+		},
+		methods: {
+			handleDateChange(dates) {
+				console.log('发送请求, 获取数据 ... 参数 => ', dates)
+				this.$nextTick(() => {
+					this.$refs.canvas.init(this.initChart)
+				})
+				this.fetchDetailData()
+			},
+			initChart(canvas, width, height, canvasDpr) {
+				let chart = echarts.init(canvas, null, {
+					width: width,
+					height: height,
+					devicePixelRatio: canvasDpr
+				})
+				canvas.setChart(chart)
+				let option = {
+					title: {
+					    text: '收入构成',
+					    left: 0,
+					    top: 0,
+					    textStyle: {
+							fontSize: 14,
+							fontWeight: 500,
+					    },
+					},
+					grid: {
+						top: '20%',
+						left: '2%',
+						right: '0%',
+						bottom: '0%',
+						containLabel: true,
+					},
+					xAxis: {
+						type: 'category',
+						data: ['基础车费', '高峰奖励', '活动奖励'],
+						boundaryGap: [50, 50],
+						axisTick: {
+							show: false,
+						},
+						splitLine: {
+						    show: false,
+						},
+						axisLine: {
+						    lineStyle: {
+						        color: '#E4E7ED',
+						    },
+						},
+						axisLabel: {
+						    color: '#333333',
+						}
+					},
+					yAxis: {
+						type: 'value',
+						boundaryGap: ['10%', '10%'],
+						splitNumber: 4,
+						axisTick: {
+							show: false,
+						},
+						axisLine: {
+							show: false,
+						},
+						splitLine: {
+						    lineStyle: {
+								color: '#E4E7ED',
+						    },
+						},
+					},
+					series: [
+						{
+							data: [2500, 800, 600],
+							type: 'bar',
+							barWidth: 30,
+							itemStyle: {
+								borderRadius: [10, 10, 0, 0],
+								color: param => this.colors[param.dataIndex]
+							},
+						},
+					]
+				}
+				chart.setOption(option)
+				return chart
+			},
+			formatNumberWithCommas(number) {
+			  return new Intl.NumberFormat().format(number)
+			},
+			fetchDetailData() {
+				console.log('获取每日明细数据', this.current, this.pageSize)
+				// this.$api.getList(this.current, this.pageSize, res => {
+				// 	let data = res.data;
+				// 	this.detailDataList.push(...data.list);//在列表后面新增新获取的数据
+				// 	this.totalPage = data.total;//获取数据总页数
+				// })
+				const timeout = setTimeout(() => {
+					this.loadmore = 'loadmore'
+					clearTimeout(timeout)
+				}, 3000)
+			},
+		},
+	}
+</script>
+
+<style lang="scss">
+	.revenue-statistics {
+		width: 100%;
+		position: relative;
+		padding: 16rpx 32rpx;
+		box-sizing: border-box;
+		
+		.chart {
+			width: 100%;
+			height: 400rpx;
+		}
+		
+		.chart-bottom {
+			margin-top: 40rpx;
+			
+			.chart-bottom-item {
+				margin: 16rpx 0;
+				display: flex;
+				justify-content: space-between;
+				align-items: center;
+			}
+		}
+	}
+</style>

+ 6 - 0
pages.json

@@ -416,6 +416,12 @@
 				"style": {
 					"navigationBarTitleText": "订单统计"
 				}
+			},
+			{
+				"path": "jgOrder/revenue",
+				"style": {
+					"navigationBarTitleText": "收入统计"
+				}
 			}
 		]
 	}],

+ 1 - 1
pages/riderMy/index.vue

@@ -141,7 +141,7 @@
 					<view class="text-sm">我的行程</view>
 					<!-- <view class="weinumber" v-if="order&&order.djdOrders">{{order.djdOrders}}</view> -->
 				</view>
-				<view class="text-center margin-tb-sm"  style="width: 20%;" @click="navgo('/my/setting/xieyi')">
+				<view class="text-center margin-tb-sm"  style="width: 20%;" @click="navgo('/my/jgOrder/revenue')">
 					<image v-if="globalImages" :src="globalImages + 'imgs/tongji (2).png'" style="width: 56rpx;height: 56rpx;" mode=""></image>
 					<view class="text-sm">收入统计</view>
 					<!-- <view class="weinumber" v-if="order&&order.yjdOrders">{{order.yjdOrders}}</view> -->

+ 178 - 0
utils/dateUtil.js

@@ -0,0 +1,178 @@
+const weekdays = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];
+
+// 格式化日期为 YYYY-MM-DD
+export function formatToMonthDay(date) {
+	const month = String(date.getMonth() + 1).padStart(2, '0')
+	const day = String(date.getDate()).padStart(2, '0')
+	return `${month}月${day}日`
+}
+
+/**
+ * 根据传入时间获取星期几
+ * @param {Date} date
+ */
+export function getWeekday(date) {
+	return weekdays[date.getDay()]
+}
+/**
+ * 判断两个时间是否是同一天
+ * @param {Object} date1
+ * @param {Object} date2
+ */
+export function isSameDay(date1, date2) {
+  // 检查两个日期是否为同一天
+  return date1.getFullYear() === date2.getFullYear() &&
+         date1.getMonth() === date2.getMonth() &&
+         date1.getDate() === date2.getDate()
+}
+// 格式化日期为 YYYY-MM-DD
+const formatDate = (date) => {
+	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}`
+}
+
+/**
+ * 根据传入时间, 获取传入时间的本周的起止日期(结束日期为昨天)
+ * @param {Date} inputDate 传入的时间
+ */
+export function getCurrentWeekDates(inputDate) {
+  const dayOfWeek = inputDate.getDay() // 0 (周日) 到 6 (周六)
+  
+  // 计算到本周一的日期差
+  // 如果今天是周一(1),则 diffToMonday = 0
+  // 如果今天是周日(0),则 diffToMonday = 6
+  const diffToMonday = dayOfWeek === 0 ? 6 : dayOfWeek - 1
+  
+  // 获取本周一(本周的第一天)
+  const monday = new Date(inputDate)
+  monday.setDate(inputDate.getDate() - diffToMonday)
+  
+  // 获取昨天的日期
+  const yesterday = new Date(inputDate)
+  // yesterday.setDate(inputDate.getDate() - 1)
+  
+  return {
+    start: formatDate(monday),
+    end: formatDate(yesterday)
+  }
+}
+
+/**
+ * 根据传入时间, 获取传入时间的上一周的起止日期
+ * @param {Date} inputDate 传入的时间
+ */
+export function getLastWeekDates(inputDate) {
+  const dayOfWeek = inputDate.getDay() // 0 (周日) 到 6 (周六)
+  // 计算到上周一的日期差
+  // 如果今天是周一(1),则diffToLastWeek = 1 + 7 = 8(回到上周一)
+  // 如果今天是周日(0),则diffToLastWeek = 0 + 7 + 1 = 8(回到上周一)
+  const diffToLastWeek = dayOfWeek === 0 ? 7 : dayOfWeek - 1 + 7
+  
+  // 获取上周一(上周的第一天)
+  const lastMonday = new Date(inputDate)
+  lastMonday.setDate(inputDate.getDate() - diffToLastWeek)
+  
+  // 获取上周日(上周的最后一天)
+  const lastSunday = new Date(lastMonday)
+  lastSunday.setDate(lastMonday.getDate() + 6)
+  
+  return {
+    start: formatDate(lastMonday),
+    end: formatDate(lastSunday)
+  }
+}
+
+/**
+ * 根据传入时间, 获取传入时间的下一周的起止日期, 结束日期不能超过今天, 如果超过今天, 则结束日期是今天
+ * @param {Date} inputDate 传入的时间
+ */
+export function getNextWeekDates(inputDate) {
+  const dayOfWeek = inputDate.getDay() // 0 (周日) 到 6 (周六)
+  
+  // 计算到下周一的日期差
+  // 如果今天是周一(1),则 diffToNextMonday = 7
+  // 如果今天是周日(0),则 diffToNextMonday = 1
+  const diffToNextMonday = dayOfWeek === 0 ? 1 : 7 - dayOfWeek + 1
+  
+  // 获取下周一(下一周的第一天)
+  const nextMonday = new Date(inputDate)
+  nextMonday.setDate(inputDate.getDate() + diffToNextMonday)
+  
+  // 获取下一周的结束日期(下周一加6天)
+  const nextSunday = new Date(nextMonday)
+  nextSunday.setDate(nextMonday.getDate() + 6)
+  
+  // 确保结束日期不超过今天
+  const today = new Date()
+  today.setHours(0, 0, 0, 0) // 设置为今天的午夜,以便比较
+  
+  if (nextSunday > today) {
+    nextSunday.setTime(today.getTime()) // 如果超过今天,则设置为今天
+  }
+  
+  return {
+    start: formatDate(nextMonday),
+    end: formatDate(nextSunday)
+  }
+}
+
+/**
+ * 根据传入时间, 获取传入时间的当月的起止日期(结束时间为当前日期)
+ * @param {Date} inputDate 传入的时间
+ */
+export function getMonthlyDates(inputDate) {
+  // 获取传入日期所在月份的第一天
+  const firstDayOfMonth = new Date(inputDate.getFullYear(), inputDate.getMonth(), 1)
+  
+  // 获取昨天的日期
+  const yesterday = new Date(inputDate)
+  // yesterday.setDate(inputDate.getDate() - 1)
+  
+  return {
+    start: formatDate(firstDayOfMonth),
+    end: formatDate(yesterday)
+  }
+}
+
+/**
+ * 根据传入时间, 获取当前时间的上月的起止日期
+ * @param {Date} inputDate 传入的时间
+ */
+export function getLastMonthDates(inputDate) {
+  // 获取传入日期所在月份的第一天
+  const firstDayOfCurrentMonth = new Date(inputDate.getFullYear(), inputDate.getMonth(), 1)
+  
+  // 获取上个月的最后一天
+  const lastDayOfLastMonth = new Date(firstDayOfCurrentMonth - 1)
+  
+  // 获取上个月的第一天
+  const firstDayOfLastMonth = new Date(lastDayOfLastMonth.getFullYear(), lastDayOfLastMonth.getMonth(), 1)
+  
+  return {
+    start: formatDate(firstDayOfLastMonth),
+    end: formatDate(lastDayOfLastMonth)
+  }
+}
+
+/**
+ * 根据传入时间, 获取传入时间的下一月的起止日期, 结束日期不能超过今天, 如果超过今天, 则结束日期是今天
+ * @param {Date} inputDate 传入的时间
+ */
+export function getNextMonthDates(inputDate) {
+  // 获取下一月的第一天
+  const nextMonthFirstDay = new Date(inputDate.getFullYear(), inputDate.getMonth() + 1, 1)
+  // 获取下一月的最后一天
+  const nextMonthLastDay = new Date(nextMonthFirstDay.getFullYear(), nextMonthFirstDay.getMonth() + 1, 0)
+  // 确保结束日期不超过今天
+  const today = new Date()
+  today.setHours(0, 0, 0, 0) // 设置为今天的午夜,以便比较
+  if (nextMonthLastDay > today) {
+    nextMonthLastDay.setTime(today.getTime()) // 如果超过今天,则设置为今天
+  }
+  return {
+    start: formatDate(nextMonthFirstDay),
+    end: formatDate(nextMonthLastDay)
+  }
+}