<template> <view class="page" v-if="loading"> <u-navbar title="订单详情" :border-bottom="false" :background="bgc" title-color='#2E4975' title-size='36' height='45'></u-navbar> <map class="map" id="map" ref="map" :scale="zoomSize" :latitude="latitude" :longitude="longitude" :show-location='true' :markers="markers" :polygons="polygons" :polyline="polyline"> <cover-view class="park" @click="toggleIconAndCallout"> <cover-image class="img" src="https://lxnapi.ccttiot.com/bike/img/static/uRiYQZQEb3l2LsltEsyW" mode=""></cover-image> </cover-view> </map> <view class="info_card"> <view class="info_tit"> 基本信息 </view> <view class="lines"></view> <view class="cont"> <view class="info_li"> 订单编号:<span>{{orderInfo.orderNo}}</span> </view> <!-- <view class="info_li"> </view> --> <view class="info_li"> <view class="half_infoli " @click="callPhone"> 租赁用户:<span style="color:#4C97E7 ;">{{orderInfo.phonenumber}}</span> </view> <view class="half_infoli"> 用户姓名:<span v-if="orderInfo.realName">{{orderInfo.realName}}</span> <span v-else>--</span> </view> </view> <view class="info_li"> 订单状态:<span>{{status()}}</span> </view> <view class="info_li"> <view class="half_infoli"> 租赁时长:<span>{{ computedList(orderInfo)}}</span> </view> <view class="half_infoli"> 行驶距离:<span>{{orderInfo.distance/1000}}Km</span> </view> </view> </view> </view> <view class="info_card" style="margin-top: 20rpx;"> <view class="info_tit"> 设备信息 </view> <view class="lines"></view> <view class="cont"> <view class="info_li"> <view class="half_infoli"> 车牌号: <span v-if="orderInfo.device.vehicleNum">{{orderInfo.device.vehicleNum}}</span> <span v-else>--</span> </view> <view class="half_infoli"> SN: <span v-if="orderInfo.device.sn">{{orderInfo.device.sn}}</span> <span v-else>--</span> </view> </view> <view class="info_li"> <!-- <view class="half_infoli"> 车辆编号:<span>{{orderInfo.device.areaName}}</span> </view> --> <view class="half_infoli"> 运营区域: <span v-if="orderInfo.area">{{orderInfo.area}}</span> <span v-else>--</span> </view> </view> </view> </view> <view class="info_card" style="margin-top: 20rpx;"> <view class="info_tit"> 行程记录 </view> <view class="lines"></view> <view class="cont"> <view v-for="(item,index) in orderInfo.tripLogs" :key="index"> <view class="info_lis" v-if="item.type==1"> 开锁时间:<view class="text">{{item.createTime}}</view> </view> <view class="info_lis" v-if="item.type==1"> 开锁地点:<view class="text"> {{item.address}}</view> </view> <view class="info_lis" v-if="item.type==2"> 临时锁车时间:<view class="text">{{item.createTime}}</view> </view> <view class="info_lis" v-if="item.type==2"> 临时锁车地点:<view class="text">{{item.address}}</view> </view> <view class="info_lis" v-if="item.type==3"> 解除时间:<view class="text">{{item.createTime}}</view> </view> <view class="info_lis" v-if="item.type==4"> 关锁时间:<view class="text">{{item.createTime}}</view> </view> <view class="info_lis" v-if="item.type==4"> 关锁地点:<view class="text">{{item.address}}</view> </view> </view> <!-- <view class="info_li"> 锁车时间:<span>120.56189,27.12379</span> </view> <view class="info_li"> 锁车地点:<span>120.56189,27.12379</span> </view> --> <!-- <view class="info_li"> 解除时间:<span>120.56189,27.12379</span> </view> --> </view> </view> <view class="info_card" style="margin-top: 20rpx;"> <view class="info_tit"> 费用明细 <view class="money"> 结算总费用:¥{{orderInfo.settlementFee}} </view> </view> <view class="lines"></view> <view class="cont"> <view class="info_li"> <view class="half_infoli"> 预约费用:<span>{{orderInfo.appointmentFee}}</span> </view> <view class="half_infoli"> 运营区外调度费:<span>{{orderInfo.dispatchFee}}</span> </view> </view> <view class="info_li"> <view class="half_infoli"> 停车点外调度费:<span>{{orderInfo.manageFee}}</span> </view> <view class="half_infoli"> 骑行费用:<span>{{orderInfo.ridingFee}}</span> </view> </view> <view class="info_li"> 实收:<span>{{orderInfo.totalFee}}</span> </view> <view class="info_li"> 支付方式:<span>微信支付</span> </view> <view class="info_li" v-if="orderInfo.rule" style="display: inline-block;"> 计费模板:<span>{{orderInfo.rule.name}}</span> </view> <view class="info_li" v-if="orderInfo.payTime"> 支付时间:<span>{{orderInfo.payTime}}</span> </view> </view> <view class="lines"></view> <view class="cont" v-if="orderInfo.etRefund"> <view class="info_li"> <view class="half_infoli"> 预约费退款:<span>{{orderInfo.etRefund.appointmentFee}}</span> </view> <view class="half_infoli"> 调度费退款:<span>{{orderInfo.etRefund.dispatchFee}}</span> </view> </view> <view class="info_li"> <view class="half_infoli"> 骑行费退款:<span>{{orderInfo.etRefund.ridingFee}}</span> </view> <view class="half_infoli"> 管理费退款:<span>{{orderInfo.etRefund.manageFee}}</span> </view> </view> <view class="info_li"> 退款原因:<span v-if="orderInfo.etRefund.reason">{{orderInfo.etRefund.reason}}</span> <span v-else>--</span> </view> <view class="info_li"> 退款时间:<span>{{orderInfo.etRefund.createTime}}</span> </view> </view> </view> <view class="bot" v-if='orderInfo.paid==1'> <view class="btn" @click="backfee"> 退款 </view> </view> <u-mask :show="show" @click="show = false" :z-index='100' /> <view class="tip_box" v-if="showtk"> <view class="top" v-if="showtk"> <view class="tip"> 退款 </view> <view class="ipt_box"> <view class="text"> 运营区外调度费 </view> <view class="ipt"> <input type="text" v-model="orderInfo.dispatchFee" placeholder="0.00" class="input" placeholder-style="color:#C7CDD3" @input="checkAndUpdate('dispatchFee')"> 元 </view> </view> <view class="ipt_box"> <view class="text"> 停车点外调度费 </view> <view class="ipt"> <input type="text" v-model="orderInfo.manageFee" placeholder="0.00" class="input" placeholder-style="color:#C7CDD3" @input="checkAndUpdate('manageFee')"> 元 </view> </view> <view class="ipt_box"> <view class="text"> 骑行费 </view> <view class="ipt"> <input type="text" v-model="orderInfo.ridingFee" placeholder="0.00" class="input" placeholder-style="color:#C7CDD3" @input="checkAndUpdate('ridingFee')"> 元 </view> </view> <view class="ipt_box"> <view class="text"> 预约费 </view> <view class="ipt"> <input type="text" v-model="orderInfo.appointmentFee" placeholder="0.00" class="input" placeholder-style="color:#C7CDD3" @input="checkAndUpdate('appointmentFee')"> 元 </view> </view> <view class="ipt_box"> <view class="text"> 原因 </view> <view class="ipt"> <input type="text" v-model="orderInfo.reason" placeholder="选填" class="input" placeholder-style="color:#C7CDD3" @input="checkAndUpdate('reason')"> </view> </view> </view> <view class="bots"> <view class="bot_left" @click="closetk()"> 取消 </view> <view class="bot_right" @click="backMoney()"> 确定 </view> </view> </view> </view> </template> <script> export default { data() { return { bgc: { backgroundColor: "#F7FAFE", }, latitude: 0, longitude: 0, isMap: false, zoomSize: 15, markers: [], polyline: [{ points: [], width: 8, arrowLine: true, color: '#00AF99' // strokeWidth: 2, // strokeColor: '#00AF99', // fillColor: '#00AF99' }, ], polygons: [], cardId: '001区域', orderId: '', orderInfo: {}, loading: false, showtk: false, show:false } }, onLoad(e) { this.orderId = e.id this.getOrderDetail() // this.getParking() }, components: { }, methods: { backfee(){ this.show = true this.showtk = true }, closetk() { this.show = false this.showtk = false // this.orderInfo = {} }, toggleIconAndCallout() { this.showIconAndCallout = !this.showIconAndCallout; if (this.showIconAndCallout) { const newMarkers = []; this.parkingList.forEach(item => { newMarkers.push({ id: parseFloat(item.parkingId), latitude: parseFloat(item.latitude), longitude: parseFloat(item.longitude), width: 20, height: 28.95, iconPath: item.type == 1 ? 'https://lxnapi.ccttiot.com/bike/img/static/up2xXqAgwCX5iER600k3' : item .type == 2 ? 'https://lxnapi.ccttiot.com/bike/img/static/u53BAQcFIX3vxsCzEZ7t' : ' https://lxnapi.ccttiot.com/bike/img/static/uDNY5Q4zOiZTCBTA2Jdq', callout: { content: item.parkingName, color: '#ffffff', fontSize: 14, borderRadius: 10, bgColor: item.type == 1 ? '#3A7EDB' : item.type == 2 ? '#FFC107' : '#FF473E', padding: 6, display: 'ALWAYS' }, isCalloutVisible: true // 添加标记 }); }); this.$set(this, 'markers', [...this.markers, ...newMarkers]); } else { // 过滤掉所有气泡显示的标记 this.$set(this, 'markers', this.markers.filter(marker => !marker.isCalloutVisible)); } }, callPhone() { uni.makePhoneCall({ phoneNumber: this.orderInfo.phonenumber }) }, computedList(item) { if (item.status == 0 || item.status == 2) { const createTime = new Date(item.createTime); const payTime = Date.now(); const timeDifference = Math.abs(createTime - payTime); const hours = Math.floor(timeDifference / (1000 * 60 * 60)); const minutes = Math.floor((timeDifference % (1000 * 60 * 60)) / (1000 * 60)); const seconds = Math.floor((timeDifference % (1000 * 60)) / 1000); let result = ''; if (hours > 0) { result += `${hours}小时`; } if (minutes > 0 || hours > 0) { // 显示分钟条件:有小时或者有分钟 result += `${minutes}分`; } // result += `${seconds}秒`; // 始终显示秒 return result; } else { const createTime = new Date(item.createTime); const payTime = new Date(item.returnTime); const timeDifference = Math.abs(createTime - payTime); const hours = Math.floor(timeDifference / (1000 * 60 * 60)); const minutes = Math.floor((timeDifference % (1000 * 60 * 60)) / (1000 * 60)); const seconds = Math.floor((timeDifference % (1000 * 60)) / 1000); let result = ''; if (hours > 0) { result += `${hours}小时`; } if (minutes > 0 || hours > 0) { // 显示分钟条件:有小时或者有分钟 result += `${minutes}分`; } // result += `${seconds}秒`; // 始终显示秒 return result; } }, convertBoundaryToPolyline(boundary) { if (!boundary) return null; const points = JSON.parse(boundary).map(coord => ({ latitude: coord[1], longitude: coord[0] })); const polyline = { points: points, fillColor: "#55888840", //填充颜色 strokeColor: "#22FF00", //描边颜色 strokeWidth: 2, //描边宽度 zIndex: 1, //层级 }; return polyline; }, convertBoundaryToPolylines(boundaries, num) { if (num == 1) { console.log('判断'); return boundaries.map(boundary => { if (!boundary) return null; let coords; try { coords = JSON.parse(boundary); } catch (error) { console.error("Error parsing boundary JSON:", error); return null; } if (!Array.isArray(coords)) { console.error("Parsed boundary is not an array:", coords); return null; } const points = coords.map(coord => ({ latitude: coord[1], longitude: coord[0] })); return { points: points, fillColor: "#3A7EDB40", //填充颜色 strokeColor: "#3A7EDB", //描边颜色 strokeWidth: 2, //描边宽度 zIndex: 1, //层级 }; }).filter(polyline => polyline !== null); // 过滤掉无效的折线数据 } else if (num == 2) { return boundaries.map(boundary => { if (!boundary) return null; let coords; try { coords = JSON.parse(boundary); } catch (error) { console.error("Error parsing boundary JSON:", error); return null; } if (!Array.isArray(coords)) { console.error("Parsed boundary is not an array:", coords); return null; } const points = coords.map(coord => ({ latitude: coord[1], longitude: coord[0] })); return { points: points, fillColor: "#FFF5D640", //填充颜色 strokeColor: "#FFC107", //描边颜色 strokeWidth: 2, //描边宽度 zIndex: 1, //层级 }; }).filter(polyline => polyline !== null); // 过滤掉无效的折线数据 } else if (num == 3) { return boundaries.map(boundary => { if (!boundary) return null; let coords; try { coords = JSON.parse(boundary); } catch (error) { console.error("Error parsing boundary JSON:", error); return null; } if (!Array.isArray(coords)) { console.error("Parsed boundary is not an array:", coords); return null; } const points = coords.map(coord => ({ latitude: coord[1], longitude: coord[0] })); return { points: points, fillColor: "#FFD1CF40", //填充颜色 strokeColor: "#FF473E", //描边颜色 strokeWidth: 2, //描边宽度 zIndex: 1, //层级 }; }).filter(polyline => polyline !== null); // 过滤掉无效的折线数据 } }, getParking() { // 发送请求获取数据 let data = { areaId: this.orderInfo.areaId } this.$u.get('/app/parking/list?', data).then((res) => { if (res.code === 200) { // 处理接口返回的数据 const type1Data = []; const type2Data = []; const type3Data = []; res.rows.forEach(row => { if (row.type == 1) { type1Data.push(row); } else if (row.type == 2) { type2Data.push(row); } else if (row.type == 3) { type3Data.push(row); } }); const validBoundaries = type1Data.map(row => row.boundaryStr).filter(boundary => typeof boundary === 'string' && boundary.trim() !== ''); const polylines = this.convertBoundaryToPolylines(validBoundaries, 1); const validBoundaries1 = type2Data.map(row => row.boundaryStr).filter(boundary => typeof boundary === 'string' && boundary.trim() !== ''); const polylines1 = this.convertBoundaryToPolylines(validBoundaries1, 2); const validBoundaries2 = type3Data.map(row => row.boundaryStr).filter(boundary => typeof boundary === 'string' && boundary.trim() !== ''); const polylines2 = this.convertBoundaryToPolylines(validBoundaries2, 3); // 将处理后的数据添加到 this.polyline 中 this.polygons = this.polygons.concat(polylines2); this.polygons = this.polygons.concat(polylines1); this.polygons = this.polygons.concat(polylines); // console.log(this.polyline); this.parkingList = res.rows } }).catch(error => { console.error("Error fetching parking data:", error); }); }, getArea() { // 发送请求获取数据 let id = this.orderInfo.areaId this.$u.get("/app/area/" + id).then((res) => { if (res.code === 200) { // 处理接口返回的数据,将边界数据转换为地图组件需要的折线结构 const polylines = this.convertBoundaryToPolyline(res.data.boundaryStr) // 更新折线数据 this.polygons.push(polylines) this.getParking() // console.log(this.polyline); } }).catch(error => { console.error("Error fetching area data:", error); }); }, status() { if (this.orderInfo != '') { if (this.orderInfo.status == 0) { return '预约中' } else if (this.orderInfo.status == 1) { return '取消预约' } else if (this.orderInfo.status == 2) { return '骑行中' } else if (this.orderInfo.status == 3) { return '骑行结束' } else if (this.orderInfo.status == 4) { return '订单完成' } } }, backMoney() { let data = { orderNo: this.orderInfo.orderNo, dispatchFee: this.orderInfo.dispatchFee, manageFee: this.orderInfo.manageFee, ridingFee: this.orderInfo.ridingFee, appointmentFee: this.orderInfo.appointmentFee, reason: this.orderInfo.reason } this.$u.put('/appVerify/order/refund', data).then((res) => { if (res.code === 200) { // 处理接口返回的数据,将边界数据转换为地图组件需要的折线结构 this.show = false this.showtk = false this.getOrderDetail() } else { uni.showToast({ title: res.msg, icon: 'none', duration: 2000 }); } }).catch(error => { console.error("Error fetching area data:", error); }); }, getOrderDetail() { this.$u.get('/appVerify/order/' + this.orderId).then((res) => { if (res.code === 200) { // 处理接口返回的数据,将边界数据转换为地图组件需要的折线结构 this.orderInfo = res.data this.getArea() this.loading = true this.latitude = parseFloat(this.orderInfo.latitude) this.longitude = parseFloat(this.orderInfo.longitude) this.polyline[0].points = JSON.parse(res.data.tripRouteStr).map(coord => ({ latitude: coord[1], longitude: coord[0] })); let abb; try { abb = JSON.parse(res.data.tripRouteStr); } catch (error) { console.error("Error parsing tripRouteStr:", error); return; } if (abb.length > 2) { this.latitude = parseFloat(abb[0][1]); this.longitude = parseFloat(abb[0][0]); this.polyline[0].points = abb.map(coord => ({ latitude: coord[1], longitude: coord[0] })); this.markers.push({ id: 0, latitude: abb[0][1], longitude: abb[0][0], width: 25, height: 38, iconPath: 'https://lxnapi.ccttiot.com/bike/img/static/u06paUGiHLvL08Pw7BGr' }, { id: 1, latitude: abb[abb.length - 1][1], longitude: abb[abb.length - 1][0], width: 25, height: 38, iconPath: 'https://lxnapi.ccttiot.com/bike/img/static/uwpAj9vYtPRmhtTOtflx' }); } } // console.log(points,''); // this.polyline[0].points=points }).catch(error => { console.error("Error fetching area data:", error); }); } } } </script> <style lang="scss"> page { overflow-x: hidden; background-color: #F3F3F3; } .page { padding-bottom: 200rpx; width: 750rpx; .tip_box { position: fixed; left: 72rpx; top: 628rpx; width: 610rpx; // height: 282rpx; background: #FFFFFF; border-radius: 30rpx 30rpx 30rpx 30rpx; z-index: 110; padding-bottom: 100rpx; .top { padding: 52rpx 38rpx 42rpx 36rpx; .ipt_box { margin-top: 22rpx; display: flex; flex-wrap: nowrap; align-items: center; .text { width: 350rpx; font-weight: 400; font-size: 32rpx; color: #3D3D3D; } .ipt { padding: 10rpx 18rpx; display: flex; align-items: center; justify-content: space-between; margin-left: 26rpx; width: 420rpx; height: 64rpx; border-radius: 0rpx 0rpx 0rpx 0rpx; border: 2rpx solid #979797; .input { width: 80%; } } } .tip { width: 100%; text-align: center; font-weight: 700; font-size: 32rpx; color: #3D3D3D; } .txt { margin-top: 32rpx; width: 100%; text-align: center; font-weight: 500; font-size: 32rpx; color: #3D3D3D; } } .bots { position: absolute; width: 610rpx; // border-top: 2rpx solid #D8D8D8; display: flex; flex-wrap: nowrap; // height: 100%; bottom: -20rpx; .bot_left { border-radius: 0rpx 0rpx 0rpx 30rpx; width: 50%; height: 86rpx; display: flex; align-items: center; justify-content: center; font-weight: 500; font-size: 32rpx; color: #3D3D3D; background: #EEEEEE; } .bot_right { border-radius: 0rpx 0rpx 30rpx 0rpx; width: 50%; height: 86rpx; background: #4C97E7; display: flex; align-items: center; justify-content: center; color: #FFFFFF; // border-left: 2rpx solid #D8D8D8; font-weight: 500; font-size: 32rpx; // color: #4C97E7; } } } .map { position: relative; width: 750rpx; height: 752rpx; .park { position: absolute; display: flex; align-items: center; justify-content: center; right: 30rpx; bottom: 40rpx; // background-color: #fff; border-radius: 50%; width: 82rpx; height: 82rpx; z-index: 10; .img { width: 82rpx; height: 82rpx; } } } .info_card { background: #FFFFFF; .info_tit { display: flex; flex-wrap: nowrap; padding: 22rpx 28rpx; font-weight: 600; font-size: 32rpx; color: #3D3D3D; .money { margin-left: auto; font-weight: 500; font-size: 32rpx; color: #4C97E7; } } .lines { width: 750rpx; height: 2rpx; border: 2rpx solid #ccc; } .cont { padding: 26rpx 28rpx; .info_li { display: flex; flex-wrap: nowrap; font-weight: 400; font-size: 28rpx; color: #808080; .text { width: 70%; white-space: nowrap; /* 禁止换行 */ overflow: hidden; /* 超出部分隐藏 */ text-overflow: ellipsis; } span { color: #3D3D3D; } line-height: 48rpx; .half_infoli { width: 50%; font-weight: 400; font-size: 28rpx; color: #808080; span { color: #3D3D3D; } } } .info_lis { width: 90%; display: inline-block; // flex-wrap: nowrap; font-weight: 400; font-size: 28rpx; color: #808080; white-space: nowrap; /* 禁止换行 */ overflow: hidden; /* 超出部分隐藏 */ text-overflow: ellipsis; .text { display: inline; // width: 70%; // white-space: nowrap; // /* 禁止换行 */ // overflow: hidden; // /* 超出部分隐藏 */ // text-overflow: ellipsis; } span { color: #3D3D3D; } line-height: 48rpx; .half_infoli { width: 50%; font-weight: 400; font-size: 28rpx; color: #808080; span { color: #3D3D3D; } } } } } .bot { position: fixed; bottom: 0; width: 750rpx; height: 184rpx; background: #FFFFFF; box-shadow: 0rpx 10rpx 64rpx 0rpx rgba(0, 0, 0, 0.08), 0rpx 10rpx 64rpx 0rpx rgba(0, 0, 0, 0.08); border-radius: 0rpx 0rpx 0rpx 0rpx; .btn { margin: 46rpx auto; display: flex; align-items: center; justify-content: center; width: 680rpx; height: 90rpx; background: #4C97E7; border-radius: 54rpx 54rpx 54rpx 54rpx; font-weight: 500; font-size: 40rpx; color: #FFFFFF; } } } </style>