|
|
@@ -21,7 +21,7 @@
|
|
|
<!-- </el-button>-->
|
|
|
<!-- </template>-->
|
|
|
|
|
|
- <template v-if="order.status === 0">
|
|
|
+ <template v-if="[0, 1].includes(order.status)">
|
|
|
<el-button v-hasPermi="['order:management:cancel']" type="danger" plain icon="CircleClose"
|
|
|
@click="emit('cancel', order)">取消订单</el-button>
|
|
|
</template>
|
|
|
@@ -36,13 +36,13 @@
|
|
|
@click="emit('care-summary', order)">护理小结</el-button>
|
|
|
</template>
|
|
|
|
|
|
- <el-dropdown trigger="click" @command="(cmd) => emit('command', cmd, order)"
|
|
|
- style="margin-left: 12px;">
|
|
|
+ <el-dropdown v-if="![7].includes(order.status)" trigger="click"
|
|
|
+ @command="(cmd) => emit('command', cmd, order)" style="margin-left: 12px;">
|
|
|
<el-button icon="More">更多操作</el-button>
|
|
|
<template #dropdown>
|
|
|
<el-dropdown-menu>
|
|
|
|
|
|
- <el-dropdown-item command="delete" v-if="[5, 4].includes(order.status)" divided
|
|
|
+ <el-dropdown-item command="delete" v-if="[4, 5, 6].includes(order.status)" divided
|
|
|
icon="Delete" style="color: #f56c6c;">删除订单</el-dropdown-item>
|
|
|
</el-dropdown-menu>
|
|
|
</template>
|
|
|
@@ -134,17 +134,20 @@
|
|
|
|
|
|
<el-descriptions-item label="归属门店">{{ order.merchantName || '-' }}
|
|
|
({{ Number(order.platformId) === 1 ? '门店下单' : '平台代下单' }})</el-descriptions-item>
|
|
|
- <el-descriptions-item label="宠主信息">{{ order.userName || '-' }} / {{ order.contactPhone || '-'
|
|
|
- }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="宠主信息">{{ order.userName || '-' }} / {{
|
|
|
+ order.contactPhone || '-'
|
|
|
+ }}</el-descriptions-item>
|
|
|
<el-descriptions-item label="订单佣金" label-class-name="money-label">
|
|
|
- <span style="color:#f56c6c; font-weight:bold;">¥ {{ order.orderCommission ? (order.orderCommission / 100).toFixed(2) : '0.00' }}</span>
|
|
|
+ <span style="color:#f56c6c; font-weight:bold;">¥ {{ order.orderCommission ?
|
|
|
+ (order.orderCommission / 100).toFixed(2) : '0.00' }}</span>
|
|
|
</el-descriptions-item>
|
|
|
|
|
|
<el-descriptions-item label="预约时间">{{ getServiceTimeRange(order.serviceTime) || '-'
|
|
|
}}</el-descriptions-item>
|
|
|
<el-descriptions-item label="团购套餐">{{ order.groupBuyPackage || '-'
|
|
|
}}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="创建时间">{{ order.createTime || '-' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="创建时间">{{ order.createTime || '-'
|
|
|
+ }}</el-descriptions-item>
|
|
|
|
|
|
<el-descriptions-item label="订单备注" :span="3">
|
|
|
{{ order.remark || '-' }}
|
|
|
@@ -182,8 +185,9 @@
|
|
|
<div v-if="['feeding', 'washing'].includes(order.type)" class="section-block">
|
|
|
<div class="sec-title-bar">服务执行要求</div>
|
|
|
<el-descriptions :column="2" border size="default" class="custom-desc">
|
|
|
- <el-descriptions-item label="服务地址" :span="2">{{ order.detail?.area || order.address || '-'
|
|
|
- }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="服务地址" :span="2">{{ order.detail?.area || order.address
|
|
|
+ || '-'
|
|
|
+ }}</el-descriptions-item>
|
|
|
</el-descriptions>
|
|
|
</div>
|
|
|
</div>
|
|
|
@@ -209,9 +213,8 @@
|
|
|
</div>
|
|
|
<div class="f-row3"
|
|
|
style="margin-top: 8px; font-size: 13px; color: #606266; background: #f9fafe; padding: 8px; border-radius: 4px; display: flex; gap: 20px;">
|
|
|
- <span><span style="color:#909399;">指派时间:</span>{{ order.createTime }}</span>
|
|
|
- <span><span style="color:#909399;">接单时间:</span>{{ order.detail?.receiveTime ||
|
|
|
- order.serviceTime }}</span>
|
|
|
+ <span><span style="color:#909399;">指派时间:</span>{{ dispatchTime }}</span>
|
|
|
+ <span><span style="color:#909399;">接单时间:</span>{{ acceptTime }}</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
@@ -295,6 +298,44 @@
|
|
|
<div class="log-card">
|
|
|
<div class="l-tit">履约者:{{ complaint.fulfiller }}</div>
|
|
|
<div class="l-txt">{{ complaint.reason }}</div>
|
|
|
+ <div class="l-txt" v-if="complaint.photoUrls">
|
|
|
+ <span>图片:</span>
|
|
|
+ <div style="display: flex; gap: 5px; flex-wrap: wrap; margin-top: 5px;">
|
|
|
+ <ImagePreview v-for="(url, imgIndex) in complaint.photoUrls.split(',')"
|
|
|
+ :key="imgIndex" :src="url" :width="40" :height="40" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-timeline-item>
|
|
|
+ </el-timeline>
|
|
|
+ </div>
|
|
|
+ </el-tab-pane>
|
|
|
+
|
|
|
+ <!-- Tab 6: Service Change Records -->
|
|
|
+ <el-tab-pane label="服务变更记录" name="serviceChanges">
|
|
|
+ <div class="tab-pane-content">
|
|
|
+ <div v-if="serviceChangeList.length === 0" class="empty-state">
|
|
|
+ <el-result icon="success" title="暂无服务变更" sub-title="该订单暂无服务变更记录"></el-result>
|
|
|
+ </div>
|
|
|
+ <el-timeline v-else>
|
|
|
+ <el-timeline-item v-for="(change, index) in serviceChangeList" :key="index"
|
|
|
+ :timestamp="change.createTime" placement="top" color="#409eff">
|
|
|
+ <div class="log-card">
|
|
|
+ <div class="l-tit">服务变更 - {{ change.service }}</div>
|
|
|
+ <div class="l-txt">申诉理由:{{ change.reason }}</div>
|
|
|
+ <div class="l-txt" v-if="change.auditStatus === 1" style="color:#67c23a;">
|
|
|
+ 审核状态:已通过</div>
|
|
|
+ <div class="l-txt" v-else-if="change.auditStatus === 2" style="color:#f56c6c;">
|
|
|
+ 审核状态:已驳回
|
|
|
+ </div>
|
|
|
+ <div class="l-txt" v-else style="color:#e6a23c;">审核状态:待审核</div>
|
|
|
+ <div class="l-txt" v-if="change.photoUrls">
|
|
|
+ <span>图片:</span>
|
|
|
+ <div style="display: flex; gap: 5px; flex-wrap: wrap; margin-top: 5px;">
|
|
|
+ <ImagePreview v-for="(url, imgIndex) in change.photoUrls.split(',')"
|
|
|
+ :key="imgIndex" :src="url" :width="40" :height="40" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</el-timeline-item>
|
|
|
</el-timeline>
|
|
|
@@ -323,8 +364,10 @@ import { getPet } from '@/api/archieves/pet'
|
|
|
import { getCustomer } from '@/api/archieves/customer'
|
|
|
import { listSubOrderLog, exportSubOrderLogUrl } from '@/api/order/subOrderLog/index'
|
|
|
import { listComplaintByOrder } from '@/api/fulfiller/complaint'
|
|
|
+import { listSubOrderAppealByOrderId } from '@/api/order/subOrderAppeal'
|
|
|
import { getFulfillerDetail } from '@/api/fulfiller/fulfiller/index'
|
|
|
import PetDetailDrawer from '@/components/PetDetailDrawer/index.vue'
|
|
|
+import ImagePreview from '@/components/ImagePreview/index.vue'
|
|
|
|
|
|
// 视频判定辅助函数
|
|
|
const isVideo = (url) => {
|
|
|
@@ -366,12 +409,15 @@ const loadSeq = ref(0)
|
|
|
const orderLogs = ref([])
|
|
|
const fulfillerLogs = ref([])
|
|
|
const complaintList = ref([])
|
|
|
+const serviceChangeList = ref([])
|
|
|
|
|
|
const loadOrderLogs = async (order) => {
|
|
|
const id = order?.id
|
|
|
if (!id) {
|
|
|
orderLogs.value = []
|
|
|
fulfillerLogs.value = []
|
|
|
+ complaintList.value = []
|
|
|
+ serviceChangeList.value = []
|
|
|
return
|
|
|
}
|
|
|
try {
|
|
|
@@ -391,6 +437,13 @@ const loadOrderLogs = async (order) => {
|
|
|
} catch {
|
|
|
complaintList.value = []
|
|
|
}
|
|
|
+
|
|
|
+ try {
|
|
|
+ const serviceChangeRes = await listSubOrderAppealByOrderId(id)
|
|
|
+ serviceChangeList.value = serviceChangeRes?.data || []
|
|
|
+ } catch {
|
|
|
+ serviceChangeList.value = []
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
const loadPetAndCustomer = async (order) => {
|
|
|
@@ -474,11 +527,11 @@ const petDetailVisible = ref(false)
|
|
|
const activeDetailTab = ref('basic')
|
|
|
|
|
|
const getStatusName = (status) => {
|
|
|
- const map = { 0: '待派单', 1: '待接单', 2: '服务中', 3: '待商家确认', 4: '已完成', 5: '已取消' }
|
|
|
+ const map = { 0: '待派单', 1: '待接单', 2: '服务中', 3: '待商家确认', 4: '已完成', 5: '已取消', 6: '已拒绝', 7: '已关闭' }
|
|
|
return map[status] || '未知'
|
|
|
}
|
|
|
const getStatusTag = (status) => {
|
|
|
- const map = { 0: 'danger', 1: 'warning', 2: 'primary', 3: 'warning', 4: 'success', 5: 'info' }
|
|
|
+ const map = { 0: 'danger', 1: 'warning', 2: 'primary', 3: 'warning', 4: 'success', 5: 'info', 6: 'danger', 7: 'info' }
|
|
|
return map[status] || 'info'
|
|
|
}
|
|
|
const getTypeName = (type) => {
|
|
|
@@ -490,6 +543,11 @@ const getTransportModeName = (type) => {
|
|
|
return map[type] || '接送服务'
|
|
|
}
|
|
|
|
|
|
+const previewImage = (url, urls) => {
|
|
|
+ // 直接在新窗口打开图片
|
|
|
+ window.open(url, '_blank')
|
|
|
+}
|
|
|
+
|
|
|
const getTransportLabel = (t) => {
|
|
|
if (t === 0 || t === '0') return '接'
|
|
|
if (t === 1 || t === '1') return '送'
|
|
|
@@ -514,66 +572,92 @@ const getServiceTimeRange = (timeStr) => {
|
|
|
|
|
|
const currentOrderSteps = computed(() => {
|
|
|
if (!props.order) return { active: 0, steps: [] }
|
|
|
- const steps = [
|
|
|
- { title: '商户下单', status: 'created', time: '' },
|
|
|
- { title: '运营派单', status: 'dispatched', time: '' },
|
|
|
- { title: '履约接单', status: 'accepted', time: '' },
|
|
|
- { title: '服务中', status: 'serving', time: '' },
|
|
|
- { title: '待商家确认', status: 'confirming', time: '' },
|
|
|
- { title: '已完成', status: 'completed', time: '' }
|
|
|
- ]
|
|
|
- const logs = orderLogs.value || []
|
|
|
const status = props.order.status
|
|
|
- let active = 0
|
|
|
+ const logs = orderLogs.value || []
|
|
|
const findTime = (keyword) => {
|
|
|
const log = logs.find(l => l.title.includes(keyword) || l.content.includes(keyword))
|
|
|
return log ? log.time : ''
|
|
|
}
|
|
|
- steps[0].time = props.order.createTime || findTime('下单') || findTime('创建')
|
|
|
- if (steps[0].time) active = 1
|
|
|
- if ([0].includes(status)) {
|
|
|
- steps[1].time = findTime('派单') || steps[0].time
|
|
|
- } else {
|
|
|
- steps[1].time = findTime('派单') || ''
|
|
|
- }
|
|
|
- if ([1, 2, 3, 4].includes(status)) active = 2
|
|
|
- steps[2].time = findTime('接单')
|
|
|
- if ([1].includes(status)) {
|
|
|
- steps[2].title = '待履约者接单'
|
|
|
- } else if ([2, 3, 4].includes(status)) {
|
|
|
- steps[2].title = '履约者已接单'
|
|
|
- active = 3
|
|
|
- }
|
|
|
- steps[3].time = findTime('到达') || findTime('出发')
|
|
|
- if ([2].includes(status)) {
|
|
|
- steps[3].title = '服务进行中'
|
|
|
- } else if ([3, 4].includes(status)) {
|
|
|
- steps[3].title = '服务已完成'
|
|
|
- active = 4
|
|
|
- }
|
|
|
- steps[4].time = findTime('等待商家确认') || findTime('待验收')
|
|
|
- if ([3].includes(status)) {
|
|
|
- steps[4].title = '待商家确认'
|
|
|
- } else if ([4].includes(status)) {
|
|
|
- steps[4].title = '商家已确认'
|
|
|
- active = 5
|
|
|
- }
|
|
|
- if (status === 4) {
|
|
|
- steps[5].time = findTime('完成')
|
|
|
- active = 6
|
|
|
- }
|
|
|
+
|
|
|
+ // 已取消 — 特殊两步流程
|
|
|
if (status === 5) {
|
|
|
+ const createTime = props.order.createTime || findTime('下单') || findTime('创建')
|
|
|
return {
|
|
|
active: 1,
|
|
|
steps: [
|
|
|
- { title: '商户下单', time: steps[0].time },
|
|
|
+ { title: '商户下单', time: createTime },
|
|
|
{ title: '已取消', time: findTime('取消') || '订单已取消' }
|
|
|
]
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ // 已拒绝 — 特殊两步流程
|
|
|
+ if (status === 6) {
|
|
|
+ const createTime = props.order.createTime || findTime('下单') || findTime('创建')
|
|
|
+ return {
|
|
|
+ active: 1,
|
|
|
+ steps: [
|
|
|
+ { title: '商户下单', time: createTime },
|
|
|
+ { title: '已拒绝', time: findTime('拒绝') || '订单已拒绝' }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 已关闭 — 特殊两步流程
|
|
|
+ if (status === 7) {
|
|
|
+ const createTime = props.order.createTime || findTime('下单') || findTime('创建')
|
|
|
+ return {
|
|
|
+ active: 1,
|
|
|
+ steps: [
|
|
|
+ { title: '商户下单', time: createTime },
|
|
|
+ { title: '已关闭', time: findTime('关闭') || '订单已关闭' }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 六步流程:商户下单 → 运营派单 → 履约接单 → 等待服务 → 服务进行 → 订单完成
|
|
|
+ const steps = [
|
|
|
+ { title: '商户下单', time: props.order.createTime || findTime('下单') || findTime('创建') },
|
|
|
+ { title: '运营派单', time: findTime('派单') || '' },
|
|
|
+ { title: '履约接单', time: findTime('接单') || '' },
|
|
|
+ { title: '等待服务', time: findTime('到达') || '' },
|
|
|
+ { title: '服务进行', time: findTime('完成') || '' },
|
|
|
+ { title: '订单完成', time: findTime('完成') || '' }
|
|
|
+ ]
|
|
|
+
|
|
|
+ let active = 1 // 默认停在「运营派单」
|
|
|
+
|
|
|
+ switch (status) {
|
|
|
+ case 0:
|
|
|
+ active = 1
|
|
|
+ break
|
|
|
+ case 1:
|
|
|
+ active = 2
|
|
|
+ steps[2].title = '等待接单'
|
|
|
+ break
|
|
|
+ case 2:
|
|
|
+ active = 3
|
|
|
+ steps[2].title = '已确认接'
|
|
|
+ break
|
|
|
+ case 3:
|
|
|
+ active = 4
|
|
|
+ steps[2].title = '已确认接'
|
|
|
+ steps[3].title = '已到达点'
|
|
|
+ break
|
|
|
+ case 4:
|
|
|
+ active = 6
|
|
|
+ steps[2].title = '已确认接'
|
|
|
+ steps[3].title = '已到达点'
|
|
|
+ break
|
|
|
+ }
|
|
|
+
|
|
|
return { active, steps }
|
|
|
})
|
|
|
|
|
|
+// 从订单日志中取派单/接单时间 @Author: Antigravity
|
|
|
+const dispatchTime = computed(() => orderLogs.value.find(l => l.title === '系统派单')?.createTime || '-')
|
|
|
+const acceptTime = computed(() => orderLogs.value.find(l => l.title === '接单成功')?.createTime || '-')
|
|
|
+
|
|
|
const serviceProgressSteps = computed(() => {
|
|
|
const list = fulfillerLogs.value || []
|
|
|
return list.map((i) => {
|