From ce06ca62a0dd65d4a8fb57126948449c804ad77e Mon Sep 17 00:00:00 2001
From: MrShi <1878285526@qq.com>
Date: 星期二, 19 五月 2026 19:40:08 +0800
Subject: [PATCH] 提交

---
 small-program/shop/pages/revenue-analysis/revenue-analysis.vue |  565 ++++++++++++++++++++++++++++++++++++++++---------------
 1 files changed, 408 insertions(+), 157 deletions(-)

diff --git a/small-program/shop/pages/revenue-analysis/revenue-analysis.vue b/small-program/shop/pages/revenue-analysis/revenue-analysis.vue
index 62da5c5..ecaaccc 100644
--- a/small-program/shop/pages/revenue-analysis/revenue-analysis.vue
+++ b/small-program/shop/pages/revenue-analysis/revenue-analysis.vue
@@ -7,17 +7,41 @@
 					:key="item.value"
 					class="quick-tab"
 					:class="{ active: currentRange === item.value }"
-					@tap="currentRange = item.value"
+					@click="currentRange = item.value"
 				>
 					{{ item.label }}
 				</view>
 			</view>
 
-			<view class="date-bar">
-				<text class="date-placeholder">寮�濮嬫椂闂�</text>
-				<text class="date-separator">-</text>
-				<text class="date-placeholder">缁撴潫鏃堕棿</text>
-			</view>
+			<view v-if="currentRange === 'custom'" class="date-bar">
+					<view class="date-item" @click="showStartDatePicker = true">
+						<text :class="startDate ? 'date-text' : 'date-placeholder'">{{ formatPickerDate(startDate) || '寮�濮嬫椂闂�' }}</text>
+					</view>
+					<text class="date-separator">-</text>
+					<view class="date-item" @click="showEndDatePicker = true">
+						<text :class="endDate ? 'date-text' : 'date-placeholder'">{{ formatPickerDate(endDate) || '缁撴潫鏃堕棿' }}</text>
+					</view>
+				</view>
+
+				<!-- 寮�濮嬫棩鏈熼�夋嫨鍣� -->
+				<u-datetime-picker
+					:show="showStartDatePicker"
+					mode="date"
+					v-model="startDate"
+					@confirm="onStartDateConfirm"
+					@cancel="showStartDatePicker = false"
+					placeholder="閫夋嫨寮�濮嬫棩鏈�"
+				></u-datetime-picker>
+				
+				<!-- 缁撴潫鏃ユ湡閫夋嫨鍣� -->
+				<u-datetime-picker
+					:show="showEndDatePicker"
+					mode="date"
+					v-model="endDate"
+					@confirm="onEndDateConfirm"
+					@cancel="showEndDatePicker = false"
+					placeholder="閫夋嫨缁撴潫鏃ユ湡"
+				></u-datetime-picker>
 		</view>
 
 		<view class="section-card metrics-card">
@@ -47,23 +71,11 @@
 
 			<view class="chart-content">
 				<view class="chart-area">
-					<view
-						class="echart-host"
-						:id="chartDomId"
-						:prop="chartOptionText"
-						:change:prop="chartRenderer.renderChart"
-					></view>
-					<view class="chart-fallback"></view>
-				</view>
-
-				<view class="legend-list">
-					<view v-for="item in luggageDistribution" :key="item.name" class="legend-item">
-						<view class="legend-left">
-							<view class="legend-dot" :style="{ background: item.color }"></view>
-							<text class="legend-name">{{ item.name }}</text>
-						</view>
-						<text class="legend-value">{{ item.value }} | {{ item.percent }}%</text>
-					</view>
+					<qiun-data-charts 
+						type="ring"
+						:opts="opts"
+						:chartData="chartData"
+						@getIndex="onRingChartClick" />
 				</view>
 			</view>
 		</view>
