|
|
@@ -1,17 +1,27 @@
|
|
|
-import { getOrderInfo, getOrderLogs, uploadFile, clockIn } from '@/api/fulfiller'
|
|
|
-import { getServiceList } from '@/api/service'
|
|
|
+import { getOrderInfo, getOrderLogs, uploadFile, clockIn, getAnomalyList } from '@/api/fulfiller'
|
|
|
+import { getServiceList, getServiceDetail } from '@/api/service'
|
|
|
+import { getDictDataByType } from '@/api/system/dict/index'
|
|
|
+import { getPetDetail } from '@/api/archieves/pet/index'
|
|
|
|
|
|
export default {
|
|
|
data() {
|
|
|
return {
|
|
|
orderId: null,
|
|
|
+ pageLoading: true, // 页面数据加载中
|
|
|
orderType: 1,
|
|
|
orderStatus: 2,
|
|
|
+ serviceId: null, // 当前订单的服务类型ID
|
|
|
+ petId: null, // 当前订单关联的宠物ID
|
|
|
+ petDetail: null, // 宠物档案详情
|
|
|
|
|
|
- stepsPickup: ['到达打卡', '确认出发', '送达打卡'],
|
|
|
- stepsWalkWash: ['到达打卡', '开始服务', '服务结束'],
|
|
|
+ // 从后端 clockInRemark 解析出的打卡步骤列表
|
|
|
+ // 格式: [{step:1, title:'到达打卡', remark:'照片视频二选一即可'}, ...]
|
|
|
+ clockInSteps: [],
|
|
|
|
|
|
- currentStep: 1,
|
|
|
+ // 当前应执行的打卡信息(从 clockInSteps 中取出)
|
|
|
+ currentClockIn: null,
|
|
|
+
|
|
|
+ currentStep: 0,
|
|
|
|
|
|
orderDetail: {
|
|
|
type: 1,
|
|
|
@@ -49,52 +59,57 @@ export default {
|
|
|
sumSigner: '张*哥',
|
|
|
|
|
|
showPetRemarkInput: false,
|
|
|
- petRemarkText: ''
|
|
|
+ petRemarkText: '',
|
|
|
+
|
|
|
+ showAnomalyModal: false,
|
|
|
+ anomalyList: [],
|
|
|
+ anomalyTypeDict: []
|
|
|
}
|
|
|
},
|
|
|
computed: {
|
|
|
+ // 从 clockInSteps 中提取 title 数组作为打卡步骤名(内部逻辑用)
|
|
|
steps() {
|
|
|
- return this.orderType === 1 ? this.stepsPickup : this.stepsWalkWash;
|
|
|
+ if (this.clockInSteps.length > 0) {
|
|
|
+ return this.clockInSteps.map(s => s.title)
|
|
|
+ }
|
|
|
+ // 兜底:如果 clockInSteps 未加载则使用默认
|
|
|
+ return this.orderType === 1
|
|
|
+ ? ['到达打卡', '确认出发', '送达打卡']
|
|
|
+ : ['到达打卡', '开始服务', '服务结束']
|
|
|
+ },
|
|
|
+ // 顶部进度条展示用:已接单 -> 各打卡步骤 -> 订单完成
|
|
|
+ progressSteps() {
|
|
|
+ return ['已接单', ...this.steps, '订单完成']
|
|
|
+ },
|
|
|
+ // 进度条当前激活索引(= currentStep + 1,因为首位是"已接单")
|
|
|
+ progressIndex() {
|
|
|
+ // 已接单是第0步,始终已完成;打卡步骤从索引1开始
|
|
|
+ return this.currentStep + 1
|
|
|
},
|
|
|
displayStatusText() {
|
|
|
- if (this.currentStep === 4) return '待商家确认';
|
|
|
if (this.currentStep >= this.steps.length) return '已完成';
|
|
|
- let status = this.steps[this.currentStep];
|
|
|
- if (status === '已完成' || status === '完成') return '已完成';
|
|
|
- if (status === '已拒绝') return '已拒绝';
|
|
|
- if (status === '接单') {
|
|
|
- return this.orderType === 1 ? '待接送' : '待服务';
|
|
|
+ // 判断是否在服务中
|
|
|
+ if (this.currentStep > 0) {
|
|
|
+ return this.orderType === 1 ? '配送中' : '服务中';
|
|
|
}
|
|
|
- // 对于其他活跃状态 (出发, 到达, 送达, 开始, 结束)
|
|
|
- return this.orderType === 1 ? '配送中' : '服务中';
|
|
|
+ return this.orderType === 1 ? '待接送' : '待服务';
|
|
|
},
|
|
|
currentStatusText() {
|
|
|
- if (this.currentStep === 4) return '待确认';
|
|
|
return this.currentStep >= this.steps.length ? '已完成' : this.steps[this.currentStep];
|
|
|
},
|
|
|
+ // 按钮文本:使用 clockInSteps 中对应步骤的 title
|
|
|
currentTaskTitle() {
|
|
|
- if (this.currentStep === 4) return '待商家确认';
|
|
|
if (this.currentStep >= this.steps.length) return '订单已完成';
|
|
|
- let action = this.steps[this.currentStep];
|
|
|
- if (action === '到达打卡') return '到达打卡';
|
|
|
- if (action === '开始服务') return '开始服务';
|
|
|
- if (action === '确认出发') return '确认出发';
|
|
|
- if (action === '送达打卡' || action === '服务结束') return '服务完成';
|
|
|
- return action;
|
|
|
+ if (this.currentClockIn) {
|
|
|
+ return this.currentClockIn.title;
|
|
|
+ }
|
|
|
+ return this.steps[this.currentStep] || '打卡';
|
|
|
},
|
|
|
+ // 任务描述小字:使用 clockInSteps 中对应步骤的 remark
|
|
|
currentTaskDesc() {
|
|
|
- if (this.currentStep === 4) return '服务已提交,请等待商家确认完成后即可结束订单';
|
|
|
if (this.currentStep >= this.steps.length) return '感谢您的服务,请注意休息';
|
|
|
- let action = this.steps[this.currentStep];
|
|
|
- if (action === '到达打卡') {
|
|
|
- return '打卡穿着工装消毒站门口的照片或视频';
|
|
|
- }
|
|
|
- if (this.orderType === 1) {
|
|
|
- if (action === '确认出发') return '拍摄宠物上车/出发时的状态照片或视频';
|
|
|
- if (action === '送达打卡') return '打卡确认送达的照片或视频';
|
|
|
- } else {
|
|
|
- if (action === '开始服务') return '开始服务并拍摄照片 or 视频';
|
|
|
- if (action === '服务结束') return '服务完成拍摄照片或视频';
|
|
|
+ if (this.currentClockIn && this.currentClockIn.remark) {
|
|
|
+ return this.currentClockIn.remark;
|
|
|
}
|
|
|
return '请按要求提交照片或视频及备注';
|
|
|
}
|
|
|
@@ -103,8 +118,17 @@ export default {
|
|
|
if (options.id) {
|
|
|
this.orderId = options.id
|
|
|
}
|
|
|
- await this.loadServiceList()
|
|
|
- await this.loadOrderDetail()
|
|
|
+ this.pageLoading = true
|
|
|
+ try {
|
|
|
+ // 先加载字典
|
|
|
+ await this.loadAnomalyTypeDict()
|
|
|
+ // 先获取服务列表(用于匹配服务类型名称等)
|
|
|
+ await this.loadServiceList()
|
|
|
+ // 获取订单详情(内部会拿到 serviceId,然后加载服务详情获取 clockInRemark)
|
|
|
+ await this.loadOrderDetail()
|
|
|
+ } finally {
|
|
|
+ this.pageLoading = false
|
|
|
+ }
|
|
|
},
|
|
|
methods: {
|
|
|
async loadServiceList() {
|
|
|
@@ -115,6 +139,28 @@ export default {
|
|
|
console.error('获取服务类型失败:', err)
|
|
|
}
|
|
|
},
|
|
|
+ /**
|
|
|
+ * 根据服务类型ID获取服务详情,解析 clockInRemark 为打卡步骤
|
|
|
+ */
|
|
|
+ async loadServiceDetail(serviceId) {
|
|
|
+ try {
|
|
|
+ const res = await getServiceDetail(serviceId)
|
|
|
+ const serviceInfo = res.data
|
|
|
+ if (serviceInfo && serviceInfo.clockInRemark) {
|
|
|
+ try {
|
|
|
+ const parsed = JSON.parse(serviceInfo.clockInRemark)
|
|
|
+ if (Array.isArray(parsed) && parsed.length > 0) {
|
|
|
+ this.clockInSteps = parsed
|
|
|
+ console.log('解析打卡步骤:', this.clockInSteps)
|
|
|
+ }
|
|
|
+ } catch (parseErr) {
|
|
|
+ console.error('解析 clockInRemark 失败:', parseErr)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (err) {
|
|
|
+ console.error('获取服务类型详情失败:', err)
|
|
|
+ }
|
|
|
+ },
|
|
|
async loadOrderDetail() {
|
|
|
if (!this.orderId) {
|
|
|
console.log('订单ID缺失')
|
|
|
@@ -132,7 +178,21 @@ export default {
|
|
|
return
|
|
|
}
|
|
|
console.log('订单数据:', order)
|
|
|
+ this.serviceId = order.service
|
|
|
+ this.petId = order.usrPet || null
|
|
|
this.transformOrderData(order)
|
|
|
+
|
|
|
+ // 根据订单的服务类型ID获取服务详情(含 clockInRemark)
|
|
|
+ if (this.serviceId) {
|
|
|
+ await this.loadServiceDetail(this.serviceId)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 加载宠物档案详情
|
|
|
+ if (this.petId) {
|
|
|
+ await this.loadPetDetail(this.petId)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 加载订单日志并根据 step 确定当前进度
|
|
|
await this.loadOrderLogs()
|
|
|
} catch (err) {
|
|
|
console.error('获取订单详情失败:', err)
|
|
|
@@ -144,9 +204,8 @@ export default {
|
|
|
const res = await getOrderLogs(this.orderId)
|
|
|
const logs = res.data || []
|
|
|
console.log('订单日志:', logs)
|
|
|
- if (logs.length > 0) {
|
|
|
- console.log('第一条日志详情:', JSON.stringify(logs[0]))
|
|
|
- }
|
|
|
+
|
|
|
+ // 渲染进度日志列表
|
|
|
const progressLogs = logs.filter(log => log.logType === 1)
|
|
|
this.orderDetail.progressLogs = progressLogs.map(log => ({
|
|
|
status: log.title || '',
|
|
|
@@ -155,37 +214,45 @@ export default {
|
|
|
remark: log.content || ''
|
|
|
}))
|
|
|
|
|
|
- // 根据打卡日志的 actionType 确定下一步骤。规则:最新的打卡记录是什么,下一步就往后延。
|
|
|
- // 仅筛选打卡类型的日志(logType: 1),并按时间倒序排列,取最新的一条作为当前进度的基准
|
|
|
- const validLogs = logs.filter(log => log.logType === 1 && log.actionType !== undefined)
|
|
|
+ // 根据打卡日志的 step 确定下一步骤
|
|
|
+ // 查找最新的一条打卡日志(logType=1),取其 step,下一步为 step+1
|
|
|
+ const validLogs = logs.filter(log => log.logType === 1 && log.step !== undefined && log.step !== null)
|
|
|
.sort((a, b) => new Date(b.createTime).getTime() - new Date(a.createTime).getTime())
|
|
|
|
|
|
if (validLogs.length > 0) {
|
|
|
const latestLog = validLogs[0]
|
|
|
- const actionType = latestLog.actionType
|
|
|
+ const latestStep = latestLog.step
|
|
|
+ console.log('最新打卡日志 step:', latestStep)
|
|
|
|
|
|
- // 如果日志显示待商家确认,则为无需打卡
|
|
|
- if (latestLog.actionType >= 7) {
|
|
|
- this.currentStep = 4 // 无需打卡状态
|
|
|
- } else if (actionType === 7) {
|
|
|
- this.currentStep = 3 // 已完成
|
|
|
- } else if (actionType === 6) {
|
|
|
- this.currentStep = 2 // 当前应进行:送达打卡/服务结束
|
|
|
- } else if (actionType === 4) {
|
|
|
- this.currentStep = 1 // 当前应进行:确认出发/开始服务
|
|
|
- } else if (actionType === 2 || actionType === 3) {
|
|
|
- this.currentStep = 0 // 已接单,当前应进行:到达打卡
|
|
|
+ // 在 clockInSteps 中找到该 step 对应的索引,然后 +1 得到下一步
|
|
|
+ const stepIndex = this.clockInSteps.findIndex(s => s.step === latestStep)
|
|
|
+ if (stepIndex >= 0) {
|
|
|
+ this.currentStep = stepIndex + 1
|
|
|
} else {
|
|
|
- this.currentStep = 0
|
|
|
+ // 兜底:直接按 step 值推算
|
|
|
+ this.currentStep = latestStep
|
|
|
}
|
|
|
} else {
|
|
|
this.currentStep = 0
|
|
|
}
|
|
|
- console.log('根据最新日志推算的当前步骤:', this.currentStep)
|
|
|
+
|
|
|
+ // 更新当前打卡信息
|
|
|
+ this.updateCurrentClockIn()
|
|
|
+ console.log('根据最新日志推算的当前步骤:', this.currentStep, '当前打卡信息:', this.currentClockIn)
|
|
|
} catch (err) {
|
|
|
console.error('获取订单日志失败:', err)
|
|
|
}
|
|
|
},
|
|
|
+ /**
|
|
|
+ * 根据 currentStep 更新当前打卡信息
|
|
|
+ */
|
|
|
+ updateCurrentClockIn() {
|
|
|
+ if (this.currentStep < this.clockInSteps.length) {
|
|
|
+ this.currentClockIn = this.clockInSteps[this.currentStep]
|
|
|
+ } else {
|
|
|
+ this.currentClockIn = null
|
|
|
+ }
|
|
|
+ },
|
|
|
transformOrderData(order) {
|
|
|
const mode = order.mode || 0
|
|
|
const isRoundTrip = mode === 1
|
|
|
@@ -215,6 +282,74 @@ export default {
|
|
|
]
|
|
|
}
|
|
|
},
|
|
|
+ /**
|
|
|
+ * 根据宠物ID获取宠物档案详情
|
|
|
+ */
|
|
|
+ async loadPetDetail(petId) {
|
|
|
+ try {
|
|
|
+ const res = await getPetDetail(petId)
|
|
|
+ const pet = res.data
|
|
|
+ if (pet) {
|
|
|
+ this.petDetail = pet
|
|
|
+ // 同步更新订单详情中的宠物信息
|
|
|
+ this.orderDetail.petAvatar = pet.avatarUrl || '/static/dog.png'
|
|
|
+ this.orderDetail.petName = pet.name || this.orderDetail.petName
|
|
|
+ this.orderDetail.petBreed = pet.breed || this.orderDetail.petBreed
|
|
|
+ console.log('宠物档案:', pet)
|
|
|
+ }
|
|
|
+ } catch (err) {
|
|
|
+ console.error('获取宠物档案失败:', err)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * 加载异常记录列表
|
|
|
+ */
|
|
|
+ async loadAnomalyList() {
|
|
|
+ if (!this.orderId) return
|
|
|
+ try {
|
|
|
+ const res = await getAnomalyList(this.orderId)
|
|
|
+ const list = res.data || []
|
|
|
+ // 过滤和转换
|
|
|
+ this.anomalyList = list.map(item => {
|
|
|
+ // 映射类型
|
|
|
+ const dict = this.anomalyTypeDict.find(d => d.value === item.type)
|
|
|
+ return {
|
|
|
+ ...item,
|
|
|
+ typeLabel: dict ? dict.label : item.type,
|
|
|
+ // 确保有图片数组供展示,如果后端没返 photoUrls,尝试兼容
|
|
|
+ photoUrls: item.photoUrls || []
|
|
|
+ }
|
|
|
+ })
|
|
|
+ } catch (err) {
|
|
|
+ console.error('获取异常列表失败:', err)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ async loadAnomalyTypeDict() {
|
|
|
+ try {
|
|
|
+ const res = await getDictDataByType('flf_anamaly_type')
|
|
|
+ this.anomalyTypeDict = res.data.map(item => ({
|
|
|
+ label: item.dictLabel,
|
|
|
+ value: item.dictValue
|
|
|
+ }))
|
|
|
+ } catch (err) {
|
|
|
+ console.error('获取异常字典失败:', err)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ openAnomalyModal() {
|
|
|
+ this.showAnomalyModal = true
|
|
|
+ this.loadAnomalyList()
|
|
|
+ },
|
|
|
+ closeAnomalyModal() {
|
|
|
+ this.showAnomalyModal = false
|
|
|
+ },
|
|
|
+ getAnomalyStatusLabel(status) {
|
|
|
+ const map = {
|
|
|
+ 0: '待审核',
|
|
|
+ 1: '已通过',
|
|
|
+ 2: '已驳回'
|
|
|
+ }
|
|
|
+ return map[status] || '未知'
|
|
|
+ },
|
|
|
updateStepByStatus() {
|
|
|
if (this.orderStatus === 2) {
|
|
|
this.currentStep = 0
|
|
|
@@ -227,23 +362,46 @@ export default {
|
|
|
}
|
|
|
},
|
|
|
showPetProfile() {
|
|
|
- // Use orderDetail basic info and mock the rest
|
|
|
- this.currentPetInfo = {
|
|
|
- ...this.orderDetail,
|
|
|
- petGender: 'M',
|
|
|
- petAge: '2岁',
|
|
|
- petWeight: '15kg',
|
|
|
- petPersonality: '活泼亲人,精力旺盛',
|
|
|
- petHobby: '喜欢追飞盘,爱吃肉干',
|
|
|
- petRemark: '肠胃较弱,不能乱喂零食;出门易爆冲,请拉紧牵引绳。',
|
|
|
- petTags: ['拉响警报', '不能吃鸡肉', '精力旺盛'],
|
|
|
- petLogs: [
|
|
|
- { date: '2026-02-09 14:00', content: '今天遛弯拉了两次粑粑,精神状态很好。', recorder: '王阿姨' },
|
|
|
- { date: '2026-02-08 10:30', content: '有些挑食,剩了小半碗狗粮。', recorder: '李师傅' },
|
|
|
- { date: '2026-02-05 09:00', content: '建档。', recorder: '系统记录' }
|
|
|
- ]
|
|
|
- };
|
|
|
- this.showPetModal = true;
|
|
|
+ const pet = this.petDetail
|
|
|
+ if (pet) {
|
|
|
+ // 使用后端返回的真实宠物数据
|
|
|
+ this.currentPetInfo = {
|
|
|
+ petAvatar: pet.avatarUrl || '/static/dog.png',
|
|
|
+ petName: pet.name || '',
|
|
|
+ petBreed: pet.breed || '',
|
|
|
+ petGender: pet.gender === 1 ? 'M' : (pet.gender === 2 ? 'F' : ''),
|
|
|
+ petAge: pet.age ? pet.age + '岁' : '未知',
|
|
|
+ petWeight: pet.weight ? pet.weight + 'kg' : '未知',
|
|
|
+ petPersonality: pet.personality || pet.cutePersonality || '无',
|
|
|
+ petHobby: '',
|
|
|
+ petRemark: pet.remark || '无',
|
|
|
+ petTags: (pet.tags || []).map(t => t.name),
|
|
|
+ petLogs: [],
|
|
|
+ // 额外信息
|
|
|
+ petSize: pet.size || '',
|
|
|
+ petIsSterilized: pet.isSterilized,
|
|
|
+ petHealthStatus: pet.healthStatus || '',
|
|
|
+ petAllergies: pet.allergies || '',
|
|
|
+ petMedicalHistory: pet.medicalHistory || '',
|
|
|
+ petVaccineStatus: pet.vaccineStatus || '',
|
|
|
+ ownerName: pet.ownerName || '',
|
|
|
+ ownerPhone: pet.ownerPhone || ''
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 兜底:如果宠物档案未加载成功,使用订单中的基本信息
|
|
|
+ this.currentPetInfo = {
|
|
|
+ ...this.orderDetail,
|
|
|
+ petGender: '',
|
|
|
+ petAge: '未知',
|
|
|
+ petWeight: '未知',
|
|
|
+ petPersonality: '无',
|
|
|
+ petHobby: '',
|
|
|
+ petRemark: '无',
|
|
|
+ petTags: [],
|
|
|
+ petLogs: []
|
|
|
+ }
|
|
|
+ }
|
|
|
+ this.showPetModal = true
|
|
|
},
|
|
|
closePetProfile() {
|
|
|
this.showPetModal = false;
|
|
|
@@ -378,15 +536,8 @@ export default {
|
|
|
|
|
|
console.log('准备打卡,ossIds:', ossIds);
|
|
|
|
|
|
- const currentAction = this.steps[this.currentStep];
|
|
|
- let clockInType = 4;
|
|
|
- if (currentAction === '到达打卡') {
|
|
|
- clockInType = 4;
|
|
|
- } else if (currentAction === '确认出发' || currentAction === '开始服务') {
|
|
|
- clockInType = 6;
|
|
|
- } else if (currentAction === '送达打卡' || currentAction === '服务结束') {
|
|
|
- clockInType = 7;
|
|
|
- }
|
|
|
+ // 使用 clockInSteps 中对应步骤的 step 值作为打卡 type
|
|
|
+ const clockInType = this.currentClockIn ? this.currentClockIn.step : (this.currentStep + 1);
|
|
|
|
|
|
const clockInData = {
|
|
|
orderId: this.orderId,
|