1、新增结算模块
2、调整业务模块 3、调整客商模块
This commit is contained in:
@@ -101,19 +101,20 @@
|
||||
</div>
|
||||
<div class="insurance-record-page__upload-text">
|
||||
<strong>上传图片,自动识别填写</strong>
|
||||
<span>支持JPG、PNG、PDF上传</span>
|
||||
<span>支持JPG、PNG、BMP上传</span>
|
||||
</div>
|
||||
</div>
|
||||
<el-upload
|
||||
v-if="hasPermission('insurance_record_ocr')"
|
||||
class="insurance-record-page__upload-action"
|
||||
:action="recognizeUrl"
|
||||
:data="recognizeData"
|
||||
action="/api/blade-resource/oss/endpoint/put-file"
|
||||
name="file"
|
||||
:headers="uploadHeaders"
|
||||
:show-file-list="false"
|
||||
:on-success="handleRecognizeSuccess"
|
||||
:on-error="handleRecognizeError"
|
||||
accept=".jpg,.jpeg,.png,.pdf"
|
||||
:on-success="handlePolicyUploadSuccess"
|
||||
:on-error="handlePolicyUploadError"
|
||||
:before-upload="validateOcrUpload"
|
||||
accept=".jpg,.jpeg,.png,.bmp"
|
||||
>
|
||||
<el-button class="insurance-record-page__upload-button" type="primary" plain>
|
||||
上传文件
|
||||
@@ -131,19 +132,20 @@
|
||||
</div>
|
||||
<div class="insurance-record-page__upload-text">
|
||||
<strong>上传图片,自动识别填写</strong>
|
||||
<span>支持JPG、PNG、PDF上传</span>
|
||||
<span>支持JPG、PNG、BMP上传</span>
|
||||
</div>
|
||||
</div>
|
||||
<el-upload
|
||||
v-if="hasPermission('insurance_record_ocr')"
|
||||
class="insurance-record-page__upload-action"
|
||||
:action="recognizeUrl"
|
||||
:data="recognizeData"
|
||||
action="/api/blade-resource/oss/endpoint/put-file"
|
||||
name="file"
|
||||
:headers="uploadHeaders"
|
||||
:show-file-list="false"
|
||||
:on-success="handleRecognizeSuccess"
|
||||
:on-error="handleRecognizeError"
|
||||
accept=".jpg,.jpeg,.png,.pdf"
|
||||
:on-success="handlePolicyUploadSuccess"
|
||||
:on-error="handlePolicyUploadError"
|
||||
:before-upload="validateOcrUpload"
|
||||
accept=".jpg,.jpeg,.png,.bmp"
|
||||
>
|
||||
<el-button class="insurance-record-page__upload-button" type="primary" plain>
|
||||
上传文件
|
||||
@@ -171,9 +173,17 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { add, getDetail, getList, remove, update } from '@/api/vehicle/insurance-record';
|
||||
import {
|
||||
add,
|
||||
getDetail,
|
||||
getList,
|
||||
recognizeGeneralOcr,
|
||||
remove,
|
||||
update,
|
||||
} from '@/api/vehicle/insurance-record';
|
||||
import { getList as getVehicleList } from '@/api/transportCapacity/transport-vehicle';
|
||||
import { getList as getShipList } from '@/api/transportCapacity/transport-ship';
|
||||
import { getList as getOcrTemplateList } from '@/api/base/insurance-ocr-template';
|
||||
import { getDeptTree } from '@/api/system/dept';
|
||||
import { exportBlob } from '@/api/common';
|
||||
import { downloadXls } from '@/utils/util';
|
||||
@@ -182,6 +192,7 @@ import { getToken } from '@/utils/auth';
|
||||
import { getUploadHeaders } from '@/utils/upload';
|
||||
import { normalizeSearchRangeParams } from '@/utils/search-range';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { ElLoading } from 'element-plus';
|
||||
import { excelOption, option } from '@/option/vehicle/insurance-record';
|
||||
import NProgress from 'nprogress';
|
||||
import 'nprogress/nprogress.css';
|
||||
@@ -210,10 +221,12 @@ export default {
|
||||
},
|
||||
selectionList: [],
|
||||
data: [],
|
||||
ocrTemplateOptions: [],
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.initDeptTree();
|
||||
this.initOcrTemplateOptions();
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['permission', 'userInfo']),
|
||||
@@ -239,15 +252,6 @@ export default {
|
||||
});
|
||||
return ids.join(',');
|
||||
},
|
||||
recognizeUrl() {
|
||||
return '/blade-transport/insurance-record/recognize';
|
||||
},
|
||||
recognizeData() {
|
||||
return {
|
||||
vehicleType: this.form.vehicleType || '车辆',
|
||||
ocrTemplate: this.form.ocrTemplate,
|
||||
};
|
||||
},
|
||||
uploadHeaders() {
|
||||
return getUploadHeaders();
|
||||
},
|
||||
@@ -278,6 +282,22 @@ export default {
|
||||
column.dicData = res.data.data;
|
||||
});
|
||||
},
|
||||
initOcrTemplateOptions() {
|
||||
getOcrTemplateList(1, 100, {})
|
||||
.then(res => {
|
||||
const records = res.data.data?.records || [];
|
||||
this.ocrTemplateOptions = records.map(item => ({
|
||||
label: item.name,
|
||||
value: item.name,
|
||||
mappingConfig: item.mappingConfig || '[]',
|
||||
}));
|
||||
const templateColumn = this.findColumn(this.option.column, 'ocrTemplate');
|
||||
if (templateColumn) templateColumn.dicData = this.ocrTemplateOptions;
|
||||
})
|
||||
.catch(() => {
|
||||
this.ocrTemplateOptions = [];
|
||||
});
|
||||
},
|
||||
fetchVehicleOptions(queryString, callback) {
|
||||
const vehicleType = this.form.vehicleType || '车辆';
|
||||
const request = vehicleType === '船舶' ? getShipList : getVehicleList;
|
||||
@@ -402,7 +422,7 @@ export default {
|
||||
this.boxType = type;
|
||||
if (type === 'add') {
|
||||
this.form.vehicleType = '车辆';
|
||||
this.form.ocrTemplate = '紫金-机动车交强险';
|
||||
this.form.ocrTemplate = this.ocrTemplateOptions[0]?.value || '';
|
||||
}
|
||||
if (['edit', 'view'].includes(type)) {
|
||||
this.isDetailLoading = true;
|
||||
@@ -499,20 +519,143 @@ export default {
|
||||
downloadXls(res.data, '保险记录模板.xlsx');
|
||||
});
|
||||
},
|
||||
handleRecognizeSuccess(res) {
|
||||
if (res && res.success && res.data) {
|
||||
this.form = {
|
||||
...this.form,
|
||||
...res.data,
|
||||
vehicleType: res.data.vehicleType || this.form.vehicleType || '车辆',
|
||||
};
|
||||
this.$message.success('识别完成');
|
||||
} else {
|
||||
this.$message.warning((res && res.msg) || '识别失败,请手动填写');
|
||||
handlePolicyUploadSuccess(res) {
|
||||
if (res?.code !== 200 || !res.data) {
|
||||
this.$message.error(res?.msg || '上传失败');
|
||||
return;
|
||||
}
|
||||
const imageUrl = res.data.link || res.data.url || res.data.domain || '';
|
||||
if (!imageUrl) {
|
||||
this.$message.warning('文件上传成功,未获取到文件地址,无法自动识别');
|
||||
return;
|
||||
}
|
||||
this.form.policyFile = imageUrl;
|
||||
this.recognizePolicyFile(imageUrl);
|
||||
},
|
||||
handlePolicyUploadError() {
|
||||
this.$message.error('上传失败');
|
||||
},
|
||||
recognizePolicyFile(imageUrl) {
|
||||
const loading = ElLoading.service({
|
||||
lock: true,
|
||||
text: '保单识别中',
|
||||
background: 'rgba(255, 255, 255, 0.7)',
|
||||
});
|
||||
recognizeGeneralOcr(imageUrl)
|
||||
.then(res => {
|
||||
const result = res.data.data?.result || {};
|
||||
const filledCount = this.applyTemplateRecognition(result);
|
||||
if (filledCount > 0) {
|
||||
this.$message.success(`保单识别完成,已填充${filledCount}项`);
|
||||
} else {
|
||||
this.$message.warning('保单识别完成,未匹配到模板字段,请手动填写保险信息');
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.$message.warning('文件上传成功,自动识别失败,请手动填写保险信息');
|
||||
})
|
||||
.finally(() => {
|
||||
loading.close();
|
||||
});
|
||||
},
|
||||
applyTemplateRecognition(data = {}) {
|
||||
const template = this.ocrTemplateOptions.find(item => item.value === this.form.ocrTemplate);
|
||||
const mappings = this.parseTemplateMappings(template?.mappingConfig);
|
||||
const words = this.getGeneralOcrWords(data);
|
||||
const fieldPropMap = {
|
||||
保险类型: 'insuranceType',
|
||||
保单号: 'policyNo',
|
||||
开始日期: 'startDate',
|
||||
结束日期: 'endDate',
|
||||
保额: 'insuredAmount',
|
||||
保费: 'premium',
|
||||
发票号: 'invoiceNo',
|
||||
开票日期: 'invoiceDate',
|
||||
备注: 'remark',
|
||||
};
|
||||
let filledCount = 0;
|
||||
mappings.forEach(item => {
|
||||
const prop = fieldPropMap[item.key];
|
||||
const value = this.getMappedOcrValue(words, item.value);
|
||||
if (!prop || !value) return;
|
||||
this.form[prop] = this.normalizeRecognizedValue(prop, value);
|
||||
filledCount += 1;
|
||||
});
|
||||
return filledCount;
|
||||
},
|
||||
parseTemplateMappings(mappingConfig) {
|
||||
try {
|
||||
const mappings = JSON.parse(mappingConfig || '[]');
|
||||
return Array.isArray(mappings)
|
||||
? mappings.filter(
|
||||
item => String(item?.key || '').trim() && String(item?.value || '').trim()
|
||||
)
|
||||
: [];
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
handleRecognizeError() {
|
||||
this.$message.warning('识别失败,请手动填写');
|
||||
getGeneralOcrWords(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);
|
||||
},
|
||||
getMappedOcrValue(words = [], mappingLabel = '') {
|
||||
const label = String(mappingLabel || '').trim();
|
||||
if (!label) return '';
|
||||
const escapedLabel = label.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
const normalizedWords = words.map(word => String(word || '').trim()).filter(Boolean);
|
||||
const exactIndex = normalizedWords.findIndex(word =>
|
||||
new RegExp(`^${escapedLabel}\\s*[::]?$`).test(word)
|
||||
);
|
||||
if (exactIndex >= 0) return normalizedWords[exactIndex + 1] || '';
|
||||
const valueLine = normalizedWords.find(word =>
|
||||
new RegExp(`${escapedLabel}\\s*[::]?\\s*(.+)$`).test(word)
|
||||
);
|
||||
if (!valueLine) return '';
|
||||
return valueLine.replace(new RegExp(`^.*?${escapedLabel}\\s*[::]?\\s*`), '').trim();
|
||||
},
|
||||
normalizeRecognizedValue(prop, value) {
|
||||
const text = String(value || '').trim();
|
||||
if (['startDate', 'endDate', 'invoiceDate'].includes(prop)) {
|
||||
const match = text.match(/(\d{4})[-/.年](\d{1,2})[-/.月](\d{1,2})日?/);
|
||||
return match
|
||||
? `${match[1]}-${match[2].padStart(2, '0')}-${match[3].padStart(2, '0')}`
|
||||
: text;
|
||||
}
|
||||
if (['insuredAmount', 'premium'].includes(prop)) {
|
||||
return text.replace(/,/g, '').replace(/[^\d.]/g, '');
|
||||
}
|
||||
if (prop === 'insuranceType') {
|
||||
const typeRules = [
|
||||
{ value: '交强险', pattern: /(交强险|交通事故责任强制保险)/ },
|
||||
{ value: '商业险', pattern: /(商业险|商业保险)/ },
|
||||
{ value: '承运人责任险', pattern: /承运人.*责任/ },
|
||||
{ value: '货运险', pattern: /(货运险|货物运输保险)/ },
|
||||
{ value: '船舶险', pattern: /(船舶险|船舶保险)/ },
|
||||
];
|
||||
return typeRules.find(item => item.pattern.test(text))?.value || text;
|
||||
}
|
||||
if (prop === 'remark') return text.slice(0, 200);
|
||||
return text;
|
||||
},
|
||||
validateOcrUpload(file) {
|
||||
if (!this.form.ocrTemplate) {
|
||||
this.$message.warning('请先选择OCR识别模板');
|
||||
return false;
|
||||
}
|
||||
const validType = ['image/jpeg', 'image/png', 'image/jpg', 'image/bmp'].includes(file.type);
|
||||
const validSize = file.size / 1024 / 1024 < 5;
|
||||
if (!validType) this.$message.error('仅支持 JPG、PNG、BMP 图片');
|
||||
if (!validSize) this.$message.error('图片大小不能超过 5MB');
|
||||
return validType && validSize;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user