875 lines
27 KiB
Vue
875 lines
27 KiB
Vue
<template>
|
||
<basic-container class="insurance-record-page">
|
||
<avue-crud
|
||
:option="option"
|
||
:table-loading="loading"
|
||
:data="data"
|
||
v-model:page="page"
|
||
:permission="permissionList"
|
||
:before-open="beforeOpen"
|
||
v-model="form"
|
||
ref="crud"
|
||
@row-update="rowUpdate"
|
||
@row-save="rowSave"
|
||
@row-del="rowDel"
|
||
@search-change="searchChange"
|
||
@search-reset="searchReset"
|
||
@selection-change="selectionChange"
|
||
@current-change="currentChange"
|
||
@size-change="sizeChange"
|
||
@refresh-change="refreshChange"
|
||
@on-load="onLoad"
|
||
>
|
||
<template #menu-left>
|
||
<el-button
|
||
type="primary"
|
||
icon="el-icon-upload"
|
||
plain
|
||
v-if="hasPermission('insurance_record_import')"
|
||
@click="handleImport"
|
||
>批量导入
|
||
</el-button>
|
||
<el-button
|
||
type="primary"
|
||
icon="el-icon-download"
|
||
plain
|
||
v-if="hasPermission('insurance_record_template')"
|
||
@click="handleTemplate"
|
||
>下载模板
|
||
</el-button>
|
||
<el-button
|
||
type="primary"
|
||
icon="el-icon-download"
|
||
plain
|
||
v-if="hasPermission('insurance_record_export')"
|
||
@click="handleExport"
|
||
>批量导出
|
||
</el-button>
|
||
<el-button
|
||
type="danger"
|
||
icon="el-icon-delete"
|
||
plain
|
||
v-if="hasPermission('insurance_record_delete')"
|
||
@click="handleDelete"
|
||
>批量删除
|
||
</el-button>
|
||
</template>
|
||
<template #vehicleNo="{ row, index }">
|
||
<el-button type="primary" link @click="$refs.crud.rowView(row, index)">
|
||
{{ row.vehicleNo }}
|
||
</el-button>
|
||
</template>
|
||
<template #vehicleNoForm>
|
||
<el-select
|
||
v-model="form.vehicleNo"
|
||
:disabled="boxType === 'view'"
|
||
:loading="vehicleOptionsLoading"
|
||
:placeholder="vehicleNoPlaceholder"
|
||
clearable
|
||
filterable
|
||
remote
|
||
:remote-method="loadVehicleOptions"
|
||
@visible-change="handleVehicleSelectVisible"
|
||
>
|
||
<el-option
|
||
v-for="item in vehicleOptions"
|
||
:key="item.value"
|
||
:label="item.label"
|
||
:value="item.value"
|
||
/>
|
||
</el-select>
|
||
</template>
|
||
<template #vehicleNo-form>
|
||
<el-select
|
||
v-model="form.vehicleNo"
|
||
:disabled="boxType === 'view'"
|
||
:loading="vehicleOptionsLoading"
|
||
:placeholder="vehicleNoPlaceholder"
|
||
clearable
|
||
filterable
|
||
remote
|
||
:remote-method="loadVehicleOptions"
|
||
@visible-change="handleVehicleSelectVisible"
|
||
>
|
||
<el-option
|
||
v-for="item in vehicleOptions"
|
||
:key="item.value"
|
||
:label="item.label"
|
||
:value="item.value"
|
||
/>
|
||
</el-select>
|
||
</template>
|
||
<template #insuredAmountForm>
|
||
<el-input
|
||
v-model="form.insuredAmount"
|
||
:disabled="boxType === 'view'"
|
||
inputmode="decimal"
|
||
placeholder="请输入"
|
||
@input="value => normalizeDecimalInput('insuredAmount', value)"
|
||
>
|
||
<template #append>元</template>
|
||
</el-input>
|
||
</template>
|
||
<template #premiumForm>
|
||
<el-input
|
||
v-model="form.premium"
|
||
:disabled="boxType === 'view'"
|
||
inputmode="decimal"
|
||
placeholder="请输入"
|
||
@input="value => normalizeDecimalInput('premium', value)"
|
||
>
|
||
<template #append>元</template>
|
||
</el-input>
|
||
</template>
|
||
<template #policyFileForm>
|
||
<div class="insurance-record-page__upload">
|
||
<div class="insurance-record-page__upload-info">
|
||
<div class="insurance-record-page__upload-icon" aria-hidden="true">
|
||
<span class="insurance-record-page__upload-card">
|
||
<span></span>
|
||
</span>
|
||
</div>
|
||
<div class="insurance-record-page__upload-text">
|
||
<strong>上传图片,自动识别填写</strong>
|
||
<span>支持JPG、PNG、BMP上传</span>
|
||
</div>
|
||
</div>
|
||
<el-upload
|
||
v-if="hasPermission('insurance_record_ocr')"
|
||
class="insurance-record-page__upload-action"
|
||
action="/api/blade-resource/oss/endpoint/put-file"
|
||
name="file"
|
||
:headers="uploadHeaders"
|
||
:show-file-list="false"
|
||
: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>
|
||
上传文件
|
||
</el-button>
|
||
</el-upload>
|
||
</div>
|
||
</template>
|
||
<template #policyFile-form>
|
||
<div class="insurance-record-page__upload">
|
||
<div class="insurance-record-page__upload-info">
|
||
<div class="insurance-record-page__upload-icon" aria-hidden="true">
|
||
<span class="insurance-record-page__upload-card">
|
||
<span></span>
|
||
</span>
|
||
</div>
|
||
<div class="insurance-record-page__upload-text">
|
||
<strong>上传图片,自动识别填写</strong>
|
||
<span>支持JPG、PNG、BMP上传</span>
|
||
</div>
|
||
</div>
|
||
<el-upload
|
||
v-if="hasPermission('insurance_record_ocr')"
|
||
class="insurance-record-page__upload-action"
|
||
action="/api/blade-resource/oss/endpoint/put-file"
|
||
name="file"
|
||
:headers="uploadHeaders"
|
||
:show-file-list="false"
|
||
: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>
|
||
上传文件
|
||
</el-button>
|
||
</el-upload>
|
||
</div>
|
||
</template>
|
||
</avue-crud>
|
||
<empty-pagination
|
||
:page="page"
|
||
@size-change="sizeChange"
|
||
@current-change="currentChange"
|
||
@load="onLoad(page, query)"
|
||
/>
|
||
<el-dialog title="保险记录数据导入" append-to-body v-model="excelBox" width="555px">
|
||
<avue-form :option="excelOption" v-model="excelForm">
|
||
<template #excelTemplate>
|
||
<el-button type="primary" @click="handleTemplate">
|
||
点击下载<i class="el-icon-download el-icon--right"></i>
|
||
</el-button>
|
||
</template>
|
||
</avue-form>
|
||
</el-dialog>
|
||
</basic-container>
|
||
</template>
|
||
|
||
<script>
|
||
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';
|
||
import { openImportDialog } from '@/utils/import-excel';
|
||
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, exportColumns, option } from '@/option/vehicle/insurance-record';
|
||
import NProgress from 'nprogress';
|
||
import 'nprogress/nprogress.css';
|
||
|
||
const createTimeRangeMap = {
|
||
createTimeRange: ['createTimeStart', 'createTimeEnd'],
|
||
};
|
||
|
||
export default {
|
||
data() {
|
||
return {
|
||
form: {},
|
||
query: {},
|
||
loading: true,
|
||
excelBox: false,
|
||
excelForm: {},
|
||
isDetailLoading: false,
|
||
boxType: '',
|
||
vehicleOptions: [],
|
||
vehicleOptionsLoading: false,
|
||
vehicleOptionsRequestId: 0,
|
||
option,
|
||
excelOption,
|
||
page: {
|
||
pageSize: 10,
|
||
pageSizes: [10, 20, 50, 100],
|
||
currentPage: 1,
|
||
total: 0,
|
||
},
|
||
selectionList: [],
|
||
data: [],
|
||
ocrTemplateOptions: [],
|
||
};
|
||
},
|
||
created() {
|
||
this.initDeptTree();
|
||
this.initOcrTemplateOptions('车辆');
|
||
},
|
||
computed: {
|
||
...mapGetters(['permission', 'userInfo']),
|
||
isAdmin() {
|
||
const authority = this.userInfo.authority || '';
|
||
return authority.includes('admin');
|
||
},
|
||
permissionList() {
|
||
return {
|
||
addBtn: this.hasPermission('insurance_record_add'),
|
||
viewBtn: this.hasPermission('insurance_record_view'),
|
||
delBtn: this.hasPermission('insurance_record_delete'),
|
||
editBtn: this.hasPermission('insurance_record_edit'),
|
||
};
|
||
},
|
||
vehicleNoPlaceholder() {
|
||
return this.form.vehicleType === '船舶' ? '请选择船号' : '请选择车牌号';
|
||
},
|
||
ids() {
|
||
const ids = [];
|
||
this.selectionList.forEach(ele => {
|
||
ids.push(ele.id);
|
||
});
|
||
return ids.join(',');
|
||
},
|
||
uploadHeaders() {
|
||
return getUploadHeaders();
|
||
},
|
||
},
|
||
watch: {
|
||
'form.vehicleType': {
|
||
handler(value, oldValue) {
|
||
this.updateVehicleTypeDisplays(value || '车辆', oldValue);
|
||
},
|
||
immediate: true,
|
||
},
|
||
},
|
||
methods: {
|
||
hasPermission(code) {
|
||
return this.isAdmin || this.validData(this.permission[code], false);
|
||
},
|
||
updateVehicleTypeDisplays(vehicleType, oldValue) {
|
||
const vehicleNoColumn = this.findColumn(this.option.column, 'vehicleNo');
|
||
vehicleNoColumn.placeholder =
|
||
vehicleType === '船舶' ? '请选择船号' : '请选择车牌号';
|
||
if (!this.isDetailLoading && oldValue && vehicleType !== oldValue) {
|
||
this.form.vehicleNo = '';
|
||
this.vehicleOptions = [];
|
||
this.loadVehicleOptions('');
|
||
this.initOcrTemplateOptions(vehicleType, true);
|
||
}
|
||
},
|
||
initDeptTree() {
|
||
getDeptTree(this.userInfo.tenantId).then(res => {
|
||
const column = this.findColumn(this.option.column, 'createDept');
|
||
column.dicData = res.data.data;
|
||
});
|
||
},
|
||
initOcrTemplateOptions(vehicleType = '车辆', resetSelected = false) {
|
||
const type = vehicleType || '车辆';
|
||
return getOcrTemplateList(1, 100, { vehicleType: type })
|
||
.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;
|
||
if (resetSelected || !this.ocrTemplateOptions.some(item => item.value === this.form.ocrTemplate)) {
|
||
this.form.ocrTemplate = this.ocrTemplateOptions[0]?.value || '';
|
||
}
|
||
})
|
||
.catch(() => {
|
||
this.ocrTemplateOptions = [];
|
||
const templateColumn = this.findColumn(this.option.column, 'ocrTemplate');
|
||
if (templateColumn) templateColumn.dicData = [];
|
||
if (resetSelected) this.form.ocrTemplate = '';
|
||
});
|
||
},
|
||
handleVehicleSelectVisible(visible) {
|
||
if (visible && !this.vehicleOptions.length) this.loadVehicleOptions('');
|
||
},
|
||
loadVehicleOptions(queryString = '') {
|
||
const vehicleType = this.form.vehicleType || '车辆';
|
||
const isShip = vehicleType === '船舶';
|
||
const request = isShip ? getShipList : getVehicleList;
|
||
const params = isShip ? { shipIdentifierNo: queryString } : { plateNo: queryString };
|
||
const requestId = ++this.vehicleOptionsRequestId;
|
||
this.vehicleOptionsLoading = true;
|
||
request(1, 20, params)
|
||
.then(res => {
|
||
if (requestId !== this.vehicleOptionsRequestId) return;
|
||
const values = (res.data.data.records || [])
|
||
.map(item => (isShip ? item.shipIdentifierNo || item.shipName : item.plateNo))
|
||
.filter(Boolean);
|
||
this.vehicleOptions = [...new Set(values)].map(value => ({ label: value, value }));
|
||
if (this.form.vehicleNo && !this.vehicleOptions.some(item => item.value === this.form.vehicleNo)) {
|
||
this.vehicleOptions.unshift({ label: this.form.vehicleNo, value: this.form.vehicleNo });
|
||
}
|
||
})
|
||
.catch(() => {
|
||
if (requestId === this.vehicleOptionsRequestId) this.vehicleOptions = [];
|
||
})
|
||
.finally(() => {
|
||
if (requestId === this.vehicleOptionsRequestId) this.vehicleOptionsLoading = false;
|
||
});
|
||
},
|
||
normalizeRow(row) {
|
||
const values = { ...row };
|
||
values.vehicleType = values.vehicleType || '车辆';
|
||
if (values.vehicleNo) {
|
||
values.vehicleNo =
|
||
values.vehicleType === '船舶'
|
||
? values.vehicleNo.trim()
|
||
: values.vehicleNo.trim().toUpperCase();
|
||
}
|
||
if (values.policyNo) {
|
||
values.policyNo = values.policyNo.trim();
|
||
}
|
||
return values;
|
||
},
|
||
normalizeDecimalInput(prop, value) {
|
||
const sanitized = String(value ?? '').replace(/[^\d.]/g, '');
|
||
const [integerPart, ...decimalParts] = sanitized.split('.');
|
||
this.form[prop] = decimalParts.length
|
||
? `${integerPart || '0'}.${decimalParts.join('').slice(0, 2)}`
|
||
: integerPart;
|
||
},
|
||
buildQuery(params = {}) {
|
||
return normalizeSearchRangeParams({ ...params, ...this.query }, createTimeRangeMap);
|
||
},
|
||
validateRow(row) {
|
||
if (row.startDate && row.endDate && row.endDate < row.startDate) {
|
||
this.$message.warning('结束日期不能早于开始日期');
|
||
return false;
|
||
}
|
||
if (Number(row.insuredAmount) < 0 || Number(row.premium) < 0) {
|
||
this.$message.warning('保额、保费不能小于 0');
|
||
return false;
|
||
}
|
||
if (row.remark && row.remark.length > 200) {
|
||
this.$message.warning('备注最多 200 个字');
|
||
return false;
|
||
}
|
||
return true;
|
||
},
|
||
rowSave(row, done, loading) {
|
||
const values = this.normalizeRow(row);
|
||
if (!this.validateRow(values)) {
|
||
loading();
|
||
return;
|
||
}
|
||
add(values).then(
|
||
() => {
|
||
this.onLoad(this.page);
|
||
this.$message({ type: 'success', message: '操作成功!' });
|
||
done();
|
||
},
|
||
error => {
|
||
window.console.log(error);
|
||
loading();
|
||
}
|
||
);
|
||
},
|
||
rowUpdate(row, index, done, loading) {
|
||
const values = this.normalizeRow(row);
|
||
if (!this.validateRow(values)) {
|
||
loading();
|
||
return;
|
||
}
|
||
update(values).then(
|
||
() => {
|
||
this.onLoad(this.page);
|
||
this.$message({ type: 'success', message: '操作成功!' });
|
||
done();
|
||
},
|
||
error => {
|
||
window.console.log(error);
|
||
loading();
|
||
}
|
||
);
|
||
},
|
||
rowDel(row) {
|
||
this.$confirm('确定删除该条数据,删除后不可恢复?', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning',
|
||
})
|
||
.then(() => remove(row.id))
|
||
.then(() => {
|
||
this.onLoad(this.page);
|
||
this.$message({ type: 'success', message: '操作成功!' });
|
||
});
|
||
},
|
||
handleDelete() {
|
||
if (this.selectionList.length === 0) {
|
||
this.$message.warning('请选择至少一条数据');
|
||
return;
|
||
}
|
||
this.$confirm('确定删除所选数据,删除后不可恢复?', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning',
|
||
})
|
||
.then(() => remove(this.ids))
|
||
.then(() => {
|
||
this.onLoad(this.page);
|
||
this.$message({ type: 'success', message: '操作成功!' });
|
||
this.$refs.crud.toggleSelection();
|
||
});
|
||
},
|
||
beforeOpen(done, type) {
|
||
this.boxType = type;
|
||
if (type === 'add') {
|
||
this.vehicleOptions = [];
|
||
this.loadVehicleOptions('');
|
||
this.form.vehicleType = '车辆';
|
||
this.initOcrTemplateOptions('车辆', true);
|
||
}
|
||
if (['edit', 'view'].includes(type)) {
|
||
this.isDetailLoading = true;
|
||
getDetail(this.form.id)
|
||
.then(res => {
|
||
this.form = {
|
||
...res.data.data,
|
||
vehicleType: res.data.data.vehicleType || '车辆',
|
||
};
|
||
this.vehicleOptions = [];
|
||
this.loadVehicleOptions(this.form.vehicleNo || '');
|
||
return this.initOcrTemplateOptions(this.form.vehicleType);
|
||
})
|
||
.finally(() => {
|
||
this.isDetailLoading = false;
|
||
});
|
||
}
|
||
done();
|
||
},
|
||
searchReset() {
|
||
this.query = {};
|
||
this.onLoad(this.page);
|
||
},
|
||
searchChange(params, done) {
|
||
this.query = this.buildQuery(params);
|
||
this.page.currentPage = 1;
|
||
this.onLoad(this.page);
|
||
done();
|
||
},
|
||
selectionChange(list) {
|
||
this.selectionList = list;
|
||
},
|
||
selectionClear() {
|
||
this.selectionList = [];
|
||
this.$refs.crud.toggleSelection();
|
||
},
|
||
currentChange(currentPage) {
|
||
this.page.currentPage = currentPage;
|
||
},
|
||
sizeChange(pageSize) {
|
||
this.page.pageSize = pageSize;
|
||
},
|
||
refreshChange() {
|
||
this.onLoad(this.page, this.query);
|
||
},
|
||
onLoad(page, params = {}) {
|
||
this.loading = true;
|
||
const query = this.buildQuery(params);
|
||
getList(page.currentPage, page.pageSize, query)
|
||
.then(res => {
|
||
const data = res.data.data;
|
||
this.page.total = data.total;
|
||
this.data = data.records;
|
||
this.selectionClear();
|
||
})
|
||
.finally(() => {
|
||
this.loading = false;
|
||
});
|
||
},
|
||
handleImport() {
|
||
openImportDialog(this, '保险记录');
|
||
},
|
||
handleExport() {
|
||
this.$confirm('是否导出保险记录数据?', '提示', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning',
|
||
}).then(() => {
|
||
NProgress.start();
|
||
exportBlob(
|
||
'/blade-transport/insurance-record/export-insurance-record',
|
||
this.buildExportParams(),
|
||
{ feedback: true }
|
||
)
|
||
.then(res => {
|
||
downloadXls(res.data, `保险记录${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`);
|
||
})
|
||
.finally(() => {
|
||
NProgress.done();
|
||
});
|
||
});
|
||
},
|
||
buildExportParams() {
|
||
return {
|
||
...this.buildQuery(),
|
||
ids: this.ids,
|
||
exportColumns: JSON.stringify(exportColumns),
|
||
[this.website.tokenHeader]: getToken(),
|
||
};
|
||
},
|
||
handleTemplate() {
|
||
exportBlob(
|
||
'/blade-transport/insurance-record/export-template',
|
||
{ exportColumns: JSON.stringify(exportColumns), [this.website.tokenHeader]: getToken() },
|
||
{ feedback: true }
|
||
).then(res => {
|
||
downloadXls(res.data, '保险记录模板.xlsx');
|
||
});
|
||
},
|
||
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 [];
|
||
}
|
||
},
|
||
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;
|
||
},
|
||
},
|
||
};
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.insurance-record-page {
|
||
:deep(.el-table th .cell),
|
||
:deep(.el-table td .cell) {
|
||
white-space: nowrap;
|
||
}
|
||
|
||
&__vehicle-input {
|
||
width: 100%;
|
||
}
|
||
}
|
||
|
||
:global(.insurance-record-page__upload) {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
//min-height: 88px;
|
||
padding: 16px 28px 16px 22px;
|
||
border: 1px dashed #8b8b8b;
|
||
border-radius: 8px;
|
||
background: #fff;
|
||
}
|
||
|
||
:global(.insurance-record-page__upload-info) {
|
||
display: flex;
|
||
align-items: center;
|
||
min-width: 0;
|
||
}
|
||
|
||
:global(.insurance-record-page__upload-icon) {
|
||
position: relative;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 42px;
|
||
height: 42px;
|
||
margin-right: 18px;
|
||
color: #707070;
|
||
}
|
||
|
||
:global(.insurance-record-page__upload-icon::before),
|
||
:global(.insurance-record-page__upload-icon::after) {
|
||
position: absolute;
|
||
width: 11px;
|
||
height: 11px;
|
||
content: '';
|
||
}
|
||
|
||
:global(.insurance-record-page__upload-icon::before) {
|
||
top: 0;
|
||
left: 0;
|
||
border-top: 3px solid currentColor;
|
||
border-left: 3px solid currentColor;
|
||
border-radius: 3px 0 0;
|
||
}
|
||
|
||
:global(.insurance-record-page__upload-icon::after) {
|
||
right: 0;
|
||
bottom: 0;
|
||
border-right: 3px solid currentColor;
|
||
border-bottom: 3px solid currentColor;
|
||
border-radius: 0 0 3px;
|
||
}
|
||
|
||
:global(.insurance-record-page__upload-card) {
|
||
position: relative;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 28px;
|
||
height: 22px;
|
||
border: 3px solid currentColor;
|
||
border-radius: 3px;
|
||
}
|
||
|
||
:global(.insurance-record-page__upload-card::before),
|
||
:global(.insurance-record-page__upload-card::after) {
|
||
position: absolute;
|
||
content: '';
|
||
}
|
||
|
||
:global(.insurance-record-page__upload-card::before) {
|
||
top: 5px;
|
||
left: 7px;
|
||
width: 6px;
|
||
height: 6px;
|
||
border: 2px solid currentColor;
|
||
border-radius: 2px;
|
||
}
|
||
|
||
:global(.insurance-record-page__upload-card::after) {
|
||
top: 6px;
|
||
right: 5px;
|
||
width: 6px;
|
||
height: 2px;
|
||
background: currentColor;
|
||
box-shadow: 0 6px 0 currentColor;
|
||
}
|
||
|
||
:global(.insurance-record-page__upload-card span) {
|
||
position: absolute;
|
||
right: 5px;
|
||
bottom: 4px;
|
||
width: 8px;
|
||
height: 2px;
|
||
background: currentColor;
|
||
}
|
||
|
||
:global(.insurance-record-page__upload-text) {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
color: #303133;
|
||
line-height: 1.25;
|
||
}
|
||
|
||
:global(.insurance-record-page__upload-text strong) {
|
||
font-weight: 700;
|
||
}
|
||
|
||
:global(.insurance-record-page__upload-text span) {
|
||
color: #303133;
|
||
font-weight: 500;
|
||
}
|
||
|
||
:global(.insurance-record-page__upload-action) {
|
||
flex: 0 0 auto;
|
||
margin-left: 18px;
|
||
}
|
||
|
||
:global(.insurance-record-page__upload-action .el-upload) {
|
||
display: block;
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
:global(.insurance-record-page__upload) {
|
||
align-items: stretch;
|
||
flex-direction: column;
|
||
gap: 12px;
|
||
padding: 16px;
|
||
}
|
||
|
||
:global(.insurance-record-page__upload-action) {
|
||
margin-left: 0;
|
||
}
|
||
|
||
:global(.insurance-record-page__upload-button) {
|
||
width: 100%;
|
||
}
|
||
}
|
||
</style>
|