dingdanquanliucheng/frontend/mini-app/pages/driver/task-detail/task-detail.js
taiyi b2b7641999 fix: 快递100 OCR 失败时不再抛400错误,改为降级提示手动输入
- recognize_waybill 捕获所有异常返回空结果而非抛出
- 修正 SysUser 导入为 User(匹配实际模型类名)
- 前端识别为空时提示"自动识别未成功,请手动输入单号"

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-07 20:26:28 +08:00

411 lines
13 KiB
JavaScript

/**
* 司机端任务详情页面
* 职责:展示单个任务完整信息,支持接单、确认揽货(需上传运单号)、确认送达等操作。
*/
function mapTaskStatus(status) {
var statusMap = { pending: "待接单", accepted: "已接单", picked_up: "已揽货", delivered: "已送达", canceled: "已取消" };
return statusMap[status] || status || "-";
}
function getStatusBadge(status) {
var map = { pending: "badge-orange", accepted: "badge-blue", picked_up: "badge-blue", delivered: "badge-green", canceled: "badge-red" };
return map[status] || "badge-gray";
}
Page({
data: {
statusBarHeight: 20,
loading: true,
message: "",
actionLoading: false,
taskId: null,
taskInfo: null,
actionRemark: "",
traceList: [],
uploadedPhotos: [],
waybillList: [],
taskAttachments: [],
recognizing: false,
},
async onLoad(options) {
var app = getApp();
this.setData({
statusBarHeight: app.globalData.statusBarHeight,
taskId: Number(options.id || 0),
});
await this.loadDetail();
},
async loadDetail() {
var app = getApp();
this.setData({ loading: true, message: "", uploadedPhotos: [] });
try {
var data = await app.request({ url: "/api/driver/tasks/" + this.data.taskId, method: "GET" });
this.setData({
taskInfo: Object.assign({}, data, {
statusText: mapTaskStatus(data.status),
badgeClass: getStatusBadge(data.status),
pickupAddress: data.pickup_address || "-",
deliveryAddress: data.delivery_address || "-",
pickupContent: data.pickup_content || "-",
factoryName: data.factory_name || "-",
salesmanName: data.salesman_name || "-",
}),
});
if (data.order_id) {
try {
var traceData = await app.request({ url: "/api/logistics/" + data.order_id + "/trace", method: "GET" });
this.setData({
traceList: (traceData.trace_list || []).map(function (item) {
item.node_time = item.node_time || item.created_at || "-";
return item;
}),
});
} catch (e) {}
}
// Load existing waybills from server
if (data.status === "accepted" || data.status === "picked_up" || data.status === "delivered") {
await this.loadWaybills();
}
// Load existing attachments for completed states
if (data.status === "picked_up" || data.status === "delivered") {
await this.loadAttachments();
}
} catch (error) {
this.setData({ message: error.message || "加载任务详情失败" });
} finally {
this.setData({ loading: false });
}
},
loadWaybills: async function () {
var app = getApp();
try {
var waybills = await app.request({ url: "/api/driver/tasks/" + this.data.taskId + "/waybills", method: "GET" });
this.setData({ waybillList: waybills || [] });
} catch (e) {
// Ignore - waybills may not exist yet
}
},
loadAttachments: async function () {
var app = getApp();
try {
var data = await app.request({
url: "/api/files/attachments?biz_type=logistics_task&biz_id=" + this.data.taskId,
method: "GET",
});
this.setData({ taskAttachments: data || [] });
} catch (e) {
this.setData({ taskAttachments: [] });
}
},
handleRemarkInput: function (event) {
this.setData({ actionRemark: event.detail.value });
},
handleChoosePhoto: function () {
var that = this;
var remaining = 9 - this.data.uploadedPhotos.length;
if (remaining <= 0) { wx.showToast({ title: "最多上传9张", icon: "none" }); return; }
wx.chooseImage({
count: remaining,
sizeType: ["compressed"],
sourceType: ["camera", "album"],
success: function (res) {
res.tempFilePaths.forEach(function (path) {
that.uploadPhotoToServer(path);
});
},
});
},
uploadPhotoToServer: function (localPath) {
var that = this;
var app = getApp();
var token = app.globalData.authToken || "";
wx.showLoading({ title: "上传中..." });
wx.uploadFile({
url: app.globalData.apiBaseUrl + "/api/files/local-upload",
filePath: localPath,
name: "file",
header: { Authorization: "Bearer " + token },
success: function (uploadRes) {
wx.hideLoading();
try {
var body = JSON.parse(uploadRes.data);
if (body.code === 0 && body.data) {
var newPhoto = {
path: body.data.url,
name: body.data.file_name || localPath.split("/").pop(),
file_size: body.data.file_size || 0,
};
that.setData({ uploadedPhotos: that.data.uploadedPhotos.concat([newPhoto]) });
} else {
wx.showToast({ title: body.message || "上传失败", icon: "none" });
}
} catch (e) {
wx.showToast({ title: "上传结果解析失败", icon: "none" });
}
},
fail: function () {
wx.hideLoading();
wx.showToast({ title: "网络错误,上传失败", icon: "none" });
},
});
},
handleRemovePhoto: function (event) {
var index = event.currentTarget.dataset.index;
var photos = this.data.uploadedPhotos.slice();
photos.splice(index, 1);
this.setData({ uploadedPhotos: photos });
},
// --- Waybill methods ---
handleWaybillPhoto: function () {
var that = this;
if (this.data.recognizing) return;
wx.chooseImage({
count: 1,
sizeType: ["compressed"],
sourceType: ["camera"],
success: function (res) {
var filePath = res.tempFilePaths[0];
that.setData({ recognizing: true });
wx.showLoading({ title: "识别中..." });
that.uploadAndRecognize(filePath);
},
});
},
uploadAndRecognize: function (filePath) {
var that = this;
var app = getApp();
var token = app.globalData.authToken || "";
wx.uploadFile({
url: app.globalData.apiBaseUrl + "/api/files/local-upload",
filePath: filePath,
name: "file",
header: { Authorization: "Bearer " + token },
success: function (uploadRes) {
try {
var body = JSON.parse(uploadRes.data);
if (body.code !== 0 || !body.data) {
wx.hideLoading();
that.setData({ recognizing: false });
wx.showToast({ title: body.message || "上传失败", icon: "none" });
return;
}
var imageUrl = body.data.url || body.data;
that.callRecognizeWaybill(imageUrl, filePath);
} catch (e) {
wx.hideLoading();
that.setData({ recognizing: false });
wx.showToast({ title: "上传结果解析失败", icon: "none" });
}
},
fail: function () {
wx.hideLoading();
that.setData({ recognizing: false });
wx.showToast({ title: "文件上传失败", icon: "none" });
},
});
},
callRecognizeWaybill: function (imageUrl, localPath) {
var that = this;
var app = getApp();
app.request({
url: "/api/driver/recognize-waybill",
method: "POST",
data: { image_url: imageUrl },
}).then(function (result) {
wx.hideLoading();
that.setData({ recognizing: false });
var waybill = {
tracking_number: result.tracking_number || "",
express_company: result.express_company || "",
express_name: result.express_name || "",
image_url: imageUrl,
local_path: localPath,
confidence: result.confidence || 0,
};
var list = that.data.waybillList.slice();
list.push(waybill);
that.setData({ waybillList: list });
if (waybill.tracking_number) {
wx.showToast({ title: "识别成功", icon: "success" });
} else {
wx.showToast({ title: "自动识别未成功,请手动输入单号", icon: "none", duration: 2500 });
}
}).catch(function (err) {
wx.hideLoading();
that.setData({ recognizing: false });
wx.showToast({ title: err.message || "识别失败,请手动输入", icon: "none" });
});
},
handleManualWaybill: function () {
var that = this;
wx.showModal({
title: "手动输入运单号",
editable: true,
placeholderText: "请输入运单号",
confirmText: "添加",
confirmColor: "#2563eb",
success: function (res) {
if (res.confirm && res.content && res.content.trim()) {
var trackingNumber = res.content.trim();
var list = that.data.waybillList.slice();
list.push({
tracking_number: trackingNumber,
express_company: "",
express_name: "",
image_url: "",
local_path: "",
confidence: 0,
});
that.setData({ waybillList: list });
}
},
});
},
handleRemoveWaybill: function (event) {
var index = event.currentTarget.dataset.index;
var that = this;
var item = this.data.waybillList[index];
// If it has an id, it's saved on server - delete from server too
if (item && item.id) {
var app = getApp();
app.request({
url: "/api/driver/tasks/" + this.data.taskId + "/waybills/" + item.id,
method: "DELETE",
}).catch(function () {});
}
var list = this.data.waybillList.slice();
list.splice(index, 1);
this.setData({ waybillList: list });
},
handleEditWaybill: function (event) {
var index = event.currentTarget.dataset.index;
var item = this.data.waybillList[index];
if (!item) return;
var that = this;
wx.showModal({
title: "修改运单号",
editable: true,
placeholderText: "请输入运单号",
content: item.tracking_number || "",
confirmText: "保存",
confirmColor: "#2563eb",
success: function (res) {
if (res.confirm && res.content && res.content.trim()) {
var list = that.data.waybillList.slice();
list[index] = Object.assign({}, list[index], {
tracking_number: res.content.trim(),
});
that.setData({ waybillList: list });
}
},
});
},
getActionConfig: function () {
var status = this.data.taskInfo && this.data.taskInfo.status;
var taskId = this.data.taskId;
if (status === "pending") {
return { text: "接单", url: "/api/driver/tasks/" + taskId + "/accept", successMessage: "接单成功", confirmMsg: "确认接受此任务?", needAttachment: false };
}
if (status === "accepted") {
return { text: "确认揽货", url: "/api/driver/tasks/" + taskId + "/pickup", successMessage: "已确认揽货", confirmMsg: "确认已揽收货物?请先录入运单号。", needWaybill: true };
}
if (status === "picked_up") {
return { text: "确认送达", url: "/api/driver/tasks/" + taskId + "/deliver", successMessage: "已确认送达", confirmMsg: "确认货物已送达目的地?", needAttachment: false };
}
return null;
},
handleAction: function () {
var that = this;
var action = this.getActionConfig();
if (!action) return;
if (action.needWaybill && this.data.waybillList.length === 0) {
wx.showToast({ title: "请先录入运单号", icon: "none" });
return;
}
// Validate all waybills have tracking numbers
if (action.needWaybill) {
var hasEmpty = this.data.waybillList.some(function (w) {
return !w.tracking_number;
});
if (hasEmpty) {
wx.showToast({ title: "请填写完整的运单号", icon: "none" });
return;
}
}
wx.showModal({
title: "操作确认",
content: action.confirmMsg,
confirmText: "确认",
confirmColor: "#2563eb",
success: function (res) {
if (res.confirm) { that.executeAction(action); }
},
});
},
executeAction: async function (action) {
var app = getApp();
this.setData({ actionLoading: true, message: "" });
try {
// For accepted status, submit waybills first
if (action.needWaybill && this.data.waybillList.length > 0) {
var waybillPayload = this.data.waybillList.map(function (w) {
return {
tracking_number: w.tracking_number,
express_company: w.express_company || "",
express_name: w.express_name || "",
image_url: w.image_url || "",
};
});
await app.request({
url: "/api/driver/tasks/" + this.data.taskId + "/waybills",
method: "POST",
data: { waybills: waybillPayload },
});
}
var photoFiles = this.data.uploadedPhotos.map(function (p) {
return { file_url: p.path, file_name: p.name };
});
await app.request({
url: action.url,
method: "POST",
data: { photo_files: photoFiles, video_files: [], remark: this.data.actionRemark || action.successMessage },
});
wx.showToast({ title: action.successMessage, icon: "success" });
this.setData({ actionRemark: "", uploadedPhotos: [], waybillList: [] });
await this.loadDetail();
} catch (error) {
wx.showToast({ title: error.message || "操作失败", icon: "none" });
} finally {
this.setData({ actionLoading: false });
}
},
handleViewTracking: function () {
if (this.data.taskInfo && this.data.taskInfo.order_no) {
wx.switchTab({ url: "/pages/driver/order-tracking/order-tracking" });
}
},
handleBack: function () { wx.navigateBack({ delta: 1 }); },
});