699 lines
16 KiB
Vue
699 lines
16 KiB
Vue
<template>
|
||
<s-layout title="订单详情" class="set-userinfo-wrap">
|
||
<view class="order-detail-page">
|
||
<!-- 地图区域 -->
|
||
<view class="map-area">
|
||
<map class="map-native" :latitude="order?.pickupLat" :longitude="order?.pickupLng" show-location
|
||
enable-3D enable-zoom :scale="16"></map>
|
||
<cover-view class="map-overlay">
|
||
<cover-view class="eta">距离商家{{ distanceText }},预计{{ etaText }}到达</cover-view>
|
||
<cover-view class="nav-btn" @click="navigateToShop">导航到商家</cover-view>
|
||
</cover-view>
|
||
</view>
|
||
|
||
<!-- 地址块 -->
|
||
<scroll-view class="content" scroll-y="true">
|
||
<view class="address-block">
|
||
<view class="addr-row">
|
||
<view class="badge pickup">取</view>
|
||
<view class="addr-body">
|
||
<text class="addr-title">{{ order?.shopName || '店铺名称' }}</text>
|
||
<text class="addr-sub">{{ order?.pickupAddress }}</text>
|
||
</view>
|
||
<view class="nav-icon" @click="openMap(order?.pickupLat, order?.pickupLng, order?.pickupAddress)">导航</view>
|
||
</view>
|
||
|
||
<view class="addr-row">
|
||
<view class="badge deliver">送</view>
|
||
<view class="addr-body">
|
||
<text class="addr-title">{{ order?.deliveryAddress }}</text>
|
||
<text
|
||
class="addr-sub">收货人:{{ order?.receiverName }} {{ order?.receiverPhone ? ('尾号' + (order.receiverPhone + '').slice(-4)) : ''}}</text>
|
||
</view>
|
||
<view class="nav-icon" @click="openMap(order?.deliveryLat, order?.deliveryLng, order?.deliveryAddress)">导航
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 顾客备注 -->
|
||
<view class="note" v-if="order?.note">
|
||
<text>顾客:{{ order.note }}</text>
|
||
</view>
|
||
|
||
<!-- 商品清单 -->
|
||
<view class="goods-list">
|
||
<view class="goods-header">
|
||
<text>商品清单</text>
|
||
<text class="item-count">{{ totalCount }}件</text>
|
||
<text class="total-price">¥{{ totalPrice.toFixed(2) }}</text>
|
||
</view>
|
||
<view class="goods-item" v-for="(g, idx) in order?.items || []" :key="idx">
|
||
<view class="g-left">
|
||
<text class="g-name">{{ g.name }}{{ g.spec ? ('·' + g.spec) : '' }}</text>
|
||
<text class="g-qty">×{{ g.quantity }}</text>
|
||
</view>
|
||
<view class="g-right">¥{{ (g.price || 0).toFixed(2) }}</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 联系/记录等 -->
|
||
<view class="action-record">
|
||
<!-- <view class="call" @click="callPhone(order?.receiverPhone)">
|
||
<text>电话联系</text>
|
||
</view> -->
|
||
<view class="record" @click="toRecord">交接记录</view>
|
||
</view>
|
||
|
||
<!-- 占位底部高度,避免内容被底部按钮遮挡 -->
|
||
<view style="height:200rpx;"></view>
|
||
</scroll-view>
|
||
|
||
<!-- 底部操作 -->
|
||
<view class="fixed-actions">
|
||
<view class="left-actions">
|
||
<view class="icon-phone" @click="callPhone(order?.receiverPhone)"></view>
|
||
</view>
|
||
<view class="right-actions">
|
||
<view class="btn remind"
|
||
@click="sheep.$helper.toast('功能开发中ing')">催单</view>
|
||
<view class="btn remind" @click="openRemindPopup">电话联系</view>
|
||
<view class="btn confirm" v-if="order?.deliveryStatus == 2" @click="confirmArrive">确认到店</view>
|
||
<view class="btn confirm" v-if="order?.deliveryStatus == 3" @click="confirmPickup">确认取餐</view>
|
||
<view class="btn handover" v-if="order?.deliveryStatus == 4" @click="deliveryHandover">送达交接点</view>
|
||
<view class="btn handover" v-if="order?.deliveryStatus == 5" @click="openDeliveryPopup">确认送达顾客</view>
|
||
</view>
|
||
</view>
|
||
<!-- 催单弹框 -->
|
||
<up-popup v-model:show="showRemind" mode="bottom" :closeable="false" border-radius="12">
|
||
<view class="remind-popup">
|
||
<view class="remind-row" @click="callShopPhone">
|
||
<text class="remind-title">联系商家</text>
|
||
<view class="remind-btn" @click.stop="callShopPhone">拨打电话</view>
|
||
</view>
|
||
<view class="remind-row" @click="callCustomerPhone">
|
||
<text class="remind-title">联系顾客</text>
|
||
<view class="remind-btn" @click.stop="callCustomerPhone">拨打电话</view>
|
||
</view>
|
||
</view>
|
||
<template #bottom>
|
||
<view class="remind-cancel" @click="showRemind = false">取消</view>
|
||
</template>
|
||
</up-popup>
|
||
|
||
<!-- 确认送达顾客弹框 -->
|
||
<DeliveryPopup
|
||
:show="showDeliveryPopup"
|
||
:receiverPhone="order?.receiverPhone || ''"
|
||
@update:show="showDeliveryPopup = $event"
|
||
@submit="handleDeliveryConfirm"
|
||
@close="showDeliveryPopup = false"
|
||
/>
|
||
</view>
|
||
</s-layout>
|
||
</template>
|
||
|
||
<script setup>
|
||
import {
|
||
ref,
|
||
computed
|
||
} from 'vue';
|
||
import {
|
||
onLoad
|
||
} from '@dcloudio/uni-app';
|
||
import sheep from '@/sheep';
|
||
import DeliveryOrderApi from '@/sheep/api/member/deliveryOrder';
|
||
import DeliveryPopup from '@/pages/index/components/delivery-popup.vue';
|
||
|
||
const orderId = ref(null);
|
||
const order = ref(null);
|
||
const loading = ref(false);
|
||
const mapAvailable = ref(true); // 如果需要使用 map 组件,置为 true
|
||
|
||
// 入口:从页面参数取 orderId,然后加载数据
|
||
onLoad((options = {}) => {
|
||
orderId.value = options.id || options.orderId || null;
|
||
fetchOrder();
|
||
});
|
||
|
||
async function fetchOrder() {
|
||
loading.value = true;
|
||
try {
|
||
// 使用 DeliveryOrderApi 获取配送单详情
|
||
const res = await DeliveryOrderApi.getDetail(orderId.value);
|
||
// 根据封装不同,这里兼容 res.data 或 res
|
||
const resData = (res && res.data) ? res.data : res;
|
||
console.log("res数据:", res);
|
||
|
||
if (resData) {
|
||
// 字段映射:将接口返回字段映射到页面使用字段
|
||
order.value = {
|
||
...resData,
|
||
// 店铺坐标 (取货点)
|
||
pickupLat: resData.shopLatitude,
|
||
pickupLng: resData.shopLongitude,
|
||
pickupAddress: resData.shopAddress,
|
||
// 收货地址
|
||
deliveryLat: resData.receiverLatitude,
|
||
deliveryLng: resData.receiverLongitude,
|
||
deliveryAddress: resData.receiverAddress,
|
||
// 商品列表
|
||
items: resData.orderItemDataList?.map(item => ({
|
||
name: item.productName,
|
||
quantity: item.quantity,
|
||
price: parseFloat(item.unitPrice) || 0
|
||
})) || [],
|
||
// 备注
|
||
note: resData.orderRemark,
|
||
deliveryStatus: resData.deliveryStatus
|
||
};
|
||
}
|
||
console.log("order.value数据:", order.value);
|
||
} catch (e) {
|
||
console.error('fetchOrder error', e);
|
||
// 友好提示
|
||
sheep.$helper && sheep.$helper.toast && sheep.$helper.toast('获取订单失败,请稍后重试');
|
||
} finally {
|
||
loading.value = false;
|
||
}
|
||
}
|
||
|
||
const totalCount = computed(() => {
|
||
if (!order.value || !order.value.items) return 0;
|
||
return order.value.items.reduce((s, it) => s + (it.quantity || 0), 0);
|
||
});
|
||
const totalPrice = computed(() => {
|
||
if (!order.value || !order.value.items) return 0;
|
||
return order.value.items.reduce((s, it) => s + ((it.price || 0) * (it.quantity || 0)), 0);
|
||
});
|
||
|
||
// 显示距离与预计时间(示例,真实项目可使用服务端或高德/百度 SDK 计算)
|
||
const distanceText = computed(() => {
|
||
// 此处为示例固定值,后续接入定位/路程计算替换
|
||
return order.value ? '873m' : '--';
|
||
});
|
||
const etaText = computed(() => {
|
||
return order.value ? '预计十分钟到达' : '--';
|
||
});
|
||
|
||
function goBack() {
|
||
uni.navigateBack();
|
||
}
|
||
|
||
function openMap(lat, lng, name) {
|
||
if (!lat || !lng) {
|
||
sheep.$helper && sheep.$helper.toast && sheep.$helper.toast('无法获取坐标');
|
||
return;
|
||
}
|
||
uni.openLocation({
|
||
latitude: Number(lat),
|
||
longitude: Number(lng),
|
||
name: name || '',
|
||
scale: 18
|
||
});
|
||
}
|
||
|
||
function navigateToShop() {
|
||
openMap(order.value?.pickupLat, order.value?.pickupLng, order.value?.pickupAddress);
|
||
}
|
||
|
||
function callPhone(phone) {
|
||
if (!phone) {
|
||
sheep.$helper && sheep.$helper.toast && sheep.$helper.toast('未找到联系电话');
|
||
return;
|
||
}
|
||
uni.makePhoneCall({
|
||
phoneNumber: phone
|
||
});
|
||
}
|
||
|
||
// 催单弹框控制
|
||
const showRemind = ref(false);
|
||
|
||
function openRemindPopup() {
|
||
showRemind.value = true;
|
||
}
|
||
|
||
function closeRemindPopup() {
|
||
showRemind.value = false;
|
||
}
|
||
|
||
// 确认送达顾客弹框控制
|
||
const showDeliveryPopup = ref(false);
|
||
|
||
function openDeliveryPopup() {
|
||
showDeliveryPopup.value = true;
|
||
}
|
||
|
||
// 确认送达顾客
|
||
async function handleDeliveryConfirm(imageUrl) {
|
||
console.log('送达照片URL:', imageUrl);
|
||
|
||
if (!orderId.value) {
|
||
sheep.$helper.toast('订单信息异常');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const res = await DeliveryOrderApi.riderConfirmDelivery({
|
||
deliveryOrderId: orderId.value,
|
||
imageUrl: imageUrl
|
||
});
|
||
|
||
if (res.code === 0 && res.data === true) {
|
||
sheep.$helper.toast('已提交送达照片');
|
||
showDeliveryPopup.value = false;
|
||
// 刷新订单状态
|
||
fetchOrder();
|
||
} else {
|
||
sheep.$helper.toast(res.msg || '提交失败');
|
||
}
|
||
} catch (error) {
|
||
console.error('确认送达异常:', error);
|
||
sheep.$helper.toast('提交失败,请重试');
|
||
}
|
||
}
|
||
|
||
function callShopPhone() {
|
||
// 商家电话优先使用 order.shopPhone,否则尝试 fallback
|
||
const phone = order.value?.shopPhone || order.value?.shopPhoneNumber || order.value?.receiverPhone || '';
|
||
if (!phone) {
|
||
sheep.$helper && sheep.$helper.toast && sheep.$helper.toast('未找到商家电话');
|
||
return;
|
||
}
|
||
callPhone(phone);
|
||
closeRemindPopup();
|
||
}
|
||
|
||
function callCustomerPhone() {
|
||
const phone = order.value?.receiverPhone || '';
|
||
if (!phone) {
|
||
sheep.$helper && sheep.$helper.toast && sheep.$helper.toast('未找到顾客电话');
|
||
return;
|
||
}
|
||
callPhone(phone);
|
||
closeRemindPopup();
|
||
}
|
||
|
||
// 确认到店
|
||
async function confirmArrive() {
|
||
if (!order.value || !orderId.value) return;
|
||
|
||
try {
|
||
uni.showLoading({ title: '提交中...' });
|
||
const res = await DeliveryOrderApi.riderConfirmArrival(orderId.value);
|
||
uni.hideLoading();
|
||
|
||
if (res.code === 0 && res.data === true) {
|
||
sheep.$helper.toast('已确认到店');
|
||
// 刷新订单状态
|
||
fetchOrder();
|
||
} else {
|
||
sheep.$helper.toast(res.msg || '确认到店失败');
|
||
}
|
||
} catch (e) {
|
||
uni.hideLoading();
|
||
console.error('confirmArrive error', e);
|
||
sheep.$helper.toast('确认失败,请重试');
|
||
}
|
||
}
|
||
|
||
// 确认取餐
|
||
async function confirmPickup() {
|
||
if (!order.value || !orderId.value) return;
|
||
|
||
try {
|
||
uni.showLoading({ title: '提交中...' });
|
||
const res = await DeliveryOrderApi.riderConfirmPickup(orderId.value);
|
||
uni.hideLoading();
|
||
|
||
if (res.code === 0 && res.data === true) {
|
||
sheep.$helper.toast('已确认取餐');
|
||
// 刷新订单状态
|
||
fetchOrder();
|
||
} else {
|
||
sheep.$helper.toast(res.msg || '确认取餐失败');
|
||
}
|
||
} catch (e) {
|
||
uni.hideLoading();
|
||
console.error('confirmPickup error', e);
|
||
sheep.$helper.toast('确认失败,请重试');
|
||
}
|
||
}
|
||
|
||
// 送达交接点
|
||
async function deliveryHandover() {
|
||
if (!order.value || !orderId.value) return;
|
||
|
||
try {
|
||
uni.showLoading({ title: '提交中...' });
|
||
await DeliveryOrderApi.riderDeliveryHandover(orderId.value);
|
||
uni.hideLoading();
|
||
sheep.$helper && sheep.$helper.toast && sheep.$helper.toast('已送达交接点');
|
||
// 刷新订单状态
|
||
fetchOrder();
|
||
} catch (e) {
|
||
console.error('deliveryHandover error', e);
|
||
uni.hideLoading();
|
||
sheep.$helper && sheep.$helper.toast && sheep.$helper.toast('提交失败,请重试');
|
||
}
|
||
}
|
||
|
||
//跳转交接记录
|
||
function toRecord(id) {
|
||
uni.navigateTo({
|
||
url: `/pages/order/handoverRecord?orderId=${orderId.value}`
|
||
})
|
||
}
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.order-detail-page {
|
||
// background: #fff;
|
||
background: #f7f7f7;
|
||
min-height: 100vh;
|
||
position: relative;
|
||
overflow-x: hidden;
|
||
box-sizing: border-box;
|
||
-webkit-overflow-scrolling: touch;
|
||
}
|
||
|
||
.header {
|
||
height: 88rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 0 20rpx;
|
||
border-bottom: 1rpx solid #eee;
|
||
}
|
||
|
||
.back {
|
||
width: 44rpx;
|
||
font-size: 40rpx;
|
||
color: #333;
|
||
}
|
||
|
||
.title {
|
||
flex: 1;
|
||
text-align: center;
|
||
font-size: 32rpx;
|
||
font-weight: 700;
|
||
color: #333;
|
||
margin-right: 44rpx;
|
||
}
|
||
|
||
.map-area {
|
||
height: 560rpx;
|
||
background: #f3f3f3;
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.map-image {
|
||
width: 100%;
|
||
height: 100%;
|
||
display: block;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.map-native {
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
.map-overlay {
|
||
position: absolute;
|
||
left: 20rpx;
|
||
right: 20rpx;
|
||
bottom: 45rpx;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
flex-direction: row;
|
||
}
|
||
|
||
.eta {
|
||
background: rgba(255, 255, 255, 0.95);
|
||
padding: 0rpx 14rpx;
|
||
border-radius: 20rpx;
|
||
font-size: 24rpx;
|
||
color: #333;
|
||
text-align: center;
|
||
// line-height: 32rpx;
|
||
}
|
||
|
||
.nav-btn {
|
||
background: #fff;
|
||
padding: 0rpx 20rpx;
|
||
border-radius: 20rpx;
|
||
color: #1e9fff;
|
||
border: 1rpx solid #dbeeff;
|
||
font-size: 24rpx;
|
||
text-align: center;
|
||
// line-height: 32rpx;
|
||
}
|
||
|
||
.content {
|
||
padding: 20rpx;
|
||
padding-left: 20rpx;
|
||
padding-right: 20rpx;
|
||
background: #f7f7f7;
|
||
min-height: 200rpx;
|
||
box-sizing: border-box;
|
||
overflow-x: hidden;
|
||
}
|
||
|
||
.address-block {
|
||
background: #fff;
|
||
padding: 16rpx;
|
||
border-radius: 12rpx;
|
||
box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.04);
|
||
}
|
||
|
||
.addr-row {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
padding: 12rpx 0;
|
||
border-bottom: 1rpx solid #f0f0f0;
|
||
}
|
||
|
||
.addr-row:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.badge {
|
||
width: 46rpx;
|
||
height: 46rpx;
|
||
border-radius: 23rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: #fff;
|
||
font-weight: 700;
|
||
font-size: 20rpx;
|
||
margin-right: 12rpx;
|
||
}
|
||
|
||
.badge.pickup {
|
||
background: #87d6ff;
|
||
}
|
||
|
||
.badge.deliver {
|
||
background: #ffd591;
|
||
color: #333;
|
||
}
|
||
|
||
.addr-body {
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.addr-title {
|
||
display: block;
|
||
font-size: 28rpx;
|
||
font-weight: 600;
|
||
word-break: break-word;
|
||
}
|
||
|
||
.addr-sub {
|
||
display: block;
|
||
font-size: 22rpx;
|
||
color: #888;
|
||
margin-top: 6rpx;
|
||
}
|
||
|
||
.nav-icon {
|
||
color: #1e9fff;
|
||
padding: 6rpx 8rpx;
|
||
}
|
||
|
||
.note {
|
||
margin-top: 12rpx;
|
||
background: #fff;
|
||
padding: 12rpx;
|
||
border-radius: 8rpx;
|
||
color: #666;
|
||
}
|
||
|
||
.goods-list {
|
||
margin-top: 14rpx;
|
||
}
|
||
|
||
.goods-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 12rpx;
|
||
background: #fff;
|
||
border-radius: 8rpx;
|
||
font-weight: 700;
|
||
color: #333;
|
||
}
|
||
|
||
.goods-item {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 12rpx;
|
||
background: #fff;
|
||
margin-top: 8rpx;
|
||
border-radius: 8rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.g-left {
|
||
display: flex;
|
||
gap: 8rpx;
|
||
align-items: center;
|
||
}
|
||
|
||
.g-left {
|
||
min-width: 0;
|
||
}
|
||
|
||
.g-name {
|
||
font-size: 26rpx;
|
||
color: #333;
|
||
max-width: 62%;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.g-qty {
|
||
color: #888;
|
||
margin-left: 8rpx;
|
||
}
|
||
|
||
.g-right {
|
||
color: #333;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.action-record {
|
||
display: flex;
|
||
gap: 12rpx;
|
||
margin-top: 14rpx;
|
||
}
|
||
|
||
.call {
|
||
flex: 1;
|
||
background: #fff;
|
||
padding: 12rpx;
|
||
border-radius: 8rpx;
|
||
text-align: center;
|
||
border: 1rpx solid #ddd;
|
||
}
|
||
|
||
.record {
|
||
flex: 2;
|
||
background: #fff;
|
||
padding: 12rpx;
|
||
border-radius: 8rpx;
|
||
text-align: center;
|
||
border: 1rpx solid #ddd;
|
||
}
|
||
|
||
.fixed-actions {
|
||
position: fixed;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
height: 140rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 20rpx;
|
||
gap: 12rpx;
|
||
background: rgba(255, 255, 255, 0.98);
|
||
box-shadow: 0 -2rpx 8rpx rgba(0, 0, 0, 0.04);
|
||
}
|
||
|
||
.left-actions {
|
||
width: 72rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.right-actions {
|
||
flex: 1;
|
||
display: flex;
|
||
gap: 12rpx;
|
||
justify-content: flex-end;
|
||
align-items: center;
|
||
}
|
||
|
||
.btn {
|
||
padding: 16rpx 18rpx;
|
||
border-radius: 12rpx;
|
||
color: #fff;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.btn.remind {
|
||
background: #f39c12;
|
||
}
|
||
|
||
.btn.confirm {
|
||
background: #1e9fff;
|
||
}
|
||
|
||
.btn.handover {
|
||
background: #2ecc71;
|
||
}
|
||
|
||
.remind-popup {
|
||
padding: 20rpx 0;
|
||
}
|
||
|
||
.remind-row {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 20rpx;
|
||
background: #f6f6f6;
|
||
margin: 10rpx 16rpx;
|
||
border-radius: 8rpx;
|
||
}
|
||
|
||
.remind-title {
|
||
font-size: 28rpx;
|
||
font-weight: 700;
|
||
color: #333;
|
||
}
|
||
|
||
.remind-btn {
|
||
background: #1e9fff;
|
||
color: #fff;
|
||
padding: 10rpx 18rpx;
|
||
border-radius: 8rpx;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.remind-cancel {
|
||
text-align: center;
|
||
padding: 18rpx 0 70rpx;
|
||
color: #666;
|
||
font-size: 26rpx;
|
||
background: #fff;
|
||
margin-top: 10rpx;
|
||
}
|
||
</style> |