@@ -74,8 +86,12 @@
 	export default {
 		data() {
 			return {
-				currentRange: 'custom',
-				chartDomId: 'luggage-distribution-chart',
+				currentRange: 'today',
+				startDate: '',
+				endDate: '',
+				showStartDatePicker: false,
+				showEndDatePicker: false,
+				minEndDate: '',
 				quickTabs: [
 					{ label: '浠婃棩', value: 'today' },
 					{ label: '杩�7鏃�', value: '7days' },
@@ -84,83 +100,308 @@
 					{ label: '鑷畾涔�', value: 'custom' }
 				],
 				metrics: [
-					{ label: '瀵勫瓨璁㈠崟(涓�)', value: '125', icon: '/shop/static/icon/yingshou_ic_jicun@2x.png' },
-					{ label: '瀵勯�佽鍗�(涓�)', value: '30', icon: '/shop/static/icon/yingshou_ic_jisong@2x.png' },
-					{ label: '鎬昏鍗�(涓�)', value: '155', icon: '/shop/static/icon/yingshou_ic_zongdingdan@2x.png' },
-					{ label: '鎬诲畬鎴愯鍗�(涓�)', value: '143', icon: '/shop/static/icon/yingshou_ic_zongwancheng@2x.png' },
-					{ label: '鎬昏惀鏀�(鍏�)', value: '300,000.00', icon: '/shop/static/icon/yingshou_ic_zongyingshou@2x.png' },
-					{ label: '鍒嗗簵鍒嗘垚(鍏�)', value: '10,000.32', icon: '/shop/static/icon/yingshou_ic_fendian@2x.png' },
-					{ label: '閫�娆捐鍗�(涓�)', value: '10', icon: '/shop/static/icon/yingshou_ic_tuikuan@2x.png' },
-					{ label: '璐d换鎵f(鍏�)', value: '300.00', icon: '/shop/static/icon/yingshou_ic_koukuan@2x.png' }
+					{ label: '瀵勫瓨璁㈠崟(涓�)', value: '0', icon: '/shop/static/icon/yingshou_ic_jicun@2x.png' },
+					{ label: '瀵勯�佽鍗�(涓�)', value: '0', icon: '/shop/static/icon/yingshou_ic_jisong@2x.png' },
+					{ label: '鎬昏鍗�(涓�)', value: '0', icon: '/shop/static/icon/yingshou_ic_zongdingdan@2x.png' },
+					{ label: '鎬诲畬鎴愯鍗�(涓�)', value: '0', icon: '/shop/static/icon/yingshou_ic_zongwancheng@2x.png' },
+					{ label: '鎬昏惀鏀�(鍏�)', value: '0.00', icon: '/shop/static/icon/yingshou_ic_zongyingshou@2x.png' },
+					{ label: '鍒嗗簵鍒嗘垚(鍏�)', value: '0.00', icon: '/shop/static/icon/yingshou_ic_fendian@2x.png' },
+					{ label: '閫�娆捐鍗�(涓�)', value: '0', icon: '/shop/static/icon/yingshou_ic_tuikuan@2x.png' },
+					{ label: '璐d换鎵f(鍏�)', value: '0.00', icon: '/shop/static/icon/yingshou_ic_koukuan@2x.png' }
 				],
-				luggageDistribution: [
-					{ name: '澶у彿琛屾潕绠�', value: 35, percent: 35, color: '#3B82F6' },
-					{ name: '鐗瑰ぇ鍙疯鏉庣', value: 20, percent: 20, color: '#64D7C7' },
-					{ name: '涓彿琛屾潕绠�', value: 18, percent: 18, color: '#FFD15C' },
-					{ name: '灏忓彿琛屾潕绠�', value: 12, percent: 12, color: '#FF8A47' },
-					{ name: '澶у彿鑳屽寘', value: 10, percent: 10, color: '#F54786' },
-					{ name: '灏忓彿鑳屽寘', value: 5, percent: 5, color: '#EE6CB2' }
-				]
-			};
-		},
-		computed: {
-			chartOptionText() {
-				return JSON.stringify({
-					color: this.luggageDistribution.map((item) => item.color),
-					series: [
-						{
-							name: '琛屾潕绫诲瀷鍒嗗竷',
-							type: 'pie',
-							radius: ['58%', '78%'],
-							center: ['50%', '50%'],
-							avoidLabelOverlap: false,
-							label: { show: false },
-							labelLine: { show: false },
-							itemStyle: {
-								borderColor: '#ffffff',
-								borderWidth: 4
-							},
-							data: this.luggageDistribution.map((item) => ({
-								value: item.value,
-								name: item.name
-							}))
+				luggageDistribution: [],
+				selectedLuggageText: '',
+
+				chartData: {},
+				opts: {
+					rotate: false,
+					rotateLock: false,
+					padding: [5,5,5,5],
+					dataLabel: false,
+					dataLine: false,
+					enableScroll: false,
+					legend: {
+						show: true,
+						position: "right",
+						lineHeight: 25
+					},
+					title: {
+						show: false,
+						name: ''
+					},
+					subtitle: {
+						show: false,
+						name: ''
+					},
+					extra: {
+						ring: {
+							ringWidth: 20,
+							activeOpacity: 0.5,
+							activeRadius: 10,
+							offsetAngle: 0,
+							labelWidth: 15,
+							border: true,
+							borderWidth: 3,
+							borderColor: "#FFFFFF",
+							linearType: "custom"
 						}
-					]
-				});
-			}
-		}
-	};
-</script>
-
-<script>
-	import echarts from "echarts"
-
-	export default {
-		data() {
-			return {
-				chart: null
-			};
-		},
-		methods: {
-			renderChart(optionText) {
-				if (!optionText) {
-					return;
+					}
 				}
+			}
+		},
+		onLoad() {
+				this.initDateRange();
+				this.getDriverKpiData();
+			},
+			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(date) {
+					return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
+				},
+				formatPickerDate(value) {
+					if (!value) {
+						return '';
+					}
 
-				this.$nextTick(() => {
-					const dom = document.getElementById('luggage-distribution-chart');
-					if (!dom) {
+					if (typeof value === 'string') {
+						if (/^\d{4}-\d{2}-\d{2}$/.test(value)) {
+							return value;
+						}
+
+						if (/^\d+$/.test(value)) {
+							const timestampDate = new Date(Number(value));
+							return Number.isNaN(timestampDate.getTime()) ? '' : this.formatDate(timestampDate);
+						}
+
+						return value;
+					}
+
+					const date = new Date(value);
+					return Number.isNaN(date.getTime()) ? '' : this.formatDate(date);
+				},
+				isStartDateAfterEndDate(startDate, endDate) {
+					if (!startDate || !endDate) {
+						return false;
+					}
+
+					return new Date(startDate).getTime() > new Date(endDate).getTime();
+				},
+				initDateRange() {
+					const now = new Date();
+					const today = this.formatDate(now);
+
+					switch (this.currentRange) {
+						case 'today':
+							this.startDate = today;
+							this.endDate = today;
+							break;
+						case '7days': {
+							const start = new Date();
+							start.setDate(start.getDate() - 6);
+							this.startDate = this.formatDate(start);
+							this.endDate = today;
+							break;
+						}
+						case '30days': {
+							const start = new Date();
+							start.setDate(start.getDate() - 29);
+							this.startDate = this.formatDate(start);
+							this.endDate = today;
+							break;
+						}
+						case 'halfYear': {
+							const start = new Date();
+							start.setMonth(start.getMonth() - 6);
+							this.startDate = this.formatDate(start);
+							this.endDate = today;
+							break;
+						}
+						default:
+							break;
+					}
+				},
+				async getDriverKpiData() {
+					if (this.currentRange === 'custom' && (!this.startDate || !this.endDate)) {
+						uni.showToast({ title: '璇烽�夋嫨鏃ユ湡鑼冨洿', icon: 'none' });
 						return;
 					}
 
-					if (!this.chart) {
-						this.chart = echarts.init(dom);
+					uni.showLoading({ title: '鍔犺浇涓�...', mask: true });
+
+					try {
+						const [kpiRes, luggageRes] = await Promise.all([
+							this.$u.api.driverKpi({
+								startDate: this.startDate,
+								endDate: this.endDate
+							}),
+							this.$u.api.shopLuggageType({
+								startDate: this.startDate,
+								endDate: this.endDate
+							})
+						]);
+
+						if (kpiRes.code === 200) {
+							this.processDriverKpiData(kpiRes.data);
+						}
+
+						if (luggageRes.code === 200) {
+							this.processLuggageTypeData(luggageRes.data);
+						}
+					} catch (err) {
+						console.error('鑾峰彇鏁版嵁澶辫触:', err);
+						uni.showToast({ title: '鑾峰彇鏁版嵁澶辫触', icon: 'none' });
+					} finally {
+						uni.hideLoading();
+					}
+				},
+				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.localOrderCount || '0', icon: '/shop/static/icon/yingshou_ic_jicun@2x.png' },
+						{ label: '瀵勯�佽鍗�(涓�)', value: data.remoteOrderCount || '0', icon: '/shop/static/icon/yingshou_ic_jisong@2x.png' },
+						{ label: '鎬昏鍗�(涓�)', value: data.totalOrderCount || '0', icon: '/shop/static/icon/yingshou_ic_zongdingdan@2x.png' },
+						{ label: '鎬诲畬鎴愯鍗�(涓�)', value: data.finishedOrderCount || '0', icon: '/shop/static/icon/yingshou_ic_zongwancheng@2x.png' },
+						{ label: '鎬昏惀鏀�(鍏�)', value: formatAmount(data.totalRevenue), icon: '/shop/static/icon/yingshou_ic_zongyingshou@2x.png' },
+						{ label: '鍒嗗簵鍒嗘垚(鍏�)', value: formatAmount(data.shopFeeTotal), icon: '/shop/static/icon/yingshou_ic_fendian@2x.png' },
+						{ label: '閫�娆捐鍗�(涓�)', value: data.refundOrderCount || '0', icon: '/shop/static/icon/yingshou_ic_tuikuan@2x.png' },
+						{ label: '璐d换鎵f(鍏�)', value: formatAmount(data.deductTotal), icon: '/shop/static/icon/yingshou_ic_koukuan@2x.png' }
+					];
+				},
+				processLuggageTypeData(data) {
+					const colorList = ["#3B82F6", "#64D7C7", "#FFD15C", "#FF8A47", "#F54786", "#EE6666", "#91CB74", "#73C0DE", "#3CA272"];
+
+					this.luggageDistribution = data.map((item, index) => ({
+						name: item.luggageName,
+						value: item.orderCount,
+						count: item.luggageCount,
+						percent: item.orderCount > 0 ? Math.round((item.orderCount / data.reduce((sum, curr) => sum + curr.orderCount, 0)) * 100) : 0,
+						color: colorList[index % colorList.length]
+					}));
+
+					this.chartData = {
+						series: [
+							{
+								data: this.luggageDistribution.map(item => ({
+									name: item.name,
+									value: item.value
+								}))
+							}
+						]
+					};
+
+					this.selectedLuggageText = '';
+					this.opts.title.name = '';
+					this.opts.subtitle.name = '';
+				},
+				onRingChartClick(e) {
+					const currentIndex = e && typeof e.currentIndex === 'number' ? e.currentIndex : -1;
+					const currentItem = this.luggageDistribution[currentIndex];
+
+					if (!currentItem) {
+						return;
 					}
 
-					this.chart.setOption(JSON.parse(optionText), true);
-				});
+					this.selectedLuggageText = `${currentItem.name}锛�${currentItem.count || 0}浠讹紝${currentItem.value || 0}鍗昤;
+					this.opts = {
+						...this.opts,
+						title: {
+							...this.opts.title,
+							name: `${currentItem.count || 0}浠禶,
+							fontSize: 18,
+							color: '#1f2430'
+						},
+						subtitle: {
+							...this.opts.subtitle,
+							name: currentItem.name,
+							fontSize: 11,
+							color: '#7a828f'
+						}
+					};
+					uni.showToast({
+						title: `${currentItem.name} ${currentItem.count || 0}浠禶,
+						icon: 'none'
+					});
+				},
+				onStartDateConfirm(e) {
+					const nextStartDate = this.formatPickerDate(e.value);
+
+					if (this.isStartDateAfterEndDate(nextStartDate, this.endDate)) {
+						this.startDate = '';
+						this.showStartDatePicker = false;
+						uni.showToast({ title: '寮�濮嬫棩鏈熶笉鑳藉ぇ浜庢埅姝㈡棩鏈�', icon: 'none' });
+						return;
+					}
+
+					this.startDate = nextStartDate;
+					this.showStartDatePicker = false;
+					this.minEndDate = nextStartDate ? new Date(nextStartDate).getTime() : '';
+
+					if (this.endDate) {
+						this.getDriverKpiData();
+					}
+				},
+				onEndDateConfirm(e) {
+					const nextEndDate = this.formatPickerDate(e.value);
+
+					if (this.isStartDateAfterEndDate(this.startDate, nextEndDate)) {
+						this.endDate = '';
+						this.showEndDatePicker = false;
+						uni.showToast({ title: '鎴鏃ユ湡涓嶈兘灏忎簬寮�濮嬫棩鏈�', icon: 'none' });
+						return;
+					}
+
+					this.endDate = nextEndDate;
+					this.showEndDatePicker = false;
+
+					if (this.startDate) {
+						this.getDriverKpiData();
+					}
+				},
+				confirmDateRange() {
+					if (!this.startDate || !this.endDate) {
+						uni.showToast({ title: '璇烽�夋嫨瀹屾暣鐨勬棩鏈熻寖鍥�', icon: 'none' });
+						return;
+					}
+
+					const start = new Date(this.startDate);
+					const end = new Date(this.endDate);
+
+					if (start > end) {
+						uni.showToast({ title: '寮�濮嬫棩鏈熶笉鑳藉ぇ浜庢埅姝㈡棩鏈�', icon: 'none' });
+						return;
+					}
+
+					const oneYear = 365 * 24 * 60 * 60 * 1000;
+					if (end - start > oneYear) {
+						uni.showToast({ title: '鏃ユ湡鍖洪棿涓嶈兘瓒呰繃涓�骞�', icon: 'none' });
+						return;
+					}
+
+					this.showDatePicker = false;
+					this.getDriverKpiData();
+				}
 			}
-		}
 	};
 </script>
 
@@ -198,6 +439,67 @@
 		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 {
@@ -264,9 +566,9 @@
 		flex-shrink: 0;
 		width: 68rpx;
 		height: 68rpx;
-		border-radius: 14rpx;
-		background: #ffffff;
-		box-shadow: 0 6rpx 18rpx rgba(20, 42, 74, 0.08);
+		// border-radius: 14rpx;
+		// background: #ffffff;
+		// box-shadow: 0 6rpx 18rpx rgba(20, 42, 74, 0.08);
 		box-sizing: border-box;
 	}
 
@@ -309,71 +611,20 @@
 	.chart-area {
 		position: relative;
 		flex-shrink: 0;
-		width: 270rpx;
-		height: 270rpx;
+		width: 100%;
+		height: 350rpx;
 	}
 
-	.echart-host,
-	.chart-fallback {
+	.chart-detail {
+		margin-top: 20rpx;
+		font-size: 28rpx;
+		line-height: 40rpx;
+		color: #5f6775;
+		text-align: center;
+	}
+
+	.echart-host {
 		width: 100%;
 		height: 100%;
-	}
-
-	.chart-fallback {
-		position: absolute;
-		left: 0;
-		top: 0;
-		border-radius: 50%;
-		background: conic-gradient(#3b82f6 0 35%, #64d7c7 35% 55%, #ffd15c 55% 73%, #ff8a47 73% 85%, #f54786 85% 95%, #ee6cb2 95% 100%);
-		-webkit-mask: radial-gradient(circle, transparent 0 42%, #000 43%);
-		mask: radial-gradient(circle, transparent 0 42%, #000 43%);
-		opacity: 0.2;
-		pointer-events: none;
-	}
-
-	.legend-list {
-		flex: 1;
-		min-width: 0;
-	}
-
-	.legend-item {
-		display: flex;
-		align-items: center;
-		justify-content: space-between;
-		gap: 12rpx;
-	}
-
-	.legend-item + .legend-item {
-		margin-top: 18rpx;
-	}
-
-	.legend-left {
-		display: flex;
-		align-items: center;
-		gap: 14rpx;
-		min-width: 0;
-	}
-
-	.legend-dot {
-		flex-shrink: 0;
-		width: 20rpx;
-		height: 20rpx;
-		border-radius: 50%;
-	}
-
-	.legend-name,
-	.legend-value {
-		font-size: 30rpx;
-		line-height: 42rpx;
-	}
-
-	.legend-name {
-		color: #6f7683;
-	}
-
-	.legend-value {
-		flex-shrink: 0;
-		font-weight: 600;
-		color: #2f333d;
 	}
 </style>

--
Gitblit v1.9.3