1、新增结算模块

2、调整业务模块
3、调整客商模块
This commit is contained in:
2026-08-20 06:33:01 +08:00
parent 4199255186
commit fd5b1484e6
59 changed files with 11745 additions and 1104 deletions
+157 -6
View File
@@ -500,7 +500,7 @@
:headers="uploadHeaders"
large
class-prefix="ship"
@success="url => setImage('ownershipCertImage', url)"
@success="url => handleShipCertificateUploadSuccess('ownershipCertImage', url)"
/>
</el-col>
<el-col :span="8">
@@ -512,7 +512,7 @@
:headers="uploadHeaders"
large
class-prefix="ship"
@success="url => setImage('safetyCertImage', url)"
@success="url => handleShipCertificateUploadSuccess('safetyCertImage', url)"
/>
</el-col>
<el-col :span="8">
@@ -524,7 +524,7 @@
:headers="uploadHeaders"
large
class-prefix="ship"
@success="url => setImage('nationalityCertImage', url)"
@success="url => handleShipCertificateUploadSuccess('nationalityCertImage', url)"
/>
</el-col>
<el-col :span="8">
@@ -536,7 +536,7 @@
:headers="uploadHeaders"
large
class-prefix="ship"
@success="url => setImage('safeManningCertImage', url)"
@success="url => handleShipCertificateUploadSuccess('safeManningCertImage', url)"
/>
</el-col>
<el-col :span="8">
@@ -551,7 +551,7 @@
:headers="uploadHeaders"
large
class-prefix="ship"
@success="url => setImage('leaseContractImage', url)"
@success="url => handleShipCertificateUploadSuccess('leaseContractImage', url)"
/>
</el-col>
<el-col :span="8">
@@ -563,7 +563,7 @@
:headers="uploadHeaders"
large
class-prefix="ship"
@success="url => setImage('businessTransportCertImage', url)"
@success="url => handleShipCertificateUploadSuccess('businessTransportCertImage', url)"
/>
</el-col>
</el-row>
@@ -582,6 +582,7 @@
<script>
import { mapGetters } from 'vuex';
import NProgress from 'nprogress';
import { ElLoading } from 'element-plus';
import { InfoFilled } from '@element-plus/icons-vue';
import { option } from '@/option/transportCapacity/transport-ship';
import { getDeptTree } from '@/api/system/dept';
@@ -592,6 +593,7 @@ import {
remove,
changeStatus,
getExpiryStat,
recognizeBaiduOcr,
} from '@/api/transportCapacity/transport-ship';
import { exportBlob } from '@/api/common';
import { getToken } from '@/utils/auth';
@@ -893,6 +895,155 @@ export default {
this.shipForm[prop] = url;
this.$refs.shipForm?.validateField(prop);
},
handleShipCertificateUploadSuccess(prop, url) {
this.setImage(prop, url);
const documentNameMap = {
ownershipCertImage: '船舶所有权证书',
safetyCertImage: '内河船舶安全与环保证书',
nationalityCertImage: '船舶国籍证书',
safeManningCertImage: '内河船舶最低安全配员证书',
leaseContractImage: '光船租赁登记证书',
businessTransportCertImage: '船舶营业运输证',
};
this.recognizeBaiduOcr(url, documentNameMap[prop], data => {
this.applyShipCertificateRecognition(prop, data);
});
},
recognizeBaiduOcr(url, documentName, applyRecognition) {
if (!url) {
this.$message.warning(`${documentName}图片上传成功,未获取到图片地址,无法自动识别`);
return;
}
const loading = ElLoading.service({
lock: true,
text: `${documentName}识别中`,
background: 'rgba(255, 255, 255, 0.7)',
});
recognizeBaiduOcr(url, 'general')
.then(res => {
applyRecognition(res.data.data?.result || {});
this.$message.success(`${documentName}识别完成`);
})
.catch(() => {
this.$message.warning(`${documentName}图片上传成功,自动识别失败,请手动填写证书信息`);
})
.finally(() => {
loading.close();
});
},
applyShipCertificateRecognition(prop, data = {}) {
const words = this.getBaiduOcrWords(data);
switch (prop) {
case 'ownershipCertImage':
this.fillShipCertificateText(words, 'ownershipRegistrationNo', ['登记号码', '登记号']);
this.fillShipCertificateText(words, 'initialRegistrationNo', ['初次登记号码', '初始登记号码']);
this.fillShipCertificateText(words, 'shipOwner', ['船舶所有人', '所有人']);
this.fillShipCertificateText(words, 'shipIdentifierNo', ['船舶识别号', '船舶识别号码']);
this.fillShipCertificateDate(words, 'ownershipAcquisitionDate', ['取得所有权日期', '取得日期']);
break;
case 'safetyCertImage':
this.fillShipCertificateNumber(words, 'grossTonnage', ['总吨', '总吨位']);
this.fillShipCertificateNumber(words, 'netTonnage', ['净吨', '净吨位']);
this.fillShipCertificateText(words, 'shipInspectionNo', ['船检登记号', '船检证书号']);
this.fillShipCertificateText(words, 'shipType', ['船舶类型']);
break;
case 'nationalityCertImage':
this.fillShipCertificateDateRange(
words,
'nationalityCertStartDate',
'nationalityCertEndDate',
'nationalityCertLongTerm'
);
break;
case 'safeManningCertImage':
this.fillShipCertificateDateRange(
words,
'safeManningCertStartDate',
'safeManningCertEndDate',
'safeManningCertLongTerm'
);
break;
case 'leaseContractImage':
this.fillShipCertificateDate(words, 'leaseStartDate', ['起租日期', '租赁开始日期']);
this.fillShipCertificateDate(words, 'leaseEndDate', ['终止日期', '租赁终止日期']);
this.fillShipCertificateText(words, 'shipLessee', ['船舶承租人', '承租人']);
break;
case 'businessTransportCertImage':
this.fillShipCertificateText(words, 'businessTransportCertNo', ['证书编号', '运输证号']);
this.fillShipCertificateDate(words, 'businessTransportCertIssueDate', ['发证日期']);
this.fillShipCertificateDate(words, 'businessTransportCertEndDate', ['有效期至', '有效期限']);
this.fillShipCertificateText(words, 'shipOperator', ['船舶经营人', '经营人']);
break;
default:
break;
}
},
getBaiduOcrWords(data = {}) {
const wordsResult = data.words_result || data.wordsResult || {};
if (Array.isArray(wordsResult)) {
return wordsResult.map(item => item.words || item.value || '').filter(Boolean);
}
return Object.entries(wordsResult)
.flatMap(([key, value]) => [key, typeof value === 'object' ? value.words || value.value || '' : value])
.filter(Boolean);
},
getShipCertificateOcrValue(words = [], labels = []) {
const normalizedWords = words.map(word => String(word || '').trim()).filter(Boolean);
for (const label of labels) {
const labelIndex = normalizedWords.findIndex(word =>
new RegExp(`^${label}\\s*[:]?$`).test(word)
);
if (labelIndex >= 0 && normalizedWords[labelIndex + 1]) {
return normalizedWords[labelIndex + 1];
}
const valueLine = normalizedWords.find(word =>
new RegExp(`${label}\\s*[:]?\\s*(.+)$`).test(word)
);
if (valueLine) {
return valueLine.replace(new RegExp(`^.*${label}\\s*[:]?\\s*`), '').trim();
}
}
return '';
},
fillShipCertificateText(words, prop, labels) {
const value = this.getShipCertificateOcrValue(words, labels);
if (value) this.shipForm[prop] = value.replace(/\s/g, '');
},
fillShipCertificateNumber(words, prop, labels) {
const value = this.getShipCertificateOcrValue(words, labels);
const number = Number(String(value || '').replace(/[^\d.]/g, ''));
if (!Number.isNaN(number) && value) this.shipForm[prop] = number;
},
fillShipCertificateDate(words, prop, labels) {
const value = this.getShipCertificateOcrValue(words, labels);
const date = this.getShipCertificateDates(value)[0];
if (date) this.shipForm[prop] = date;
},
fillShipCertificateDateRange(words, startProp, endProp, longTermProp) {
const text =
this.getShipCertificateOcrValue(words, ['证书有效期', '有效期限', '有效期至', '有效期']) ||
words.join(' ');
if (/(长期|永久)/.test(text)) {
this.shipForm[longTermProp] = 1;
this.shipForm[startProp] = '';
this.shipForm[endProp] = '';
return;
}
const dateList = this.getShipCertificateDates(text);
if (dateList[0] && dateList[1]) {
this.shipForm[startProp] = dateList[0];
this.shipForm[endProp] = dateList[dateList.length - 1];
this.shipForm[longTermProp] = 0;
}
},
getShipCertificateDates(value = '') {
return (String(value || '').match(/\d{4}[-/.年]\d{1,2}[-/.月]\d{1,2}日?/g) || []).map(
date => {
const [, year, month, day] = date.match(/(\d{4})[-/.年](\d{1,2})[-/.月](\d{1,2})/) || [];
return year ? `${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')}` : '';
}
);
},
handleSubmit() {
this.$refs.shipForm.validate(valid => {
if (!valid) return;