1、调整导入运单

2、调整对账
This commit is contained in:
2026-09-10 02:01:28 +08:00
parent 70bb94a2e6
commit 6ff672211f
9 changed files with 272 additions and 38 deletions
@@ -1949,18 +1949,61 @@
label="批量补录"
name="batchSupplement"
>
<div
v-if="waybillVoucherFolderView"
class="waybill-manage-page__voucher-folder-toolbar"
>
<el-button text type="primary" @click="backToWaybillVoucherFolders">
返回文件夹
</el-button>
<span>{{ waybillVoucherFolderName(waybillVoucherFolder) }}</span>
</div>
<div
v-loading="waybillVoucherImagesLoading"
class="waybill-manage-page__voucher-image-grid"
>
<div
v-for="(image, index) in waybillVoucherImages"
:key="image.id || image.objectKey || index"
class="waybill-manage-page__voucher-image-item"
:title="image.imageName"
@click="previewWaybillVoucherImage(index)"
:key="
image.id ||
image.objectKey ||
(isWaybillVoucherFolder(image)
? `${image.voucherId || 'folder'}-${image.folderName || image.name || index}`
: index)
"
:class="[
'waybill-manage-page__voucher-image-item',
{
'waybill-manage-page__voucher-image-item--folder':
isWaybillVoucherFolder(image),
},
]"
:title="
isWaybillVoucherFolder(image)
? waybillVoucherFolderName(image)
: image.imageName || image.name
"
@click="
isWaybillVoucherFolder(image)
? openWaybillVoucherFolder(image)
: previewWaybillVoucherImage(index)
"
>
<el-image :src="image.url" fit="cover" lazy />
<el-image
:src="
isWaybillVoucherFolder(image)
? image.icon || '/img/文件夹.png'
: image.url
"
:fit="isWaybillVoucherFolder(image) ? 'contain' : 'cover'"
lazy
/>
<div
v-if="isWaybillVoucherFolder(image)"
class="waybill-manage-page__voucher-folder-name"
>
{{ waybillVoucherFolderName(image) }}
</div>
</div>
<el-empty
v-if="!waybillVoucherImagesLoading && !waybillVoucherImages.length"
@@ -3039,6 +3082,9 @@ export default {
waybillProcessDetailTab: 'punch',
waybillHasRelatedVoucher: false,
waybillVoucherImages: [],
waybillVoucherFolders: [],
waybillVoucherFolder: null,
waybillVoucherFolderView: false,
waybillVoucherImagesLoading: false,
waybillVoucherImagesLoaded: false,
waybillRouteChangeBox: false,
@@ -4118,6 +4164,9 @@ export default {
this.waybillProcessDetailTab = 'punch';
this.waybillHasRelatedVoucher = false;
this.waybillVoucherImages = [];
this.waybillVoucherFolders = [];
this.waybillVoucherFolder = null;
this.waybillVoucherFolderView = false;
this.waybillVoucherImagesLoaded = false;
const request =
typeof this.api.getDetail === 'function'
@@ -4222,6 +4271,11 @@ export default {
getProcessConfigVoucherImages(this.detailRow.id)
.then(res => {
this.waybillVoucherImages = extractRecords(res);
this.waybillVoucherFolders = this.waybillVoucherImages.filter(item =>
this.isWaybillVoucherFolder(item)
);
this.waybillVoucherFolder = null;
this.waybillVoucherFolderView = false;
if (this.waybillVoucherImages.length) this.waybillHasRelatedVoucher = true;
this.waybillVoucherImagesLoaded = true;
})
@@ -4229,11 +4283,49 @@ export default {
this.waybillVoucherImagesLoading = false;
});
},
isWaybillVoucherFolder(item) {
return Boolean(item && (item.isFolder === true || item.type === 'folder'));
},
waybillVoucherFolderName(folder) {
return folder?.folderName || folder?.name || folder?.plateNo || '凭证文件夹';
},
openWaybillVoucherFolder(folder) {
if (!this.detailRow.id || !this.isWaybillVoucherFolder(folder)) return;
if (!folder.voucherId || !folder.folderName) {
this.$message.warning('凭证文件夹信息不完整');
return;
}
this.waybillVoucherImagesLoading = true;
getProcessConfigVoucherImages(this.detailRow.id, {
voucherId: folder.voucherId,
folderName: folder.folderName,
})
.then(res => {
this.waybillVoucherFolder = folder;
this.waybillVoucherImages = extractRecords(res).filter(
item => !this.isWaybillVoucherFolder(item)
);
this.waybillVoucherFolderView = true;
})
.finally(() => {
this.waybillVoucherImagesLoading = false;
});
},
backToWaybillVoucherFolders() {
this.waybillVoucherImages = [...this.waybillVoucherFolders];
this.waybillVoucherFolder = null;
this.waybillVoucherFolderView = false;
},
previewWaybillVoucherImage(index) {
this.attachmentImagePreviewUrls = this.waybillVoucherImages
const imageRows = this.waybillVoucherImages.filter(
image => !this.isWaybillVoucherFolder(image)
);
const currentImage = this.waybillVoucherImages[index];
const imageIndex = imageRows.findIndex(image => image === currentImage);
this.attachmentImagePreviewUrls = imageRows
.map(image => image.url)
.filter(Boolean);
this.attachmentImagePreviewIndex = index;
this.attachmentImagePreviewIndex = imageIndex >= 0 ? imageIndex : 0;
this.attachmentImagePreviewVisible = this.attachmentImagePreviewUrls.length > 0;
},
buildWaybillRouteNodes(rows = []) {
@@ -8984,6 +9076,7 @@ export default {
}
&__voucher-image-item {
position: relative;
aspect-ratio: 1;
overflow: hidden;
cursor: pointer;
@@ -8995,6 +9088,42 @@ export default {
width: 100%;
height: 100%;
}
&--folder {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 6px;
padding: 12px;
background: #fff;
.el-image {
width: 64px;
height: 64px;
flex: none;
}
}
}
&__voucher-folder-toolbar {
display: flex;
align-items: center;
gap: 8px;
min-height: 32px;
margin-bottom: 8px;
color: #606266;
font-size: 13px;
}
&__voucher-folder-name {
width: 100%;
overflow: hidden;
color: #606266;
font-size: 13px;
text-align: center;
text-overflow: ellipsis;
white-space: nowrap;
}
&__route-change-dialog {
+1 -1
View File
@@ -1389,7 +1389,7 @@ export default {
this.page.total = result.total || 0;
this.data = result.records || [];
this.selectionClear();
this.refreshFundRiskStats({ ...params, ...this.query, allDept: this.allDept });
// this.refreshFundRiskStats({ ...params, ...this.query, allDept: this.allDept });
})
.finally(() => {
this.loading = false;
+10 -2
View File
@@ -22,7 +22,7 @@
<el-table-column prop="processStatus" label="处理状态" width="130" />
<el-table-column prop="createTime" label="创建时间" min-width="180" />
<el-table-column prop="updateTime" label="更新时间" min-width="180" />
<el-table-column label="操作" width="220" fixed="right"><template #default="{ row }"><div class="voucher-folder-page__actions"><el-link v-if="row.matched !== 1" type="primary" @click="openReplace(row)">单个上传</el-link><el-link type="primary" @click="openViewer(row)">查看</el-link><el-link type="danger" @click="removeFolder(row)">删除</el-link></div></template></el-table-column>
<el-table-column label="操作" width="220" fixed="right"><template #default="{ row }"><div class="voucher-folder-page__actions"><el-link v-if="isSuperAdmin && row.matched !== 1" type="primary" @click="openReplace(row)">单个上传</el-link><el-link type="primary" @click="openViewer(row)">查看</el-link><el-link type="danger" @click="removeFolder(row)">删除</el-link></div></template></el-table-column>
</el-table>
<div class="voucher-folder-page__pagination"><el-pagination v-model:current-page="page.current" v-model:page-size="page.size" :total="page.total" :page-sizes="[10, 20, 50]" layout="total, prev, pager, next, sizes" @current-change="load" @size-change="load" /></div>
</section>
@@ -39,15 +39,23 @@
</template>
<script setup>
import { onMounted, reactive, ref } from 'vue';
import { computed, onMounted, reactive, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useStore } from 'vuex';
import { ElMessage, ElMessageBox } from 'element-plus';
import md5 from 'js-md5';
import * as api from '@/api/business/voucher-manage';
const route = useRoute();
const router = useRouter();
const store = useStore();
const voucherId = route.query.id;
const isSuperAdmin = computed(() => {
const authority = store.getters.userInfo?.authority;
return Array.isArray(authority)
? authority.includes('admin')
: String(authority || '').includes('admin');
});
const loading = ref(false), rows = ref([]), voucher = ref({ voucherBatchNo: route.query.batchNo || '' });
const page = reactive({ current: 1, size: 10, total: 0 });
const query = reactive({ plateNo: '', matched: undefined });
+100 -12
View File
@@ -90,9 +90,9 @@
width="100"
/><el-table-column
prop="relatedWaybillCount"
label="已关联运单"
width="115"
/><el-table-column prop="unRelatedWaybillCount" label="未关联运单" width="115" />
label="凭证已关联数量"
width="125"
/><el-table-column prop="unRelatedWaybillCount" label="凭证未关联数量" width="125" />
<el-table-column prop="createTime" label="创建时间" min-width="170" />
<el-table-column
prop="auditStatus"
@@ -100,7 +100,16 @@
width="120"
fixed="right"
align="center"
/><el-table-column label="操作" width="320" fixed="right" align="left"
><template #default="{ row }"
><span class="voucher-manage-page__audit-status"
><span>{{ row.auditStatus }}</span
><el-tooltip
v-if="row.auditStatus === '审核驳回' && hasRejectReason(row)"
:content="row.rejectReason"
placement="top"
><el-icon class="voucher-manage-page__audit-reject-icon"><WarnTriangleFilled /></el-icon
></el-tooltip></span></template
></el-table-column><el-table-column label="操作" width="320" fixed="right" align="center"
><template #default="{ row }"
><div class="voucher-manage-page__actions">
<el-link
@@ -128,12 +137,12 @@
><el-link
v-if="row.auditStatus === '审核驳回'"
type="primary"
@click="openUpload(row, 'changeBatch')"
@click="openBatchDialog(row)"
>更换运单批次</el-link
><el-link
v-if="row.processStatus !== '处理完成' || row.auditStatus === '待审核'"
v-if="(row.processStatus !== '处理完成' || row.auditStatus === '待审核') && row.auditStatus !== '审核驳回'"
type="primary"
@click="openUpload(row)"
@click="openBatchDialog(row)"
>更换运单批次</el-link
><el-link
v-if="row.processStatus === '上传中' || row.auditStatus === '审核驳回'"
@@ -328,7 +337,11 @@
placeholder="请输入" /></el-form-item></el-col
><el-col :span="12"
><el-form-item label="运单数"
><el-input-number v-model="batchQuery.waybillCount" :min="0" /></el-form-item></el-col
><el-input
v-model="batchQuery.waybillCount"
clearable
placeholder="请输入"
@input="handleWaybillCountInput" /></el-form-item></el-col
></el-row>
<div class="voucher-manage-page__search-actions">
<el-button type="primary" @click="loadBatches">查询</el-button
@@ -373,7 +386,7 @@
</div>
<template #footer
><el-button @click="batchVisible = false">取消</el-button
><el-button type="primary" @click="confirmBatches">确认</el-button></template
><el-button type="primary" :loading="batchSaving" @click="confirmBatches">确认</el-button></template
>
</el-dialog>
@@ -484,6 +497,7 @@ import { useRouter } from 'vue-router';
import { useStore } from 'vuex';
import md5 from 'js-md5';
import { ElMessage, ElMessageBox } from 'element-plus';
import { WarnTriangleFilled } from '@element-plus/icons-vue';
import { getList as getProjectList } from '@/api/business/project-apply';
import * as api from '@/api/business/voucher-manage';
import SectionCard from '@/components/section-card/main.vue';
@@ -499,6 +513,12 @@ const hasPermission = code => {
if (isAdmin.value) return true;
return permission.value?.[code] === true;
};
const hasRejectReason = row => {
const reason = row?.rejectReason;
return typeof reason === 'string'
? reason.trim().length > 0
: reason !== undefined && reason !== null;
};
// 判断当前用户是否为承运商(顶级组织是否为"外部组织")
const isCarrier = ref(false);
@@ -521,6 +541,9 @@ const loading = ref(false),
detailRow = ref({}),
uploadVisible = ref(false),
batchVisible = ref(false),
batchDialogMode = ref('upload'),
batchTarget = ref(),
batchSaving = ref(false),
progressVisible = ref(false),
uploadFormRef = ref(),
uploading = ref(false),
@@ -862,9 +885,16 @@ watch(uploadVisible, visible => {
// 兜底处理:右上角、遮罩、Esc 直接修改 v-model 时仍必须停止上传。
if (!visible && !closingUploadDialog.value) void stopCurrentUpload(editing.fileTaskId);
});
const openBatchDialog = async () => {
const openBatchDialog = async row => {
batchDialogMode.value = row ? 'change' : 'upload';
batchTarget.value = row;
batchSelection.value = [];
batchVisible.value = true;
await loadBatches();
if (row?.waybillBatchNo) {
const currentBatch = batchRows.value.find(item => item.batchNo === row.waybillBatchNo);
if (currentBatch) batchSelection.value = [currentBatch];
}
};
const loadBatches = async () => {
const res = await api.getWaybillBatches(batchPage.current, batchPage.size, {
@@ -878,13 +908,56 @@ const loadBatches = async () => {
batchRows.value = data.records || [];
batchPage.total = data.total || 0;
};
const handleWaybillCountInput = value => {
// 只允许输入正整数
const filtered = value.replace(/[^\d]/g, '');
if (filtered !== value) {
batchQuery.waybillCount = filtered;
}
// 移除前导零
if (filtered.length > 1 && filtered.startsWith('0')) {
batchQuery.waybillCount = filtered.replace(/^0+/, '');
}
};
const resetBatchQuery = () => {
Object.assign(batchQuery, { batchNo: '', createUser: '', waybillCount: undefined });
batchCreateRange.value = [];
batchPage.current = 1;
loadBatches();
};
const selectBatch = row => {
const updateWaybillBatch = async (target, selected) => {
if (!target?.id) {
ElMessage.error('缺少凭证批次信息');
return false;
}
if (!selected?.batchNo) {
ElMessage.warning('请选择运单批次');
return false;
}
batchSaving.value = true;
try {
await api.changeWaybillBatch({
voucherId: target.id,
waybillImportBatchIds: [selected.id],
});
target.waybillBatchNo = selected.batchNo;
batchVisible.value = false;
ElMessage.success('运单批次更换成功,已提交重新匹配任务');
await load();
return true;
} catch (error) {
ElMessage.error(error.message || '运单批次更换失败');
return false;
} finally {
batchSaving.value = false;
}
};
const selectBatch = async row => {
if (batchDialogMode.value === 'change') {
batchSelection.value = [row];
await updateWaybillBatch(batchTarget.value, row);
return;
}
const batches = new Map();
selectedBatches.value.forEach(item => {
if (!batches.has(item.batchNo)) batches.set(item.batchNo, item);
@@ -894,7 +967,12 @@ const selectBatch = row => {
editing.waybillImportBatchIds = selectedBatches.value.map(item => item.id);
batchVisible.value = false;
};
const confirmBatches = () => {
const confirmBatches = async () => {
if (batchDialogMode.value === 'change') {
const selected = batchSelection.value[0];
await updateWaybillBatch(batchTarget.value, selected);
return;
}
const batches = new Map();
batchSelection.value.forEach(item => {
if (!batches.has(item.batchNo)) batches.set(item.batchNo, item);
@@ -1240,9 +1318,19 @@ load();
padding: 0;
min-height: auto;
}
&__audit-status {
display: inline-flex;
align-items: center;
}
&__audit-reject-icon {
margin-left: 4px;
color: #f56c6c;
cursor: help;
}
&__actions {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 2px 12px;
}
&__pagination {
@@ -50,6 +50,9 @@
:precision="2"
disabled
/>
<span v-else-if="field.prop === 'reconciliationMode'">
{{ reconciliationModeLabel }}
</span>
<span v-else>{{ displayValue(form[field.prop]) }}</span>
</el-form-item>
</el-col>
@@ -1084,6 +1087,7 @@ export default {
const params = {
settlementType: this.settlementType,
keyword: this.formalDialog.query.keyword,
approvalStatus: 'draft',
};
const data = this.unwrapData(
await api.getFormalOptions(
@@ -1098,7 +1102,7 @@ export default {
const fallback = await formalSettlementApi.getList(
this.formalDialog.page.current,
this.formalDialog.page.size,
{ ...params, approvalStatus: 'approved' }
params
);
const fallbackData = this.unwrapData(fallback);
rows = fallbackData.records || [];