diff --git a/src/views/business/components/waybill-manage-page.vue b/src/views/business/components/waybill-manage-page.vue
index 78f93be..fe7ec36 100644
--- a/src/views/business/components/waybill-manage-page.vue
+++ b/src/views/business/components/waybill-manage-page.vue
@@ -2057,6 +2057,100 @@
+
+
+
+
+
+
+
+
+
+
+
+ {{ row.disposalStatusName || '-' }}
+
+
+
+
+
+
+
+
+ 跟进
+
+
+ 编辑
+
+
+ 完成
+
+
+
+
+ 详情
+
+
+ 编辑
+
+
+
+
+
+
+
@@ -2356,6 +2450,284 @@
+
+
+
+
+
+
+ {{ waybillExceptionValue(field.prop) }}
+
+
+
+
+
+
+
+
+ {{
+ waybillExceptionValue('reporterName')
+ }}
+
+
+
+
+ {{
+ waybillExceptionValue('reportTime')
+ }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ waybillExceptionValue(field.prop) }}
+
+
+
+
+
+
+
+ {{ waybillExceptionValue(field.prop) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ file.name || '附件' + (index + 1) }}
+
+
+ -
+
+
+
+
+
+
+
+
+ 提交
+
+
+ 提交
+
+
+ 完成
+
+ 取消
+
+
+
({ label: item.label, value: item.value }));
+ },
+ waybillExceptionReasonOptions() {
+ return (
+ exceptionCategoryOptions.find(
+ item => item.value === this.waybillExceptionEditForm.exceptionType
+ )?.children || []
+ );
+ },
+ waybillExceptionPhotoUrls() {
+ return this.parseWaybillExceptionPhotos(this.waybillExceptionDetail.scenePhotos);
+ },
+ waybillExceptionUploadHeaders() {
+ return getUploadHeaders();
+ },
permissionList() {
return {
addBtn: this.canCreate,
@@ -4629,6 +5081,7 @@ export default {
this.waybillVoucherFolder = null;
this.waybillVoucherFolderView = false;
this.waybillVoucherImagesLoaded = false;
+ this.waybillExceptionRows = [];
const request =
typeof this.api.getDetail === 'function'
? this.api.getDetail(row.id)
@@ -4639,6 +5092,7 @@ export default {
this.detailRow = detail;
this.loadWaybillPunchRecords();
this.loadWaybillVoucherImages();
+ this.loadWaybillExceptions(detail);
if (detail.contractId) {
return getContractDetail(detail.contractId)
.then(contractRes => {
@@ -4668,6 +5122,206 @@ export default {
this.detailLoading = false;
});
},
+ // 异常处置列表接口不支持按 waybillId 精确过滤,只有 waybillNo 模糊匹配,
+ // 而运单号存在前缀包含(YD001 会命中 YD0011),故取回后必须再按 waybillId 精确筛一次。
+ // 多取一页降低「同前缀运单占满首页」导致漏数据的概率。
+ // 注意 String() 比较只是避免类型不一致,挡不住 int64 精度:运单 id 超出 JS 安全整数范围,
+ // 两个相差很小的 id 在响应 JSON 解析时就已被解析成同一个浮点值,此处无从分辨。
+ async loadWaybillExceptions(detail) {
+ const waybillId = detail?.id;
+ const waybillNo = detail?.waybillNo;
+ if (!waybillId || !waybillNo) {
+ this.waybillExceptionRows = [];
+ return;
+ }
+ this.waybillExceptionLoading = true;
+ try {
+ const res = await exceptionDisposalApi.getList(1, 100, { waybillNo });
+ const data = res?.data?.data || res?.data || res || {};
+ this.waybillExceptionRows = (data.records || [])
+ .filter(item => String(item.waybillId) === String(waybillId))
+ .map(this.decorateWaybillExceptionRow);
+ } catch (error) {
+ this.waybillExceptionRows = [];
+ } finally {
+ this.waybillExceptionLoading = false;
+ }
+ },
+ decorateWaybillExceptionRow(row) {
+ return {
+ ...row,
+ exceptionTypeName: this.waybillExceptionName(row.exceptionType),
+ disposalStatusName:
+ disposalStatusOptions.find(item => item.value === row.disposalStatus)?.label ||
+ row.disposalStatus ||
+ '-',
+ };
+ },
+ waybillExceptionName(value) {
+ if (!value) return '-';
+ // 一级存中文大类名(如「交通异常」),二级存子类名(如「货损」),两级都查
+ const group = exceptionCategoryOptions.find(item => item.value === value);
+ if (group) return group.label;
+ for (const item of exceptionCategoryOptions) {
+ const match = item.children.find(child => child.value === value);
+ if (match) return match.label;
+ }
+ return value;
+ },
+ async openWaybillException(row, mode) {
+ this.waybillExceptionDialog = {
+ visible: true,
+ mode,
+ loading: true,
+ submitting: false,
+ };
+ this.waybillExceptionFollowForm = { followContent: '', attachments: [] };
+ try {
+ const res = await exceptionDisposalApi.getDetail(row.id);
+ const detail = res?.data?.data || res?.data || res || {};
+ this.waybillExceptionDetail = this.decorateWaybillExceptionRow(detail);
+ if (mode === 'edit') {
+ this.waybillExceptionEditForm = {
+ exceptionType: this.waybillExceptionDetail.exceptionType || '',
+ exceptionReason: this.waybillExceptionDetail.exceptionReason || '',
+ reportDescription: this.waybillExceptionDetail.reportDescription || '',
+ scenePhotoList: this.parseWaybillExceptionPhotos(
+ this.waybillExceptionDetail.scenePhotos
+ ),
+ };
+ }
+ } finally {
+ this.waybillExceptionDialog.loading = false;
+ }
+ },
+ waybillExceptionValue(prop) {
+ const value = this.waybillExceptionDetail[prop];
+ return value === null || value === undefined || value === '' ? '-' : value;
+ },
+ handleWaybillExceptionTypeChange() {
+ // 一级类型变了,旧二级可能不属于新类型
+ const stillValid = this.waybillExceptionReasonOptions.some(
+ item => item.value === this.waybillExceptionEditForm.exceptionReason
+ );
+ if (!stillValid) {
+ this.waybillExceptionEditForm.exceptionReason = '';
+ }
+ },
+ beforeWaybillExceptionPhotoUpload(file) {
+ const validType = ['image/jpeg', 'image/png', 'image/jpg', 'image/bmp'].includes(file.type);
+ if (!validType) {
+ this.$message.error('仅支持 JPG、PNG、BMP 图片');
+ return false;
+ }
+ if (file.size / 1024 / 1024 >= 5) {
+ this.$message.error('图片大小不能超过 5MB');
+ return false;
+ }
+ return true;
+ },
+ handleWaybillExceptionPhotoSuccess(res) {
+ if (res.code === 200 && res.data) {
+ const url = res.data.link || res.data.url || res.data.domain || '';
+ if (url) {
+ this.waybillExceptionEditForm.scenePhotoList = [
+ ...this.waybillExceptionEditForm.scenePhotoList,
+ url,
+ ];
+ }
+ } else {
+ this.$message.error(res.msg || '上传失败');
+ }
+ },
+ handleWaybillExceptionPhotoError() {
+ this.$message.error('上传失败');
+ },
+ removeWaybillExceptionPhoto(index) {
+ const list = [...this.waybillExceptionEditForm.scenePhotoList];
+ list.splice(index, 1);
+ this.waybillExceptionEditForm.scenePhotoList = list;
+ },
+ parseWaybillExceptionPhotos(value) {
+ if (!value) return [];
+ try {
+ const parsed = JSON.parse(value);
+ if (Array.isArray(parsed)) return parsed.filter(Boolean);
+ } catch (error) {
+ return String(value)
+ .split(',')
+ .map(item => item.trim())
+ .filter(Boolean);
+ }
+ return [];
+ },
+ parseWaybillExceptionAttachments(value) {
+ if (!value) return [];
+ if (Array.isArray(value)) return value;
+ try {
+ const parsed = JSON.parse(value);
+ return Array.isArray(parsed) ? parsed.filter(item => item && item.url) : [];
+ } catch (error) {
+ return [];
+ }
+ },
+ async submitWaybillExceptionEdit() {
+ await this.$refs.waybillExceptionEditFormRef.validate();
+ this.waybillExceptionDialog.submitting = true;
+ try {
+ await exceptionDisposalApi.update({
+ id: this.waybillExceptionDetail.id,
+ exceptionType: this.waybillExceptionEditForm.exceptionType,
+ exceptionReason: this.waybillExceptionEditForm.exceptionReason,
+ reportDescription: this.waybillExceptionEditForm.reportDescription,
+ scenePhotos: this.waybillExceptionEditForm.scenePhotoList.join(','),
+ });
+ this.$message.success('编辑成功');
+ this.waybillExceptionDialog.visible = false;
+ this.refreshWaybillExceptions();
+ } finally {
+ this.waybillExceptionDialog.submitting = false;
+ }
+ },
+ async submitWaybillExceptionFollow() {
+ await this.$refs.waybillExceptionFollowFormRef.validate();
+ const followContent = (this.waybillExceptionFollowForm.followContent || '').trim();
+ const attachmentsJson = this.waybillExceptionFollowForm.attachments.length
+ ? JSON.stringify(this.waybillExceptionFollowForm.attachments)
+ : '';
+ // 后端要求二者至少有其一
+ if (!followContent && !attachmentsJson) {
+ this.$message.warning('请填写跟进说明或上传附件');
+ return;
+ }
+ this.waybillExceptionDialog.submitting = true;
+ try {
+ await exceptionDisposalApi.follow({
+ id: this.waybillExceptionDetail.id,
+ followContent,
+ attachmentsJson,
+ });
+ this.$message.success('跟进成功');
+ this.waybillExceptionDialog.visible = false;
+ this.refreshWaybillExceptions();
+ } finally {
+ this.waybillExceptionDialog.submitting = false;
+ }
+ },
+ async completeWaybillExceptionRow(row) {
+ await this.$confirm('确认完成该异常处置?', '提示', { type: 'warning' });
+ await exceptionDisposalApi.complete({ id: row.id });
+ this.$message.success('完成成功');
+ this.refreshWaybillExceptions();
+ },
+ async completeWaybillExceptionFromDialog() {
+ await this.$confirm('确认完成该异常处置?', '提示', { type: 'warning' });
+ await exceptionDisposalApi.complete({ id: this.waybillExceptionDetail.id });
+ this.$message.success('完成成功');
+ this.waybillExceptionDialog.visible = false;
+ this.refreshWaybillExceptions();
+ },
+ refreshWaybillExceptions() {
+ this.loadWaybillExceptions(this.detailRow);
+ },
closeDetail() {
this.destroyWaybillLocateMap();
this.destroyWaybillTrackMap();
@@ -10046,6 +10700,81 @@ export default {
padding-top: 12px;
}
+ &__exception-actions {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ }
+
+ &__exception-dialog {
+ padding: 4px 0 0;
+
+ :deep(.section-card + .section-card) {
+ margin-top: 16px;
+ }
+ }
+
+ &__exception-readonly {
+ color: #606266;
+ word-break: break-all;
+ }
+
+ &__exception-attachment-link {
+ display: block;
+ margin-bottom: 2px;
+ }
+
+ &__exception-photos {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 12px;
+ min-height: 80px;
+
+ :deep(.el-image) {
+ width: 96px;
+ height: 96px;
+ border: 1px solid #eff1f7;
+ }
+ }
+
+ &__exception-edit-photos {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 12px;
+ }
+
+ &__exception-edit-photo {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 4px;
+
+ :deep(.el-image) {
+ width: 96px;
+ height: 96px;
+ border: 1px solid #eff1f7;
+ }
+ }
+
+ &__exception-edit-photo-add {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 96px;
+ height: 96px;
+ border: 1px dashed #dcdfe6;
+ border-radius: 4px;
+ color: #909399;
+ cursor: pointer;
+
+ &:hover {
+ border-color: #409eff;
+ color: #409eff;
+ }
+ }
+
&__waybill-detail-summary {
:deep(.el-card__body) {
padding: 18px 22px;