From 1edc355d5229d1f864dea3484752e4cae11cf1cc Mon Sep 17 00:00:00 2001
From: MrShi <1878285526@qq.com>
Date: 星期四, 23 四月 2026 20:05:38 +0800
Subject: [PATCH] app

---
 app/pages/index/index.vue |  539 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 files changed, 513 insertions(+), 26 deletions(-)

diff --git a/app/pages/index/index.vue b/app/pages/index/index.vue
index b9b40e1..4bab0f1 100644
--- a/app/pages/index/index.vue
+++ b/app/pages/index/index.vue
@@ -1,6 +1,20 @@
 <template>
-	<view class="hall-page">
-		<view class="hall-page__header" :style="{ paddingTop: statusBarHeight + 'px' }">
+		<view class="hall-page">
+			<view v-if="showOrderDetail && isStatusDetail" class="order-detail-map-layer" :style="{ top: statusBarHeight + 'px' }">
+				<map
+					class="order-detail-map-layer__map"
+					:latitude="detailMap.center.latitude"
+					:longitude="detailMap.center.longitude"
+					:markers="detailMap.markers"
+					:polyline="detailMap.polyline"
+					:include-points="detailMap.includePoints"
+					:scale="detailMap.scale"
+					:enable-zoom="true"
+					:enable-scroll="true"
+				></map>
+			</view>
+
+			<view class="hall-page__header" :style="{ paddingTop: statusBarHeight + 'px' }">
 			<view class="hall-page__user-row">
 				<view class="hall-page__user">
 					<image class="hall-page__avatar" src="/static/image/login_bg@2x.png" mode="aspectFill"></image>
@@ -186,23 +200,24 @@
 		<!-- 璁㈠崟璇︽儏 -->
 		<u-popup :show="showOrderDetail" round="20" mode="bottom" :overlayStyle="{ background: 'rgba(0, 0, 0, 0.32)' }" @close="showOrderDetail = false">
 			<view class="order-detail" :style="{ height: 'calc(100vh - ' + statusBarHeight + 'px)' }">
-				<scroll-view class="order-detail__scroll" scroll-y>
-					<view v-if="isStatusDetail" class="order-detail__map-section">
-						<view class="order-detail__map">
-							<image class="order-detail__map-image" mode="aspectFill"></image>
-							<view class="order-detail__map-bubble">{{ detailOrder.mapTips }}</view>
+				<view v-if="isStatusDetail" class="order-detail__map-section">
+					<view class="order-detail__map">
+						<view class="order-detail__map-placeholder"></view>
+						<view class="order-detail__map-bubble">{{ detailMap.tips }}</view>
+					</view>
+					<view class="order-detail__status-bar">
+						<view class="order-detail__status-left">
+							<image class="order-detail__status-icon" mode="aspectFit"></image>
+							<text class="order-detail__status-name">{{ detailOrder.statusText }}</text>
 						</view>
-						<view class="order-detail__status-bar">
-							<view class="order-detail__status-left">
-								<image class="order-detail__status-icon" mode="aspectFit"></image>
-								<text class="order-detail__status-name">{{ detailOrder.statusText }}</text>
-							</view>
-							<view class="order-detail__status-right">
-								<text v-if="detailOrder.showCancelTag" class="order-detail__cancel-tag">鍙栨秷璁㈠崟</text>
-								<text class="order-detail__status-no">#{{ detailOrder.serialNo }}</text>
-							</view>
+						<view class="order-detail__status-right">
+							<text v-if="detailOrder.showCancelTag" class="order-detail__cancel-tag">鍙栨秷璁㈠崟</text>
+							<text class="order-detail__status-no">#{{ detailOrder.serialNo }}</text>
 						</view>
 					</view>
+				</view>
+
+				<scroll-view class="order-detail__scroll" scroll-y>
 
 					<view class="order-detail__content">
 						<view class="order-detail__head">
@@ -301,11 +316,50 @@
 						<image class="order-detail__cancel-icon" src="/static/image/ic_close2@2x.png" mode="aspectFit"></image>
 					</view>
 					<button v-if="!isStatusDetail" class="order-detail__confirm" hover-class="order-detail__confirm--hover">纭鎶㈠崟</button>
-					<button v-else class="order-detail__confirm order-detail__confirm--status" hover-class="order-detail__confirm--hover">
-						<image class="order-detail__confirm-icon" mode="aspectFit"></image>
+					<button v-else class="order-detail__confirm order-detail__confirm--status" hover-class="order-detail__confirm--hover" @click="handleStatusAction">
+						<image class="order-detail__confirm-icon" src="/static/image/ic_photo@2x.png" mode="aspectFit"></image>
 						<text>{{ detailPopupType === 'pickup' ? '鎷嶇収鍙栬揣' : '鎷嶇収閫佽揪' }}</text>
 					</button>
 				</view>
