|
|
@@ -1,5 +1,6 @@
|
|
|
<template>
|
|
|
<view class="order-detail-page">
|
|
|
+ <nav-bar title="订单详情"></nav-bar>
|
|
|
<!-- 订单号与状态 -->
|
|
|
<view class="order-header">
|
|
|
<view class="order-id-row">
|
|
|
@@ -83,7 +84,7 @@
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
- <!-- 订单基础信息 -->
|
|
|
+ <!-- 任务详情扩展板块 -->
|
|
|
<view class="tab-content" v-if="activeTab === 'base'">
|
|
|
<view class="base-info-grid">
|
|
|
<view class="bi-item" v-for="item in baseInfoList" :key="item.label">
|
|
|
@@ -92,40 +93,42 @@
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
- <template v-if="activeService === 'transport'">
|
|
|
+ <!-- 接送任务详情 -->
|
|
|
+ <block v-if="order.type === 'transport'">
|
|
|
<text class="sub-title">接送任务详情</text>
|
|
|
- <view class="route-block">
|
|
|
- <view class="route-header">
|
|
|
- <text class="route-tag pick">宠物接送</text>
|
|
|
- <text class="route-tag arrow">接</text>
|
|
|
- <text class="route-time">{{ order.pickTime }}</text>
|
|
|
+ <view class="task-card transport-card">
|
|
|
+ <view class="task-header">
|
|
|
+ <text class="type-tag" :class="getTransportClass(order.subOrderType)">
|
|
|
+ {{ getTransportLabel(order.subOrderType) }}
|
|
|
+ </text>
|
|
|
+ <text class="task-time">{{ order.serviceTime }}</text>
|
|
|
</view>
|
|
|
- <view class="route-addr">
|
|
|
- <uni-icons type="location" size="14" color="#999"></uni-icons>
|
|
|
- <text>{{ order.pickAddress }}</text>
|
|
|
+ <view class="task-body">
|
|
|
+ <view class="task-row">
|
|
|
+ <text class="task-label">起点</text>
|
|
|
+ <text class="task-value">{{ order.fromAddress || '获取中...' }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="task-row">
|
|
|
+ <text class="task-label">终点</text>
|
|
|
+ <text class="task-value">{{ order.toAddress || '获取中...' }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="task-row contact-row">
|
|
|
+ <text class="task-value">{{ order.userName }} — {{ order.userPhone }}</text>
|
|
|
+ </view>
|
|
|
</view>
|
|
|
</view>
|
|
|
- <view class="route-block">
|
|
|
- <view class="route-header">
|
|
|
- <text class="route-tag send">宠物接送</text>
|
|
|
- <text class="route-tag arrow send-arrow">送</text>
|
|
|
- <text class="route-time">{{ order.sendTime }}</text>
|
|
|
- </view>
|
|
|
- <view class="route-addr">
|
|
|
- <uni-icons type="location" size="14" color="#999"></uni-icons>
|
|
|
- <text>{{ order.sendAddress }}</text>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
- </template>
|
|
|
- <template v-else>
|
|
|
- <text class="sub-title">上门服务地址</text>
|
|
|
- <view class="route-block">
|
|
|
- <view class="route-addr">
|
|
|
- <uni-icons type="location" size="14" color="#999"></uni-icons>
|
|
|
- <text>{{ order.address }}</text>
|
|
|
+ </block>
|
|
|
+
|
|
|
+ <!-- 上门服务执行要求 -->
|
|
|
+ <block v-if="['feeding', 'washing'].includes(order.type)">
|
|
|
+ <text class="sub-title">服务执行要求</text>
|
|
|
+ <view class="task-card req-card">
|
|
|
+ <view class="req-item">
|
|
|
+ <text class="req-label">服务地址</text>
|
|
|
+ <text class="req-value">{{ order.address }}</text>
|
|
|
</view>
|
|
|
</view>
|
|
|
- </template>
|
|
|
+ </block>
|
|
|
</view>
|
|
|
|
|
|
<!-- 指派履约者 -->
|
|
|
@@ -189,11 +192,19 @@
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
-import { ref, reactive, computed } from 'vue'
|
|
|
+import { ref, reactive, computed, watch } from 'vue'
|
|
|
import { onLoad } from '@dcloudio/uni-app'
|
|
|
+import navBar from '@/components/nav-bar/index.vue'
|
|
|
+import { getSubOrderInfo } from '@/api/order/subOrder'
|
|
|
+import { getPet } from '@/api/archieves/pet'
|
|
|
+import { getCustomer } from '@/api/archieves/customer'
|
|
|
+import { listSubOrderLog } from '@/api/order/subOrderLog'
|
|
|
+import { listComplaintByOrder } from '@/api/fulfiller/complaint'
|
|
|
|
|
|
const activeTab = ref('base')
|
|
|
const activeService = ref('transport')
|
|
|
+const orderId = ref('')
|
|
|
+const loading = ref(false)
|
|
|
|
|
|
const tabList = [
|
|
|
{ title: '基础信息', name: 'base' },
|
|
|
@@ -207,38 +218,178 @@ const currentServiceName = computed(() => {
|
|
|
return map[activeService.value]
|
|
|
})
|
|
|
|
|
|
-onLoad((options) => {
|
|
|
- if (options.service) activeService.value = options.service
|
|
|
- if (options.status) {
|
|
|
- const statusTextMap = { wait_dispatch: '待派单', wait_accept: '待接单', serving: '服务中', done: '已完成', cancel: '已取消' }
|
|
|
- order.statusKey = options.status
|
|
|
- order.statusText = statusTextMap[options.status] || '服务中'
|
|
|
- }
|
|
|
- if (options.cancelTime) order.cancelTime = decodeURIComponent(options.cancelTime)
|
|
|
-})
|
|
|
-
|
|
|
const order = reactive({
|
|
|
- id: 'ORD202402048803',
|
|
|
+ id: '',
|
|
|
+ code: '',
|
|
|
statusKey: 'serving',
|
|
|
statusText: '服务中',
|
|
|
- petName: 'Cookie',
|
|
|
- petBreed: '柯基',
|
|
|
- petAge: 2,
|
|
|
- petWeight: 15,
|
|
|
- userName: '王先生',
|
|
|
- userPhone: '13612345678',
|
|
|
- address: '北京市朝阳区 某小区5号楼2单元101',
|
|
|
- shopName: '爱宠生活馆 (中关村店)',
|
|
|
- createTime: '2024-02-04 12:00',
|
|
|
- bookTime: '2024-02-04 18:00',
|
|
|
- packageName: '新春特惠接送套餐',
|
|
|
- remark: '暂无备注',
|
|
|
- assigneeName: '张小美',
|
|
|
+ status: 2,
|
|
|
+ petName: '',
|
|
|
+ petBreed: '',
|
|
|
+ petAge: '',
|
|
|
+ petWeight: '',
|
|
|
+ petGender: '',
|
|
|
+ petVaccine: '',
|
|
|
+ petCharacter: '',
|
|
|
+ petHealth: '',
|
|
|
+ userName: '',
|
|
|
+ userPhone: '',
|
|
|
+ address: '',
|
|
|
+ shopName: '',
|
|
|
+ createTime: '',
|
|
|
+ bookTime: '',
|
|
|
+ packageName: '',
|
|
|
+ remark: '',
|
|
|
+ assigneeName: '',
|
|
|
cancelTime: '',
|
|
|
- pickAddress: '北京市朝阳区三里屯SOHO 6号楼',
|
|
|
- pickTime: '2024-02-04 09:30',
|
|
|
- sendAddress: '北京市朝阳区某小区5号楼2单元101',
|
|
|
- sendTime: '2024-02-04 18:00'
|
|
|
+ pickAddress: '',
|
|
|
+ pickTime: '',
|
|
|
+ sendAddress: '',
|
|
|
+ sendTime: '',
|
|
|
+ fromAddress: '',
|
|
|
+ toAddress: '',
|
|
|
+ type: 'transport', // 对应后端 transport/feeding/washing
|
|
|
+ subOrderType: 0, // 接送子类型
|
|
|
+ service: '',
|
|
|
+ pet: '',
|
|
|
+ customer: '',
|
|
|
+ fulfiller: '',
|
|
|
+ fulfillerName: ''
|
|
|
+})
|
|
|
+
|
|
|
+const orderLogsData = ref([])
|
|
|
+const fulfillerLogsData = ref([])
|
|
|
+const complaintList = ref([])
|
|
|
+
|
|
|
+const loadOrderDetail = async (id) => {
|
|
|
+ if (!id) return
|
|
|
+ loading.value = true
|
|
|
+ try {
|
|
|
+ const res = await getSubOrderInfo(id)
|
|
|
+ console.log('订单详情返回:', res)
|
|
|
+ if (res) {
|
|
|
+ Object.assign(order, res)
|
|
|
+ order.code = res.code || res.id
|
|
|
+ order.status = res.status
|
|
|
+ order.statusKey = getStatusKey(res.status)
|
|
|
+ order.statusText = getStatusName(res.status)
|
|
|
+ order.bookTime = res.serviceTime || ''
|
|
|
+ order.shopName = res.storeName || ''
|
|
|
+ order.userName = res.customerName || ''
|
|
|
+ order.userPhone = res.contactPhoneNumber || ''
|
|
|
+ order.assigneeName = res.fulfillerName || ''
|
|
|
+ order.remark = res.remark || '暂无备注'
|
|
|
+ order.fromAddress = res.fromAddress || ''
|
|
|
+ order.toAddress = res.toAddress || ''
|
|
|
+ order.type = res.type || 'transport'
|
|
|
+ order.subOrderType = res.subOrderType
|
|
|
+
|
|
|
+ if (res.pet) await loadPetInfo(res.pet)
|
|
|
+ if (res.customer) await loadCustomerInfo(res.customer)
|
|
|
+ await loadOrderLogs(id)
|
|
|
+ await loadComplaints(id)
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载订单详情失败:', error)
|
|
|
+ uni.showToast({ title: '加载失败', icon: 'none' })
|
|
|
+ } finally {
|
|
|
+ loading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const loadPetInfo = async (petId) => {
|
|
|
+ try {
|
|
|
+ const res = await getPet(petId)
|
|
|
+ if (res) {
|
|
|
+ order.petName = res.name || order.petName
|
|
|
+ order.petBreed = res.breed || order.petBreed
|
|
|
+ order.petAge = res.age ? `${res.age}岁` : order.petAge
|
|
|
+ order.petWeight = res.weight ? `${res.weight}kg` : order.petWeight
|
|
|
+ order.petGender = res.gender || order.petGender
|
|
|
+ order.petVaccine = res.vaccineStatus || '未知'
|
|
|
+ order.petCharacter = res.personality || '温顺'
|
|
|
+ order.petHealth = res.healthStatus || '健康'
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载宠物信息失败:', error)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const loadCustomerInfo = async (customerId) => {
|
|
|
+ try {
|
|
|
+ const res = await getCustomer(customerId)
|
|
|
+ if (res) {
|
|
|
+ order.userName = res.name || order.userName
|
|
|
+ order.userPhone = res.phone || order.userPhone
|
|
|
+ order.address = res.address || order.address
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载客户信息失败:', error)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const loadOrderLogs = async (id) => {
|
|
|
+ try {
|
|
|
+ const res = await listSubOrderLog({ orderId: id })
|
|
|
+ const list = res || []
|
|
|
+ orderLogsData.value = list.filter(i => Number(i?.logType) === 0)
|
|
|
+ fulfillerLogsData.value = list.filter(i => Number(i?.logType) === 1)
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载订单日志失败:', error)
|
|
|
+ orderLogsData.value = []
|
|
|
+ fulfillerLogsData.value = []
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const loadComplaints = async (id) => {
|
|
|
+ try {
|
|
|
+ const res = await listComplaintByOrder(id)
|
|
|
+ complaintList.value = res || []
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载投诉记录失败:', error)
|
|
|
+ complaintList.value = []
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const getStatusKey = (status) => {
|
|
|
+ const map = { 0: 'wait_dispatch', 1: 'wait_accept', 2: 'serving', 3: 'confirming', 4: 'done', 5: 'cancel' }
|
|
|
+ return map[status] || 'serving'
|
|
|
+}
|
|
|
+
|
|
|
+const getStatusName = (status) => {
|
|
|
+ const map = { 0: '待派单', 1: '待接单', 2: '服务中', 3: '待商家确认', 4: '已完成', 5: '已取消' }
|
|
|
+ return map[status] || '未知'
|
|
|
+}
|
|
|
+
|
|
|
+const getTransportLabel = (t) => {
|
|
|
+ if (t === 0 || t === '0') return '接'
|
|
|
+ if (t === 1 || t === '1') return '送'
|
|
|
+ if (t === 2 || t === '2') return '单程接'
|
|
|
+ if (t === 3 || t === '3') return '单程送'
|
|
|
+ return '接送'
|
|
|
+}
|
|
|
+
|
|
|
+const getTransportClass = (t) => {
|
|
|
+ if (t === 0 || t === '0' || t === 2 || t === '2') return 'tag-blue'
|
|
|
+ return 'tag-orange'
|
|
|
+}
|
|
|
+
|
|
|
+const getTypeName = (type) => {
|
|
|
+ const map = { transport: '宠物接送', feeding: '上门喂遛', washing: '上门洗护' }
|
|
|
+ return map[type] || '未知服务'
|
|
|
+}
|
|
|
+
|
|
|
+onLoad((options) => {
|
|
|
+ // 防御性校验:id 必须是有效值(非空、非字符串 "undefined")
|
|
|
+ if (options.id && options.id !== 'undefined') {
|
|
|
+ orderId.value = options.id
|
|
|
+ console.log('订单详情页:接收到的订单ID =', options.id)
|
|
|
+ loadOrderDetail(options.id)
|
|
|
+ } else {
|
|
|
+ console.error('订单详情页:缺少有效的订单ID,options =', options)
|
|
|
+ uni.showToast({ title: '订单ID无效', icon: 'none' })
|
|
|
+ }
|
|
|
+ if (options.service) activeService.value = options.service
|
|
|
})
|
|
|
|
|
|
const progressSteps = computed(() => {
|
|
|
@@ -652,13 +803,17 @@ const onCancelOrder = () => {
|
|
|
background: #f8f8f8;
|
|
|
border-radius: 16rpx;
|
|
|
padding: 16rpx 20rpx;
|
|
|
+ box-sizing: border-box;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ justify-content: center;
|
|
|
}
|
|
|
|
|
|
.bi-label {
|
|
|
display: block;
|
|
|
font-size: 20rpx;
|
|
|
color: #aaa;
|
|
|
- margin-bottom: 6rpx;
|
|
|
+ margin-bottom: 8rpx;
|
|
|
}
|
|
|
|
|
|
.bi-val {
|
|
|
@@ -666,6 +821,8 @@ const onCancelOrder = () => {
|
|
|
font-size: 24rpx;
|
|
|
color: #333;
|
|
|
font-weight: 500;
|
|
|
+ word-break: break-all;
|
|
|
+ line-height: 1.4;
|
|
|
}
|
|
|
|
|
|
.bi-val.highlight {
|
|
|
@@ -674,65 +831,109 @@ const onCancelOrder = () => {
|
|
|
|
|
|
.sub-title {
|
|
|
display: block;
|
|
|
- font-size: 26rpx;
|
|
|
- font-weight: 700;
|
|
|
+ font-size: 28rpx;
|
|
|
+ font-weight: bold;
|
|
|
color: #333;
|
|
|
- margin-bottom: 20rpx;
|
|
|
- padding-left: 12rpx;
|
|
|
- border-left: 6rpx solid #ff9500;
|
|
|
+ margin: 12rpx 0 24rpx;
|
|
|
}
|
|
|
|
|
|
-.route-block {
|
|
|
- background: #f9f9f9;
|
|
|
- border-radius: 20rpx;
|
|
|
- padding: 20rpx 24rpx;
|
|
|
+/* 任务卡片样式 */
|
|
|
+.task-card {
|
|
|
+ padding: 24rpx;
|
|
|
+ background: #fff;
|
|
|
+ border: 1rpx solid #f0f0f0;
|
|
|
+ border-radius: 12rpx;
|
|
|
margin-bottom: 20rpx;
|
|
|
+ box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.02);
|
|
|
}
|
|
|
|
|
|
-.route-header {
|
|
|
+.task-header {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
- gap: 12rpx;
|
|
|
- margin-bottom: 16rpx;
|
|
|
+ gap: 16rpx;
|
|
|
+ margin-bottom: 20rpx;
|
|
|
}
|
|
|
|
|
|
-.route-tag {
|
|
|
+.type-tag {
|
|
|
font-size: 22rpx;
|
|
|
padding: 4rpx 12rpx;
|
|
|
- border-radius: 8rpx;
|
|
|
+ border-radius: 6rpx;
|
|
|
+ border: 1rpx solid;
|
|
|
}
|
|
|
|
|
|
-.route-tag.pick {
|
|
|
- background: #e3f2fd;
|
|
|
- color: #2196f3;
|
|
|
+.tag-blue {
|
|
|
+ color: #007aff;
|
|
|
+ background: #eef6ff;
|
|
|
+ border-color: #007aff;
|
|
|
}
|
|
|
|
|
|
-.route-tag.send {
|
|
|
- background: #e8f5e9;
|
|
|
- color: #4caf50;
|
|
|
+.tag-orange {
|
|
|
+ color: #ff9500;
|
|
|
+ background: #fff8f0;
|
|
|
+ border-color: #ff9500;
|
|
|
}
|
|
|
|
|
|
-.route-tag.arrow {
|
|
|
- background: #2196f3;
|
|
|
- color: #fff;
|
|
|
+.task-time {
|
|
|
+ font-size: 26rpx;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #f56c6c;
|
|
|
}
|
|
|
|
|
|
-.route-tag.send-arrow {
|
|
|
- background: #4caf50;
|
|
|
+.task-body {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 12rpx;
|
|
|
}
|
|
|
|
|
|
-.route-time {
|
|
|
- font-size: 22rpx;
|
|
|
- color: #ff9500;
|
|
|
- margin-left: auto;
|
|
|
+.task-row {
|
|
|
+ display: flex;
|
|
|
+ align-items: flex-start;
|
|
|
+ gap: 20rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.task-label {
|
|
|
+ width: 60rpx;
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: #999;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.task-value {
|
|
|
+ font-size: 26rpx;
|
|
|
+ color: #333;
|
|
|
+ line-height: 1.4;
|
|
|
+}
|
|
|
+
|
|
|
+.contact-row {
|
|
|
+ margin-top: 8rpx;
|
|
|
+ padding-top: 8rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.contact-row .task-value {
|
|
|
+ color: #999;
|
|
|
+}
|
|
|
+
|
|
|
+/* 执行要求卡片 */
|
|
|
+.req-card {
|
|
|
+ background: #f9f9f9;
|
|
|
+ border: none;
|
|
|
}
|
|
|
|
|
|
-.route-addr {
|
|
|
+.req-item {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
- gap: 8rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.req-label {
|
|
|
+ width: 140rpx;
|
|
|
font-size: 24rpx;
|
|
|
- color: #555;
|
|
|
+ color: #333;
|
|
|
+}
|
|
|
+
|
|
|
+.req-value {
|
|
|
+ flex: 1;
|
|
|
+ font-size: 26rpx;
|
|
|
+ color: #333;
|
|
|
}
|
|
|
|
|
|
.assignee-card {
|