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