+			</view>
+		</u-popup>
+
+		<u-popup :show="showPhotoDeliverPopup" round="20" mode="bottom">
+			<view class="photo-deliver">
+				<view class="photo-deliver__header">
+					<image class="photo-deliver__close-placeholder" mode="aspectFit"></image>
+					<text class="photo-deliver__title">鎷嶇収閫佽揪</text>
+					<image class="photo-deliver__close" mode="aspectFit" @click="showPhotoDeliverPopup = false"></image>
+				</view>
+
+				<view class="photo-deliver__section">
+					<view class="photo-deliver__label-row">
+						<text class="photo-deliver__label">鎷嶆憚閫佽揪鐓х墖</text>
+						<text class="photo-deliver__required">*</text>
+						<text class="photo-deliver__hint">鏈�澶�3寮犵収鐗�</text>
+					</view>
+
+					<view class="photo-deliver__photos">
+						<view class="photo-deliver__upload-card">
+							<image class="photo-deliver__upload-icon" mode="aspectFit"></image>
+							<text class="photo-deliver__upload-text">鐐瑰嚮鎷嶇収</text>
+						</view>
+
+						<view class="photo-deliver__preview-card">
+							<image class="photo-deliver__preview-image" mode="aspectFill"></image>
+							<view class="photo-deliver__preview-mask">
+								<text class="photo-deliver__preview-delete">鍒犻櫎</text>
+							</view>
+						</view>
+					</view>
+				</view>
+
+				<view class="photo-deliver__section photo-deliver__section--remark">
+					<text class="photo-deliver__remark-title">澶囨敞淇℃伅</text>
+					<textarea class="photo-deliver__textarea" maxlength="200" placeholder="璇疯緭鍏�" placeholder-style="color: #c7cbd3;" />
+				</view>
+
+				<button class="photo-deliver__submit" hover-class="photo-deliver__submit--hover" @click="showPhotoDeliverPopup = false">纭閫佽揪</button>
 			</view>
 		</u-popup>
 	</view>
@@ -318,8 +372,10 @@
 				tts: null,
 				show: false,
 				show1: false,
+				showPhotoDeliverPopup: false,
 				showOrderDetail: false,
 				detailPopupType: 'hall',
+				routeInfo: null,
 				statusBarHeight: 0,
 				headerHeight: 0,
 				tabbarHeight: 0,
@@ -344,8 +400,16 @@
 					statusText: '鎶㈠崟澶у巺',
 					qrcodeValue: '767889',
 					qrcodeLabel: '鍙栬揣鐮�',
-					mapTips: '鍓╀綑3.2km锛岀害4鍒嗛挓',
+					mapTips: '',
 					showCancelTag: false,
+					startPoint: {
+						latitude: 31.8269,
+						longitude: 117.2334
+					},
+					endPoint: {
+						latitude: 31.8435,
+						longitude: 117.2852
+					},
 					tags: [
 						{ text: '鏍囬�熻揪', type: 'blue' },
 						{ text: '璐甸噸鐗╁搧', type: 'orange' }
@@ -385,6 +449,14 @@
 						time: '45鍒嗛挓鍐�',
 						price: '楼20.5',
 						extra: '3.0',
+						startPoint: {
+							latitude: 31.829512,
+							longitude: 117.239211
+						},
+						endPoint: {
+							latitude: 31.841268,
+							longitude: 117.278695
+						},
 						tags: [
 							{ text: '鏋侀�熻揪', type: 'blue' },
 							{ text: '璐甸噸鐗╁搧', type: 'orange' }
@@ -402,6 +474,14 @@
 						time: '45鍒嗛挓鍐�',
 						price: '楼20.5',
 						extra: '3.0',
+						startPoint: {
+							latitude: 31.827106,
+							longitude: 117.232884
+						},
+						endPoint: {
+							latitude: 31.847331,
+							longitude: 117.289762
+						},
 						tags: [
 							{ text: '鏋侀�熻揪', type: 'red' },
 							{ text: '澶т欢鐗╁搧', type: 'blue-light' }
@@ -419,6 +499,14 @@
 						time: '45鍒嗛挓鍐�',
 						price: '楼20.5',
 						extra: '3.0',
+						startPoint: {
+							latitude: 31.823761,
+							longitude: 117.228947
+						},
+						endPoint: {
+							latitude: 31.838473,
+							longitude: 117.272513
+						},
 						tags: [
 							{ text: '鏋侀�熻揪', type: 'red' },
 							{ text: '澶т欢鐗╁搧', type: 'blue-light' }
@@ -439,8 +527,16 @@
 						statusText: '寰呭彇璐�',
 						qrcodeValue: '767889',
 						qrcodeLabel: '鍙栬揣鐮�',
-						mapTips: '鍓╀綑3.2km锛岀害4鍒嗛挓',
+						mapTips: '',
 						showCancelTag: true,
+						startPoint: {
+							latitude: 31.829512,
+							longitude: 117.239211
+						},
+						endPoint: {
+							latitude: 31.841268,
+							longitude: 117.278695
+						},
 						time: '45鍒嗛挓鍐�',
 						price: '楼20.5',
 						extra: '3.0',
@@ -464,8 +560,16 @@
 						statusText: '閰嶉�佷腑',
 						qrcodeValue: '767889',
 						qrcodeLabel: '瀛樹欢鐮�',
-						mapTips: '鍓╀綑3.2km锛岀害4鍒嗛挓',
+						mapTips: '',
 						showCancelTag: false,
+						startPoint: {
+							latitude: 31.827106,
+							longitude: 117.232884
+						},
+						endPoint: {
+							latitude: 31.847331,
+							longitude: 117.289762
+						},
 						time: '45鍒嗛挓鍐�',
 						price: '楼20.5',
 						extra: '3.0',
@@ -500,6 +604,65 @@
 				return this.detailPopupType === 'pickup' || this.detailPopupType === 'delivering'
 			},
 
+			detailMap() {
+				const fallbackPoint = {
+					latitude: 31.8269,
+					longitude: 117.2334
+				}
+				const startPoint = this.detailOrder.startPoint || fallbackPoint
+				const endPoint = this.detailOrder.endPoint || fallbackPoint
+				const routePoints = this.routeInfo && this.routeInfo.points && this.routeInfo.points.length ? this.routeInfo.points : []
+				const center = {
+					latitude: (startPoint.latitude + endPoint.latitude) / 2,
+					longitude: (startPoint.longitude + endPoint.longitude) / 2
+				}
+				const distanceKm = this.routeInfo && this.routeInfo.distanceKm ? this.routeInfo.distanceKm : 0
+				const durationMinutes = this.routeInfo && this.routeInfo.durationMinutes ? this.routeInfo.durationMinutes : 0
+				const tips = this.detailOrder.mapTips || (distanceKm ? `鍓╀綑${distanceKm.toFixed(1)}km锛岀害${durationMinutes}鍒嗛挓` : '璺嚎瑙勫垝涓�...')
+
+				return {
+					center,
+					markers: [
+						{
+							id: 1,
+							latitude: startPoint.latitude,
+							longitude: startPoint.longitude,
+							iconPath: '/static/image/map_marker_start.svg',
+							width: 32,
+							height: 38,
+							anchor: {
+								x: 0.5,
+								y: 1
+							}
+						},
+						{
+							id: 2,
+							latitude: endPoint.latitude,
+							longitude: endPoint.longitude,
+							iconPath: '/static/image/map_marker_end.svg',
+							width: 32,
+							height: 38,
+							anchor: {
+								x: 0.5,
+								y: 1
+							}
+						}
+					],
+					polyline: [
+						...(routePoints.length ? [{
+							points: routePoints,
+							color: '#2A7FFF',
+							width: 8,
+							dottedLine: false,
+							arrowLine: true
+						}] : [])
+					],
+					includePoints: routePoints.length ? routePoints : [startPoint, endPoint],
+					scale: 12,
+					tips
+				}
+			},
+
 			currentOrderList() {
 				const orderMap = {
 					hall: this.orderList,
@@ -523,8 +686,42 @@
 		},
 
 		methods: {
+			calculateDistance(startPoint, endPoint) {
+				if (!startPoint || !endPoint) {
+					return 0
+				}
+
+				const toRad = (value) => value * Math.PI / 180
+				const earthRadius = 6371
+				const deltaLat = toRad(endPoint.latitude - startPoint.latitude)
+				const deltaLng = toRad(endPoint.longitude - startPoint.longitude)
+				const lat1 = toRad(startPoint.latitude)
+				const lat2 = toRad(endPoint.latitude)
+				const a = Math.sin(deltaLat / 2) * Math.sin(deltaLat / 2) + Math.cos(lat1) * Math.cos(lat2) * Math.sin(deltaLng / 2) * Math.sin(deltaLng / 2)
+				const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
+
+				return earthRadius * c
+			},
+
+			estimateDuration(distanceKm) {
+				if (!distanceKm) {
+					return 1
+				}
+
+				const averageSpeedKmPerHour = 35
+				return Math.max(1, Math.round(distanceKm / averageSpeedKmPerHour * 60))
+			},
+
+			handleStatusAction() {
+				if (this.detailPopupType === 'delivering') {
+					this.showOrderDetail = false
+					this.showPhotoDeliverPopup = true
+				}
+			},
+
 			openDetailPopup(item) {
 				this.detailPopupType = this.activeTab
+				this.routeInfo = null
 				this.detailOrder = {
 					...this.detailOrder,
 					...item,
@@ -534,6 +731,114 @@
 					photos: item.photos || this.detailOrder.photos
 				}
 				this.showOrderDetail = true
+
+				if (this.activeTab === 'pickup' || this.activeTab === 'delivering') {
+					this.fetchDrivingRoute(this.detailOrder)
+				}
+			},
+
+			fetchDrivingRoute(order) {
+				if (!order || !order.startPoint || !order.endPoint) {
+					return
+				}
+
+				const origin = `${order.startPoint.longitude},${order.startPoint.latitude}`
+				const destination = `${order.endPoint.longitude},${order.endPoint.latitude}`
+
+				uni.request({
+					url: 'https://restapi.amap.com/v3/direction/driving',
+					method: 'GET',
+					data: {
+						key: 'e4d46c87adf151dca20060317592b1b6',
+						origin,
+						destination,
+						extensions: 'all',
+						strategy: 0,
+						output: 'json'
+					},
+					success: (response) => {
+						const path = response.data && response.data.route && response.data.route.paths && response.data.route.paths[0]
+						if (!path) {
+							this.routeInfo = this.buildMockRouteInfo(order.startPoint, order.endPoint)
+							return
+						}
+
+						const points = this.parseRoutePoints(path.steps || [], order.startPoint, order.endPoint)
+						const distanceKm = Number(path.distance || 0) / 1000
+						const durationMinutes = Math.max(1, Math.round(Number(path.duration || 0) / 60))
+
+						this.routeInfo = {
+							points,
+							distanceKm: distanceKm || this.calculateDistance(order.startPoint, order.endPoint),
+							durationMinutes
+						}
+					},
+					fail: () => {
+						this.routeInfo = this.buildMockRouteInfo(order.startPoint, order.endPoint)
+					}
+				})
+			},
+
+			buildMockRouteInfo(startPoint, endPoint) {
+				const middleLatitude = (startPoint.latitude + endPoint.latitude) / 2
+				const middleLongitude = (startPoint.longitude + endPoint.longitude) / 2
+				const points = [
+					startPoint,
+					{
+						latitude: startPoint.latitude + (middleLatitude - startPoint.latitude) * 0.65,
+						longitude: startPoint.longitude + 0.008
+					},
+					{
+						latitude: middleLatitude + 0.004,
+						longitude: middleLongitude + 0.012
+					},
+					{
+						latitude: endPoint.latitude - 0.003,
+						longitude: endPoint.longitude - 0.008
+					},
+					endPoint
+				]
+				const distanceKm = this.calculatePathDistance(points)
+				const durationMinutes = this.estimateDuration(distanceKm)
+
+				return {
+					points,
+					distanceKm,
+					durationMinutes
+				}
+			},
+
+			parseRoutePoints(steps, startPoint, endPoint) {
+				const points = []
+
+				steps.forEach((step) => {
+					const polyline = step.polyline || ''
+					polyline.split(';').forEach((pointText) => {
+						const [longitude, latitude] = pointText.split(',').map(Number)
+						if (!Number.isNaN(latitude) && !Number.isNaN(longitude)) {
+							points.push({ latitude, longitude })
+						}
+					})
+				})
+
+				if (!points.length) {
+					return [startPoint, endPoint]
+				}
+
+				return points
+			},
+
+			calculatePathDistance(points) {
+				if (!points || points.length < 2) {
+					return 0
+				}
+
+				let totalDistance = 0
+				for (let index = 1; index < points.length; index += 1) {
+					totalDistance += this.calculateDistance(points[index - 1], points[index])
+				}
+
+				return totalDistance
 			},
 
 			toggleFilterPopup(show) {
@@ -631,6 +936,22 @@
 		height: 100vh;
 		background: #f5f6f8;
 		overflow: hidden;
+
+		.order-detail-map-layer {
+			position: fixed;
+			left: 0;
+			right: 0;
+			height: 330rpx;
+			z-index: 10081;
+			overflow: hidden;
+			border-top-left-radius: 28rpx;
+			border-top-right-radius: 28rpx;
+
+			&__map {
+				width: 100%;
+				height: 100%;
+			}
+		}
 		
 		.qrcode {
 			padding: 36rpx 30rpx;
@@ -680,6 +1001,168 @@
 			}
 		}
 
+		.photo-deliver {
+			padding: 32rpx 28rpx calc(env(safe-area-inset-bottom) + 28rpx);
+			background: #ffffff;
+			box-sizing: border-box;
+			border-top-left-radius: 20rpx;
+			border-top-right-radius: 20rpx;
+			overflow: hidden;
+
+			&__header {
+				display: flex;
+				align-items: center;
+				justify-content: space-between;
+			}
+
+			&__title {
+				font-size: 34rpx;
+				font-weight: 700;
+				color: #111111;
+			}
+
+			&__close,
+			&__close-placeholder {
+				width: 36rpx;
+				height: 36rpx;
+				flex-shrink: 0;
+			}
+
+			&__close-placeholder {
+				opacity: 0;
+			}
+
+			&__section {
+				margin-top: 56rpx;
+
+				&--remark {
+					margin-top: 46rpx;
+				}
+			}
+
+			&__label-row {
+				display: flex;
+				align-items: center;
+				flex-wrap: wrap;
+			}
+
+			&__label,
+			&__remark-title {
+				font-size: 28rpx;
+				font-weight: 700;
+				color: #23262d;
+			}
+
+			&__required {
+				margin-left: 4rpx;
+				font-size: 28rpx;
+				font-weight: 700;
+				color: #ff3b30;
+			}
+
+			&__hint {
+				margin-left: 12rpx;
+				font-size: 24rpx;
+				color: #a8adb7;
+			}
+
+			&__photos {
+				display: flex;
+				gap: 18rpx;
+				margin-top: 30rpx;
+			}
+
+			&__upload-card,
+			&__preview-card {
+				position: relative;
+				width: 160rpx;
+				height: 160rpx;
+				border-radius: 8rpx;
+				overflow: hidden;
+			}
+
+			&__upload-card {
+				display: flex;
+				flex-direction: column;
+				align-items: center;
+				justify-content: center;
+				border: 2rpx dashed #c9ced6;
+				background: #ffffff;
+				box-sizing: border-box;
+			}
+
+			&__upload-icon {
+				width: 52rpx;
+				height: 52rpx;
+			}
+
+			&__upload-text {
+				margin-top: 14rpx;
+				font-size: 26rpx;
+				color: #9da3ae;
+			}
+
+			&__preview-card {
+				background: #eef1f5;
+			}
+
+			&__preview-image {
+				width: 100%;
+				height: 100%;
+			}
+
+			&__preview-mask {
+				position: absolute;
+				left: 0;
+				right: 0;
+				bottom: 0;
+				display: flex;
+				justify-content: center;
+				align-items: center;
+				height: 48rpx;
+				background: rgba(0, 0, 0, 0.46);
+			}
+
+			&__preview-delete {
+				font-size: 26rpx;
+				color: #ffffff;
+			}
+
+			&__textarea {
+				width: 100%;
+				height: 110rpx;
+				margin-top: 24rpx;
+				padding: 28rpx 24rpx;
+				border-radius: 12rpx;
+				background: #f7f8fa;
+				box-sizing: border-box;
+				font-size: 30rpx;
+				color: #2c3139;
+			}
+
+			&__submit {
+				width: 100%;
+				height: 88rpx;
+				line-height: 88rpx;
+				margin-top: 86rpx;
+				border-radius: 50rpx;
+				background: #106efa;
+				font-size: 32rpx;
+				font-weight: 700;
+				color: #ffffff;
+				border: 0;
+				padding: 0;
+
+				&::after {
+					border: 0;
+				}
+
+				&--hover {
+					opacity: 0.92;
+				}
+			}
+		}
+
 		.order-detail {
 			display: flex;
 			flex-direction: column;
@@ -700,14 +1183,18 @@
 			&__map {
 				position: relative;
 				height: 330rpx;
-				background: linear-gradient(180deg, #eef5ff 0%, #dbe9ff 100%);
+				background: #eef5ff;
 				overflow: hidden;
 			}
 
-			&__map-image {
+			&__map-view {
 				width: 100%;
 				height: 100%;
-				opacity: 0.2;
+			}
+
+			&__map-placeholder {
+				width: 100%;
+				height: 100%;
 			}
 
 			&__map-bubble {
@@ -1110,8 +1597,8 @@
 			}
 
 			&__confirm-icon {
-				width: 32rpx;
-				height: 32rpx;
+				width: 44rpx;
+				height: 44rpx;
 				flex-shrink: 0;
 			}
 		}

--
Gitblit v1.9.3